chore: merge remote-tracking branch 'main' into develop (#2530)

* feat: show unscheduled events in calendar toolbar (#2411)

* refactor: use same show row detail function

* fix: adjust popover offset

* feat: show unscheduled events in toolbar

* chore: apply suggestions from Xazin

* refactor: refactor list item into separate widget

---------

Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>

* fix: default include time (#2444)

* fix: default include time

* chore: clarify logic and add comments

* chore: bump version 0.1.4 (#2455)

* chore: Update README.md

Update product screenshots

* fix: wrong day of week (#2468)

* feat: select which properties to show in calendar (#2482)

* feat: improve sidebar item dragged appearance (#2471)

* fix: show delete icon for document icon properly (#2475)

* feat: add hover effect on an event card (#2487)

* chore: delete unncessary openCard method in RowCardContainer

* chore: delete unnessary code and add comment

* chore: update editor v0.1.12 and format the readme (#2489)

* fix: number sort (#2507)

* bump version 0.1.5 (#2506)

* chore: bump version 0.1.5

* fix: could not trigger slash menu after inserting an emoji

* Revert "feat: add hover effect on an event card (#2487)"

This reverts commit f0a4b4b77d.

* feat: add hover effect on an event card

* fix: #2469 duplicated cover

* chore: update changelog.md (#2510)

* chore: Update README.md

Add a screenshot of the calendar view

* fix: some regressions

---------

Co-authored-by: Nathan.fooo <86001920+appflowy@users.noreply.github.com>
Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
Co-authored-by: Annie <anqi.annie.wang@gmail.com>
Co-authored-by: Yijing Huang <hyj891204@gmail.com>
This commit is contained in:
Richard Shiue 2023-05-15 11:18:08 +08:00 committed by GitHub
parent 17a0a79379
commit a978b29748
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 556 additions and 513 deletions

View File

@ -138,7 +138,7 @@ jobs:
job:
- {
target: x86_64-apple-darwin,
os: macos-10.15,
os: macos-11,
extra-build-args: "",
}
steps:

View File

@ -1,5 +1,27 @@
# Release Notes
## Version 0.1.5 - 11/05/2023
### Bug Fixes
- Fix: calendar dates don't match with weekdays.
- Fix: sort numbers in Grid.
## Version 0.1.4 - 04/05/2023
### New features
- Use AppFlowys calendar views to plan and manage tasks and deadlines.
- Writing can be improved with the help of OpenAI.
## Version 0.1.3 - 24/04/2023
### New features
- Launch the official Dark Mode.
- Customize the font color and highlight color by setting a hex color value and an opacity level.
### Bug Fixes
- Fix: the slash menu can be triggered by all other keyboards than English.
- Fix: convert the single asterisk to italic text and the double asterisks to bold text.
## Version 0.1.2 - 03/28/2023
### Bug Fixes

View File

@ -23,9 +23,11 @@ You are in charge of your data and customizations.
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
</p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/200787830-96be260b-d0a0-4152-864e-6730b19095cd.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/190650183-a940f1e0-a2c5-4797-ab3a-56758f6f696c.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664610-fc209a97-815e-4716-af07-d94a859d1907.png" alt="AppFlowy Docs & Notes & Wikis" width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664628-5def2450-914a-4b2d-b907-92b7476b9863.png" alt="AppFlowy Databases for Tasks and Projects" width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664642-22e26c1b-5eae-4635-9aa6-b12ecf1c3c46.png" alt="AppFlowy Kanban Board for To-Dos" width="1000px" /></p>
<p align="center"><img src="https://github.com/AppFlowy-IO/AppFlowy/assets/12026239/6be93d2b-a5c5-48a9-b7cf-c599d5f5140c" alt="AppFlowy Calendars for Plan and Manage Content" width="1000px" /></p>
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664657-dc5291f3-67b0-4a43-a818-640e92735deb.png" alt="AppFlowy OpenAI GPT Writers" width="1000px" /></p>
## User Installation

View File

@ -23,7 +23,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
CURRENT_APP_VERSION = "0.1.3"
CURRENT_APP_VERSION = "0.1.5"
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
PRODUCT_NAME = "AppFlowy"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html

View File

@ -418,7 +418,9 @@
"showWeekNumbers": "Show week numbers",
"showWeekends": "Show weekends",
"firstDayOfWeek": "Start week on",
"layoutDateField": "Layout calendar by"
"layoutDateField": "Layout calendar by",
"noDateTitle": "No Date",
"emptyNoDate": "No unscheduled events"
}
}
}

View File

@ -51,5 +51,6 @@ class CalendarSettingEvent with _$CalendarSettingEvent {
}
enum CalendarSettingAction {
properties,
layout,
}

View File

@ -1,24 +1,23 @@
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/number_card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart';
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../grid/presentation/layout/sizes.dart';
import '../../widgets/row/cells/select_option_cell/extension.dart';
import '../application/calendar_bloc.dart';
import 'calendar_page.dart';
class CalendarDayCard extends StatelessWidget {
final String viewId;
@ -102,7 +101,7 @@ class CalendarDayCard extends StatelessWidget {
);
}
GestureDetector _buildCard(BuildContext context, CalendarDayEvent event) {
Widget _buildCard(BuildContext context, CalendarDayEvent event) {
final styles = <FieldType, CardCellStyle>{
FieldType.Number: NumberCardCellStyle(10),
FieldType.URL: URLCardCellStyle(10),
@ -193,7 +192,12 @@ class CalendarDayCard extends StatelessWidget {
cardData: event.dateFieldId,
isEditing: false,
cellBuilder: cellBuilder,
openCard: (context) => _showRowDetailPage(event, context),
openCard: (context) => showEventDetails(
context: context,
event: event,
viewId: viewId,
rowCache: _rowCache,
),
styleConfiguration: const RowCardStyleConfiguration(
showAccessory: false,
cellPadding: EdgeInsets.zero,
@ -203,44 +207,24 @@ class CalendarDayCard extends StatelessWidget {
onEndEditing: () {},
);
return GestureDetector(
onTap: () => _showRowDetailPage(event, context),
child: MouseRegion(
cursor: SystemMouseCursors.click,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(
color: Theme.of(context).dividerColor,
width: 1.5,
),
),
borderRadius: Corners.s6Border,
),
child: card,
),
return FlowyHover(
style: HoverStyle(
hoverColor: Theme.of(context).colorScheme.tertiaryContainer,
foregroundColorOnHover: Theme.of(context).colorScheme.onBackground,
),
);
}
void _showRowDetailPage(CalendarDayEvent event, BuildContext context) {
final dataController = RowController(
rowId: event.eventId,
viewId: viewId,
rowCache: _rowCache,
);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: GridCellBuilder(
cellCache: _rowCache.cellCache,
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 2),
decoration: BoxDecoration(
border: Border.fromBorderSide(
BorderSide(
color: Theme.of(context).dividerColor,
width: 1.5,
),
),
dataController: dataController,
);
},
borderRadius: Corners.s6Border,
),
child: card,
),
);
}

View File

@ -9,6 +9,7 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/row/row_cache.dart';
import '../../application/row/row_data_controller.dart';
import '../../widgets/row/cell_builder.dart';
import '../../widgets/row/row_detail.dart';
@ -76,7 +77,12 @@ class _CalendarPageState extends State<CalendarPage> {
listenWhen: (p, c) => p.editEvent != c.editEvent,
listener: (context, state) {
if (state.editEvent != null) {
_showEditEventPage(state.editEvent!.event!, context);
showEventDetails(
context: context,
event: state.editEvent!.event!,
viewId: widget.view.id,
rowCache: _calendarBloc.rowCache,
);
}
},
),
@ -165,8 +171,9 @@ class _CalendarPageState extends State<CalendarPage> {
}
Widget _headerWeekDayBuilder(day) {
// incoming day starts from Monday, the symbols start from Sunday
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
final weekDayString = symbols.WEEKDAYS[day];
final weekDayString = symbols.WEEKDAYS[(day + 1) % 7];
return Center(
child: Padding(
padding: CalendarSize.daysOfWeekInsets,
@ -210,27 +217,32 @@ class _CalendarPageState extends State<CalendarPage> {
}
WeekDays _weekdayFromInt(int dayOfWeek) {
// MonthView places the first day of week on the second column for some reason.
return WeekDays.values[(dayOfWeek + 1) % 7];
}
void _showEditEventPage(CalendarDayEvent event, BuildContext context) {
final dataController = RowController(
rowId: event.eventId,
viewId: widget.view.id,
rowCache: _calendarBloc.rowCache,
);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: GridCellBuilder(
cellCache: _calendarBloc.rowCache.cellCache,
),
dataController: dataController,
);
},
);
// dayOfWeek starts from Sunday, WeekDays starts from Monday
return WeekDays.values[(dayOfWeek - 1) % 7];
}
}
void showEventDetails({
required BuildContext context,
required CalendarDayEvent event,
required String viewId,
required RowCache rowCache,
}) {
final dataController = RowController(
rowId: event.eventId,
viewId: viewId,
rowCache: rowCache,
);
FlowyOverlay.show(
context: context,
builder: (BuildContext context) {
return RowDetailPage(
cellBuilder: GridCellBuilder(
cellCache: rowCache.cellCache,
),
dataController: dataController,
);
},
);
}

View File

@ -216,6 +216,7 @@ class LayoutDateField extends StatelessWidget {
direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex,
offset: const Offset(-16, 0),
popupBuilder: (context) {
return BlocProvider(
create: (context) => getIt<DatabasePropertyBloc>(
@ -236,9 +237,9 @@ class LayoutDateField extends StatelessWidget {
onUpdated(fieldInfo.id);
popoverMutex.close();
},
leftIcon: svgWidget('grid/field/date'),
leftIcon: const FlowySvg(name: 'grid/field/date'),
rightIcon: fieldInfo.id == fieldId
? svgWidget('grid/checkmark')
? const FlowySvg(name: 'grid/checkmark')
: null,
),
);
@ -332,12 +333,13 @@ class FirstDayOfWeek extends StatelessWidget {
direction: PopoverDirection.leftWithTopAligned,
constraints: BoxConstraints.loose(const Size(300, 400)),
mutex: popoverMutex,
offset: const Offset(-16, 0),
popupBuilder: (context) {
final symbols =
DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
// starts from sunday
final items = symbols.WEEKDAYS.asMap().entries.map((entry) {
final index = (entry.key - 1) % 7;
final index = entry.key;
final string = entry.value;
return SizedBox(
height: GridSize.popoverItemHeight,
@ -347,8 +349,9 @@ class FirstDayOfWeek extends StatelessWidget {
onUpdated(index);
popoverMutex.close();
},
rightIcon:
firstDayOfWeek == index ? svgWidget('grid/checkmark') : null,
rightIcon: firstDayOfWeek == index
? const FlowySvg(name: 'grid/checkmark')
: null,
),
);
}).toList();

View File

@ -2,8 +2,10 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@ -38,6 +40,11 @@ class CalendarSetting extends StatelessWidget {
final CalendarSettingAction? action =
state.selectedAction.foldLeft(null, (previous, action) => action);
switch (action) {
case CalendarSettingAction.properties:
return GridPropertyList(
viewId: settingContext.viewId,
fieldController: settingContext.fieldController,
);
case CalendarSettingAction.layout:
return CalendarLayoutSetting(
onUpdated: onUpdated,
@ -78,9 +85,16 @@ class AllCalendarSettings extends StatelessWidget {
}
Widget _settingItem(BuildContext context, CalendarSettingAction action) {
Widget? icon;
if (action.iconName() != null) {
icon = FlowySvg(
name: action.iconName()!,
);
}
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyButton(
leftIcon: icon,
text: FlowyText.medium(action.title()),
onTap: () {
context
@ -93,8 +107,19 @@ class AllCalendarSettings extends StatelessWidget {
}
extension _SettingExtension on CalendarSettingAction {
String? iconName() {
switch (this) {
case CalendarSettingAction.properties:
return 'grid/setting/properties';
case CalendarSettingAction.layout:
return null;
}
}
String title() {
switch (this) {
case CalendarSettingAction.properties:
return LocaleKeys.grid_settings_Properties.tr();
case CalendarSettingAction.layout:
return LocaleKeys.grid_settings_layout.tr();
}

View File

@ -1,6 +1,8 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:calendar_view/calendar_view.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -19,7 +21,8 @@ class CalendarToolbar extends StatelessWidget {
height: 40,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
children: const [
_UnscheduleEventsButton(),
_SettingButton(),
],
),
@ -28,25 +31,22 @@ class CalendarToolbar extends StatelessWidget {
}
class _SettingButton extends StatefulWidget {
const _SettingButton({Key? key}) : super(key: key);
@override
State<StatefulWidget> createState() => _SettingButtonState();
}
class _SettingButtonState extends State<_SettingButton> {
late PopoverController popoverController;
@override
void initState() {
popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
return AppFlowyPopover(
controller: popoverController,
direction: PopoverDirection.bottomWithRightAligned,
triggerActions: PopoverTriggerFlags.none,
constraints: BoxConstraints.loose(const Size(300, 400)),
margin: EdgeInsets.zero,
child: FlowyTextButton(
@ -54,7 +54,6 @@ class _SettingButtonState extends State<_SettingButton> {
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
onPressed: () => popoverController.show(),
),
popupBuilder: (BuildContext popoverContext) {
final bloc = context.watch<CalendarBloc>();
@ -81,3 +80,100 @@ class _SettingButtonState extends State<_SettingButton> {
);
}
}
class _UnscheduleEventsButton extends StatefulWidget {
const _UnscheduleEventsButton({Key? key}) : super(key: key);
@override
State<_UnscheduleEventsButton> createState() =>
_UnscheduleEventsButtonState();
}
class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> {
late final PopoverController _controller;
@override
void initState() {
super.initState();
_controller = PopoverController();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<CalendarBloc, CalendarState>(
builder: (context, state) {
final unscheduledEvents = state.allEvents
.where((e) => e.date == DateTime.fromMillisecondsSinceEpoch(0))
.toList();
final viewId = context.read<CalendarBloc>().viewId;
final rowCache = context.read<CalendarBloc>().rowCache;
return AppFlowyPopover(
direction: PopoverDirection.bottomWithCenterAligned,
controller: _controller,
offset: const Offset(0, 8),
child: FlowyTextButton(
"${LocaleKeys.calendar_settings_noDateTitle.tr()} (${unscheduledEvents.length})",
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
),
popupBuilder: (context) {
if (unscheduledEvents.isEmpty) {
return SizedBox(
height: GridSize.popoverItemHeight,
child: Center(
child: FlowyText.medium(
LocaleKeys.calendar_settings_emptyNoDate.tr(),
color: Theme.of(context).hintColor,
),
),
);
}
return ListView.separated(
itemBuilder: (context, index) => _UnscheduledEventItem(
event: unscheduledEvents[index],
onPressed: () {
showEventDetails(
context: context,
event: unscheduledEvents[index].event!,
viewId: viewId,
rowCache: rowCache,
);
_controller.close();
},
),
itemCount: unscheduledEvents.length,
separatorBuilder: (context, index) =>
VSpace(GridSize.typeOptionSeparatorHeight),
shrinkWrap: true,
);
},
);
},
);
}
}
class _UnscheduledEventItem extends StatelessWidget {
final CalendarEventData<CalendarDayEvent> event;
final VoidCallback onPressed;
const _UnscheduledEventItem({
required this.event,
required this.onPressed,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
height: GridSize.popoverItemHeight,
child: FlowyTextButton(
event.title,
fillColor: Colors.transparent,
hoverColor: AFThemeExtension.of(context).lightGreyHover,
padding: GridSize.typeOptionContentInsets,
onPressed: onPressed,
),
);
}
}

View File

@ -43,6 +43,7 @@ class RowCardContainer extends StatelessWidget {
}
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => openCard(context),
child: Padding(
padding: const EdgeInsets.all(8),

View File

@ -75,7 +75,7 @@ class DateCellCalendarBloc
String? time,
bool? includeTime,
}) async {
// make sure date and time are not updated together from the UI
// make sure that not both date and time are updated at the same time
assert(
date == null && time == null ||
date == null && time != null ||
@ -83,7 +83,7 @@ class DateCellCalendarBloc
);
String? newTime = time ?? state.time;
DateTime? newDate = date;
DateTime? newDate = _utcToLocalAddTime(date);
if (time != null && time.isNotEmpty) {
newDate = state.dateTime ?? DateTime.now();
}
@ -122,6 +122,24 @@ class DateCellCalendarBloc
);
}
DateTime? _utcToLocalAddTime(DateTime? date) {
if (date == null) {
return null;
}
final now = DateTime.now();
// the incoming date is Utc. this trick converts it into Local
// and add the current time, though the time may be overwritten by
// explicitly provided time string
return DateTime(
date.year,
date.month,
date.day,
now.hour,
now.minute,
now.second,
);
}
String timeFormatPrompt(FlowyError error) {
String msg = "${LocaleKeys.grid_field_invalidTimeFormat.tr()}.";
switch (state.dateTypeOptionPB.timeFormat) {

View File

@ -115,8 +115,11 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
AnimatedSwitcher(
duration: const Duration(milliseconds: 300),
child: state.includeTime
? _TimeTextField(popoverMutex: popoverMutex)
: const SizedBox(),
? _TimeTextField(
timeStr: state.time,
popoverMutex: popoverMutex,
)
: const SizedBox.shrink(),
),
const TypeOptionSeparator(spacing: 12.0),
const _IncludeTimeButton(),
@ -265,9 +268,11 @@ class _IncludeTimeButton extends StatelessWidget {
}
class _TimeTextField extends StatefulWidget {
final String? timeStr;
final PopoverMutex popoverMutex;
const _TimeTextField({
required this.timeStr,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@ -278,10 +283,12 @@ class _TimeTextField extends StatefulWidget {
class _TimeTextFieldState extends State<_TimeTextField> {
late final FocusNode _focusNode;
late final TextEditingController _textController;
@override
void initState() {
_focusNode = FocusNode();
_textController = TextEditingController()..text = widget.timeStr ?? "";
_focusNode.addListener(() {
if (_focusNode.hasFocus) {
@ -300,7 +307,8 @@ class _TimeTextFieldState extends State<_TimeTextField> {
@override
Widget build(BuildContext context) {
return BlocBuilder<DateCellCalendarBloc, DateCellCalendarState>(
return BlocConsumer<DateCellCalendarBloc, DateCellCalendarState>(
listener: (context, state) => _textController.text = state.time ?? "",
builder: (context, state) {
return Column(
children: [
@ -310,13 +318,14 @@ class _TimeTextFieldState extends State<_TimeTextField> {
child: FlowyTextField(
text: state.time ?? "",
focusNode: _focusNode,
controller: _textController,
submitOnLeave: true,
hintText: state.timeHintText,
errorText: state.timeFormatError,
onSubmitted: (timeString) {
onSubmitted: (timeStr) {
context
.read<DateCellCalendarBloc>()
.add(DateCellCalendarEvent.setTime(timeString));
.add(DateCellCalendarEvent.setTime(timeStr));
},
),
),

View File

@ -226,6 +226,9 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
if (temporaryNodeTypes.contains(node.type)) {
transaction.deleteNode(node);
}
if (kCoverType == node.type && !node.path.equals([0])) {
transaction.deleteNode(node);
}
}
if (transaction.operations.isNotEmpty) {
await editorState.apply(transaction, withUpdateCursor: false);

View File

@ -1,11 +1,10 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/default_emoji_picker_view.dart';
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/emoji_view_state.dart';
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart';
class EmojiPopover extends StatefulWidget {
@ -33,57 +32,53 @@ class _EmojiPopoverState extends State<EmojiPopover> {
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(15),
child: EmojiPicker(
onEmojiSelected: (category, emoji) {
widget.onEmojiChanged(emoji);
},
customWidget: (Config config, EmojiViewState state) {
return Stack(
alignment: Alignment.topRight,
children: [
Container(
padding: EdgeInsets.only(top: widget.showRemoveButton ? 25 : 0),
child: DefaultEmojiPickerView(config, state),
),
_buildDeleteButtonIfNeed(),
],
);
},
config: Config(
columns: 8,
emojiSizeMax: 28,
bgColor: Colors.transparent,
iconColor: Theme.of(context).iconTheme.color!,
iconColorSelected: Theme.of(context).colorScheme.onSurface,
selectedHoverColor: Theme.of(context).colorScheme.secondary,
progressIndicatorColor: Theme.of(context).iconTheme.color!,
buttonMode: ButtonMode.CUPERTINO,
initCategory: Category.RECENT,
),
),
);
}
Widget _buildDeleteButtonIfNeed() {
if (!widget.showRemoveButton) {
return const SizedBox();
}
return FlowyButton(
onTap: () => widget.removeIcon(),
useIntrinsicWidth: true,
text: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
child: Column(
children: [
const FlowySvg(name: 'editor/delete'),
const SizedBox(
width: 5,
),
FlowyText(
LocaleKeys.document_plugins_cover_removeIcon.tr(),
if (widget.showRemoveButton)
Padding(
padding: const EdgeInsets.only(bottom: 4.0),
child: Align(
alignment: Alignment.centerRight,
child: DeleteButton(onPressed: widget.removeIcon),
),
),
Expanded(
child: EmojiPicker(
onEmojiSelected: (category, emoji) {
widget.onEmojiChanged(emoji);
},
config: Config(
columns: 8,
emojiSizeMax: 28,
bgColor: Colors.transparent,
iconColor: Theme.of(context).iconTheme.color!,
iconColorSelected: Theme.of(context).colorScheme.onSurface,
selectedHoverColor: Theme.of(context).colorScheme.secondary,
progressIndicatorColor: Theme.of(context).iconTheme.color!,
buttonMode: ButtonMode.CUPERTINO,
initCategory: Category.RECENT,
),
),
),
],
),
);
}
}
class DeleteButton extends StatelessWidget {
final VoidCallback onPressed;
const DeleteButton({required this.onPressed, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return FlowyButton(
onTap: () => onPressed,
useIntrinsicWidth: true,
text: FlowyText(
LocaleKeys.document_plugins_cover_removeIcon.tr(),
),
leftIcon: const FlowySvg(name: 'editor/delete'),
);
}
}

View File

@ -29,29 +29,32 @@ void _showEmojiSelectionMenu(
menuService.dismiss();
_emojiSelectionMenu?.remove();
_emojiSelectionMenu = OverlayEntry(builder: (context) {
return Positioned(
top: alignment == Alignment.bottomLeft ? offset.dy : null,
bottom: alignment == Alignment.topLeft ? offset.dy : null,
left: offset.dx,
child: Material(
child: EmojiSelectionMenu(
editorState: editorState,
onSubmitted: (text) {
// insert emoji
editorState.insertEmoji(text);
},
onExit: () {
_dismissEmojiSelectionMenu();
//close emoji panel
},
_emojiSelectionMenu = OverlayEntry(
builder: (context) {
return Positioned(
top: alignment == Alignment.bottomLeft ? offset.dy : null,
bottom: alignment == Alignment.topLeft ? offset.dy : null,
left: offset.dx,
child: Material(
child: EmojiSelectionMenu(
editorState: editorState,
onSubmitted: (text) {
// insert emoji
editorState.insertEmoji(text);
},
onExit: () {
_dismissEmojiSelectionMenu();
//close emoji panel
},
),
),
),
);
},);
);
},
);
Overlay.of(context).insert(_emojiSelectionMenu!);
_editorState = editorState;
editorState.service.selectionService.currentSelection
.addListener(_dismissEmojiSelectionMenu);
}
@ -62,6 +65,7 @@ void _dismissEmojiSelectionMenu() {
_editorState?.service.selectionService.currentSelection
.removeListener(_dismissEmojiSelectionMenu);
_editorState?.service.keyboardService?.enable();
_editorState = null;
}

View File

@ -61,6 +61,10 @@ class ViewSection extends StatelessWidget {
.add(ViewSectionEvent.moveView(oldIndex, index));
},
ignorePrimaryScrollController: true,
buildDraggableFeedback: (context, constraints, child) => ConstrainedBox(
constraints: constraints,
child: Material(color: Colors.transparent, child: child),
),
children: children,
);
}

View File

@ -149,6 +149,8 @@ class HomeMenu extends StatelessWidget {
),
);
},
proxyDecorator: (child, index, animation) =>
Material(color: Colors.transparent, child: child),
);
},
),

View File

@ -592,6 +592,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
ONLY_ACTIVE_ARCH = YES;
PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.macos;
PRODUCT_NAME = AppFlowy;
PROVISIONING_PROFILE_SPECIFIER = "";

View File

@ -132,6 +132,21 @@ class FlowyHoverContainer extends StatelessWidget {
width: style.borderWidth,
);
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final iconTheme = theme.iconTheme;
// override text's theme with foregroundColorOnHover when it is hovered
final hoverTheme = theme.copyWith(
textTheme: textTheme.copyWith(
bodyMedium: textTheme.bodyMedium?.copyWith(
color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
),
),
iconTheme: iconTheme.copyWith(
color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
),
);
return Container(
margin: style.contentMargin,
decoration: BoxDecoration(
@ -139,19 +154,8 @@ class FlowyHoverContainer extends StatelessWidget {
color: style.hoverColor ?? Theme.of(context).colorScheme.secondary,
borderRadius: style.borderRadius,
),
child:
//override text's theme with foregroundColorOnHover when it is hovered
Theme(
data: Theme.of(context).copyWith(
textTheme: Theme.of(context).textTheme.copyWith(
bodyMedium: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: style.foregroundColorOnHover ??
Theme.of(context).colorScheme.onSurface),
),
iconTheme: Theme.of(context).iconTheme.copyWith(
color: style.foregroundColorOnHover ??
Theme.of(context).colorScheme.onSurface),
),
child: Theme(
data: hoverTheme,
child: child,
),
);

View File

@ -45,10 +45,10 @@ packages:
dependency: "direct main"
description:
name: appflowy_editor
sha256: a1dbca3d7d33f4669f1d44bfa1e06b6646f46726c219921b8a59d9ee22bf252d
sha256: "6f7d2b0b54ca1049cb396229549d228b5bbd7ea6d09f1f7325a20db2d7586a5f"
url: "https://pub.dev"
source: hosted
version: "0.1.9"
version: "0.1.12"
appflowy_popover:
dependency: "direct main"
description:

View File

@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.1.2
version: 0.1.5
environment:
sdk: ">=2.19.0 <3.0.0"
@ -42,7 +42,7 @@ dependencies:
git:
url: https://github.com/AppFlowy-IO/appflowy-board.git
ref: a183c57
appflowy_editor: ^0.1.9
appflowy_editor: ^0.1.12
appflowy_popover:
path: packages/appflowy_popover

View File

@ -216,7 +216,17 @@ impl TypeOptionCellDataCompare for NumberTypeOptionPB {
cell_data: &<Self as TypeOption>::CellData,
other_cell_data: &<Self as TypeOption>::CellData,
) -> Ordering {
cell_data.0.cmp(&other_cell_data.0)
let left = NumberCellData::from_format_str(&cell_data.0, self.sign_positive, &self.format);
let right =
NumberCellData::from_format_str(&other_cell_data.0, self.sign_positive, &self.format);
match (left, right) {
(Ok(left), Ok(right)) => {
return left.decimal().cmp(&right.decimal());
},
(Ok(_), Err(_)) => Ordering::Greater,
(Err(_), Ok(_)) => Ordering::Less,
(Err(_), Err(_)) => Ordering::Equal,
}
}
}
impl std::default::Default for NumberTypeOptionPB {

View File

@ -161,7 +161,7 @@ pub fn make_test_grid() -> BuildDatabaseContext {
for field_type in FieldType::iter() {
match field_type {
FieldType::RichText => row_builder.insert_text_cell("DA"),
FieldType::Number => row_builder.insert_number_cell("4"),
FieldType::Number => row_builder.insert_number_cell("14"),
FieldType::DateTime => row_builder.insert_date_cell("1668704685"),
FieldType::SingleSelect => {
row_builder.insert_single_select_cell(|mut options| options.remove(0))

View File

@ -212,7 +212,7 @@ async fn sort_number_by_descending_test() {
let scripts = vec![
AssertCellContentOrder {
field_id: number_field.id.clone(),
orders: vec!["$1", "$2", "$3", "$4", "", "$5"],
orders: vec!["$1", "$2", "$3", "$14", "", "$5"],
},
InsertSort {
field_rev: number_field.clone(),
@ -220,7 +220,7 @@ async fn sort_number_by_descending_test() {
},
AssertCellContentOrder {
field_id: number_field.id.clone(),
orders: vec!["$5", "$4", "$3", "$2", "$1", ""],
orders: vec!["$14", "$5", "$3", "$2", "$1", ""],
},
];
test.run_scripts(scripts).await;

View File

@ -2,175 +2,78 @@
"document": {
"type": "editor",
"children": [
{ "type": "cover" },
{
"type": "text",
"attributes": {
"subtype": "heading",
"heading": "h1"
},
"attributes": { "subtype": "heading", "heading": "h1" },
"delta": [{ "insert": "Welcome to AppFlowy!" }]
},
{
"type": "text",
"attributes": { "subtype": "heading", "heading": "h2" },
"delta": [{ "insert": "Here are the basics" }]
},
{
"type": "text",
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [{ "insert": "Click anywhere and just start typing." }]
},
{
"type": "text",
"attributes": { "subtype": "checkbox", "checkbox": false },
"delta": [
{
"insert": "🌟 Welcome to AppFlowy!"
}
"insert": "Highlight ",
"attributes": { "backgroundColor": "0x4dffeb3b" }
},
{ "insert": "any text, and use the editing menu to " },
{ "insert": "style", "attributes": { "italic": true } },
{ "insert": " " },
{ "insert": "your", "attributes": { "bold": true } },
{ "insert": " " },
{ "insert": "writing", "attributes": { "underline": true } },
{ "insert": " " },
{ "insert": "however", "attributes": { "code": true } },
{ "insert": " you " },
{ "insert": "like.", "attributes": { "strikethrough": true } }
]
},
{
"type": "text",
"attributes": {
"subtype": "heading",
"heading": "h2"
},
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [
{
"insert": "Here are the basics"
}
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"delta": [
{
"insert": "Click anywhere and just start typing."
}
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"delta": [
{
"insert": "Highlight",
"attributes": {
"backgroundColor": "0x6000BCF0"
}
},
{
"insert": " any text, and use the editing menu to "
},
{
"insert": "style",
"attributes": {
"italic": true
}
},
{
"insert": " "
},
{
"insert": "your",
"attributes": {
"bold": true
}
},
{
"insert": " "
},
{
"insert": "writing",
"attributes": {
"underline": true
}
},
{
"insert": " "
},
{
"insert": "however",
"attributes": {
"code": true
}
},
{
"insert": " you "
},
{
"insert": "like.",
"attributes": {
"strikethrough": true
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"delta": [
{
"insert": "As soon as you type "
},
{ "insert": "As soon as you type " },
{
"insert": "/",
"attributes": {
"code": true
}
"attributes": { "code": true, "color": "0xff00b5ff" }
},
{ "insert": " a menu will pop up. Select " },
{
"insert": " a menu will pop up. Select different types of content blocks you can add."
}
"insert": "different types",
"attributes": { "backgroundColor": "0x4d9c27b0" }
},
{ "insert": " of content blocks you can add." }
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [
{
"insert": "Type "
},
{
"insert": "/",
"attributes": {
"code": true
}
},
{
"insert": " followed by "
},
{
"insert": "/bullet",
"attributes": {
"code": true
}
},
{
"insert": " or "
},
{
"insert": "/c.",
"attributes": {
"code": true
}
}
{ "insert": "Type " },
{ "insert": "/", "attributes": { "code": true } },
{ "insert": " followed by " },
{ "insert": "/bullet", "attributes": { "code": true } },
{ "insert": " or " },
{ "insert": "/num", "attributes": { "code": true } },
{ "insert": " to create a list.", "attributes": { "code": false } }
]
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": true
},
"attributes": { "subtype": "checkbox", "checkbox": true },
"delta": [
{
"insert": "Click "
},
{
"insert": "+ New Page ",
"attributes": {
"code": true
}
},
{ "insert": "Click " },
{ "insert": "+ New Page ", "attributes": { "code": true } },
{
"insert": "button at the bottom of your sidebar to add a new page."
}
@ -178,32 +81,24 @@
},
{
"type": "text",
"attributes": {
"subtype": "checkbox",
"checkbox": null
},
"attributes": { "subtype": "checkbox", "checkbox": null },
"delta": [
{
"insert": "Click "
},
{
"insert": "+",
"attributes": {
"code": true
}
},
{
"insert": " next to any page title in the sidebar to quickly add a new subpage."
}
{ "insert": "Click " },
{ "insert": "+", "attributes": { "code": true } },
{ "insert": " next to any page title in the sidebar to " },
{ "insert": "quickly", "attributes": { "color": "0xff8427e0" } },
{ "insert": " add a new subpage, " },
{ "insert": "Document", "attributes": { "code": true } },
{ "insert": ", ", "attributes": { "code": false } },
{ "insert": "Grid", "attributes": { "code": true } },
{ "insert": ", or ", "attributes": { "code": false } },
{ "insert": "Kanban Board", "attributes": { "code": true } },
{ "insert": ".", "attributes": { "code": false } }
]
},
{
"type": "text",
"attributes": {
"checkbox": null
},
"delta": []
},
{ "type": "text", "delta": [] },
{ "type": "divider" },
{ "type": "text", "attributes": { "checkbox": null }, "delta": [] },
{
"type": "text",
"attributes": {
@ -211,11 +106,7 @@
"checkbox": null,
"heading": "h2"
},
"delta": [
{
"insert": "Markdown"
}
]
"delta": [{ "insert": "Keyboard shortcuts, markdown, and code block" }]
},
{
"type": "text",
@ -225,98 +116,64 @@
"heading": null
},
"delta": [
{ "insert": "Keyboard shortcuts " },
{
"insert": "Heading "
}
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 2
},
"delta": [
{
"insert": "bold text",
"insert": "guide",
"attributes": {
"bold": true,
"defaultFormatting": true
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 3
},
"delta": [
{
"insert": "italicized text",
"attributes": {
"italic": true
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 4,
"number-list": null
},
"delta": [
{
"insert": "Ordered List"
}
]
},
{
"type": "text",
"attributes": {
"number": 5,
"subtype": "number-list"
},
"delta": [
{
"insert": "code",
"attributes": {
"code": true
}
}
]
},
{
"type": "text",
"attributes": {
"number": 6,
"subtype": "number-list"
},
"delta": [
{
"insert": "Strikethrough",
"attributes": {
"strikethrough": true
"href": "https://appflowy.gitbook.io/docs/essential-documentation/shortcuts"
}
},
{ "retain": 1, "attributes": { "strikethrough": true } }
]
},
{
"type": "text",
"attributes": {
"subtype": "number-list",
"number": 2,
"heading": null
},
"delta": [
{ "insert": "Markdown " },
{
"retain": 1,
"insert": "reference",
"attributes": {
"strikethrough": true
"href": "https://appflowy.gitbook.io/docs/essential-documentation/markdown"
}
},
{ "retain": 1, "attributes": { "strikethrough": true } }
]
},
{
"type": "text",
"attributes": { "number": 3, "subtype": "number-list" },
"delta": [
{ "insert": "Type " },
{ "insert": "/code", "attributes": { "code": true } },
{
"insert": " to insert a code block",
"attributes": { "code": false }
}
]
},
{
"type": "text",
"attributes": {
"checkbox": null
"subtype": "code_block",
"number": 3,
"heading": null,
"number-list": null,
"theme": "vs",
"language": "rust"
},
"delta": []
"delta": [
{
"insert": "// This is the main function.\nfn main() {\n // Print text to the console.\n println!(\"Hello World!\");\n}"
},
{ "retain": 1, "attributes": { "strikethrough": true } }
]
},
{ "type": "text", "attributes": { "checkbox": null }, "delta": [] },
{
"type": "text",
"attributes": {
@ -324,94 +181,72 @@
"checkbox": null,
"heading": "h2"
},
"delta": [
{
"insert": "Have a question?"
}
]
"delta": [{ "insert": "Have a question❓" }]
},
{
"type": "text",
"attributes": {
"subtype": "quote"
},
"attributes": { "subtype": "quote" },
"delta": [
{
"insert": "Click "
},
{
"insert": "?",
"attributes": {
"code": true
}
},
{
"insert": " at the bottom right for help and support."
}
{ "insert": "Click " },
{ "insert": "?", "attributes": { "code": true } },
{ "insert": " at the bottom right for help and support." }
]
},
{ "type": "text", "delta": [] },
{
"type": "callout",
"children": [
{ "type": "text", "delta": [] },
{
"type": "text",
"attributes": { "subtype": "heading", "heading": "h2" },
"delta": [{ "insert": "Like AppFlowy? Follow us:" }]
},
{
"type": "text",
"attributes": { "subtype": "bulleted-list" },
"delta": [
{
"insert": "GitHub",
"attributes": {
"href": "https://github.com/AppFlowy-IO/AppFlowy"
}
}
]
},
{
"type": "text",
"attributes": { "subtype": "bulleted-list" },
"delta": [
{
"insert": "Twitter",
"attributes": { "href": "https://twitter.com/appflowy" }
},
{ "insert": ": @appflowy" }
]
},
{
"type": "text",
"attributes": { "subtype": "bulleted-list" },
"delta": [
{
"insert": "Newsletter",
"attributes": { "href": "https://blog-appflowy.ghost.io/" }
}
]
}
],
"attributes": { "emoji": "🥰" }
},
{ "type": "text", "delta": [] },
{
"type": "text",
"attributes": { "subtype": null, "heading": null },
"delta": []
},
{
"type": "text",
"attributes": {
"subtype": "heading",
"heading": "h2"
},
"delta": [
{
"insert": "Like AppFlowy? Follow us:"
}
]
},
{
"type": "text",
"attributes": {
"subtype": "bulleted-list",
"quote": null
},
"delta": [
{
"insert": "GitHub",
"attributes": {
"href": "https://github.com/AppFlowy-IO/AppFlowy"
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": "bulleted-list"
},
"delta": [
{
"insert": "Twitter: @appflowy"
}
]
},
{
"type": "text",
"attributes": {
"subtype": "bulleted-list"
},
"delta": [
{
"insert": "Newsletter",
"attributes": {
"href": "https://blog-appflowy.ghost.io/"
}
}
]
},
{
"type": "text",
"attributes": {
"subtype": null,
"heading": null
},
"attributes": { "subtype": null, "heading": null },
"delta": []
}
]

View File

@ -23,6 +23,11 @@ args[1]: The appflowy version to be built (github ref_name).
assert(await repositoryRoot.exists(),
'$repositoryRoot is an invalid directory. Please try again with a valid directory.\n\n$help');
final appVersion = args[1];
await _BuildTool(repositoryRoot: repositoryRoot.path, appVersion: appVersion)
.run();
String? arch;
if (args.length > 2) arch = args[2];
await _BuildTool(
repositoryRoot: repositoryRoot.path,
appVersion: appVersion,
arch: arch,
).run();
}

View File

@ -14,10 +14,12 @@ class _BuildTool {
const _BuildTool({
required this.repositoryRoot,
required this.appVersion,
this.arch,
});
final String repositoryRoot;
final String appVersion;
final String? arch;
String get projectRoot =>
[repositoryRoot, 'appflowy_flutter'].join(Platform.pathSeparator);
@ -30,8 +32,9 @@ class _BuildTool {
Future<String> get _commandForOS async {
// Check the operating system and CPU architecture
var os = Platform.operatingSystem;
var arch = Platform.isMacOS ? await _architecture : Platform.localHostname;
final os = Platform.operatingSystem;
final arch = this.arch ??
(Platform.isMacOS ? await _architecture : Platform.localHostname);
// Determine the appropriate command based on the OS and architecture
if (os == 'windows') {

View File

@ -1,6 +1,7 @@
[Setup]
AppName=AppFlowy
AppVersion={#AppVersion}
AppPublisher=AppFlowy-IO
WizardStyle=modern
Compression=lzma2
SolidCompression=yes
@ -9,8 +10,8 @@ DefaultGroupName=AppFlowy
SetupIconFile=flowy_logo.ico
UninstallDisplayIcon={app}\AppFlowy.exe
UninstallDisplayName=AppFlowy
AppPublisher=AppFlowy-IO
VersionInfoVersion={#AppVersion}
UsePreviousAppDir=no
[Files]
Source: "AppFlowy\AppFlowy.exe";DestDir: "{app}";DestName: "AppFlowy.exe"
@ -18,4 +19,5 @@ Source: "AppFlowy\*";DestDir: "{app}"
Source: "AppFlowy\data\*";DestDir: "{app}\data\"; Flags: recursesubdirs
[Icons]
Name: "{group}\AppFlowy";Filename: "{app}\AppFlowy.exe"
Name: "{userdesktop}\AppFlowy"; Filename: "{app}\AppFlowy.exe"
Name: "{group}\AppFlowy"; Filename: "{app}\AppFlowy.exe"