Merge pull request #903 from AppFlowy-IO/feat/flowy-overlay

Feat: appflowy_popover
This commit is contained in:
Nathan.fooo 2022-09-06 16:25:54 +08:00 committed by GitHub
commit 05b9bc50f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
177 changed files with 6822 additions and 848 deletions

View File

@ -14,6 +14,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/widget/error_page.dart';
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
@ -285,10 +286,15 @@ class _BoardContentState extends State<BoardContent> {
rowCache: rowCache,
);
RowDetailPage(
cellBuilder: GridCellBuilder(delegate: dataController),
dataController: dataController,
).show(context);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: GridCellBuilder(delegate: dataController),
dataController: dataController,
);
},
);
}
}

View File

@ -2,7 +2,6 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/board/application/toolbar/board_setting_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_group.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
@ -50,7 +49,7 @@ class BoardSettingList extends StatelessWidget {
previous.selectedAction != current.selectedAction,
listener: (context, state) {
state.selectedAction.foldLeft(null, (_, action) {
FlowyOverlay.of(context).remove(identifier());
// FlowyOverlay.of(context).remove(identifier());
onAction(action, settingContext);
});
},
@ -84,43 +83,6 @@ class BoardSettingList extends StatelessWidget {
),
);
}
static void show(BuildContext context, BoardSettingContext settingContext) {
final list = BoardSettingList(
settingContext: settingContext,
onAction: (action, settingContext) {
switch (action) {
case BoardSettingAction.properties:
GridPropertyList(
gridId: settingContext.viewId,
fieldController: settingContext.fieldController)
.show(context);
break;
case BoardSettingAction.groups:
GridGroupList(
viewId: settingContext.viewId,
fieldController: settingContext.fieldController)
.show(context);
break;
}
},
);
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(140, 400)),
child: list,
),
identifier: identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomRight,
style: FlowyOverlayStyle(blur: false),
);
}
static String identifier() {
return (BoardSettingList).toString();
}
}
class _SettingItem extends StatelessWidget {
@ -177,3 +139,50 @@ extension _GridSettingExtension on BoardSettingAction {
}
}
}
class BoardSettingListPopover extends StatefulWidget {
final BoardSettingContext settingContext;
const BoardSettingListPopover({
Key? key,
required this.settingContext,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _BoardSettingListPopoverState();
}
class _BoardSettingListPopoverState extends State<BoardSettingListPopover> {
bool _showGridPropertyList = false;
@override
Widget build(BuildContext context) {
if (_showGridPropertyList) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(260, 400)),
child: GridPropertyList(
gridId: widget.settingContext.viewId,
fieldController: widget.settingContext.fieldController,
),
);
}
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(140, 400)),
child: BoardSettingList(
settingContext: widget.settingContext,
onAction: (action, settingContext) {
switch (action) {
case BoardSettingAction.groups:
break;
case BoardSettingAction.properties:
setState(() {
_showGridPropertyList = true;
});
break;
}
},
),
);
}
}

View File

@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
@ -47,14 +48,22 @@ class _SettingButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
return FlowyIconButton(
hoverColor: theme.hover,
width: 22,
onPressed: () => BoardSettingList.show(context, settingContext),
icon: Padding(
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
child: svgWidget("grid/setting/setting"),
return Popover(
triggerActions: PopoverTriggerActionFlags.click,
child: FlowyIconButton(
hoverColor: theme.hover,
width: 22,
onPressed: () {},
icon: Padding(
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
child: svgWidget("grid/setting/setting"),
),
),
popupBuilder: (BuildContext popoverContext) {
return BoardSettingListPopover(
settingContext: settingContext,
);
},
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart'
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scrollview.dart';
@ -290,10 +291,14 @@ class _GridRowsState extends State<_GridRows> {
rowCache: rowCache,
);
RowDetailPage(
cellBuilder: cellBuilder,
dataController: dataController,
).show(context);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: cellBuilder,
dataController: dataController,
);
});
}
}

View File

@ -20,14 +20,36 @@ class GridCellAccessoryBuildContext {
});
}
abstract class GridCellAccessory implements Widget {
class GridCellAccessoryBuilder {
final GlobalKey _key = GlobalKey();
final Widget Function(Key key) _builder;
GridCellAccessoryBuilder({required Widget Function(Key key) builder})
: _builder = builder;
Widget build() => _builder(_key);
void onTap() {
(_key.currentState as GridCellAccessoryState).onTap();
}
bool enable() {
if (_key.currentState == null) {
return true;
}
return (_key.currentState as GridCellAccessoryState).enable();
}
}
abstract class GridCellAccessoryState {
void onTap();
// The accessory will be hidden if enable() return false;
bool enable() => true;
}
class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
class PrimaryCellAccessory extends StatefulWidget {
final VoidCallback onTapCallback;
final bool isCellEditing;
const PrimaryCellAccessory({
@ -36,9 +58,15 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _PrimaryCellAccessoryState();
}
class _PrimaryCellAccessoryState extends State<PrimaryCellAccessory>
with GridCellAccessoryState {
@override
Widget build(BuildContext context) {
if (isCellEditing) {
if (widget.isCellEditing) {
return const SizedBox();
} else {
final theme = context.watch<AppTheme>();
@ -53,10 +81,10 @@ class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
}
@override
void onTap() => onTapCallback();
void onTap() => widget.onTapCallback();
@override
bool enable() => !isCellEditing;
bool enable() => !widget.isCellEditing;
}
class AccessoryHover extends StatefulWidget {
@ -170,7 +198,7 @@ class _Background extends StatelessWidget {
}
class CellAccessoryContainer extends StatelessWidget {
final List<GridCellAccessory> accessories;
final List<GridCellAccessoryBuilder> accessories;
const CellAccessoryContainer({required this.accessories, Key? key})
: super(key: key);
@ -186,7 +214,7 @@ class CellAccessoryContainer extends StatelessWidget {
width: 26,
height: 26,
padding: const EdgeInsets.all(3),
child: accessory,
child: accessory.build(),
),
);
return GestureDetector(

View File

@ -94,7 +94,7 @@ abstract class CellEditable {
ValueNotifier<bool> get onCellEditing;
}
typedef AccessoryBuilder = List<GridCellAccessory> Function(
typedef AccessoryBuilder = List<GridCellAccessoryBuilder> Function(
GridCellAccessoryBuildContext buildContext);
abstract class CellAccessory extends Widget {
@ -125,8 +125,8 @@ abstract class GridCellWidget extends StatefulWidget
final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
@override
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)?
get accessoryBuilder => null;
List<GridCellAccessoryBuilder> Function(
GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null;
@override
final GridCellFocusListener beginFocus = GridCellFocusListener();

View File

@ -80,7 +80,7 @@ class CellContainer extends StatelessWidget {
class _GridCellEnterRegion extends StatelessWidget {
final Widget child;
final List<GridCellAccessory> accessories;
final List<GridCellAccessoryBuilder> accessories;
const _GridCellEnterRegion(
{required this.child, required this.accessories, Key? key})
: super(key: key);

View File

@ -3,6 +3,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:appflowy_popover/popover.dart';
import '../cell_builder.dart';
import 'date_editor.dart';
@ -39,10 +40,12 @@ class GridDateCell extends GridCellWidget {
}
class _DateCellState extends GridCellState<GridDateCell> {
late PopoverController _popover;
late DateCellBloc _cellBloc;
@override
void initState() {
_popover = PopoverController();
final cellController = widget.cellControllerBuilder.build();
_cellBloc = getIt<DateCellBloc>(param1: cellController)
..add(const DateCellEvent.initial());
@ -58,19 +61,34 @@ class _DateCellState extends GridCellState<GridDateCell> {
value: _cellBloc,
child: BlocBuilder<DateCellBloc, DateCellState>(
builder: (context, state) {
return SizedBox.expand(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _showCalendar(context),
child: MouseRegion(
opaque: false,
cursor: SystemMouseCursors.click,
child: Align(
alignment: alignment,
child: FlowyText.medium(state.dateStr, fontSize: 12),
return Popover(
controller: _popover,
offset: const Offset(0, 20),
direction: PopoverDirection.bottomWithLeftAligned,
child: SizedBox.expand(
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => _showCalendar(context),
child: MouseRegion(
opaque: false,
cursor: SystemMouseCursors.click,
child: Align(
alignment: alignment,
child: FlowyText.medium(state.dateStr, fontSize: 12),
),
),
),
),
popupBuilder: (BuildContext popoverContent) {
final bloc = context.read<DateCellBloc>();
return DateCellEditor(
cellController: bloc.cellController.clone(),
onDismissed: () => widget.onCellEditing.value = false,
);
},
onClose: () {
widget.onCellEditing.value = false;
},
);
},
),
@ -78,14 +96,7 @@ class _DateCellState extends GridCellState<GridDateCell> {
}
void _showCalendar(BuildContext context) {
final bloc = context.read<DateCellBloc>();
widget.onCellEditing.value = true;
final calendar =
DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
calendar.show(
context,
cellController: bloc.cellController.clone(),
);
_popover.show();
}
@override

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -23,58 +24,54 @@ final kFirstDay = DateTime(kToday.year, kToday.month - 3, kToday.day);
final kLastDay = DateTime(kToday.year, kToday.month + 3, kToday.day);
const kMargin = EdgeInsets.symmetric(horizontal: 6, vertical: 10);
class DateCellEditor with FlowyOverlayDelegate {
class DateCellEditor extends StatefulWidget {
final VoidCallback onDismissed;
final GridDateCellController cellController;
const DateCellEditor({
Key? key,
required this.onDismissed,
});
required this.cellController,
}) : super(key: key);
Future<void> show(
BuildContext context, {
required GridDateCellController cellController,
}) async {
DateCellEditor.remove(context);
@override
State<StatefulWidget> createState() => _DateCellEditor();
}
final result =
await cellController.getFieldTypeOption(DateTypeOptionDataParser());
class _DateCellEditor extends State<DateCellEditor> {
DateTypeOptionPB? _dateTypeOptionPB;
result.fold(
(dateTypeOptionPB) {
final calendar = _CellCalendarWidget(
cellContext: cellController,
dateTypeOptionPB: dateTypeOptionPB,
);
@override
void initState() {
super.initState();
_fetchData();
}
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(320, 500)),
child: calendar,
),
identifier: DateCellEditor.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.leftWithCenterAligned,
style: FlowyOverlayStyle(blur: false),
delegate: this,
);
},
(err) => Log.error(err),
_fetchData() async {
final result = await widget.cellController
.getFieldTypeOption(DateTypeOptionDataParser());
result.fold((dateTypeOptionPB) {
setState(() {
_dateTypeOptionPB = dateTypeOptionPB;
});
}, (err) => Log.error(err));
}
@override
Widget build(BuildContext context) {
if (_dateTypeOptionPB == null) {
return Container();
}
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(320, 500)),
child: _CellCalendarWidget(
cellContext: widget.cellController,
dateTypeOptionPB: _dateTypeOptionPB!,
),
);
}
static void remove(BuildContext context) {
FlowyOverlay.of(context).remove(identifier());
}
static String identifier() {
return (DateCellEditor).toString();
}
@override
void didRemove() => onDismissed();
@override
bool asBarrier() => true;
}
class _CellCalendarWidget extends StatelessWidget {
@ -169,17 +166,14 @@ class _CellCalendarWidget extends StatelessWidget {
);
},
onDaySelected: (selectedDay, focusedDay) {
_CalDateTimeSetting.hide(context);
context
.read<DateCalBloc>()
.add(DateCalEvent.selectDay(selectedDay));
},
onFormatChanged: (format) {
_CalDateTimeSetting.hide(context);
context.read<DateCalBloc>().add(DateCalEvent.setCalFormat(format));
},
onPageChanged: (focusedDay) {
_CalDateTimeSetting.hide(context);
context
.read<DateCalBloc>()
.add(DateCalEvent.setFocusedDay(focusedDay));
@ -247,7 +241,6 @@ class _TimeTextFieldState extends State<_TimeTextField> {
if (widget.bloc.state.dateTypeOptionPB.includeTime) {
_focusNode.addListener(() {
if (mounted) {
_CalDateTimeSetting.hide(context);
widget.bloc.add(DateCalEvent.setTime(_controller.text));
}
});
@ -304,29 +297,34 @@ class _DateTypeOptionButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final title = "${LocaleKeys.grid_field_dateFormat.tr()} &${LocaleKeys.grid_field_timeFormat.tr()}";
final title =
"${LocaleKeys.grid_field_dateFormat.tr()} &${LocaleKeys.grid_field_timeFormat.tr()}";
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
selector: (state) => state.dateTypeOptionPB,
builder: (context, dateTypeOptionPB) {
return FlowyButton(
text: FlowyText.medium(title, fontSize: 12),
hoverColor: theme.hover,
margin: kMargin,
onTap: () => _showTimeSetting(dateTypeOptionPB, context),
rightIcon: svgWidget("grid/more", color: theme.iconColor),
return Popover(
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
offset: const Offset(20, 0),
child: FlowyButton(
text: FlowyText.medium(title, fontSize: 12),
hoverColor: theme.hover,
margin: kMargin,
rightIcon: svgWidget("grid/more", color: theme.iconColor),
),
popupBuilder: (BuildContext popContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(140, 100)),
child: _CalDateTimeSetting(
dateTypeOptionPB: dateTypeOptionPB,
onEvent: (event) => context.read<DateCalBloc>().add(event),
),
);
},
);
},
);
}
void _showTimeSetting(
DateTypeOptionPB dateTypeOptionPB, BuildContext context) {
final setting = _CalDateTimeSetting(
dateTypeOptionPB: dateTypeOptionPB,
onEvent: (event) => context.read<DateCalBloc>().add(event),
);
setting.show(context);
}
}
class _CalDateTimeSetting extends StatefulWidget {
@ -338,54 +336,48 @@ class _CalDateTimeSetting extends StatefulWidget {
@override
State<_CalDateTimeSetting> createState() => _CalDateTimeSettingState();
static String identifier() {
return (_CalDateTimeSetting).toString();
}
void show(BuildContext context) {
hide(context);
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(140, 100)),
child: this,
),
identifier: _CalDateTimeSetting.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.rightWithCenterAligned,
anchorOffset: const Offset(20, 0),
);
}
static void hide(BuildContext context) {
FlowyOverlay.of(context).remove(identifier());
}
}
class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
String? overlayIdentifier;
final _popoverMutex = PopoverMutex();
@override
Widget build(BuildContext context) {
List<Widget> children = [
DateFormatButton(onTap: () {
final list = DateFormatList(
selectedFormat: widget.dateTypeOptionPB.dateFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setDateFormat(format)),
);
_showOverlay(context, list);
}),
TimeFormatButton(
timeFormat: widget.dateTypeOptionPB.timeFormat,
onTap: () {
final list = TimeFormatList(
selectedFormat: widget.dateTypeOptionPB.timeFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setTimeFormat(format)),
Popover(
mutex: _popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
offset: const Offset(20, 0),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: DateFormatList(
selectedFormat: widget.dateTypeOptionPB.dateFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setDateFormat(format)),
),
);
_showOverlay(context, list);
},
child: const DateFormatButton(),
),
Popover(
mutex: _popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
offset: const Offset(20, 0),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: TimeFormatList(
selectedFormat: widget.dateTypeOptionPB.timeFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setTimeFormat(format)),
),
);
},
child: TimeFormatButton(timeFormat: widget.dateTypeOptionPB.timeFormat),
),
];
@ -404,23 +396,4 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
),
);
}
void _showOverlay(BuildContext context, Widget child) {
if (overlayIdentifier != null) {
FlowyOverlay.of(context).remove(overlayIdentifier!);
}
overlayIdentifier = child.toString();
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: child,
),
identifier: overlayIdentifier!,
anchorContext: context,
anchorDirection: AnchorDirection.rightWithCenterAligned,
style: FlowyOverlayStyle(blur: false),
anchorOffset: const Offset(20, 0),
);
}
}

View File

@ -1,7 +1,9 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
// ignore: unused_import
import 'package:flowy_sdk/log.dart';
@ -133,7 +135,7 @@ class _MultiSelectCellState extends State<GridMultiSelectCell> {
}
}
class SelectOptionWrap extends StatelessWidget {
class SelectOptionWrap extends StatefulWidget {
final List<SelectOptionPB> selectOptions;
final void Function(bool)? onFocus;
final SelectOptionCellStyle? cellStyle;
@ -146,15 +148,28 @@ class SelectOptionWrap extends StatelessWidget {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _SelectOptionWrapState();
}
class _SelectOptionWrapState extends State<SelectOptionWrap> {
late PopoverController _popover;
@override
void initState() {
_popover = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final Widget child;
if (selectOptions.isEmpty && cellStyle != null) {
if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
child = Align(
alignment: Alignment.centerLeft,
child: FlowyText.medium(
cellStyle!.placeholder,
widget.cellStyle!.placeholder,
fontSize: 14,
color: theme.shader3,
),
@ -165,7 +180,7 @@ class SelectOptionWrap extends StatelessWidget {
child: Wrap(
spacing: 4,
runSpacing: 2,
children: selectOptions
children: widget.selectOptions
.map((option) => SelectOptionTag.fromOption(
context: context,
option: option,
@ -179,14 +194,37 @@ class SelectOptionWrap extends StatelessWidget {
alignment: AlignmentDirectional.center,
fit: StackFit.expand,
children: [
child,
Popover(
controller: _popover,
offset: const Offset(0, 20),
direction: PopoverDirection.bottomWithLeftAligned,
// triggerActions: PopoverTriggerActionFlags.c,
popupBuilder: (BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
widget.onFocus?.call(true);
});
return OverlayContainer(
constraints: BoxConstraints.loose(
Size(SelectOptionCellEditor.editorPanelWidth, 300)),
child: SizedBox(
width: SelectOptionCellEditor.editorPanelWidth,
child: SelectOptionCellEditor(
cellController: widget.cellControllerBuilder.build()
as GridSelectOptionCellController,
onDismissed: () {
widget.onFocus?.call(false);
},
),
),
);
},
onClose: () {
widget.onFocus?.call(false);
},
child: child,
),
InkWell(onTap: () {
onFocus?.call(true);
SelectOptionCellEditor.show(
context,
cellControllerBuilder.build() as GridSelectOptionCellController,
() => onFocus?.call(false),
);
_popover.show();
}),
],
);

View File

@ -1,6 +1,7 @@
import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -28,6 +29,8 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
final GridSelectOptionCellController cellController;
final VoidCallback onDismissed;
static double editorPanelWidth = 300;
const SelectOptionCellEditor({
required this.cellController,
required this.onDismissed,
@ -226,76 +229,82 @@ class _CreateOptionCell extends StatelessWidget {
}
}
class _SelectOptionCell extends StatelessWidget {
class _SelectOptionCell extends StatefulWidget {
final SelectOptionPB option;
final bool isSelected;
const _SelectOptionCell(this.option, this.isSelected, {Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: Row(
children: [
Flexible(
fit: FlexFit.loose,
child: SelectOptionTagCell(
option: option,
onSelected: (option) {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.selectOption(option.id));
},
children: [
if (isSelected)
Padding(
padding: const EdgeInsets.only(right: 6),
child: svgWidget("grid/checkmark"),
),
],
),
),
FlowyIconButton(
width: 30,
onPressed: () => _showEditPannel(context),
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
icon: svgWidget("editor/details", color: theme.iconColor),
)
],
),
);
State<_SelectOptionCell> createState() => _SelectOptionCellState();
}
class _SelectOptionCellState extends State<_SelectOptionCell> {
late PopoverController _popoverController;
@override
void initState() {
_popoverController = PopoverController();
super.initState();
}
void _showEditPannel(BuildContext context) {
final pannel = SelectOptionTypeOptionEditor(
option: option,
onDeleted: () {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.deleteOption(option));
},
onUpdated: (updatedOption) {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.updateOption(updatedOption));
},
key: ValueKey(option
.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
);
final overlayIdentifier = (SelectOptionTypeOptionEditor).toString();
FlowyOverlay.of(context).remove(overlayIdentifier);
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(200, 300)),
child: pannel,
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return Popover(
controller: _popoverController,
offset: const Offset(20, 0),
child: SizedBox(
height: GridSize.typeOptionItemHeight,
child: Row(
children: [
Flexible(
fit: FlexFit.loose,
child: SelectOptionTagCell(
option: widget.option,
onSelected: (option) {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.selectOption(option.id));
},
children: [
if (widget.isSelected)
Padding(
padding: const EdgeInsets.only(right: 6),
child: svgWidget("grid/checkmark"),
),
],
),
),
FlowyIconButton(
width: 30,
onPressed: () => _popoverController.show(),
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
icon: svgWidget("editor/details", color: theme.iconColor),
)
],
),
),
identifier: overlayIdentifier,
anchorContext: context,
anchorDirection: AnchorDirection.rightWithCenterAligned,
anchorOffset: Offset(2 * overlayContainerPadding.left, 0),
popupBuilder: (BuildContext popoverContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(200, 300)),
child: SelectOptionTypeOptionEditor(
option: widget.option,
onDeleted: () {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.deleteOption(widget.option));
},
onUpdated: (updatedOption) {
context
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.updateOption(updatedOption));
},
key: ValueKey(widget.option
.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
),
);
},
);
}
}

View File

@ -6,56 +6,13 @@ import 'dart:async';
import 'package:flutter_bloc/flutter_bloc.dart';
class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
class URLCellEditor extends StatefulWidget {
final GridURLCellController cellController;
final VoidCallback completed;
const URLCellEditor(
{required this.cellController, required this.completed, Key? key})
const URLCellEditor({required this.cellController, Key? key})
: super(key: key);
@override
State<URLCellEditor> createState() => _URLCellEditorState();
static void show(
BuildContext context,
GridURLCellController cellContext,
VoidCallback completed,
) {
FlowyOverlay.of(context).remove(identifier());
final editor = URLCellEditor(
cellController: cellContext,
completed: completed,
);
//
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(300, 160)),
child: SizedBox(
width: 200,
child: Padding(padding: const EdgeInsets.all(6), child: editor),
),
),
identifier: URLCellEditor.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomWithCenterAligned,
delegate: editor,
);
}
static String identifier() {
return (URLCellEditor).toString();
}
@override
bool asBarrier() {
return true;
}
@override
void didRemove() {
completed();
}
}
class _URLCellEditorState extends State<URLCellEditor> {
@ -114,3 +71,25 @@ class _URLCellEditorState extends State<URLCellEditor> {
}
}
}
class URLEditorPopover extends StatelessWidget {
final GridURLCellController cellController;
const URLEditorPopover({required this.cellController, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(300, 160)),
child: SizedBox(
width: 200,
child: Padding(
padding: const EdgeInsets.all(6),
child: URLCellEditor(
cellController: cellController,
),
),
),
);
}
}

View File

@ -2,6 +2,7 @@ import 'dart:async';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/cell/url_cell_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -48,27 +49,37 @@ class GridURLCell extends GridCellWidget {
@override
GridCellState<GridURLCell> createState() => _GridURLCellState();
GridCellAccessory accessoryFromType(
GridCellAccessoryBuilder accessoryFromType(
GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) {
switch (ty) {
case GridURLCellAccessoryType.edit:
final cellController =
cellControllerBuilder.build() as GridURLCellController;
return _EditURLAccessory(
return GridCellAccessoryBuilder(
builder: (Key key) => _EditURLAccessory(
key: key,
cellContext: cellController,
anchorContext: buildContext.anchorContext);
anchorContext: buildContext.anchorContext,
),
);
case GridURLCellAccessoryType.copyURL:
final cellContext =
cellControllerBuilder.build() as GridURLCellController;
return _CopyURLAccessory(cellContext: cellContext);
return GridCellAccessoryBuilder(
builder: (Key key) => _CopyURLAccessory(
key: key,
cellContext: cellContext,
),
);
}
}
@override
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)
List<GridCellAccessoryBuilder> Function(
GridCellAccessoryBuildContext buildContext)
get accessoryBuilder => (buildContext) {
final List<GridCellAccessory> accessories = [];
final List<GridCellAccessoryBuilder> accessories = [];
if (cellStyle != null) {
accessories.addAll(cellStyle!.accessoryTypes.map((ty) {
return accessoryFromType(ty, buildContext);
@ -86,6 +97,8 @@ class GridURLCell extends GridCellWidget {
}
class _GridURLCellState extends GridCellState<GridURLCell> {
final _popoverController = PopoverController();
GridURLCellController? _cellContext;
late URLCellBloc _cellBloc;
@override
@ -116,14 +129,28 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
),
);
return SizedBox.expand(
return Popover(
controller: _popoverController,
direction: PopoverDirection.bottomWithLeftAligned,
offset: const Offset(0, 20),
child: SizedBox.expand(
child: GestureDetector(
child: Align(alignment: Alignment.centerLeft, child: richText),
onTap: () async {
final url = context.read<URLCellBloc>().state.url;
await _openUrlOrEdit(url);
child: Align(alignment: Alignment.centerLeft, child: richText),
onTap: () async {
final url = context.read<URLCellBloc>().state.url;
await _openUrlOrEdit(url);
},
),
),
popupBuilder: (BuildContext popoverContext) {
return URLEditorPopover(
cellController: _cellContext!,
);
},
));
onClose: () {
widget.onCellEditing.value = false;
},
);
},
),
);
@ -140,12 +167,10 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
if (url.isNotEmpty && await canLaunchUrl(uri)) {
await launchUrl(uri);
} else {
final cellContext =
_cellContext =
widget.cellControllerBuilder.build() as GridURLCellController;
widget.onCellEditing.value = true;
URLCellEditor.show(context, cellContext, () {
widget.onCellEditing.value = false;
});
_popoverController.show();
}
}
@ -163,7 +188,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
}
}
class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
class _EditURLAccessory extends StatefulWidget {
final GridURLCellController cellContext;
final BuildContext anchorContext;
const _EditURLAccessory({
@ -172,24 +197,55 @@ class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _EditURLAccessoryState();
}
class _EditURLAccessoryState extends State<_EditURLAccessory>
with GridCellAccessoryState {
late PopoverController _popoverController;
@override
void initState() {
_popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return svgWidget("editor/edit", color: theme.iconColor);
return Popover(
controller: _popoverController,
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerActionFlags.click,
offset: const Offset(0, 20),
child: svgWidget("editor/edit", color: theme.iconColor),
popupBuilder: (BuildContext popoverContext) {
return URLEditorPopover(
cellController: widget.cellContext.clone(),
);
},
);
}
@override
void onTap() {
URLCellEditor.show(anchorContext, cellContext, () {});
_popoverController.show();
}
}
class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
class _CopyURLAccessory extends StatefulWidget {
final GridURLCellController cellContext;
const _CopyURLAccessory({required this.cellContext, Key? key})
: super(key: key);
@override
State<StatefulWidget> createState() => _CopyURLAccessoryState();
}
class _CopyURLAccessoryState extends State<_CopyURLAccessory>
with GridCellAccessoryState {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return svgWidget("editor/copy", color: theme.iconColor);
@ -198,7 +254,7 @@ class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
@override
void onTap() {
final content =
cellContext.getCellData(loadIfNotExist: false)?.content ?? "";
widget.cellContext.getCellData(loadIfNotExist: false)?.content ?? "";
Clipboard.setData(ClipboardData(text: content));
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
}

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
@ -13,23 +13,35 @@ import '../../layout/sizes.dart';
import 'field_type_extension.dart';
import 'field_cell_action_sheet.dart';
import 'field_editor.dart';
class GridFieldCell extends StatelessWidget {
final GridFieldCellContext cellContext;
const GridFieldCell(this.cellContext, {Key? key}) : super(key: key);
const GridFieldCell({
Key? key,
required this.cellContext,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FieldCellBloc(cellContext: cellContext)
..add(const FieldCellEvent.initial()),
create: (context) {
return FieldCellBloc(cellContext: cellContext);
},
child: BlocBuilder<FieldCellBloc, FieldCellState>(
// buildWhen: (p, c) => p.field != c.field,
builder: (context, state) {
final button = FieldCellButton(
field: state.field,
onTap: () => _showActionSheet(context),
final button = Popover(
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerActionFlags.click,
offset: const Offset(0, 10),
popupBuilder: (BuildContext context) {
return GridFieldCellActionSheet(
cellContext: cellContext,
);
},
child: FieldCellButton(
field: cellContext.field,
onTap: () {},
),
);
const line = Positioned(
@ -51,29 +63,6 @@ class GridFieldCell extends StatelessWidget {
),
);
}
void _showActionSheet(BuildContext context) {
final state = context.read<FieldCellBloc>().state;
GridFieldCellActionSheet(
cellContext:
GridFieldCellContext(gridId: state.gridId, field: state.field),
onEdited: () => _showFieldEditor(context),
).show(context);
}
void _showFieldEditor(BuildContext context) {
final state = context.read<FieldCellBloc>().state;
final field = state.field;
FieldEditor(
gridId: state.gridId,
fieldName: field.name,
typeOptionLoader: FieldTypeOptionLoader(
gridId: state.gridId,
field: field,
),
).show(context);
}
}
class _GridHeaderCellContainer extends StatelessWidget {
@ -119,6 +108,7 @@ class _DragToExpandLine extends StatelessWidget {
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragUpdate: (value) {
debugPrint("update new width: ${value.delta.dx}");
context
.read<FieldCellBloc>()
.add(FieldCellEvent.startUpdateWidth(value.delta.dx));

View File

@ -1,3 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:flowy_infra/image.dart';
@ -13,75 +15,81 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import '../../layout/sizes.dart';
class GridFieldCellActionSheet extends StatelessWidget
with FlowyOverlayDelegate {
class GridFieldCellActionSheet extends StatefulWidget {
final GridFieldCellContext cellContext;
final VoidCallback onEdited;
const GridFieldCellActionSheet(
{required this.cellContext, required this.onEdited, Key? key})
const GridFieldCellActionSheet({required this.cellContext, Key? key})
: super(key: key);
void show(BuildContext overlayContext) {
FlowyOverlay.of(overlayContext).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: this,
),
identifier: GridFieldCellActionSheet.identifier(),
anchorContext: overlayContext,
anchorDirection: AnchorDirection.bottomWithLeftAligned,
delegate: this,
);
}
@override
State<StatefulWidget> createState() => _GridFieldCellActionSheetState();
}
class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
bool _showFieldEditor = false;
@override
Widget build(BuildContext context) {
if (_showFieldEditor) {
final field = widget.cellContext.field;
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FieldEditor(
gridId: widget.cellContext.gridId,
fieldName: field.name,
typeOptionLoader: FieldTypeOptionLoader(
gridId: widget.cellContext.gridId,
field: field,
),
),
);
}
return BlocProvider(
create: (context) => getIt<FieldActionSheetBloc>(param1: cellContext),
child: SingleChildScrollView(
child: Column(
children: [
_EditFieldButton(
onEdited: () {
FlowyOverlay.of(context).remove(identifier());
onEdited();
},
),
const VSpace(6),
_FieldOperationList(cellContext,
() => FlowyOverlay.of(context).remove(identifier())),
],
create: (context) =>
getIt<FieldActionSheetBloc>(param1: widget.cellContext),
child: OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: SingleChildScrollView(
child: Column(
children: [
_EditFieldButton(
cellContext: widget.cellContext,
onTap: () {
setState(() {
_showFieldEditor = true;
});
},
),
const VSpace(6),
_FieldOperationList(widget.cellContext, () {}),
],
),
),
),
);
}
static String identifier() {
return (GridFieldCellActionSheet).toString();
}
@override
bool asBarrier() {
return true;
}
}
class _EditFieldButton extends StatelessWidget {
final Function() onEdited;
const _EditFieldButton({required this.onEdited, Key? key}) : super(key: key);
final GridFieldCellContext cellContext;
final void Function()? onTap;
const _EditFieldButton({required this.cellContext, Key? key, this.onTap})
: super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocBuilder<FieldActionSheetBloc, FieldActionSheetState>(
builder: (context, state) {
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyButton(
text: FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(),
fontSize: 12),
text: FlowyText.medium(
LocaleKeys.grid_field_editProperty.tr(),
fontSize: 12,
),
hoverColor: theme.hover,
onTap: onEdited,
onTap: onTap,
),
);
},

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
@ -10,7 +10,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart';
import 'field_type_option_editor.dart';
class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
class FieldEditor extends StatefulWidget {
final String gridId;
final String fieldName;
@ -22,13 +22,26 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _FieldEditorState();
}
class _FieldEditorState extends State<FieldEditor> {
late PopoverMutex popoverMutex;
@override
void initState() {
popoverMutex = PopoverMutex();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FieldEditorBloc(
gridId: gridId,
fieldName: fieldName,
loader: typeOptionLoader,
gridId: widget.gridId,
fieldName: widget.fieldName,
loader: widget.typeOptionLoader,
)..add(const FieldEditorEvent.initial()),
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
buildWhen: (p, c) => false,
@ -41,42 +54,22 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
const VSpace(10),
const _FieldNameCell(),
const VSpace(10),
const _FieldTypeOptionCell(),
_FieldTypeOptionCell(popoverMutex: popoverMutex),
],
);
},
),
);
}
void show(
BuildContext context, {
AnchorDirection anchorDirection = AnchorDirection.bottomWithLeftAligned,
}) {
FlowyOverlay.of(context).remove(identifier());
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(280, 400)),
child: this,
),
identifier: identifier(),
anchorContext: context,
anchorDirection: anchorDirection,
style: FlowyOverlayStyle(blur: false),
delegate: this,
);
}
static String identifier() {
return (FieldEditor).toString();
}
@override
bool asBarrier() => true;
}
class _FieldTypeOptionCell extends StatelessWidget {
const _FieldTypeOptionCell({Key? key}) : super(key: key);
final PopoverMutex popoverMutex;
const _FieldTypeOptionCell({
Key? key,
required this.popoverMutex,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -88,7 +81,10 @@ class _FieldTypeOptionCell extends StatelessWidget {
(fieldContext) {
final dataController =
context.read<FieldEditorBloc>().dataController;
return FieldTypeOptionEditor(dataController: dataController);
return FieldTypeOptionEditor(
dataController: dataController,
popoverMutex: popoverMutex,
);
},
);
},

View File

@ -1,5 +1,6 @@
import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:dartz/dartz.dart' show Either;
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -23,30 +24,25 @@ typedef SwitchToFieldCallback
FieldType fieldType,
);
class FieldTypeOptionEditor extends StatefulWidget {
class FieldTypeOptionEditor extends StatelessWidget {
final TypeOptionDataController dataController;
final PopoverMutex popoverMutex;
const FieldTypeOptionEditor({
required this.dataController,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
State<FieldTypeOptionEditor> createState() => _FieldTypeOptionEditorState();
}
class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
String? currentOverlayIdentifier;
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => FieldTypeOptionEditBloc(widget.dataController)
create: (context) => FieldTypeOptionEditBloc(dataController)
..add(const FieldTypeOptionEditEvent.initial()),
child: BlocBuilder<FieldTypeOptionEditBloc, FieldTypeOptionEditState>(
builder: (context, state) {
List<Widget> children = [
_switchFieldTypeButton(context, widget.dataController.field)
_switchFieldTypeButton(context, dataController.field)
];
final typeOptionWidget =
_typeOptionWidget(context: context, state: state);
@ -68,18 +64,28 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
final theme = context.watch<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyButton(
text: FlowyText.medium(field.fieldType.title(), fontSize: 12),
margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
hoverColor: theme.hover,
onTap: () {
child: Popover(
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
mutex: popoverMutex,
offset: const Offset(20, 0),
popupBuilder: (context) {
final list = FieldTypeList(onSelectField: (newFieldType) {
widget.dataController.switchToField(newFieldType);
dataController.switchToField(newFieldType);
});
_showOverlay(context, list);
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: list,
);
},
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
rightIcon: svgWidget("grid/more", color: theme.iconColor),
child: FlowyButton(
text: FlowyText.medium(field.fieldType.title(), fontSize: 12),
margin: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
hoverColor: theme.hover,
leftIcon:
svgWidget(field.fieldType.iconName(), color: theme.iconColor),
rightIcon: svgWidget("grid/more", color: theme.iconColor),
),
),
);
}
@ -88,44 +94,12 @@ class _FieldTypeOptionEditorState extends State<FieldTypeOptionEditor> {
required BuildContext context,
required FieldTypeOptionEditState state,
}) {
final overlayDelegate = TypeOptionOverlayDelegate(
showOverlay: _showOverlay,
hideOverlay: _hideOverlay,
);
return makeTypeOptionWidget(
context: context,
overlayDelegate: overlayDelegate,
dataController: widget.dataController,
dataController: dataController,
popoverMutex: popoverMutex,
);
}
void _showOverlay(BuildContext context, Widget child,
{VoidCallback? onRemoved}) {
final identifier = child.toString();
if (currentOverlayIdentifier != null) {
FlowyOverlay.of(context).remove(currentOverlayIdentifier!);
}
currentOverlayIdentifier = identifier;
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: child,
),
identifier: identifier,
anchorContext: context,
anchorDirection: AnchorDirection.leftWithCenterAligned,
style: FlowyOverlayStyle(blur: false),
anchorOffset: const Offset(-20, 0),
);
}
void _hideOverlay(BuildContext context) {
if (currentOverlayIdentifier != null) {
FlowyOverlay.of(context).remove(currentOverlayIdentifier!);
}
}
}
abstract class TypeOptionWidget extends StatelessWidget {

View File

@ -4,8 +4,10 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
@ -75,6 +77,21 @@ class _GridHeader extends StatefulWidget {
}
class _GridHeaderState extends State<_GridHeader> {
final Map<String, ValueKey<String>> _gridMap = {};
/// This is a workaround for [ReorderableRow].
/// [ReorderableRow] warps the child's key with a [GlobalKey].
/// It will trigger the child's widget's to recreate.
/// The state will lose.
_getKeyById(String id) {
if (_gridMap.containsKey(id)) {
return _gridMap[id];
}
final newKey = ValueKey(id);
_gridMap[id] = newKey;
return newKey;
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
@ -85,7 +102,10 @@ class _GridHeaderState extends State<_GridHeader> {
.where((field) => field.visibility)
.map((field) =>
GridFieldCellContext(gridId: widget.gridId, field: field.field))
.map((ctx) => GridFieldCell(ctx, key: ValueKey(ctx.field.id)))
.map((ctx) => GridFieldCell(
key: _getKeyById(ctx.field.id),
cellContext: ctx,
))
.toList();
return Container(
@ -156,18 +176,28 @@ class CreateFieldButton extends StatelessWidget {
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_newColumn.tr(),
fontSize: 12,
return Popover(
triggerActions: PopoverTriggerActionFlags.click,
direction: PopoverDirection.bottomWithRightAligned,
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_newColumn.tr(),
fontSize: 12,
),
hoverColor: theme.shader6,
onTap: () {},
leftIcon: svgWidget("home/add"),
),
hoverColor: theme.shader6,
onTap: () => FieldEditor(
gridId: gridId,
fieldName: "",
typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId),
).show(context),
leftIcon: svgWidget("home/add"),
popupBuilder: (BuildContext popover) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FieldEditor(
gridId: gridId,
fieldName: "",
typeOptionLoader: NewFieldTypeOptionLoader(gridId: gridId),
),
);
},
);
}
}

View File

@ -3,6 +3,7 @@ import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
@ -46,19 +47,16 @@ abstract class TypeOptionWidgetBuilder {
Widget? makeTypeOptionWidget({
required BuildContext context,
required TypeOptionDataController dataController,
required TypeOptionOverlayDelegate overlayDelegate,
required PopoverMutex popoverMutex,
}) {
final builder = makeTypeOptionWidgetBuilder(
dataController: dataController,
overlayDelegate: overlayDelegate,
);
dataController: dataController, popoverMutex: popoverMutex);
return builder.build(context);
}
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
required TypeOptionDataController dataController,
required TypeOptionOverlayDelegate overlayDelegate,
}) {
TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder(
{required TypeOptionDataController dataController,
required PopoverMutex popoverMutex}) {
final gridId = dataController.gridId;
final fieldType = dataController.field.fieldType;
@ -73,13 +71,12 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
);
case FieldType.DateTime:
return DateTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
);
makeTypeOptionContextWithDataController<DateTypeOptionPB>(
gridId: gridId,
fieldType: fieldType,
dataController: dataController,
),
popoverMutex);
case FieldType.SingleSelect:
return SingleSelectTypeOptionWidgetBuilder(
makeTypeOptionContextWithDataController<SingleSelectTypeOptionPB>(
@ -87,7 +84,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
popoverMutex,
);
case FieldType.MultiSelect:
return MultiSelectTypeOptionWidgetBuilder(
@ -96,7 +93,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
popoverMutex,
);
case FieldType.Number:
return NumberTypeOptionWidgetBuilder(
@ -105,7 +102,7 @@ TypeOptionWidgetBuilder makeTypeOptionWidgetBuilder({
fieldType: fieldType,
dataController: dataController,
),
overlayDelegate,
popoverMutex,
);
case FieldType.RichText:
return RichTextTypeOptionWidgetBuilder(

View File

@ -11,6 +11,7 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import '../../../layout/sizes.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';
@ -20,10 +21,10 @@ class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
DateTypeOptionWidgetBuilder(
DateTypeOptionContext typeOptionContext,
TypeOptionOverlayDelegate overlayDelegate,
PopoverMutex popoverMutex,
) : _widget = DateTypeOptionWidget(
typeOptionContext: typeOptionContext,
overlayDelegate: overlayDelegate,
popoverMutex: popoverMutex,
);
@override
@ -34,11 +35,10 @@ class DateTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
class DateTypeOptionWidget extends TypeOptionWidget {
final DateTypeOptionContext typeOptionContext;
final TypeOptionOverlayDelegate overlayDelegate;
final PopoverMutex popoverMutex;
const DateTypeOptionWidget({
required this.typeOptionContext,
required this.overlayDelegate,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@ -62,39 +62,58 @@ class DateTypeOptionWidget extends TypeOptionWidget {
}
Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
return DateFormatButton(onTap: () {
final list = DateFormatList(
selectedFormat: dataFormat,
onSelected: (format) {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectDateFormat(format));
},
);
overlayDelegate.showOverlay(context, list);
});
}
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
return TimeFormatButton(
timeFormat: timeFormat,
onTap: () {
final list = TimeFormatList(
selectedFormat: timeFormat,
return Popover(
mutex: popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
offset: const Offset(20, 0),
popupBuilder: (popoverContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: DateFormatList(
selectedFormat: dataFormat,
onSelected: (format) {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
});
overlayDelegate.showOverlay(context, list);
.add(DateTypeOptionEvent.didSelectDateFormat(format));
PopoverContainerState.of(popoverContext).closeAll();
},
),
);
},
child: const DateFormatButton(),
);
}
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
return Popover(
mutex: popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
offset: const Offset(20, 0),
popupBuilder: (BuildContext popoverContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: TimeFormatList(
selectedFormat: timeFormat,
onSelected: (format) {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
PopoverContainerState.of(popoverContext).closeAll();
}),
);
},
child: TimeFormatButton(timeFormat: timeFormat),
);
}
}
class DateFormatButton extends StatelessWidget {
final VoidCallback onTap;
const DateFormatButton({required this.onTap, Key? key}) : super(key: key);
final VoidCallback? onTap;
final void Function(bool)? onHover;
const DateFormatButton({this.onTap, this.onHover, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
@ -107,6 +126,7 @@ class DateFormatButton extends StatelessWidget {
margin: GridSize.typeOptionContentInsets,
hoverColor: theme.hover,
onTap: onTap,
onHover: onHover,
rightIcon: svgWidget("grid/more", color: theme.iconColor),
),
);
@ -115,9 +135,10 @@ class DateFormatButton extends StatelessWidget {
class TimeFormatButton extends StatelessWidget {
final TimeFormat timeFormat;
final VoidCallback onTap;
final VoidCallback? onTap;
final void Function(bool)? onHover;
const TimeFormatButton(
{required this.timeFormat, required this.onTap, Key? key})
{required this.timeFormat, this.onTap, this.onHover, Key? key})
: super(key: key);
@override
@ -131,6 +152,7 @@ class TimeFormatButton extends StatelessWidget {
margin: GridSize.typeOptionContentInsets,
hoverColor: theme.hover,
onTap: onTap,
onHover: onHover,
rightIcon: svgWidget("grid/more", color: theme.iconColor),
),
);

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import 'package:appflowy_popover/popover.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';
@ -11,14 +12,14 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
MultiSelectTypeOptionWidgetBuilder(
MultiSelectTypeOptionContext typeOptionContext,
TypeOptionOverlayDelegate overlayDelegate,
PopoverMutex popoverMutex,
) : _widget = MultiSelectTypeOptionWidget(
selectOptionAction: MultiSelectAction(
fieldId: typeOptionContext.fieldId,
gridId: typeOptionContext.gridId,
typeOptionContext: typeOptionContext,
),
overlayDelegate: overlayDelegate,
popoverMutex: popoverMutex,
);
@override
@ -27,20 +28,22 @@ class MultiSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
class MultiSelectTypeOptionWidget extends TypeOptionWidget {
final MultiSelectAction selectOptionAction;
final TypeOptionOverlayDelegate overlayDelegate;
final PopoverMutex? popoverMutex;
const MultiSelectTypeOptionWidget({
required this.selectOptionAction,
required this.overlayDelegate,
Key? key,
required this.selectOptionAction,
this.popoverMutex,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget(
options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context),
overlayDelegate: overlayDelegate,
beginEdit: () {
PopoverContainerState.of(context).closeAll();
},
popoverMutex: popoverMutex,
typeOptionAction: selectOptionAction,
// key: ValueKey(state.typeOption.hashCode),
);

View File

@ -1,6 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/number_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/number_format_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -23,10 +24,10 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
NumberTypeOptionWidgetBuilder(
NumberTypeOptionContext typeOptionContext,
TypeOptionOverlayDelegate overlayDelegate,
PopoverMutex popoverMutex,
) : _widget = NumberTypeOptionWidget(
typeOptionContext: typeOptionContext,
overlayDelegate: overlayDelegate,
popoverMutex: popoverMutex,
);
@override
@ -34,11 +35,11 @@ class NumberTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
}
class NumberTypeOptionWidget extends TypeOptionWidget {
final TypeOptionOverlayDelegate overlayDelegate;
final NumberTypeOptionContext typeOptionContext;
final PopoverMutex popoverMutex;
const NumberTypeOptionWidget({
required this.typeOptionContext,
required this.overlayDelegate,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@ -54,34 +55,40 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
listener: (context, state) =>
typeOptionContext.typeOption = state.typeOption,
builder: (context, state) {
return FlowyButton(
text: Row(
children: [
FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(),
fontSize: 12),
// const HSpace(6),
const Spacer(),
FlowyText.regular(state.typeOption.format.title(),
fontSize: 12),
],
return Popover(
mutex: popoverMutex,
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
offset: const Offset(20, 0),
child: FlowyButton(
margin: GridSize.typeOptionContentInsets,
hoverColor: theme.hover,
rightIcon: svgWidget("grid/more", color: theme.iconColor),
text: Row(
children: [
FlowyText.medium(LocaleKeys.grid_field_numberFormat.tr(),
fontSize: 12),
// const HSpace(6),
const Spacer(),
FlowyText.regular(state.typeOption.format.title(),
fontSize: 12),
],
),
),
margin: GridSize.typeOptionContentInsets,
hoverColor: theme.hover,
onTap: () {
final list = NumberFormatList(
onSelected: (format) {
context
.read<NumberTypeOptionBloc>()
.add(NumberTypeOptionEvent.didSelectFormat(format));
},
selectedFormat: state.typeOption.format,
);
overlayDelegate.showOverlay(
context,
list,
popupBuilder: (BuildContext popoverContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: NumberFormatList(
onSelected: (format) {
context
.read<NumberTypeOptionBloc>()
.add(NumberTypeOptionEvent.didSelectFormat(format));
PopoverContainerState.of(popoverContext).closeAll();
},
selectedFormat: state.typeOption.format,
),
);
},
rightIcon: svgWidget("grid/more", color: theme.iconColor),
);
},
),

View File

@ -1,6 +1,8 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
@ -13,20 +15,19 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import '../../../layout/sizes.dart';
import '../../cell/select_option_cell/extension.dart';
import '../../common/text_field.dart';
import 'builder.dart';
import 'select_option_editor.dart';
class SelectOptionTypeOptionWidget extends StatelessWidget {
final List<SelectOptionPB> options;
final VoidCallback beginEdit;
final TypeOptionOverlayDelegate overlayDelegate;
final ISelectOptionAction typeOptionAction;
final PopoverMutex? popoverMutex;
const SelectOptionTypeOptionWidget({
required this.options,
required this.beginEdit,
required this.overlayDelegate,
required this.typeOptionAction,
this.popoverMutex,
Key? key,
}) : super(key: key);
@ -50,7 +51,7 @@ class SelectOptionTypeOptionWidget extends StatelessWidget {
),
if (state.options.isEmpty && !state.isEditingOption)
const _AddOptionButton(),
_OptionList(overlayDelegate)
_OptionList(popoverMutex: popoverMutex)
];
return Column(children: children);
@ -111,8 +112,8 @@ class _OptionTitleButton extends StatelessWidget {
}
class _OptionList extends StatelessWidget {
final TypeOptionOverlayDelegate delegate;
const _OptionList(this.delegate, {Key? key}) : super(key: key);
final PopoverMutex? popoverMutex;
const _OptionList({Key? key, this.popoverMutex}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -122,7 +123,11 @@ class _OptionList extends StatelessWidget {
},
builder: (context, state) {
final cells = state.options.map((option) {
return _makeOptionCell(context, option);
return _makeOptionCell(
context: context,
option: option,
popoverMutex: popoverMutex,
);
}).toList();
return ListView.separated(
@ -140,57 +145,81 @@ class _OptionList extends StatelessWidget {
);
}
_OptionCell _makeOptionCell(BuildContext context, SelectOptionPB option) {
_OptionCell _makeOptionCell({
required BuildContext context,
required SelectOptionPB option,
PopoverMutex? popoverMutex,
}) {
return _OptionCell(
option: option,
onSelected: (option) {
final pannel = SelectOptionTypeOptionEditor(
option: option,
onDeleted: () {
delegate.hideOverlay(context);
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.deleteOption(option));
},
onUpdated: (updatedOption) {
delegate.hideOverlay(context);
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
},
key: ValueKey(option.id),
);
delegate.showOverlay(context, pannel);
},
popoverMutex: popoverMutex,
);
}
}
class _OptionCell extends StatelessWidget {
class _OptionCell extends StatefulWidget {
final SelectOptionPB option;
final Function(SelectOptionPB) onSelected;
const _OptionCell({
required this.option,
required this.onSelected,
Key? key,
}) : super(key: key);
final PopoverMutex? popoverMutex;
const _OptionCell({required this.option, Key? key, this.popoverMutex})
: super(key: key);
@override
State<_OptionCell> createState() => _OptionCellState();
}
class _OptionCellState extends State<_OptionCell> {
late PopoverController _popoverController;
@override
void initState() {
_popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: SelectOptionTagCell(
option: option,
onSelected: onSelected,
children: [
svgWidget(
"grid/details",
color: theme.iconColor,
),
],
return Popover(
controller: _popoverController,
mutex: widget.popoverMutex,
offset: const Offset(20, 0),
child: SizedBox(
height: GridSize.typeOptionItemHeight,
child: SelectOptionTagCell(
option: widget.option,
onSelected: (SelectOptionPB pb) {
_popoverController.show();
},
children: [
svgWidget(
"grid/details",
color: theme.iconColor,
),
],
),
),
popupBuilder: (BuildContext popoverContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: SelectOptionTypeOptionEditor(
option: widget.option,
onDeleted: () {
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
PopoverContainerState.of(popoverContext).closeAll();
},
onUpdated: (updatedOption) {
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
PopoverContainerState.of(popoverContext).closeAll();
},
key: ValueKey(widget.option.id),
),
);
},
);
}
}

View File

@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/single_sele
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import '../field_type_option_editor.dart';
import 'package:appflowy_popover/popover.dart';
import 'builder.dart';
import 'select_option.dart';
@ -10,14 +11,14 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
SingleSelectTypeOptionWidgetBuilder(
SingleSelectTypeOptionContext singleSelectTypeOption,
TypeOptionOverlayDelegate overlayDelegate,
PopoverMutex popoverMutex,
) : _widget = SingleSelectTypeOptionWidget(
selectOptionAction: SingleSelectAction(
fieldId: singleSelectTypeOption.fieldId,
gridId: singleSelectTypeOption.gridId,
typeOptionContext: singleSelectTypeOption,
),
overlayDelegate: overlayDelegate,
popoverMutex: popoverMutex,
);
@override
@ -26,20 +27,22 @@ class SingleSelectTypeOptionWidgetBuilder extends TypeOptionWidgetBuilder {
class SingleSelectTypeOptionWidget extends TypeOptionWidget {
final SingleSelectAction selectOptionAction;
final TypeOptionOverlayDelegate overlayDelegate;
final PopoverMutex? popoverMutex;
const SingleSelectTypeOptionWidget({
required this.selectOptionAction,
required this.overlayDelegate,
Key? key,
required this.selectOptionAction,
this.popoverMutex,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SelectOptionTypeOptionWidget(
options: selectOptionAction.typeOption.options,
beginEdit: () => overlayDelegate.hideOverlay(context),
overlayDelegate: overlayDelegate,
beginEdit: () {
PopoverContainerState.of(context).closeAll();
},
popoverMutex: popoverMutex,
typeOptionAction: selectOptionAction,
// key: ValueKey(state.typeOption.hashCode),
);

View File

@ -194,12 +194,17 @@ class RowContent extends StatelessWidget {
Provider.of<RegionStateNotifier>(context, listen: false),
accessoryBuilder: (buildContext) {
final builder = child.accessoryBuilder;
List<GridCellAccessory> accessories = [];
List<GridCellAccessoryBuilder> accessories = [];
if (cellId.fieldContext.isPrimary) {
accessories.add(PrimaryCellAccessory(
onTapCallback: onExpand,
isCellEditing: buildContext.isCellEditing,
));
accessories.add(
GridCellAccessoryBuilder(
builder: (key) => PrimaryCellAccessory(
key: key,
onTapCallback: onExpand,
isCellEditing: buildContext.isCellEditing,
),
),
);
}
if (builder != null) {

View File

@ -15,6 +15,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import '../../layout/sizes.dart';
import '../cell/cell_accessory.dart';
@ -35,23 +36,6 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
@override
State<RowDetailPage> createState() => _RowDetailPageState();
void show(BuildContext context) async {
final windowSize = MediaQuery.of(context).size;
final size = windowSize * 0.5;
FlowyOverlay.of(context).insertWithRect(
widget: OverlayContainer(
constraints: BoxConstraints.tight(size),
child: this,
),
identifier: RowDetailPage.identifier(),
anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
anchorSize: windowSize,
anchorDirection: AnchorDirection.center,
style: FlowyOverlayStyle(blur: false),
delegate: this,
);
}
static String identifier() {
return (RowDetailPage).toString();
}
@ -60,31 +44,33 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
class _RowDetailPageState extends State<RowDetailPage> {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) {
final bloc = RowDetailBloc(
dataController: widget.dataController,
);
bloc.add(const RowDetailEvent.initial());
return bloc;
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 30, vertical: 20),
child: Column(
children: [
SizedBox(
height: 30,
child: Row(
children: const [Spacer(), _CloseButton()],
return FlowyDialog(
child: BlocProvider(
create: (context) {
final bloc = RowDetailBloc(
dataController: widget.dataController,
);
bloc.add(const RowDetailEvent.initial());
return bloc;
},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 40, vertical: 20),
child: Column(
children: [
SizedBox(
height: 40,
child: Row(
children: const [Spacer(), _CloseButton()],
),
),
),
Expanded(
child: _PropertyList(
cellBuilder: widget.cellBuilder,
viewId: widget.dataController.rowInfo.gridId,
Expanded(
child: _PropertyList(
cellBuilder: widget.cellBuilder,
viewId: widget.dataController.rowInfo.gridId,
),
),
),
],
],
),
),
),
);
@ -99,8 +85,9 @@ class _CloseButton extends StatelessWidget {
final theme = context.watch<AppTheme>();
return FlowyIconButton(
width: 24,
onPressed: () =>
FlowyOverlay.of(context).remove(RowDetailPage.identifier()),
onPressed: () {
FlowyOverlay.pop(context);
},
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
icon: svgWidget("home/close", color: theme.iconColor),
);
@ -161,26 +148,35 @@ class _CreateFieldButton extends StatelessWidget {
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
return SizedBox(
height: 40,
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_newColumn.tr(),
fontSize: 12,
return Popover(
triggerActions: PopoverTriggerActionFlags.click,
child: SizedBox(
height: 40,
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_newColumn.tr(),
fontSize: 12,
),
hoverColor: theme.shader6,
onTap: () {},
leftIcon: svgWidget("home/add"),
),
hoverColor: theme.shader6,
onTap: () => FieldEditor(
gridId: viewId,
fieldName: "",
typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
).show(context),
leftIcon: svgWidget("home/add"),
),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FieldEditor(
gridId: viewId,
fieldName: "",
typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
),
);
},
);
}
}
class _RowDetailCell extends StatelessWidget {
class _RowDetailCell extends StatefulWidget {
final GridCellIdentifier cellId;
final GridCellBuilder cellBuilder;
const _RowDetailCell({
@ -189,11 +185,18 @@ class _RowDetailCell extends StatelessWidget {
Key? key,
}) : super(key: key);
@override
State<StatefulWidget> createState() => _RowDetailCellState();
}
class _RowDetailCellState extends State<_RowDetailCell> {
final PopoverController popover = PopoverController();
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final style = _customCellStyle(theme, cellId.fieldType);
final cell = cellBuilder.build(cellId, style: style);
final style = _customCellStyle(theme, widget.cellId.fieldType);
final cell = widget.cellBuilder.build(widget.cellId, style: style);
final gesture = GestureDetector(
behavior: HitTestBehavior.translucent,
@ -214,9 +217,26 @@ class _RowDetailCell extends StatelessWidget {
children: [
SizedBox(
width: 150,
child: FieldCellButton(
field: cellId.fieldContext.field,
onTap: () => _showFieldEditor(context),
child: Popover(
controller: popover,
offset: const Offset(20, 0),
popupBuilder: (context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FieldEditor(
gridId: widget.cellId.gridId,
fieldName: widget.cellId.fieldContext.field.name,
typeOptionLoader: FieldTypeOptionLoader(
gridId: widget.cellId.gridId,
field: widget.cellId.fieldContext.field,
),
),
);
},
child: FieldCellButton(
field: widget.cellId.fieldContext.field,
onTap: () => popover.show(),
),
),
),
const HSpace(10),
@ -226,17 +246,6 @@ class _RowDetailCell extends StatelessWidget {
),
);
}
void _showFieldEditor(BuildContext context) {
FieldEditor(
gridId: cellId.gridId,
fieldName: cellId.fieldContext.name,
typeOptionLoader: FieldTypeOptionLoader(
gridId: cellId.gridId,
field: cellId.fieldContext.field,
),
).show(context);
}
}
GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {

View File

@ -1,7 +1,9 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/setting/property_bloc.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -15,9 +17,8 @@ import 'package:styled_widget/styled_widget.dart';
import '../../../application/field/field_controller.dart';
import '../../layout/sizes.dart';
import '../header/field_editor.dart';
class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
class GridPropertyList extends StatefulWidget {
final String gridId;
final GridFieldController fieldController;
const GridPropertyList({
@ -26,34 +27,38 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
Key? key,
}) : super(key: key);
void show(BuildContext context) {
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(260, 400)),
child: this,
),
identifier: identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomRight,
style: FlowyOverlayStyle(blur: false),
delegate: this,
);
@override
State<StatefulWidget> createState() => _GridPropertyListState();
}
class _GridPropertyListState extends State<GridPropertyList> {
late PopoverMutex _popoverMutex;
@override
void initState() {
_popoverMutex = PopoverMutex();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) =>
getIt<GridPropertyBloc>(param1: gridId, param2: fieldController)
..add(const GridPropertyEvent.initial()),
create: (context) => getIt<GridPropertyBloc>(
param1: widget.gridId, param2: widget.fieldController)
..add(const GridPropertyEvent.initial()),
child: BlocBuilder<GridPropertyBloc, GridPropertyState>(
builder: (context, state) {
final cells = state.fieldContexts.map((field) {
return _GridPropertyCell(
gridId: gridId, fieldContext: field, key: ValueKey(field.id));
popoverMutex: _popoverMutex,
gridId: widget.gridId,
fieldContext: field,
key: ValueKey(field.id),
);
}).toList();
return ListView.separated(
controller: ScrollController(),
shrinkWrap: true,
itemCount: cells.length,
itemBuilder: (BuildContext context, int index) {
@ -67,21 +72,18 @@ class GridPropertyList extends StatelessWidget with FlowyOverlayDelegate {
),
);
}
String identifier() {
return (GridPropertyList).toString();
}
@override
bool asBarrier() => true;
}
class _GridPropertyCell extends StatelessWidget {
final GridFieldContext fieldContext;
final String gridId;
const _GridPropertyCell(
{required this.gridId, required this.fieldContext, Key? key})
: super(key: key);
final PopoverMutex popoverMutex;
const _GridPropertyCell({
required this.gridId,
required this.fieldContext,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -113,21 +115,27 @@ class _GridPropertyCell extends StatelessWidget {
);
}
FlowyButton _editFieldButton(AppTheme theme, BuildContext context) {
return FlowyButton(
text: FlowyText.medium(fieldContext.name, fontSize: 12),
hoverColor: theme.hover,
leftIcon:
svgWidget(fieldContext.fieldType.iconName(), color: theme.iconColor),
onTap: () {
FieldEditor(
gridId: gridId,
fieldName: fieldContext.name,
typeOptionLoader: FieldTypeOptionLoader(
Widget _editFieldButton(AppTheme theme, BuildContext context) {
return Popover(
mutex: popoverMutex,
triggerActions: PopoverTriggerActionFlags.click,
offset: const Offset(20, 0),
child: FlowyButton(
text: FlowyText.medium(fieldContext.name, fontSize: 12),
hoverColor: theme.hover,
leftIcon: svgWidget(fieldContext.fieldType.iconName(),
color: theme.iconColor),
),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FieldEditor(
gridId: gridId,
field: fieldContext.field,
fieldName: fieldContext.name,
typeOptionLoader: FieldTypeOptionLoader(
gridId: gridId, field: fieldContext.field),
),
).show(context, anchorDirection: AnchorDirection.bottomRight);
);
},
);
}

View File

@ -13,7 +13,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import '../../../application/field/field_controller.dart';
import '../../layout/sizes.dart';
import 'grid_property.dart';
class GridSettingContext {
final String gridId;
@ -32,37 +31,6 @@ class GridSettingList extends StatelessWidget {
{required this.settingContext, required this.onAction, Key? key})
: super(key: key);
static void show(BuildContext context, GridSettingContext settingContext) {
final list = GridSettingList(
settingContext: settingContext,
onAction: (action, settingContext) {
switch (action) {
case GridSettingAction.filter:
break;
case GridSettingAction.sortBy:
break;
case GridSettingAction.properties:
GridPropertyList(
gridId: settingContext.gridId,
fieldController: settingContext.fieldController)
.show(context);
break;
}
},
);
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(140, 400)),
child: list,
),
identifier: list.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomRight,
style: FlowyOverlayStyle(blur: false),
);
}
@override
Widget build(BuildContext context) {
return BlocProvider(

View File

@ -1,5 +1,8 @@
import 'package:appflowy_popover/popover.dart';
import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/extension.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/material.dart';
@ -7,6 +10,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
import '../../../application/field/field_controller.dart';
import '../../layout/sizes.dart';
import 'grid_property.dart';
import 'grid_setting.dart';
class GridToolbarContext {
@ -49,12 +53,57 @@ class _SettingButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyIconButton(
hoverColor: theme.hover,
width: 22,
onPressed: () => GridSettingList.show(context, settingContext),
icon:
svgWidget("grid/setting/setting").padding(horizontal: 3, vertical: 3),
return Popover(
triggerActions: PopoverTriggerActionFlags.click,
offset: const Offset(0, 10),
child: FlowyIconButton(
width: 22,
hoverColor: theme.hover,
icon: svgWidget("grid/setting/setting")
.padding(horizontal: 3, vertical: 3),
),
popupBuilder: (BuildContext context) {
return _GridSettingListPopover(settingContext: settingContext);
},
);
}
}
class _GridSettingListPopover extends StatefulWidget {
final GridSettingContext settingContext;
const _GridSettingListPopover({Key? key, required this.settingContext})
: super(key: key);
@override
State<StatefulWidget> createState() => _GridSettingListPopoverState();
}
class _GridSettingListPopoverState extends State<_GridSettingListPopover> {
GridSettingAction? _action;
@override
Widget build(BuildContext context) {
if (_action == GridSettingAction.properties) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(260, 400)),
child: GridPropertyList(
gridId: widget.settingContext.gridId,
fieldController: widget.settingContext.fieldController,
),
);
}
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(140, 400)),
child: GridSettingList(
settingContext: widget.settingContext,
onAction: (action, settingContext) {
setState(() {
_action = action;
});
},
),
);
}
}

View File

@ -6,6 +6,7 @@ import 'package:app_flowy/workspace/presentation/settings/widgets/settings_langu
import 'package:app_flowy/workspace/presentation/settings/widgets/settings_user_view.dart';
import 'package:app_flowy/workspace/presentation/settings/widgets/settings_menu.dart';
import 'package:app_flowy/workspace/application/settings/settings_dialog_bloc.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_sdk/protobuf/flowy-user/user_profile.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
@ -28,46 +29,50 @@ class SettingsDialog extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider<SettingsDialogBloc>(
create: (context) => getIt<SettingsDialogBloc>(param1: user)..add(const SettingsDialogEvent.initial()),
create: (context) => getIt<SettingsDialogBloc>(param1: user)
..add(const SettingsDialogEvent.initial()),
child: BlocBuilder<SettingsDialogBloc, SettingsDialogState>(
builder: (context, state) => ChangeNotifierProvider.value(
value: Provider.of<AppearanceSettingModel>(context, listen: true),
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
value: Provider.of<AppearanceSettingModel>(context,
listen: true),
child: FlowyDialog(
title: Text(
LocaleKeys.settings_title.tr(),
style: const TextStyle(
fontWeight: FontWeight.bold,
),
),
content: ConstrainedBox(
constraints: const BoxConstraints(
maxHeight: 600,
minWidth: 600,
maxWidth: 1000,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 200,
child: SettingsMenu(
changeSelectedIndex: (index) {
context.read<SettingsDialogBloc>().add(SettingsDialogEvent.setViewIndex(index));
},
currentIndex: context.read<SettingsDialogBloc>().state.viewIndex,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: 200,
child: SettingsMenu(
changeSelectedIndex: (index) {
context
.read<SettingsDialogBloc>()
.add(SettingsDialogEvent.setViewIndex(index));
},
currentIndex: context
.read<SettingsDialogBloc>()
.state
.viewIndex,
),
const VerticalDivider(),
const SizedBox(width: 10),
Expanded(
child: getSettingsView(context.read<SettingsDialogBloc>().state.viewIndex,
context.read<SettingsDialogBloc>().state.userProfile),
)
],
),
),
const VerticalDivider(),
const SizedBox(width: 10),
Expanded(
child: getSettingsView(
context
.read<SettingsDialogBloc>()
.state
.viewIndex,
context
.read<SettingsDialogBloc>()
.state
.userProfile),
)
],
),
),
)));

View File

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View File

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f1875d570e39de09040c8f79aa13cc56baab8db1
channel: stable
project_type: package

View File

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

View File

@ -0,0 +1 @@
TODO: Add your license here.

View File

@ -0,0 +1,107 @@
# AppFlowy Popover
A Popover can be used to display some content on top of another.
It can be used to display a dropdown menu.
> A popover is a transient view that appears above other content onscreen when you tap a control or in an area. Typically, a popover includes an arrow pointing to the location from which it emerged. Popovers can be nonmodal or modal. A nonmodal popover is dismissed by tapping another part of the screen or a button on the popover. A modal popover is dismissed by tapping a Cancel or other button on the popover.
Source: [Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/ios/views/popovers/).
## Features
- Basic popover style
- Follow the target automatically
- Nested popover support
- Exclusive API
![](./screenshot.png)
## Example
```dart
Popover(
// Define how to trigger the popover
triggerActions: PopoverTriggerActionFlags.click,
child: TextButton(child: Text("Popover"), onPressed: () {}),
// Define the direction of the popover
direction: PopoverDirection.bottomWithLeftAligned,
popupBuilder(BuildContext context) {
return PopoverMenu();
},
);
```
### Trigger the popover manually
Sometimes, if you want to trigger the popover manually, you can use a `PopoverController`.
```dart
class MyWidgetState extends State<GridDateCell> {
late PopoverController _popover;
@override
void initState() {
_popover = PopoverController();
super.initState();
}
// triggered by another widget
_onClick() {
_popover.show();
}
@override
Widget build(BuildContext context) {
return Popover(
controller: _popover,
...
)
}
}
```
### Make several popovers exclusive
The popover has a mechanism to make sure there are only one popover is shown in a group of popovers.
It's called `PopoverMutex`.
If you pass the same mutex object to the popovers, there will be only one popover is triggered.
```dart
class MyWidgetState extends State<GridDateCell> {
final _popoverMutex = PopoverMutex();
@override
Widget build(BuildContext context) {
return Row(
children: [
Popover(
mutex: _popoverMutex,
...
),
Popover(
mutex: _popoverMutex,
...
),
Popover(
mutex: _popoverMutex,
...
),
]
)
}
}
```
## API
| Param | Description | Type |
| -------------- | ---------------------------------------------------------------- | --------------------------------------- |
| offset | The offset between the popover and the child | `Offset` |
| popupBuilder | The function used to build the popover | `Widget Function(BuildContext context)` |
| triggerActions | Define the actions about how to trigger the popover | `int` |
| mutex | If multiple popovers are exclusive, pass the same mutex to them. | `PopoverMutex` |
| direction | The direction where the popover should be placed | `PopoverDirection` |
| onClose | The callback will be called after the popover is closed | `void Function()` |
| child | The child to trigger the popover | `Widget` |

View File

@ -0,0 +1,4 @@
include: package:flutter_lints/flutter.yaml
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,47 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# The .vscode folder contains launch configuration and tasks you configure in
# VS Code which you may wish to be included in version control, so this line
# is commented out by default.
#.vscode/
# Flutter/Dart/Pub related
**/doc/api/
**/ios/Flutter/.last_build_id
.dart_tool/
.flutter-plugins
.flutter-plugins-dependencies
.packages
.pub-cache/
.pub/
/build/
# Web related
lib/generated_plugin_registrant.dart
# Symbolication related
app.*.symbols
# Obfuscation related
app.*.map.json
# Android Studio will place build artifacts here
/android/app/debug
/android/app/profile
/android/app/release

View File

@ -0,0 +1,45 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled.
version:
revision: f1875d570e39de09040c8f79aa13cc56baab8db1
channel: stable
project_type: app
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: android
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: ios
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: linux
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: macos
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: web
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
- platform: windows
create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View File

@ -0,0 +1,16 @@
# example
A new Flutter project.
## Getting Started
This project is a starting point for a Flutter application.
A few resources to get you started if this is your first Flutter project:
- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook)
For help getting started with Flutter development, view the
[online documentation](https://docs.flutter.dev/), which offers tutorials,
samples, guidance on mobile development, and a full API reference.

View File

@ -0,0 +1,29 @@
# This file configures the analyzer, which statically analyzes Dart code to
# check for errors, warnings, and lints.
#
# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
# invoked from the command line by running `flutter analyze`.
# The following line activates a set of recommended lints for Flutter apps,
# packages, and plugins designed to encourage good coding practices.
include: package:flutter_lints/flutter.yaml
linter:
# The lint rules applied to this project can be customized in the
# section below to disable rules from the `package:flutter_lints/flutter.yaml`
# included above or to enable additional rules. A list of all available lints
# and their documentation is published at
# https://dart-lang.github.io/linter/lints/index.html.
#
# Instead of disabling a lint rule for the entire project in the
# section below, it can also be suppressed for a single line of code
# or a specific dart file by using the `// ignore: name_of_lint` and
# `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint.
rules:
# avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
# Additional information about this file can be found at
# https://dart.dev/guides/language/analysis-options

View File

@ -0,0 +1,13 @@
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
# Remember to never publicly share your keystore.
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
key.properties
**/*.keystore
**/*.jks

View File

@ -0,0 +1,71 @@
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
sourceSets {
main.java.srcDirs += 'src/main/kotlin'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.example.example"
// You can update the following values to match your application needs.
// For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration.
minSdkVersion flutter.minSdkVersion
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
}

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,34 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<application
android:label="example"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>

View File

@ -0,0 +1,6 @@
package com.example.example
import io.flutter.embedding.android.FlutterActivity
class MainActivity: FlutterActivity() {
}

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="?android:colorBackground" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" />
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap
android:gravity="center"
android:src="@mipmap/launch_image" />
</item> -->
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 544 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 442 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item>
</style>
</resources>

View File

@ -0,0 +1,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.example">
<!-- The INTERNET permission is required for development. Specifically,
the Flutter tool needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View File

@ -0,0 +1,31 @@
buildscript {
ext.kotlin_version = '1.6.10'
repositories {
google()
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.1.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}

View File

@ -0,0 +1,3 @@
org.gradle.jvmargs=-Xmx1536M
android.useAndroidX=true
android.enableJetifier=true

View File

@ -0,0 +1,6 @@
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View File

@ -0,0 +1,11 @@
include ':app'
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
def properties = new Properties()
assert localPropertiesFile.exists()
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
def flutterSdkPath = properties.getProperty("flutter.sdk")
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"

View File

@ -0,0 +1,34 @@
**/dgph
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/ephemeral/
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>9.0</string>
</dict>
</plist>

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1 @@
#include "Generated.xcconfig"

View File

@ -0,0 +1,481 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 50;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
);
path = Runner;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1300;
ORGANIZATIONNAME = "";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
LastSwiftMigration = 1100;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 9.3";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.example.example;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1300"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>

View File

@ -0,0 +1,13 @@
import UIKit
import Flutter
@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
GeneratedPluginRegistrant.register(with: self)
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
}

View File

@ -0,0 +1,122 @@
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 B

View File

@ -0,0 +1,5 @@
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.

View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
</imageView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="168" height="185"/>
</resources>
</document>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>

View File

@ -0,0 +1,49 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Example</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>example</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(FLUTTER_BUILD_NAME)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(FLUTTER_BUILD_NUMBER)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>

View File

@ -0,0 +1 @@
#import "GeneratedPluginRegistrant.h"

View File

@ -0,0 +1,98 @@
import 'package:flutter/material.dart';
import 'package:appflowy_popover/popover.dart';
class PopoverMenu extends StatefulWidget {
const PopoverMenu({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _PopoverMenuState();
}
class _PopoverMenuState extends State<PopoverMenu> {
final PopoverMutex popOverMutex = PopoverMutex();
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: Container(
width: 200,
height: 200,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: const BorderRadius.all(Radius.circular(8)),
boxShadow: [
BoxShadow(
color: Colors.grey.withOpacity(0.5),
spreadRadius: 5,
blurRadius: 7,
offset: const Offset(0, 3), // changes position of shadow
),
],
),
child: ListView(children: [
Container(
margin: const EdgeInsets.all(8),
child: const Text("Popover",
style: TextStyle(
fontSize: 14,
color: Colors.black,
fontStyle: null,
decoration: null)),
),
Popover(
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
mutex: popOverMutex,
offset: const Offset(10, 0),
popupBuilder: (BuildContext context) {
return const PopoverMenu();
},
child: TextButton(
onPressed: () {},
child: const Text("First"),
),
),
Popover(
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
mutex: popOverMutex,
offset: const Offset(10, 0),
popupBuilder: (BuildContext context) {
return const PopoverMenu();
},
child: TextButton(
onPressed: () {},
child: const Text("Second"),
),
),
]),
));
}
}
class ExampleButton extends StatelessWidget {
final String label;
final Offset? offset;
final PopoverDirection? direction;
const ExampleButton({
Key? key,
required this.label,
this.direction,
this.offset = Offset.zero,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Popover(
triggerActions: PopoverTriggerActionFlags.click,
offset: offset,
direction: direction ?? PopoverDirection.rightWithTopAligned,
child: TextButton(child: Text(label), onPressed: () {}),
popupBuilder: (BuildContext context) {
return const PopoverMenu();
},
);
}
}

View File

@ -0,0 +1,129 @@
import 'package:appflowy_popover/popover.dart';
import 'package:flutter/material.dart';
import "./example_button.dart";
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'AppFlowy Popover Example'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
final String title;
@override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
@override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Row(children: [
Column(children: [
const ExampleButton(
label: "Left top",
offset: Offset(0, 10),
direction: PopoverDirection.bottomWithLeftAligned,
),
Expanded(child: Container()),
const ExampleButton(
label: "Left bottom",
offset: Offset(0, -10),
direction: PopoverDirection.topWithLeftAligned,
),
]),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const ExampleButton(
label: "Top",
offset: Offset(0, 10),
direction: PopoverDirection.bottomWithCenterAligned,
),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: const [
ExampleButton(
label: "Central",
offset: Offset(0, 10),
direction: PopoverDirection.bottomWithCenterAligned,
),
],
),
),
const ExampleButton(
label: "Bottom",
offset: Offset(0, -10),
direction: PopoverDirection.topWithCenterAligned,
),
],
),
),
Column(
children: [
const ExampleButton(
label: "Right top",
offset: Offset(0, 10),
direction: PopoverDirection.bottomWithRightAligned,
),
Expanded(child: Container()),
const ExampleButton(
label: "Right bottom",
offset: Offset(0, -10),
direction: PopoverDirection.topWithRightAligned,
),
],
)
]),
);
}
}

View File

@ -0,0 +1 @@
flutter/ephemeral

Some files were not shown because too many files have changed in this diff Show More