Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
966547faa0 | ||
|
|
3647af44c9 | ||
|
|
25b4a647b0 | ||
|
|
c6e3c1fd7c | ||
|
|
4fd35d0de4 | ||
|
|
7406c5e6a5 | ||
|
|
aa70885b21 | ||
|
|
8699c9c125 | ||
|
|
bc758e0b06 | ||
|
|
e38980ccc3 | ||
|
|
8dd180046f | ||
|
|
0cd2726752 | ||
|
|
11daa15b89 | ||
|
|
8436bc6144 | ||
|
|
59851f46b7 | ||
|
|
62f1d6eb82 | ||
|
|
ec80f72c8f | ||
|
|
b2c80981cc | ||
|
|
8fa425a76c | ||
|
|
ebe112581d | ||
|
|
bc8f35d7db | ||
|
|
058eeec932 | ||
|
|
8e10cba8e5 | ||
|
|
7a24f6b6f0 | ||
|
|
4f40945458 | ||
|
|
e59870e421 | ||
|
|
cf521fe20b | ||
|
|
f0d47ee50c | ||
|
|
2a5d393c9f | ||
|
|
98e0b832ec | ||
|
|
77e7cbe9ec | ||
|
|
acd53eb640 |
No files matched your search
@@ -49,6 +49,15 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
# the following step is required to avoid running out of space
|
||||
- name: Maximize build space
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf "/usr/local/share/boost"
|
||||
sudo rm -rf "$AGENT_TOOLSDIRECTORY"
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
@@ -45,6 +45,15 @@ jobs:
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
# the following step is required to avoid running out of space
|
||||
- name: Maximize build space
|
||||
if: matrix.os == 'ubuntu-latest'
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/share/boost
|
||||
df -h
|
||||
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
|
||||
@@ -60,8 +60,8 @@ jobs:
|
||||
echo SUPABASE_ANON_KEY=${{ secrets.SUPABASE_ANON_KEY }} >> .env.ci
|
||||
echo SUPABASE_JWT_SECRET=${{ secrets.SUPABASE_JWT_SECRET }} >> .env.ci
|
||||
|
||||
- name: Create .env file in flowy-test
|
||||
working-directory: frontend/rust-lib/flowy-test
|
||||
- name: Create .env file in event-integration
|
||||
working-directory: frontend/rust-lib/event-integration
|
||||
run: |
|
||||
touch .env.ci
|
||||
echo SUPABASE_URL=${{ secrets.SUPABASE_URL }} >> .env.ci
|
||||
|
||||
@@ -1,5 +1,20 @@
|
||||
# Release Notes
|
||||
|
||||
## Version 0.3.6 - 10/16/2023
|
||||
|
||||
### New Features
|
||||
- Support setting Markdown styles through keyboard shortcuts.
|
||||
- Added Ukrainian language.
|
||||
- Support auto-hiding sidebar feature, ensuring a streamlined view even when resizing to a smaller window.
|
||||
- Support toggling the notifitcation on/off.
|
||||
- Added Lemonade theme.
|
||||
|
||||
|
||||
### Bug fixes
|
||||
- Improve Vietnamese translations.
|
||||
- Improve reminder feature.
|
||||
- Fix some UI issues.
|
||||
|
||||
## Version 0.3.5 - 10/09/2023
|
||||
|
||||
### New Features
|
||||
|
||||
@@ -25,7 +25,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.3.5"
|
||||
CURRENT_APP_VERSION = "0.3.6"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
|
||||
@@ -245,7 +245,7 @@ void main() {
|
||||
await tester.wait(500);
|
||||
|
||||
// Focus on the editor
|
||||
final textBlock = find.byType(TextBlockComponentWidget);
|
||||
final textBlock = find.byType(ParagraphBlockComponentWidget);
|
||||
await tester.tapAt(tester.getCenter(textBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
|
||||
+1
-1
@@ -24,7 +24,7 @@ void main() {
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
);
|
||||
// and with one paragraph block
|
||||
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
|
||||
expect(find.byType(ParagraphBlockComponentWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('delete the readme page and restore it', (tester) async {
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
+22
-6
@@ -9,6 +9,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/image/uplo
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide UploadImageMenu;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
@@ -41,8 +42,13 @@ void main() {
|
||||
await tester.editor.tapSlashMenuItemWithName('Image');
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
|
||||
await tester.tapButton(find.byType(ImagePlaceholder));
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ImagePlaceholder),
|
||||
matching: find.byType(AppFlowyPopover),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
final image = await rootBundle.load('assets/test/images/sample.jpeg');
|
||||
@@ -84,8 +90,13 @@ void main() {
|
||||
await tester.editor.tapSlashMenuItemWithName('Image');
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
|
||||
await tester.tapButton(find.byType(ImagePlaceholder));
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ImagePlaceholder),
|
||||
matching: find.byType(AppFlowyPopover),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
await tester.tapButtonWithName(
|
||||
@@ -133,8 +144,13 @@ void main() {
|
||||
await tester.editor.tapSlashMenuItemWithName('Image');
|
||||
expect(find.byType(CustomImageBlockComponent), findsOneWidget);
|
||||
expect(find.byType(ImagePlaceholder), findsOneWidget);
|
||||
|
||||
await tester.tapButton(find.byType(ImagePlaceholder));
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(ImagePlaceholder),
|
||||
matching: find.byType(AppFlowyPopover),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
expect(find.byType(UploadImageMenu), findsOneWidget);
|
||||
|
||||
await tester.tapButtonWithName(
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'util/mock/mock_file_picker.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
@@ -78,7 +79,7 @@ void main() {
|
||||
});
|
||||
}
|
||||
|
||||
const expectedMarkdown = r'''
|
||||
const expectedMarkdown = '''
|
||||
# Welcome to AppFlowy!
|
||||
## Here are the basics
|
||||
- [ ] Click anywhere and just start typing.
|
||||
@@ -105,6 +106,15 @@ fn main() {
|
||||
## Have a question❓
|
||||
> Click `?` at the bottom right for help and support.
|
||||
|
||||
> 🥰
|
||||
>
|
||||
> Like AppFlowy? Follow us:
|
||||
> [GitHub](https://github.com/AppFlowy-IO/AppFlowy)
|
||||
> [Twitter](https://twitter.com/appflowy): @appflowy
|
||||
> [Newsletter](https://blog-appflowy.ghost.io/)
|
||||
>
|
||||
|
||||
|
||||
|
||||
|
||||
''';
|
||||
@@ -30,7 +30,7 @@ void main() {
|
||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||
);
|
||||
// and with one paragraph block
|
||||
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
|
||||
expect(find.byType(ParagraphBlockComponentWidget), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('create a new document, grid, board and calendar',
|
||||
@@ -55,7 +55,7 @@ void main() {
|
||||
switch (layout) {
|
||||
case ViewLayoutPB.Document:
|
||||
// and with one paragraph block
|
||||
expect(find.byType(TextBlockComponentWidget), findsOneWidget);
|
||||
expect(find.byType(ParagraphBlockComponentWidget), findsOneWidget);
|
||||
break;
|
||||
case ViewLayoutPB.Grid:
|
||||
expect(find.byType(GridPage), findsOneWidget);
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import 'util/mock/mock_file_picker.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
@@ -11,6 +14,10 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('customize the folder path', () {
|
||||
if (Platform.isWindows) {
|
||||
return;
|
||||
}
|
||||
|
||||
testWidgets('switch to B from A, then switch to A again', (tester) async {
|
||||
const userA = 'UserA';
|
||||
const userB = 'UserB';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_menu_element.dart';
|
||||
|
||||
+4
-8
@@ -381,14 +381,10 @@ Widget? _buildHeaderIcon(GroupData customData) {
|
||||
switch (customData.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
final group = customData.asCheckboxGroup()!;
|
||||
if (group.isCheck) {
|
||||
widget = const FlowySvg(
|
||||
FlowySvgs.check_filled_s,
|
||||
blendMode: BlendMode.dst,
|
||||
);
|
||||
} else {
|
||||
widget = const FlowySvg(FlowySvgs.uncheck_s);
|
||||
}
|
||||
widget = FlowySvg(
|
||||
group.isCheck ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
);
|
||||
break;
|
||||
case FieldType.DateTime:
|
||||
case FieldType.LastEditedTime:
|
||||
|
||||
+1
-1
@@ -230,7 +230,7 @@ class _RowMenuButtonState extends State<RowMenuButton> {
|
||||
onPressed: () => widget.openMenu(),
|
||||
iconPadding: const EdgeInsets.all(3),
|
||||
icon: FlowySvg(
|
||||
FlowySvgs.details_s,
|
||||
FlowySvgs.drag_element_s,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
);
|
||||
|
||||
+5
-6
@@ -39,12 +39,11 @@ class _CheckboxCellState extends State<CheckboxCardCell> {
|
||||
buildWhen: (previous, current) =>
|
||||
previous.isSelected != current.isSelected,
|
||||
builder: (context, state) {
|
||||
final icon = state.isSelected
|
||||
? const FlowySvg(
|
||||
FlowySvgs.check_filled_s,
|
||||
blendMode: BlendMode.dst,
|
||||
)
|
||||
: const FlowySvg(FlowySvgs.uncheck_s);
|
||||
final icon = FlowySvg(
|
||||
state.isSelected ? FlowySvgs.check_filled_s : FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
);
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ class _PrimaryCellAccessoryState extends State<PrimaryCellAccessory>
|
||||
with GridCellAccessoryState {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyTooltip.delayed(
|
||||
return FlowyTooltip(
|
||||
message: LocaleKeys.tooltip_openAsPage.tr(),
|
||||
child: SizedBox(
|
||||
width: 26,
|
||||
|
||||
+4
-1
@@ -116,6 +116,9 @@ class CheckboxCellUncheck extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const FlowySvg(FlowySvgs.uncheck_s);
|
||||
return const FlowySvg(
|
||||
FlowySvgs.uncheck_s,
|
||||
blendMode: BlendMode.dst,
|
||||
);
|
||||
}
|
||||
}
|
||||
+8
@@ -366,6 +366,9 @@ class EndTimeButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
const _maxLengthTwelveHour = 8;
|
||||
const _maxLengthTwentyFourHour = 5;
|
||||
|
||||
class _TimeTextField extends StatefulWidget {
|
||||
final bool isEndTime;
|
||||
final String? timeStr;
|
||||
@@ -433,6 +436,11 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
||||
errorText: widget.isEndTime
|
||||
? state.parseEndTimeError
|
||||
: state.parseTimeError,
|
||||
maxLength:
|
||||
state.dateTypeOptionPB.timeFormat == TimeFormatPB.TwelveHour
|
||||
? _maxLengthTwelveHour
|
||||
: _maxLengthTwentyFourHour,
|
||||
showCounter: false,
|
||||
onSubmitted: (timeStr) {
|
||||
if (widget.isEndTime) {
|
||||
context
|
||||
|
||||
@@ -9,6 +9,7 @@ import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_b
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_cell.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_editor.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
@@ -112,6 +113,7 @@ class RowPropertyList extends StatelessWidget {
|
||||
class _PropertyCell extends StatefulWidget {
|
||||
final DatabaseCellContext cellContext;
|
||||
final GridCellBuilder cellBuilder;
|
||||
|
||||
final int index;
|
||||
const _PropertyCell({
|
||||
required this.cellContext,
|
||||
@@ -126,6 +128,8 @@ class _PropertyCell extends StatefulWidget {
|
||||
|
||||
class _PropertyCellState extends State<_PropertyCell> {
|
||||
final PopoverController _popoverController = PopoverController();
|
||||
final PopoverController _fieldPopoverController = PopoverController();
|
||||
|
||||
bool _isFieldHover = false;
|
||||
|
||||
@override
|
||||
@@ -140,7 +144,23 @@ class _PropertyCellState extends State<_PropertyCell> {
|
||||
child: SizedBox(
|
||||
width: 16,
|
||||
height: 30,
|
||||
child: _isFieldHover ? const FlowySvg(FlowySvgs.drag_element_s) : null,
|
||||
child: AppFlowyPopover(
|
||||
controller: _fieldPopoverController,
|
||||
constraints: BoxConstraints.loose(const Size(240, 600)),
|
||||
margin: EdgeInsets.zero,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
popupBuilder: (popoverContext) => buildFieldEditor(),
|
||||
child: _isFieldHover
|
||||
? BlockActionButton(
|
||||
onTap: () => _fieldPopoverController.show(),
|
||||
svg: FlowySvgs.drag_element_s,
|
||||
richMessage: TextSpan(
|
||||
text: LocaleKeys.grid_rowPage_fieldDragEelementTooltip.tr(),
|
||||
),
|
||||
)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -104,6 +104,7 @@ class DatabaseShareActionListState extends State<DatabaseShareActionList> {
|
||||
buildChild: (controller) {
|
||||
return RoundedTextButton(
|
||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||
textColor: Theme.of(context).colorScheme.onSurface,
|
||||
onPressed: () => controller.show(),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/background_color/theme_background_color.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
@@ -7,7 +8,7 @@ import 'package:appflowy/plugins/inline_actions/handlers/inline_page_reference.d
|
||||
import 'package:appflowy/plugins/inline_actions/handlers/reminder_reference.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/inline_actions_command.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/inline_actions_service.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/shortcuts/settings_shortcuts_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@@ -154,7 +155,15 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
supportSlashMenuNodeWhiteList.addAll([
|
||||
ToggleListBlockKeys.type,
|
||||
]);
|
||||
toolbarItemWhiteList.addAll([
|
||||
ToggleListBlockKeys.type,
|
||||
CalloutBlockKeys.type,
|
||||
TableBlockKeys.type,
|
||||
]);
|
||||
AppFlowyRichTextKeys.supportSliced.add(AppFlowyRichTextKeys.fontFamily);
|
||||
|
||||
// customize the dynamic theme color
|
||||
_customizeBlockComponentBackgroundColorDecorator();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -240,7 +249,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
|
||||
final customBlockComponentBuilderMap = {
|
||||
PageBlockKeys.type: PageBlockComponentBuilder(),
|
||||
ParagraphBlockKeys.type: TextBlockComponentBuilder(
|
||||
ParagraphBlockKeys.type: ParagraphBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
),
|
||||
TodoListBlockKeys.type: TodoListBlockComponentBuilder(
|
||||
@@ -536,4 +545,33 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _customizeBlockComponentBackgroundColorDecorator() {
|
||||
blockComponentBackgroundColorDecorator = (Node node, String colorString) {
|
||||
// the color string is from FlowyTint.
|
||||
final tintColor = FlowyTint.values.firstWhereOrNull(
|
||||
(e) => e.id == colorString,
|
||||
);
|
||||
if (tintColor != null) {
|
||||
return tintColor.color(context);
|
||||
}
|
||||
final themeColor = themeBackgroundColors[colorString];
|
||||
if (themeColor != null) {
|
||||
return themeColor.color(context);
|
||||
}
|
||||
|
||||
if (colorString == optionActionColorDefaultColor) {
|
||||
final defaultColor = node.type == CalloutBlockKeys.type
|
||||
? AFThemeExtension.of(context).calloutBGColor
|
||||
: Colors.transparent;
|
||||
return defaultColor;
|
||||
}
|
||||
|
||||
if (colorString == tableCellDefaultColor) {
|
||||
return AFThemeExtension.of(context).tableCellBGColor;
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -21,7 +21,7 @@ class BlockActionButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: FlowyTooltip.delayed(
|
||||
child: FlowyTooltip(
|
||||
preferBelow: false,
|
||||
richMessage: richMessage,
|
||||
child: MouseRegion(
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/option_action.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
@@ -47,7 +47,7 @@ class BlockOptionButton extends StatelessWidget {
|
||||
: PopoverDirection.leftWithCenterAligned,
|
||||
actions: popoverActions,
|
||||
onPopupBuilder: () {
|
||||
keepEditorFocusNotifier.value += 1;
|
||||
keepEditorFocusNotifier.increase();
|
||||
blockComponentState.alwaysShowActions = true;
|
||||
},
|
||||
onClosed: () {
|
||||
@@ -55,7 +55,7 @@ class BlockOptionButton extends StatelessWidget {
|
||||
editorState.selectionType = null;
|
||||
editorState.selection = null;
|
||||
blockComponentState.alwaysShowActions = false;
|
||||
keepEditorFocusNotifier.value -= 1;
|
||||
keepEditorFocusNotifier.decrease();
|
||||
});
|
||||
},
|
||||
onSelected: (action, controller) {
|
||||
|
||||
-1
@@ -1 +0,0 @@
|
||||
|
||||
+8
-5
@@ -10,6 +10,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
const optionActionColorDefaultColor = 'appflowy_theme_default_color';
|
||||
|
||||
enum OptionAction {
|
||||
delete,
|
||||
duplicate,
|
||||
@@ -251,12 +253,14 @@ class ColorOptionAction extends PopoverActionCell {
|
||||
// reset to default background color
|
||||
FlowyColorOption(
|
||||
color: defaultColor,
|
||||
name: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
|
||||
i18n: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
|
||||
id: optionActionColorDefaultColor,
|
||||
),
|
||||
...FlowyTint.values.map(
|
||||
(e) => FlowyColorOption(
|
||||
color: e.color(context),
|
||||
name: e.tintName(AppFlowyEditorLocalizations.current),
|
||||
i18n: e.tintName(AppFlowyEditorLocalizations.current),
|
||||
id: e.id,
|
||||
),
|
||||
),
|
||||
];
|
||||
@@ -268,11 +272,10 @@ class ColorOptionAction extends PopoverActionCell {
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
width: 1,
|
||||
),
|
||||
onTap: (color, index) async {
|
||||
onTap: (option, index) async {
|
||||
final transaction = editorState.transaction;
|
||||
final backgroundColor = color.toHex();
|
||||
transaction.updateNode(node, {
|
||||
blockComponentBackgroundColor: backgroundColor,
|
||||
blockComponentBackgroundColor: option.id,
|
||||
});
|
||||
await editorState.apply(transaction);
|
||||
|
||||
|
||||
-27
@@ -3,7 +3,6 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/op
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
|
||||
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -112,32 +111,6 @@ class OptionActionList extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class BlockComponentActionButton extends StatelessWidget {
|
||||
const BlockComponentActionButton({
|
||||
super.key,
|
||||
required this.icon,
|
||||
required this.onTap,
|
||||
});
|
||||
|
||||
final bool isHovering = false;
|
||||
final Widget icon;
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
cursor: SystemMouseCursors.grab,
|
||||
child: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: onTap,
|
||||
onTapDown: (details) {},
|
||||
onTapUp: (details) {},
|
||||
child: icon,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class OptionActionButton extends StatelessWidget {
|
||||
const OptionActionButton({
|
||||
super.key,
|
||||
|
||||
+4
-4
@@ -11,7 +11,7 @@ final alignToolbarItem = ToolbarItem(
|
||||
id: 'editor.align',
|
||||
group: 4,
|
||||
isActive: onlyShowInTextType,
|
||||
builder: (context, editorState, highlightColor) {
|
||||
builder: (context, editorState, highlightColor, _) {
|
||||
final selection = editorState.selection!;
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
|
||||
@@ -89,11 +89,11 @@ class _AlignmentButtonsState extends State<_AlignmentButtons> {
|
||||
borderRadius: const BorderRadius.all(Radius.circular(4)),
|
||||
),
|
||||
popupBuilder: (_) {
|
||||
keepEditorFocusNotifier.value += 1;
|
||||
keepEditorFocusNotifier.increase();
|
||||
return _AlignButtons(onAlignChanged: widget.onAlignChanged);
|
||||
},
|
||||
onClose: () {
|
||||
keepEditorFocusNotifier.value -= 1;
|
||||
keepEditorFocusNotifier.decrease();
|
||||
},
|
||||
child: widget.child,
|
||||
);
|
||||
@@ -156,7 +156,7 @@ class _AlignButton extends StatelessWidget {
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: GestureDetector(
|
||||
onTap: onTap,
|
||||
child: FlowyTooltip.delayed(
|
||||
child: FlowyTooltip(
|
||||
message: tooltips,
|
||||
child: FlowySvg(
|
||||
icon,
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
|
||||
// DON'T MODIFY THIS KEY BECAUSE IT'S SAVED IN THE DATABASE!
|
||||
// Used for the block component background color
|
||||
const themeBackgroundColors = {
|
||||
'appflowy_them_color_tint1': FlowyTint.tint1,
|
||||
'appflowy_them_color_tint2': FlowyTint.tint2,
|
||||
'appflowy_them_color_tint3': FlowyTint.tint3,
|
||||
'appflowy_them_color_tint4': FlowyTint.tint4,
|
||||
'appflowy_them_color_tint5': FlowyTint.tint5,
|
||||
'appflowy_them_color_tint6': FlowyTint.tint6,
|
||||
'appflowy_them_color_tint7': FlowyTint.tint7,
|
||||
'appflowy_them_color_tint8': FlowyTint.tint8,
|
||||
'appflowy_them_color_tint9': FlowyTint.tint9,
|
||||
};
|
||||
+6
-8
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/insert_page_command.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_service.dart';
|
||||
@@ -6,13 +7,12 @@ import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
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_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
void showLinkToPageMenu(
|
||||
OverlayState container,
|
||||
@@ -27,13 +27,13 @@ void showLinkToPageMenu(
|
||||
final top = alignment == Alignment.bottomLeft ? offset.dy : null;
|
||||
final bottom = alignment == Alignment.topLeft ? offset.dy : null;
|
||||
|
||||
keepEditorFocusNotifier.value += 1;
|
||||
keepEditorFocusNotifier.increase();
|
||||
late OverlayEntry linkToPageMenuEntry;
|
||||
linkToPageMenuEntry = FullScreenOverlayEntry(
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
left: offset.dx,
|
||||
dismissCallback: () => keepEditorFocusNotifier.value -= 1,
|
||||
dismissCallback: () => keepEditorFocusNotifier.decrease(),
|
||||
builder: (context) => Material(
|
||||
color: Colors.transparent,
|
||||
child: LinkToPageMenu(
|
||||
@@ -225,11 +225,9 @@ class _LinkToPageMenuState extends State<LinkToPageMenu> {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: children,
|
||||
);
|
||||
} else {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
},
|
||||
future: items,
|
||||
);
|
||||
|
||||
+9
-7
@@ -88,8 +88,7 @@ class CalloutBlockComponentBuilder extends BlockComponentBuilder {
|
||||
bool validate(Node node) =>
|
||||
node.delta != null &&
|
||||
node.children.isEmpty &&
|
||||
node.attributes[CalloutBlockKeys.icon] is String &&
|
||||
node.attributes[CalloutBlockKeys.backgroundColor] is String;
|
||||
node.attributes[CalloutBlockKeys.icon] is String;
|
||||
}
|
||||
|
||||
// the main widget for rendering the callout block
|
||||
@@ -117,7 +116,8 @@ class _CalloutBlockComponentWidgetState
|
||||
DefaultSelectableMixin,
|
||||
BlockComponentConfigurable,
|
||||
BlockComponentTextDirectionMixin,
|
||||
BlockComponentAlignMixin {
|
||||
BlockComponentAlignMixin,
|
||||
BlockComponentBackgroundColorMixin {
|
||||
// the key used to forward focus to the richtext child
|
||||
@override
|
||||
final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text');
|
||||
@@ -137,11 +137,13 @@ class _CalloutBlockComponentWidgetState
|
||||
@override
|
||||
Node get node => widget.node;
|
||||
|
||||
// get the background color of the note block from the node's attributes
|
||||
@override
|
||||
Color get backgroundColor {
|
||||
final colorString =
|
||||
node.attributes[CalloutBlockKeys.backgroundColor] as String;
|
||||
return colorString.tryToColor() ?? Colors.transparent;
|
||||
final color = super.backgroundColor;
|
||||
if (color == Colors.transparent) {
|
||||
return AFThemeExtension.of(context).calloutBGColor;
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
// get the emoji of the note block from the node's attributes or default to '📌'
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/base/strin
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide TextDirection;
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:highlight/highlight.dart' as highlight;
|
||||
@@ -190,7 +191,7 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
||||
Widget child = Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
|
||||
color: Colors.grey.withOpacity(0.1),
|
||||
color: AFThemeExtension.of(context).calloutBGColor,
|
||||
),
|
||||
width: MediaQuery.of(context).size.width,
|
||||
child: Column(
|
||||
|
||||
+6
-6
@@ -24,7 +24,7 @@ class FindAndReplaceMenuWidget extends StatefulWidget {
|
||||
class _FindAndReplaceMenuWidgetState extends State<FindAndReplaceMenuWidget> {
|
||||
bool showReplaceMenu = false;
|
||||
|
||||
late SearchServiceV2 searchService = SearchServiceV2(
|
||||
late SearchServiceV3 searchService = SearchServiceV3(
|
||||
editorState: widget.editorState,
|
||||
);
|
||||
|
||||
@@ -72,7 +72,7 @@ class FindMenu extends StatefulWidget {
|
||||
|
||||
final EditorState editorState;
|
||||
final VoidCallback onDismiss;
|
||||
final SearchServiceV2 searchService;
|
||||
final SearchServiceV3 searchService;
|
||||
final void Function(bool value) onShowReplace;
|
||||
|
||||
@override
|
||||
@@ -93,7 +93,7 @@ class _FindMenuState extends State<FindMenu> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.searchService.matchedPositions.addListener(_setState);
|
||||
widget.searchService.matchWrappers.addListener(_setState);
|
||||
widget.searchService.currentSelectedIndex.addListener(_setState);
|
||||
|
||||
findTextEditingController.addListener(_searchPattern);
|
||||
@@ -105,7 +105,7 @@ class _FindMenuState extends State<FindMenu> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.searchService.matchedPositions.removeListener(_setState);
|
||||
widget.searchService.matchWrappers.removeListener(_setState);
|
||||
widget.searchService.currentSelectedIndex.removeListener(_setState);
|
||||
widget.searchService.dispose();
|
||||
findTextEditingController.removeListener(_searchPattern);
|
||||
@@ -117,7 +117,7 @@ class _FindMenuState extends State<FindMenu> {
|
||||
Widget build(BuildContext context) {
|
||||
// the selectedIndex from searchService is 0-based
|
||||
final selectedIndex = widget.searchService.selectedIndex + 1;
|
||||
final matches = widget.searchService.matchedPositions.value;
|
||||
final matches = widget.searchService.matchWrappers.value;
|
||||
return Row(
|
||||
children: [
|
||||
const HSpace(4.0),
|
||||
@@ -232,7 +232,7 @@ class ReplaceMenu extends StatefulWidget {
|
||||
/// The localizations of the find and replace menu
|
||||
final FindReplaceLocalizations? localizations;
|
||||
|
||||
final SearchServiceV2 searchService;
|
||||
final SearchServiceV3 searchService;
|
||||
|
||||
@override
|
||||
State<ReplaceMenu> createState() => _ReplaceMenuState();
|
||||
|
||||
+3
-3
@@ -9,7 +9,7 @@ final customizeFontToolbarItem = ToolbarItem(
|
||||
id: 'editor.font',
|
||||
group: 4,
|
||||
isActive: onlyShowInTextType,
|
||||
builder: (context, editorState, highlightColor) {
|
||||
builder: (context, editorState, highlightColor, _) {
|
||||
final selection = editorState.selection!;
|
||||
final popoverController = PopoverController();
|
||||
return MouseRegion(
|
||||
@@ -18,8 +18,8 @@ final customizeFontToolbarItem = ToolbarItem(
|
||||
currentFontFamily: '',
|
||||
offset: const Offset(0, 12),
|
||||
popoverController: popoverController,
|
||||
onOpen: () => keepEditorFocusNotifier.value += 1,
|
||||
onClose: () => keepEditorFocusNotifier.value -= 1,
|
||||
onOpen: () => keepEditorFocusNotifier.increase(),
|
||||
onClose: () => keepEditorFocusNotifier.decrease(),
|
||||
showResetButton: true,
|
||||
onFontFamilyChanged: (fontFamily) async {
|
||||
popoverController.close();
|
||||
|
||||
+4
@@ -3,6 +3,8 @@ import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
const kImagePlaceholderKey = 'imagePlaceholderKey';
|
||||
|
||||
typedef CustomImageBlockComponentMenuBuilder = Widget Function(
|
||||
Node node,
|
||||
CustomImageBlockComponentState state,
|
||||
@@ -93,8 +95,10 @@ class CustomImageBlockComponentState extends State<CustomImageBlockComponent>
|
||||
MediaQuery.of(context).size.width;
|
||||
final height = attributes[ImageBlockKeys.height]?.toDouble();
|
||||
|
||||
final imagePlaceholderKey = node.extraInfos?[kImagePlaceholderKey];
|
||||
Widget child = src.isEmpty
|
||||
? ImagePlaceholder(
|
||||
key: imagePlaceholderKey is GlobalKey ? imagePlaceholderKey : null,
|
||||
node: node,
|
||||
)
|
||||
: ResizableImage(
|
||||
|
||||
+2
-2
@@ -28,10 +28,10 @@ class ImagePlaceholder extends StatefulWidget {
|
||||
final Node node;
|
||||
|
||||
@override
|
||||
State<ImagePlaceholder> createState() => _ImagePlaceholderState();
|
||||
State<ImagePlaceholder> createState() => ImagePlaceholderState();
|
||||
}
|
||||
|
||||
class _ImagePlaceholderState extends State<ImagePlaceholder> {
|
||||
class ImagePlaceholderState extends State<ImagePlaceholder> {
|
||||
final controller = PopoverController();
|
||||
late final editorState = context.read<EditorState>();
|
||||
|
||||
|
||||
+52
-1
@@ -1,4 +1,7 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/custom_image_block_component.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_placeholder.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final customImageMenuItem = SelectionMenuItem(
|
||||
name: AppFlowyEditorLocalizations.current.image,
|
||||
@@ -9,6 +12,54 @@ final customImageMenuItem = SelectionMenuItem(
|
||||
),
|
||||
keywords: ['image', 'picture', 'img', 'photo'],
|
||||
handler: (editorState, menuService, context) async {
|
||||
return await editorState.insertImageNode('');
|
||||
// use the key to retrieve the state of the image block to show the popover automatically
|
||||
final imagePlaceholderKey = GlobalKey<ImagePlaceholderState>();
|
||||
await editorState.insertEmptyImageBlock(imagePlaceholderKey);
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
imagePlaceholderKey.currentState?.controller.show();
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
extension InsertImage on EditorState {
|
||||
Future<void> insertEmptyImageBlock(GlobalKey key) async {
|
||||
final selection = this.selection;
|
||||
if (selection == null || !selection.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
final node = getNodeAtPath(selection.end.path);
|
||||
if (node == null) {
|
||||
return;
|
||||
}
|
||||
final emptyImage = imageNode(url: '')
|
||||
..extraInfos = {
|
||||
kImagePlaceholderKey: key,
|
||||
};
|
||||
final transaction = this.transaction;
|
||||
// if the current node is empty paragraph, replace it with image node
|
||||
if (node.type == ParagraphBlockKeys.type &&
|
||||
(node.delta?.isEmpty ?? false)) {
|
||||
transaction
|
||||
..insertNode(
|
||||
node.path,
|
||||
emptyImage,
|
||||
)
|
||||
..deleteNode(node);
|
||||
} else {
|
||||
transaction.insertNode(
|
||||
node.path.next,
|
||||
emptyImage,
|
||||
);
|
||||
}
|
||||
|
||||
transaction.afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: node.path.next,
|
||||
offset: 0,
|
||||
),
|
||||
);
|
||||
|
||||
return apply(transaction);
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -3,14 +3,13 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final ToolbarItem inlineMathEquationItem = ToolbarItem(
|
||||
id: 'editor.inline_math_equation',
|
||||
group: 2,
|
||||
isActive: onlyShowInSingleSelectionAndTextType,
|
||||
builder: (context, editorState, highlightColor) {
|
||||
builder: (context, editorState, highlightColor, _) {
|
||||
final selection = editorState.selection!;
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
|
||||
|
||||
+13
-5
@@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/date_picker/widgets/date_picker_dialog.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@@ -82,11 +82,13 @@ class MentionDateBlock extends StatelessWidget {
|
||||
_updateBlock(parsedDate!.withoutTime, includeTime);
|
||||
|
||||
// We can remove time from the date/reminder
|
||||
// block when toggled off.
|
||||
if (!includeTime && isReminder) {
|
||||
// block when toggled off.
|
||||
if (isReminder) {
|
||||
_updateScheduledAt(
|
||||
reminderId: reminderId!,
|
||||
selectedDay: parsedDate!.withoutTime,
|
||||
selectedDay:
|
||||
includeTime ? parsedDate! : parsedDate!.withoutTime,
|
||||
includeTime: includeTime,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -99,6 +101,7 @@ class MentionDateBlock extends StatelessWidget {
|
||||
_updateScheduledAt(
|
||||
reminderId: reminderId!,
|
||||
selectedDay: selectedDay,
|
||||
includeTime: includeTime,
|
||||
);
|
||||
}
|
||||
},
|
||||
@@ -171,10 +174,15 @@ class MentionDateBlock extends StatelessWidget {
|
||||
void _updateScheduledAt({
|
||||
required String reminderId,
|
||||
required DateTime selectedDay,
|
||||
bool? includeTime,
|
||||
}) {
|
||||
editorContext.read<ReminderBloc>().add(
|
||||
ReminderEvent.update(
|
||||
ReminderUpdate(id: reminderId, scheduledAt: selectedDay),
|
||||
ReminderUpdate(
|
||||
id: reminderId,
|
||||
scheduledAt: selectedDay,
|
||||
includeTime: includeTime,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+3
-3
@@ -14,7 +14,7 @@ final ToolbarItem smartEditItem = ToolbarItem(
|
||||
id: 'appflowy.editor.smart_edit',
|
||||
group: 0,
|
||||
isActive: onlyShowInSingleSelectionAndTextType,
|
||||
builder: (context, editorState, _) => SmartEditActionList(
|
||||
builder: (context, editorState, _, __) => SmartEditActionList(
|
||||
editorState: editorState,
|
||||
),
|
||||
);
|
||||
@@ -55,9 +55,9 @@ class _SmartEditActionListState extends State<SmartEditActionList> {
|
||||
actions: SmartEditAction.values
|
||||
.map((action) => SmartEditActionWrapper(action))
|
||||
.toList(),
|
||||
onClosed: () => keepEditorFocusNotifier.value -= 1,
|
||||
onClosed: () => keepEditorFocusNotifier.decrease(),
|
||||
buildChild: (controller) {
|
||||
keepEditorFocusNotifier.value += 1;
|
||||
keepEditorFocusNotifier.increase();
|
||||
return FlowyIconButton(
|
||||
hoverColor: Colors.transparent,
|
||||
tooltipText: isOpenAIEnabled
|
||||
|
||||
+4
-11
@@ -68,23 +68,16 @@ class OutlineBlockWidget extends BlockComponentStatefulWidget {
|
||||
}
|
||||
|
||||
class _OutlineBlockWidgetState extends State<OutlineBlockWidget>
|
||||
with BlockComponentConfigurable, BlockComponentTextDirectionMixin {
|
||||
with
|
||||
BlockComponentConfigurable,
|
||||
BlockComponentTextDirectionMixin,
|
||||
BlockComponentBackgroundColorMixin {
|
||||
@override
|
||||
BlockComponentConfiguration get configuration => widget.configuration;
|
||||
|
||||
@override
|
||||
Node get node => widget.node;
|
||||
|
||||
// get the background color of the note block from the node's attributes
|
||||
Color get backgroundColor {
|
||||
final colorString =
|
||||
node.attributes[OutlineBlockKeys.backgroundColor] as String?;
|
||||
if (colorString == null) {
|
||||
return Colors.transparent;
|
||||
}
|
||||
return colorString.tryToColor() ?? Colors.transparent;
|
||||
}
|
||||
|
||||
@override
|
||||
late EditorState editorState = context.read<EditorState>();
|
||||
late Stream<(TransactionTime, Transaction)> stream =
|
||||
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
class CalloutNodeParser extends NodeParser {
|
||||
const CalloutNodeParser();
|
||||
|
||||
@override
|
||||
String get id => CalloutBlockKeys.type;
|
||||
|
||||
@override
|
||||
String transform(Node node, DocumentMarkdownEncoder? encoder) {
|
||||
assert(node.children.isEmpty);
|
||||
final icon = node.attributes[CalloutBlockKeys.icon];
|
||||
final delta = node.delta ?? Delta()
|
||||
..insert('');
|
||||
final String markdown = DeltaMarkdownEncoder()
|
||||
.convert(delta)
|
||||
.split('\n')
|
||||
.map((e) => '> $e')
|
||||
.join('\n');
|
||||
return '''
|
||||
> $icon
|
||||
$markdown
|
||||
|
||||
''';
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -1,13 +1,23 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
class CodeBlockNodeParser extends NodeParser {
|
||||
const CodeBlockNodeParser();
|
||||
|
||||
@override
|
||||
String get id => 'code_block';
|
||||
String get id => CodeBlockKeys.type;
|
||||
|
||||
@override
|
||||
String transform(Node node) {
|
||||
return '```\n${node.attributes['code_block']}\n```';
|
||||
String transform(Node node, DocumentMarkdownEncoder? encoder) {
|
||||
final delta = node.delta;
|
||||
final language = node.attributes[CodeBlockKeys.language] ?? '';
|
||||
if (delta == null) {
|
||||
throw Exception('Delta is null');
|
||||
}
|
||||
final markdown = DeltaMarkdownEncoder().convert(delta);
|
||||
final result = '```$language\n$markdown\n```';
|
||||
final suffix = node.next == null ? '' : '\n';
|
||||
|
||||
return '$result$suffix';
|
||||
}
|
||||
}
|
||||
+16
@@ -0,0 +1,16 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
class CustomImageNodeParser extends NodeParser {
|
||||
const CustomImageNodeParser();
|
||||
|
||||
@override
|
||||
String get id => ImageBlockKeys.type;
|
||||
|
||||
@override
|
||||
String transform(Node node, DocumentMarkdownEncoder? encoder) {
|
||||
assert(node.children.isEmpty);
|
||||
final url = node.attributes[ImageBlockKeys.url];
|
||||
assert(url != null);
|
||||
return '\n';
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -4,10 +4,10 @@ class DividerNodeParser extends NodeParser {
|
||||
const DividerNodeParser();
|
||||
|
||||
@override
|
||||
String get id => 'divider';
|
||||
String get id => DividerBlockKeys.type;
|
||||
|
||||
@override
|
||||
String transform(Node node) {
|
||||
String transform(Node node, DocumentMarkdownEncoder? encoder) {
|
||||
return '---\n';
|
||||
}
|
||||
}
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
export 'callout_node_parser.dart';
|
||||
export 'code_block_node_parser.dart';
|
||||
export 'custom_image_node_parser.dart';
|
||||
export 'divider_node_parser.dart';
|
||||
export 'math_equation_node_parser.dart';
|
||||
export 'toggle_list_node_parser.dart';
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
export 'callout_node_parser.dart';
|
||||
export 'code_block_node_parser.dart';
|
||||
export 'divider_node_parser.dart';
|
||||
export 'math_equation_node_parser.dart';
|
||||
export 'toggle_list_node_parser.dart';
|
||||
+1
-1
@@ -8,7 +8,7 @@ class MathEquationNodeParser extends NodeParser {
|
||||
String get id => MathEquationBlockKeys.type;
|
||||
|
||||
@override
|
||||
String transform(Node node) {
|
||||
String transform(Node node, DocumentMarkdownEncoder? encoder) {
|
||||
return '\$\$${node.attributes[id]}\$\$';
|
||||
}
|
||||
}
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
|
||||
enum ToggleListExportStyle {
|
||||
github,
|
||||
markdown,
|
||||
}
|
||||
|
||||
class ToggleListNodeParser extends NodeParser {
|
||||
const ToggleListNodeParser({
|
||||
this.exportStyle = ToggleListExportStyle.markdown,
|
||||
});
|
||||
|
||||
final ToggleListExportStyle exportStyle;
|
||||
|
||||
@override
|
||||
String get id => ToggleListBlockKeys.type;
|
||||
|
||||
@override
|
||||
String transform(Node node, DocumentMarkdownEncoder? encoder) {
|
||||
final delta = node.delta ?? Delta()
|
||||
..insert('');
|
||||
String markdown = DeltaMarkdownEncoder().convert(delta);
|
||||
final details = encoder?.convertNodes(
|
||||
node.children,
|
||||
withIndent: true,
|
||||
);
|
||||
switch (exportStyle) {
|
||||
case ToggleListExportStyle.github:
|
||||
return '''<details>
|
||||
<summary>$markdown</summary>
|
||||
|
||||
$details
|
||||
</details>
|
||||
''';
|
||||
case ToggleListExportStyle.markdown:
|
||||
markdown = '- $markdown\n';
|
||||
if (details != null && details.isNotEmpty) {
|
||||
markdown += details;
|
||||
}
|
||||
return markdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
+1
@@ -28,6 +28,7 @@ export 'openai/widgets/auto_completion_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_toolbar_item.dart';
|
||||
export 'outline/outline_block_component.dart';
|
||||
export 'parsers/markdown_parsers.dart';
|
||||
export 'table/table_menu.dart';
|
||||
export 'table/table_option_action.dart';
|
||||
export 'toggle/toggle_block_component.dart';
|
||||
|
||||
+9
-4
@@ -11,6 +11,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const tableCellDefaultColor = 'appflowy_table_cell_default_color';
|
||||
|
||||
enum TableOptionAction {
|
||||
addAfter,
|
||||
addBefore,
|
||||
@@ -119,12 +121,14 @@ class TableColorOptionAction extends PopoverActionCell {
|
||||
// reset to default background color
|
||||
FlowyColorOption(
|
||||
color: defaultColor,
|
||||
name: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
|
||||
i18n: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
|
||||
id: tableCellDefaultColor,
|
||||
),
|
||||
...FlowyTint.values.map(
|
||||
(e) => FlowyColorOption(
|
||||
color: e.color(context),
|
||||
name: e.tintName(AppFlowyEditorLocalizations.current),
|
||||
i18n: e.tintName(AppFlowyEditorLocalizations.current),
|
||||
id: e.id,
|
||||
),
|
||||
),
|
||||
];
|
||||
@@ -136,8 +140,9 @@ class TableColorOptionAction extends PopoverActionCell {
|
||||
color: Theme.of(context).colorScheme.onBackground,
|
||||
width: 1,
|
||||
),
|
||||
onTap: (color, index) async {
|
||||
final backgroundColor = selectedColor != color ? color.toHex() : "";
|
||||
onTap: (option, index) async {
|
||||
final backgroundColor =
|
||||
selectedColor != option.color ? option.id : '';
|
||||
TableActions.setBgColor(
|
||||
node,
|
||||
position,
|
||||
|
||||
+11
-8
@@ -163,16 +163,19 @@ class _ToggleListBlockComponentWidgetState
|
||||
textDirection: textDirection,
|
||||
children: [
|
||||
// the emoji picker button for the note
|
||||
FlowyIconButton(
|
||||
width: 24.0,
|
||||
icon: Icon(
|
||||
collapsed ? Icons.arrow_right : Icons.arrow_drop_down,
|
||||
Container(
|
||||
constraints: const BoxConstraints(minWidth: 26, minHeight: 22),
|
||||
padding: const EdgeInsets.only(right: 4.0),
|
||||
child: FlowyIconButton(
|
||||
width: 18.0,
|
||||
icon: Icon(
|
||||
collapsed ? Icons.arrow_right : Icons.arrow_drop_down,
|
||||
size: 18.0,
|
||||
),
|
||||
onPressed: onCollapsed,
|
||||
),
|
||||
onPressed: onCollapsed,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 4.0,
|
||||
),
|
||||
|
||||
Flexible(
|
||||
child: AppFlowyRichText(
|
||||
key: forwardKey,
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
|
||||
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
|
||||
|
||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -63,12 +60,13 @@ class EditorStyleCustomizer {
|
||||
color: theme.colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
code: GoogleFonts.robotoMono(
|
||||
code: GoogleFonts.arefRuqaaInk(
|
||||
textStyle: baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.normal,
|
||||
fontStyle: FontStyle.italic,
|
||||
color: Colors.red,
|
||||
backgroundColor: theme.colorScheme.inverseSurface,
|
||||
backgroundColor: theme.colorScheme.inverseSurface.withOpacity(0.8),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -102,7 +100,7 @@ class EditorStyleCustomizer {
|
||||
color: theme.colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
code: GoogleFonts.robotoMono(
|
||||
code: GoogleFonts.arefRuqaaInk(
|
||||
textStyle: baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.normal,
|
||||
|
||||
@@ -29,7 +29,7 @@ class DocumentMoreButton extends StatelessWidget {
|
||||
),
|
||||
];
|
||||
},
|
||||
child: FlowyTooltip.delayed(
|
||||
child: FlowyTooltip(
|
||||
message: LocaleKeys.moreAction_moreOptions.tr(),
|
||||
child: FlowySvg(
|
||||
FlowySvgs.details_s,
|
||||
|
||||
@@ -112,6 +112,7 @@ class ShareActionListState extends State<ShareActionList> {
|
||||
return RoundedTextButton(
|
||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||
onPressed: () => controller.show(),
|
||||
textColor: Theme.of(context).colorScheme.onSurface,
|
||||
);
|
||||
},
|
||||
onSelected: (action, controller) async {
|
||||
|
||||
+4
-1
@@ -4,6 +4,7 @@ import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/base/string_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/inline_actions/inline_actions_result.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/reminder.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
@@ -209,7 +210,9 @@ class ReminderReferenceService {
|
||||
objectId: viewId,
|
||||
title: LocaleKeys.reminderNotification_title.tr(),
|
||||
message: LocaleKeys.reminderNotification_message.tr(),
|
||||
meta: {"document_id": viewId},
|
||||
meta: {
|
||||
ReminderMetaKeys.includeTime.name: false.toString(),
|
||||
},
|
||||
scheduledAt: Int64(date.millisecondsSinceEpoch ~/ 1000),
|
||||
isAck: date.isBefore(DateTime.now()),
|
||||
);
|
||||
|
||||
@@ -23,7 +23,8 @@ import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_action_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/user/prelude.dart';
|
||||
@@ -168,14 +169,22 @@ void _resolveHomeDeps(GetIt getIt) {
|
||||
|
||||
getIt.registerLazySingleton<TabsBloc>(() => TabsBloc());
|
||||
|
||||
getIt.registerSingleton<ReminderBloc>(ReminderBloc());
|
||||
getIt.registerSingleton<NotificationSettingsCubit>(
|
||||
NotificationSettingsCubit(),
|
||||
);
|
||||
|
||||
getIt.registerSingleton<ReminderBloc>(
|
||||
ReminderBloc(notificationSettings: getIt<NotificationSettingsCubit>()),
|
||||
);
|
||||
}
|
||||
|
||||
void _resolveFolderDeps(GetIt getIt) {
|
||||
//workspace
|
||||
getIt.registerFactoryParam<WorkspaceListener, UserProfilePB, String>(
|
||||
(user, workspaceId) =>
|
||||
WorkspaceListener(user: user, workspaceId: workspaceId),
|
||||
(user, workspaceId) => WorkspaceListener(
|
||||
user: user,
|
||||
workspaceId: workspaceId,
|
||||
),
|
||||
);
|
||||
|
||||
getIt.registerFactoryParam<ViewBloc, ViewPB, void>(
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'deps_resolver.dart';
|
||||
import 'entry_point.dart';
|
||||
import 'launch_configuration.dart';
|
||||
import 'plugin/plugin.dart';
|
||||
import 'tasks/appflowy_cloud_task.dart';
|
||||
import 'tasks/prelude.dart';
|
||||
|
||||
final getIt = GetIt.instance;
|
||||
@@ -71,6 +72,7 @@ class FlowyRunner {
|
||||
if (!mode.isUnitTest) ...[
|
||||
const HotKeyTask(),
|
||||
InitSupabaseTask(),
|
||||
InitAppFlowyCloudTask(),
|
||||
const InitAppWidgetTask(),
|
||||
const InitPlatformServiceTask()
|
||||
],
|
||||
|
||||
@@ -1,19 +1,23 @@
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_service.dart';
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart';
|
||||
|
||||
import 'prelude.dart';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
|
||||
import '../../user/application/user_settings_service.dart';
|
||||
import '../../workspace/application/appearance.dart';
|
||||
import '../startup.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/user/application/user_settings_service.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
|
||||
class InitAppWidgetTask extends LaunchTask {
|
||||
const InitAppWidgetTask();
|
||||
@@ -68,9 +72,11 @@ class InitAppWidgetTask extends LaunchTask {
|
||||
Locale('tr', 'TR'),
|
||||
Locale('uk', 'UA'),
|
||||
Locale('ur'),
|
||||
Locale('vi', 'VN'),
|
||||
Locale('zh', 'CN'),
|
||||
Locale('zh', 'TW'),
|
||||
Locale('fa'),
|
||||
Locale('hin'),
|
||||
],
|
||||
path: 'assets/translations',
|
||||
fallbackLocale: const Locale('en'),
|
||||
@@ -94,8 +100,8 @@ class ApplicationWidget extends StatefulWidget {
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
final AppearanceSettingsPB appearanceSetting;
|
||||
final AppTheme appTheme;
|
||||
final AppearanceSettingsPB appearanceSetting;
|
||||
final DateTimeSettingsPB dateTimeSettings;
|
||||
|
||||
@override
|
||||
@@ -124,6 +130,9 @@ class _ApplicationWidgetState extends State<ApplicationWidget> {
|
||||
widget.appTheme,
|
||||
)..readLocaleWhenAppLaunch(context),
|
||||
),
|
||||
BlocProvider<NotificationSettingsCubit>(
|
||||
create: (_) => getIt<NotificationSettingsCubit>(),
|
||||
),
|
||||
BlocProvider<DocumentAppearanceCubit>(
|
||||
create: (_) => DocumentAppearanceCubit()..fetch(),
|
||||
),
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_auth_listener.dart';
|
||||
|
||||
class InitAppFlowyCloudTask extends LaunchTask {
|
||||
final _authStateListener = UserAuthStateListener();
|
||||
bool isLoggingOut = false;
|
||||
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
if (!isAppFlowyCloudEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
_authStateListener.start(
|
||||
didSignIn: () {
|
||||
isLoggingOut = false;
|
||||
},
|
||||
onInvalidAuth: (message) async {
|
||||
await getIt<AuthService>().signOut();
|
||||
// TODO(nathan): Show a dialog to notify the user that the session is expired.
|
||||
if (!isLoggingOut) {
|
||||
await runAppFlowy();
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'notification_filter_bloc.freezed.dart';
|
||||
|
||||
class NotificationFilterBloc
|
||||
extends Bloc<NotificationFilterEvent, NotificationFilterState> {
|
||||
NotificationFilterBloc() : super(const NotificationFilterState()) {
|
||||
on<NotificationFilterEvent>((event, emit) async {
|
||||
event.when(
|
||||
reset: () => emit(const NotificationFilterState()),
|
||||
toggleShowUnreadsOnly: () => emit(
|
||||
state.copyWith(showUnreadsOnly: !state.showUnreadsOnly),
|
||||
),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NotificationFilterEvent with _$NotificationFilterEvent {
|
||||
const factory NotificationFilterEvent.toggleShowUnreadsOnly() =
|
||||
_ToggleShowUnreadsOnly;
|
||||
|
||||
const factory NotificationFilterEvent.reset() = _Reset;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NotificationFilterState with _$NotificationFilterState {
|
||||
const NotificationFilterState._();
|
||||
|
||||
const factory NotificationFilterState({
|
||||
@Default(false) bool showUnreadsOnly,
|
||||
}) = _NotificationFilterState;
|
||||
|
||||
// If state is not default values, then there are custom changes
|
||||
bool get hasFilters => showUnreadsOnly != false;
|
||||
}
|
||||
@@ -2,10 +2,12 @@ import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_service.dart';
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_action.dart';
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_action_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_service.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/notifications/notification_settings_cubit.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
@@ -18,35 +20,58 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'reminder_bloc.freezed.dart';
|
||||
|
||||
class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
final NotificationSettingsCubit _notificationSettings;
|
||||
|
||||
late final NotificationActionBloc actionBloc;
|
||||
late final ReminderService reminderService;
|
||||
late final Timer timer;
|
||||
|
||||
ReminderBloc() : super(ReminderState()) {
|
||||
ReminderBloc({
|
||||
required NotificationSettingsCubit notificationSettings,
|
||||
}) : _notificationSettings = notificationSettings,
|
||||
super(ReminderState()) {
|
||||
actionBloc = getIt<NotificationActionBloc>();
|
||||
reminderService = const ReminderService();
|
||||
timer = _periodicCheck();
|
||||
|
||||
on<ReminderEvent>((event, emit) async {
|
||||
await event.when(
|
||||
markAllRead: () async {
|
||||
final unreadReminders =
|
||||
state.pastReminders.where((reminder) => !reminder.isRead);
|
||||
|
||||
final reminders = [...state.reminders];
|
||||
final updatedReminders = <ReminderPB>[];
|
||||
for (final reminder in unreadReminders) {
|
||||
reminders.remove(reminder);
|
||||
|
||||
reminder.isRead = true;
|
||||
await reminderService.updateReminder(reminder: reminder);
|
||||
|
||||
updatedReminders.add(reminder);
|
||||
}
|
||||
|
||||
reminders.addAll(updatedReminders);
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
started: () async {
|
||||
final remindersOrFailure = await reminderService.fetchReminders();
|
||||
|
||||
remindersOrFailure.fold(
|
||||
(error) => Log.error(error),
|
||||
(reminders) => _updateState(emit, reminders),
|
||||
(reminders) => emit(state.copyWith(reminders: reminders)),
|
||||
);
|
||||
},
|
||||
remove: (reminderId) async {
|
||||
remove: (reminder) async {
|
||||
final unitOrFailure =
|
||||
await reminderService.removeReminder(reminderId: reminderId);
|
||||
await reminderService.removeReminder(reminderId: reminder.id);
|
||||
|
||||
unitOrFailure.fold(
|
||||
(error) => Log.error(error),
|
||||
(_) {
|
||||
final reminders = [...state.reminders];
|
||||
reminders.removeWhere((e) => e.id == reminderId);
|
||||
_updateState(emit, reminders);
|
||||
reminders.removeWhere((e) => e.id == reminder.id);
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -57,8 +82,8 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
return unitOrFailure.fold(
|
||||
(error) => Log.error(error),
|
||||
(_) {
|
||||
state.reminders.add(reminder);
|
||||
_updateState(emit, state.reminders);
|
||||
final reminders = [...state.reminders, reminder];
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -82,7 +107,7 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
state.reminders.indexWhere((r) => r.id == reminder.id);
|
||||
final reminders = [...state.reminders];
|
||||
reminders.replaceRange(index, index + 1, [newReminder]);
|
||||
_updateState(emit, reminders);
|
||||
emit(state.copyWith(reminders: reminders));
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -108,23 +133,13 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
});
|
||||
}
|
||||
|
||||
void _updateState(Emitter emit, List<ReminderPB> reminders) {
|
||||
final now = DateTime.now();
|
||||
final hasUnreads = reminders.any(
|
||||
(r) =>
|
||||
DateTime.fromMillisecondsSinceEpoch(r.scheduledAt.toInt() * 1000)
|
||||
.isBefore(now) &&
|
||||
!r.isRead,
|
||||
);
|
||||
emit(state.copyWith(reminders: reminders, hasUnreads: hasUnreads));
|
||||
}
|
||||
|
||||
Timer _periodicCheck() {
|
||||
return Timer.periodic(
|
||||
const Duration(minutes: 1),
|
||||
(_) {
|
||||
final now = DateTime.now();
|
||||
for (final reminder in state.reminders) {
|
||||
|
||||
for (final reminder in state.upcomingReminders) {
|
||||
if (reminder.isAck) {
|
||||
continue;
|
||||
}
|
||||
@@ -134,16 +149,18 @@ class ReminderBloc extends Bloc<ReminderEvent, ReminderState> {
|
||||
);
|
||||
|
||||
if (scheduledAt.isBefore(now)) {
|
||||
NotificationMessage(
|
||||
identifier: reminder.id,
|
||||
title: LocaleKeys.reminderNotification_title.tr(),
|
||||
body: LocaleKeys.reminderNotification_message.tr(),
|
||||
onClick: () => actionBloc.add(
|
||||
NotificationActionEvent.performAction(
|
||||
action: NotificationAction(objectId: reminder.objectId),
|
||||
if (_notificationSettings.state.isNotificationsEnabled) {
|
||||
NotificationMessage(
|
||||
identifier: reminder.id,
|
||||
title: LocaleKeys.reminderNotification_title.tr(),
|
||||
body: LocaleKeys.reminderNotification_message.tr(),
|
||||
onClick: () => actionBloc.add(
|
||||
NotificationActionEvent.performAction(
|
||||
action: NotificationAction(objectId: reminder.objectId),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
||||
add(
|
||||
ReminderEvent.update(
|
||||
@@ -163,7 +180,7 @@ class ReminderEvent with _$ReminderEvent {
|
||||
const factory ReminderEvent.started() = _Started;
|
||||
|
||||
// Remove a reminder
|
||||
const factory ReminderEvent.remove({required String reminderId}) = _Remove;
|
||||
const factory ReminderEvent.remove({required ReminderPB reminder}) = _Remove;
|
||||
|
||||
// Add a reminder
|
||||
const factory ReminderEvent.add({required ReminderPB reminder}) = _Add;
|
||||
@@ -171,6 +188,9 @@ class ReminderEvent with _$ReminderEvent {
|
||||
// Update a reminder (eg. isAck, isRead, etc.)
|
||||
const factory ReminderEvent.update(ReminderUpdate update) = _Update;
|
||||
|
||||
// Mark all unread reminders as read
|
||||
const factory ReminderEvent.markAllRead() = _MarkAllRead;
|
||||
|
||||
const factory ReminderEvent.pressReminder({required String reminderId}) =
|
||||
_PressReminder;
|
||||
}
|
||||
@@ -183,12 +203,14 @@ class ReminderUpdate {
|
||||
final bool? isAck;
|
||||
final bool? isRead;
|
||||
final DateTime? scheduledAt;
|
||||
final bool? includeTime;
|
||||
|
||||
ReminderUpdate({
|
||||
required this.id,
|
||||
this.isAck,
|
||||
this.isRead,
|
||||
this.scheduledAt,
|
||||
this.includeTime,
|
||||
});
|
||||
|
||||
ReminderPB merge({required ReminderPB a}) {
|
||||
@@ -196,6 +218,11 @@ class ReminderUpdate {
|
||||
? scheduledAt!.isBefore(DateTime.now())
|
||||
: a.isAck;
|
||||
|
||||
final meta = a.meta;
|
||||
if (includeTime != a.includeTime) {
|
||||
meta[ReminderMetaKeys.includeTime.name] = includeTime.toString();
|
||||
}
|
||||
|
||||
return ReminderPB(
|
||||
id: a.id,
|
||||
objectId: a.objectId,
|
||||
@@ -206,27 +233,52 @@ class ReminderUpdate {
|
||||
isRead: isRead ?? a.isRead,
|
||||
title: a.title,
|
||||
message: a.message,
|
||||
meta: a.meta,
|
||||
meta: meta,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ReminderState {
|
||||
ReminderState({
|
||||
List<ReminderPB>? reminders,
|
||||
bool? hasUnreads,
|
||||
}) : reminders = reminders ?? [],
|
||||
hasUnreads = hasUnreads ?? false;
|
||||
ReminderState({List<ReminderPB>? reminders}) {
|
||||
_reminders = reminders ?? [];
|
||||
|
||||
final List<ReminderPB> reminders;
|
||||
final bool hasUnreads;
|
||||
pastReminders = [];
|
||||
upcomingReminders = [];
|
||||
|
||||
ReminderState copyWith({
|
||||
List<ReminderPB>? reminders,
|
||||
bool? hasUnreads,
|
||||
}) =>
|
||||
ReminderState(
|
||||
reminders: reminders ?? this.reminders,
|
||||
hasUnreads: hasUnreads ?? this.hasUnreads,
|
||||
if (_reminders.isEmpty) {
|
||||
hasUnreads = false;
|
||||
return;
|
||||
}
|
||||
|
||||
final now = DateTime.now();
|
||||
|
||||
bool hasUnreadReminders = false;
|
||||
for (final reminder in _reminders) {
|
||||
final scheduledDate = DateTime.fromMillisecondsSinceEpoch(
|
||||
reminder.scheduledAt.toInt() * 1000,
|
||||
);
|
||||
|
||||
if (scheduledDate.isBefore(now)) {
|
||||
pastReminders.add(reminder);
|
||||
|
||||
if (!hasUnreadReminders && !reminder.isRead) {
|
||||
hasUnreadReminders = true;
|
||||
}
|
||||
} else {
|
||||
upcomingReminders.add(reminder);
|
||||
}
|
||||
}
|
||||
|
||||
hasUnreads = hasUnreadReminders;
|
||||
}
|
||||
|
||||
late final List<ReminderPB> _reminders;
|
||||
List<ReminderPB> get reminders => _reminders;
|
||||
|
||||
late final List<ReminderPB> pastReminders;
|
||||
late final List<ReminderPB> upcomingReminders;
|
||||
late final bool hasUnreads;
|
||||
|
||||
ReminderState copyWith({List<ReminderPB>? reminders}) =>
|
||||
ReminderState(reminders: reminders ?? _reminders);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
|
||||
enum ReminderMetaKeys {
|
||||
includeTime("include_time");
|
||||
|
||||
const ReminderMetaKeys(this.name);
|
||||
|
||||
final String name;
|
||||
}
|
||||
|
||||
extension ReminderExtension on ReminderPB {
|
||||
bool? get includeTime {
|
||||
final String? includeTimeStr = meta[ReminderMetaKeys.includeTime.name];
|
||||
|
||||
return includeTimeStr != null ? includeTimeStr == true.toString() : null;
|
||||
}
|
||||
}
|
||||
@@ -35,7 +35,7 @@ class SupbaseRealtimeService {
|
||||
_subscribeTablesChanges();
|
||||
isLoggingOut = false;
|
||||
},
|
||||
onForceLogout: (message) async {
|
||||
onInvalidAuth: (message) async {
|
||||
await getIt<AuthService>().signOut();
|
||||
channel?.unsubscribe();
|
||||
channel = null;
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/core/notification/user_notification.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'dart:typed_data';
|
||||
import 'package:appflowy_backend/protobuf/flowy-notification/protobuf.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/notification.pb.dart'
|
||||
as user;
|
||||
import 'package:appflowy_backend/rust_stream.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
|
||||
class UserAuthStateListener {
|
||||
void Function(String)? _onForceLogout;
|
||||
void Function(String)? _onInvalidAuth;
|
||||
void Function()? _didSignIn;
|
||||
StreamSubscription<SubscribeObject>? _subscription;
|
||||
UserNotificationParser? _userParser;
|
||||
|
||||
void start({
|
||||
void Function(String)? onForceLogout,
|
||||
void Function(String)? onInvalidAuth,
|
||||
void Function()? didSignIn,
|
||||
}) {
|
||||
_onForceLogout = onForceLogout;
|
||||
_onInvalidAuth = onInvalidAuth;
|
||||
_didSignIn = didSignIn;
|
||||
|
||||
_userParser = UserNotificationParser(
|
||||
@@ -35,7 +36,7 @@ class UserAuthStateListener {
|
||||
Future<void> stop() async {
|
||||
_userParser = null;
|
||||
await _subscription?.cancel();
|
||||
_onForceLogout = null;
|
||||
_onInvalidAuth = null;
|
||||
}
|
||||
|
||||
void _userNotificationCallback(
|
||||
@@ -51,8 +52,8 @@ class UserAuthStateListener {
|
||||
case AuthStatePB.AuthStateSignIn:
|
||||
_didSignIn?.call();
|
||||
break;
|
||||
case AuthStatePB.AuthStateForceSignOut:
|
||||
_onForceLogout?.call("");
|
||||
case AuthStatePB.InvalidAuth:
|
||||
_onInvalidAuth?.call("");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
|
||||
@@ -41,4 +41,20 @@ class UserSettingsBackendService {
|
||||
) async {
|
||||
return (await UserEventSetDateTimeSettings(settings).send()).swap();
|
||||
}
|
||||
|
||||
Future<Either<FlowyError, Unit>> setNotificationSettings(
|
||||
NotificationSettingsPB settings,
|
||||
) async {
|
||||
return (await UserEventSetNotificationSettings(settings).send()).swap();
|
||||
}
|
||||
|
||||
Future<NotificationSettingsPB> getNotificationSettings() async {
|
||||
final result = await UserEventGetNotificationSettings().send();
|
||||
|
||||
return result.fold(
|
||||
(NotificationSettingsPB setting) => setting,
|
||||
(error) =>
|
||||
throw FlowySDKException(ExceptionType.AppearanceSettingsIsEmpty),
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -6,7 +6,7 @@ import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/presentation.dart';
|
||||
import 'package:appflowy/util/platform_extension.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
@@ -8,7 +8,7 @@ import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/historical_user_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
@@ -3,15 +3,22 @@ import 'dart:convert';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/code_block_node_parser.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/divider_node_parser.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/math_equation_node_parser.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/parsers/document_markdown_parsers.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
const List<NodeParser> _customParsers = [
|
||||
DividerNodeParser(),
|
||||
MathEquationNodeParser(),
|
||||
CodeBlockNodeParser(),
|
||||
CalloutNodeParser(),
|
||||
ToggleListNodeParser(),
|
||||
CustomImageNodeParser(),
|
||||
];
|
||||
|
||||
enum DocumentExportType {
|
||||
json,
|
||||
markdown,
|
||||
@@ -43,11 +50,7 @@ class DocumentExporter {
|
||||
case DocumentExportType.markdown:
|
||||
final markdown = documentToMarkdown(
|
||||
document,
|
||||
customParsers: [
|
||||
const DividerNodeParser(),
|
||||
const MathEquationNodeParser(),
|
||||
const CodeBlockNodeParser(),
|
||||
],
|
||||
customParsers: _customParsers,
|
||||
);
|
||||
return right(markdown);
|
||||
case DocumentExportType.text:
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/edit_panel/edit_context.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/workspace.pb.dart'
|
||||
show WorkspaceSettingPB;
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
@@ -19,12 +20,14 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
|
||||
UserProfilePB user,
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
AppearanceSettingsCubit appearanceSettingsCubit,
|
||||
double screenWidthPx,
|
||||
) : _listener = UserWorkspaceListener(userProfile: user),
|
||||
_appearanceSettingsCubit = appearanceSettingsCubit,
|
||||
super(
|
||||
HomeSettingState.initial(
|
||||
workspaceSetting,
|
||||
appearanceSettingsCubit.state,
|
||||
screenWidthPx,
|
||||
),
|
||||
) {
|
||||
on<HomeSettingEvent>(
|
||||
@@ -43,7 +46,28 @@ class HomeSettingBloc extends Bloc<HomeSettingEvent, HomeSettingState> {
|
||||
collapseMenu: (_CollapseMenu e) {
|
||||
final isMenuCollapsed = !state.isMenuCollapsed;
|
||||
_appearanceSettingsCubit.saveIsMenuCollapsed(isMenuCollapsed);
|
||||
emit(state.copyWith(isMenuCollapsed: isMenuCollapsed));
|
||||
emit(
|
||||
state.copyWith(
|
||||
isMenuCollapsed: isMenuCollapsed,
|
||||
keepMenuCollapsed: isMenuCollapsed,
|
||||
),
|
||||
);
|
||||
},
|
||||
checkScreenSize: (_CheckScreenSize e) {
|
||||
final bool isScreenSmall =
|
||||
e.screenWidthPx < PageBreaks.tabletLandscape;
|
||||
|
||||
if (state.isScreenSmall != isScreenSmall) {
|
||||
final isMenuCollapsed = isScreenSmall || state.keepMenuCollapsed;
|
||||
emit(
|
||||
state.copyWith(
|
||||
isMenuCollapsed: isMenuCollapsed,
|
||||
isScreenSmall: isScreenSmall,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
emit(state.copyWith(isScreenSmall: isScreenSmall));
|
||||
}
|
||||
},
|
||||
editPanelResizeStart: (_EditPanelResizeStart e) {
|
||||
emit(
|
||||
@@ -102,6 +126,8 @@ class HomeSettingEvent with _$HomeSettingEvent {
|
||||
WorkspaceSettingPB setting,
|
||||
) = _DidReceiveWorkspaceSetting;
|
||||
const factory HomeSettingEvent.collapseMenu() = _CollapseMenu;
|
||||
const factory HomeSettingEvent.checkScreenSize(double screenWidthPx) =
|
||||
_CheckScreenSize;
|
||||
const factory HomeSettingEvent.editPanelResized(double offset) =
|
||||
_EditPanelResized;
|
||||
const factory HomeSettingEvent.editPanelResizeStart() = _EditPanelResizeStart;
|
||||
@@ -115,6 +141,8 @@ class HomeSettingState with _$HomeSettingState {
|
||||
required WorkspaceSettingPB workspaceSetting,
|
||||
required bool unauthorized,
|
||||
required bool isMenuCollapsed,
|
||||
required bool keepMenuCollapsed,
|
||||
required bool isScreenSmall,
|
||||
required double resizeOffset,
|
||||
required double resizeStart,
|
||||
required MenuResizeType resizeType,
|
||||
@@ -123,14 +151,18 @@ class HomeSettingState with _$HomeSettingState {
|
||||
factory HomeSettingState.initial(
|
||||
WorkspaceSettingPB workspaceSetting,
|
||||
AppearanceSettingsState appearanceSettingsState,
|
||||
) =>
|
||||
HomeSettingState(
|
||||
panelContext: none(),
|
||||
workspaceSetting: workspaceSetting,
|
||||
unauthorized: false,
|
||||
isMenuCollapsed: appearanceSettingsState.isMenuCollapsed,
|
||||
resizeOffset: appearanceSettingsState.menuOffset,
|
||||
resizeStart: 0,
|
||||
resizeType: MenuResizeType.slide,
|
||||
);
|
||||
double screenWidthPx,
|
||||
) {
|
||||
return HomeSettingState(
|
||||
panelContext: none(),
|
||||
workspaceSetting: workspaceSetting,
|
||||
unauthorized: false,
|
||||
isMenuCollapsed: appearanceSettingsState.isMenuCollapsed,
|
||||
isScreenSmall: screenWidthPx < PageBreaks.tabletLandscape,
|
||||
keepMenuCollapsed: false,
|
||||
resizeOffset: appearanceSettingsState.menuOffset,
|
||||
resizeStart: 0,
|
||||
resizeType: MenuResizeType.slide,
|
||||
);
|
||||
}
|
||||
}
|
||||
File renamed without changes.
+1
-1
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_action.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
File renamed without changes.
+39
-30
@@ -16,33 +16,40 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
part 'appearance.freezed.dart';
|
||||
part 'appearance_cubit.freezed.dart';
|
||||
|
||||
const _white = Color(0xFFFFFFFF);
|
||||
|
||||
/// [AppearanceSettingsCubit] is used to modify the appearance of AppFlowy.
|
||||
/// It includes the [AppTheme], [ThemeMode], [TextStyles] and [Locale].
|
||||
/// It includes:
|
||||
/// - [AppTheme]
|
||||
/// - [ThemeMode]
|
||||
/// - [TextStyle]'s
|
||||
/// - [Locale]
|
||||
/// - [UserDateFormatPB]
|
||||
/// - [UserTimeFormatPB]
|
||||
///
|
||||
class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
final AppearanceSettingsPB _setting;
|
||||
final AppearanceSettingsPB _appearanceSettings;
|
||||
final DateTimeSettingsPB _dateTimeSettings;
|
||||
|
||||
AppearanceSettingsCubit(
|
||||
AppearanceSettingsPB setting,
|
||||
AppearanceSettingsPB appearanceSettings,
|
||||
DateTimeSettingsPB dateTimeSettings,
|
||||
AppTheme appTheme,
|
||||
) : _setting = setting,
|
||||
) : _appearanceSettings = appearanceSettings,
|
||||
_dateTimeSettings = dateTimeSettings,
|
||||
super(
|
||||
AppearanceSettingsState.initial(
|
||||
appTheme,
|
||||
setting.themeMode,
|
||||
setting.font,
|
||||
setting.monospaceFont,
|
||||
setting.layoutDirection,
|
||||
setting.textDirection,
|
||||
setting.locale,
|
||||
setting.isMenuCollapsed,
|
||||
setting.menuOffset,
|
||||
appearanceSettings.themeMode,
|
||||
appearanceSettings.font,
|
||||
appearanceSettings.monospaceFont,
|
||||
appearanceSettings.layoutDirection,
|
||||
appearanceSettings.textDirection,
|
||||
appearanceSettings.locale,
|
||||
appearanceSettings.isMenuCollapsed,
|
||||
appearanceSettings.menuOffset,
|
||||
dateTimeSettings.dateFormat,
|
||||
dateTimeSettings.timeFormat,
|
||||
dateTimeSettings.timezoneId,
|
||||
@@ -52,7 +59,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
/// Update selected theme in the user's settings and emit an updated state
|
||||
/// with the AppTheme named [themeName].
|
||||
Future<void> setTheme(String themeName) async {
|
||||
_setting.theme = themeName;
|
||||
_appearanceSettings.theme = themeName;
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(appTheme: await AppTheme.fromName(themeName)));
|
||||
}
|
||||
@@ -63,7 +70,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
|
||||
/// Update the theme mode in the user's settings and emit an updated state.
|
||||
void setThemeMode(ThemeMode themeMode) {
|
||||
_setting.themeMode = _themeModeToPB(themeMode);
|
||||
_appearanceSettings.themeMode = _themeModeToPB(themeMode);
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(themeMode: themeMode));
|
||||
}
|
||||
@@ -81,13 +88,13 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
}
|
||||
|
||||
void setLayoutDirection(LayoutDirection layoutDirection) {
|
||||
_setting.layoutDirection = layoutDirection.toLayoutDirectionPB();
|
||||
_appearanceSettings.layoutDirection = layoutDirection.toLayoutDirectionPB();
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(layoutDirection: layoutDirection));
|
||||
}
|
||||
|
||||
void setTextDirection(AppFlowyTextDirection? textDirection) {
|
||||
_setting.textDirection =
|
||||
_appearanceSettings.textDirection =
|
||||
textDirection?.toTextDirectionPB() ?? TextDirectionPB.FALLBACK;
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(textDirection: textDirection));
|
||||
@@ -96,7 +103,7 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
/// Update selected font in the user's settings and emit an updated state
|
||||
/// with the font name.
|
||||
void setFontFamily(String fontFamilyName) {
|
||||
_setting.font = fontFamilyName;
|
||||
_appearanceSettings.font = fontFamilyName;
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(font: fontFamilyName));
|
||||
}
|
||||
@@ -118,8 +125,8 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
});
|
||||
|
||||
if (state.locale != newLocale) {
|
||||
_setting.locale.languageCode = newLocale.languageCode;
|
||||
_setting.locale.countryCode = newLocale.countryCode ?? "";
|
||||
_appearanceSettings.locale.languageCode = newLocale.languageCode;
|
||||
_appearanceSettings.locale.countryCode = newLocale.countryCode ?? "";
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(locale: newLocale));
|
||||
}
|
||||
@@ -127,13 +134,13 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
|
||||
// Saves the menus current visibility
|
||||
void saveIsMenuCollapsed(bool collapsed) {
|
||||
_setting.isMenuCollapsed = collapsed;
|
||||
_appearanceSettings.isMenuCollapsed = collapsed;
|
||||
_saveAppearanceSettings();
|
||||
}
|
||||
|
||||
// Saves the current resize offset of the menu
|
||||
void saveMenuOffset(double offset) {
|
||||
_setting.menuOffset = offset;
|
||||
_appearanceSettings.menuOffset = offset;
|
||||
_saveAppearanceSettings();
|
||||
}
|
||||
|
||||
@@ -146,14 +153,14 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
_setting.settingKeyValue.remove(key);
|
||||
_appearanceSettings.settingKeyValue.remove(key);
|
||||
}
|
||||
|
||||
if (_setting.settingKeyValue[key] != value) {
|
||||
if (_appearanceSettings.settingKeyValue[key] != value) {
|
||||
if (value == null) {
|
||||
_setting.settingKeyValue.remove(key);
|
||||
_appearanceSettings.settingKeyValue.remove(key);
|
||||
} else {
|
||||
_setting.settingKeyValue[key] = value;
|
||||
_appearanceSettings.settingKeyValue[key] = value;
|
||||
}
|
||||
}
|
||||
_saveAppearanceSettings();
|
||||
@@ -164,14 +171,14 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
Log.warn("The key should not be empty");
|
||||
return null;
|
||||
}
|
||||
return _setting.settingKeyValue[key];
|
||||
return _appearanceSettings.settingKeyValue[key];
|
||||
}
|
||||
|
||||
/// Called when the application launches.
|
||||
/// Uses the device locale when the application is opened for the first time.
|
||||
void readLocaleWhenAppLaunch(BuildContext context) {
|
||||
if (_setting.resetToDefault) {
|
||||
_setting.resetToDefault = false;
|
||||
if (_appearanceSettings.resetToDefault) {
|
||||
_appearanceSettings.resetToDefault = false;
|
||||
_saveAppearanceSettings();
|
||||
setLocale(context, context.deviceLocale);
|
||||
return;
|
||||
@@ -204,7 +211,9 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
}
|
||||
|
||||
Future<void> _saveAppearanceSettings() async {
|
||||
UserSettingsBackendService().setAppearanceSetting(_setting).then((result) {
|
||||
UserSettingsBackendService()
|
||||
.setAppearanceSetting(_appearanceSettings)
|
||||
.then((result) {
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(error) => Log.error(error),
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/user/application/user_settings_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'notification_settings_cubit.freezed.dart';
|
||||
|
||||
class NotificationSettingsCubit extends Cubit<NotificationSettingsState> {
|
||||
final Completer<void> _initCompleter = Completer();
|
||||
|
||||
late final NotificationSettingsPB _notificationSettings;
|
||||
|
||||
NotificationSettingsCubit() : super(NotificationSettingsState.initial()) {
|
||||
UserSettingsBackendService()
|
||||
.getNotificationSettings()
|
||||
.then((notificationSettings) {
|
||||
_notificationSettings = notificationSettings;
|
||||
_initCompleter.complete();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> toggleNotificationsEnabled() async {
|
||||
await _initCompleter.future;
|
||||
|
||||
_notificationSettings.notificationsEnabled = !state.isNotificationsEnabled;
|
||||
_saveNotificationSettings();
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
isNotificationsEnabled: _notificationSettings.notificationsEnabled,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _saveNotificationSettings() async {
|
||||
await _initCompleter.future;
|
||||
|
||||
UserSettingsBackendService()
|
||||
.setNotificationSettings(_notificationSettings)
|
||||
.then((result) {
|
||||
result.fold(
|
||||
(error) => Log.error(error),
|
||||
(r) => null,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class NotificationSettingsState with _$NotificationSettingsState {
|
||||
const NotificationSettingsState._();
|
||||
|
||||
const factory NotificationSettingsState({
|
||||
required bool isNotificationsEnabled,
|
||||
}) = _NotificationSettingsState;
|
||||
|
||||
factory NotificationSettingsState.initial() =>
|
||||
const NotificationSettingsState(isNotificationsEnabled: true);
|
||||
}
|
||||
@@ -13,6 +13,7 @@ enum SettingsPage {
|
||||
language,
|
||||
files,
|
||||
user,
|
||||
notifications,
|
||||
syncSetting,
|
||||
shortcuts,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_service.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||
@@ -23,6 +23,7 @@ import 'package:flowy_infra_ui/style_widget/container.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:sized_context/sized_context.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../widgets/edit_panel/edit_panel.dart';
|
||||
@@ -75,11 +76,12 @@ class DesktopHomeScreen extends StatelessWidget {
|
||||
},
|
||||
),
|
||||
BlocProvider<HomeSettingBloc>(
|
||||
create: (context) {
|
||||
create: (_) {
|
||||
return HomeSettingBloc(
|
||||
userProfile,
|
||||
workspaceSetting,
|
||||
context.read<AppearanceSettingsCubit>(),
|
||||
context.widthPx,
|
||||
)..add(const HomeSettingEvent.initial());
|
||||
},
|
||||
),
|
||||
|
||||
@@ -29,6 +29,11 @@ class HomeLayout {
|
||||
|
||||
menuWidth += homeSetting.resizeOffset;
|
||||
|
||||
final screenWidthPx = context.widthPx;
|
||||
context
|
||||
.read<HomeSettingBloc>()
|
||||
.add(HomeSettingEvent.checkScreenSize(screenWidthPx));
|
||||
|
||||
if (homeSetting.isMenuCollapsed) {
|
||||
showMenu = false;
|
||||
} else {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import 'dart:io';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/favorite/favorite_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_action.dart';
|
||||
import 'package:appflowy/workspace/application/local_notifications/notification_action_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_action.dart';
|
||||
import 'package:appflowy/workspace/application/notifications/notification_action_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/menu/menu_bloc.dart';
|
||||
import 'package:appflowy/workspace/application/tabs/tabs_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/sidebar_folder.dart';
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ class SidebarTopMenu extends StatelessWidget {
|
||||
),
|
||||
],
|
||||
);
|
||||
return FlowyTooltip.delayed(
|
||||
return FlowyTooltip(
|
||||
richMessage: textSpan,
|
||||
child: FlowyIconButton(
|
||||
width: 28,
|
||||
|
||||
+10
-3
@@ -2,8 +2,9 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/notification_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_button.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/user_avatar.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
@@ -65,7 +66,7 @@ class SidebarUser extends StatelessWidget {
|
||||
|
||||
Widget _buildSettingsButton(BuildContext context, MenuUserState state) {
|
||||
final userProfile = state.userProfile;
|
||||
return FlowyTooltip.delayed(
|
||||
return FlowyTooltip(
|
||||
message: LocaleKeys.settings_menu_open.tr(),
|
||||
child: IconButton(
|
||||
onPressed: () {
|
||||
@@ -81,7 +82,13 @@ class SidebarUser extends StatelessWidget {
|
||||
Navigator.of(dialogContext).pop();
|
||||
await runAppFlowy();
|
||||
},
|
||||
dismissDialog: () => Navigator.of(context).pop(),
|
||||
dismissDialog: () {
|
||||
if (Navigator.of(dialogContext).canPop()) {
|
||||
Navigator.of(dialogContext).pop();
|
||||
} else {
|
||||
Log.warn("Can't pop dialog context");
|
||||
}
|
||||
},
|
||||
didOpenUser: () async {
|
||||
// Pop the dialog using the dialog context
|
||||
Navigator.of(dialogContext).pop();
|
||||
|
||||
@@ -339,7 +339,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
|
||||
// + button
|
||||
Widget _buildViewAddButton(BuildContext context) {
|
||||
return FlowyTooltip.delayed(
|
||||
return FlowyTooltip(
|
||||
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
||||
child: ViewAddButton(
|
||||
parentViewId: widget.view.id,
|
||||
@@ -379,7 +379,7 @@ class _SingleInnerViewItemState extends State<SingleInnerViewItem> {
|
||||
|
||||
// ··· more action button
|
||||
Widget _buildViewMoreActionButton(BuildContext context) {
|
||||
return FlowyTooltip.delayed(
|
||||
return FlowyTooltip(
|
||||
message: LocaleKeys.menuAppHeader_moreButtonToolTip.tr(),
|
||||
child: ViewMoreActionButton(
|
||||
view: widget.view,
|
||||
|
||||
@@ -66,7 +66,7 @@ class FlowyNavigation extends StatelessWidget {
|
||||
if (state.isMenuCollapsed) {
|
||||
return RotationTransition(
|
||||
turns: const AlwaysStoppedAnimation(180 / 360),
|
||||
child: FlowyTooltip.delayed(
|
||||
child: FlowyTooltip(
|
||||
richMessage: sidebarTooltipTextSpan(
|
||||
context,
|
||||
LocaleKeys.sideBar_openSidebar.tr(),
|
||||
|
||||
+274
-86
@@ -1,25 +1,29 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/notification_filter/notification_filter_bloc.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/notification_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_hub_title.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_tab_bar.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_view.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/reminder.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.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:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
extension _ReminderReady on ReminderPB {
|
||||
DateTime get scheduledDate =>
|
||||
DateTime.fromMillisecondsSinceEpoch(scheduledAt.toInt() * 1000);
|
||||
|
||||
bool isBefore(DateTime date) => scheduledDate.isBefore(date);
|
||||
extension _ReminderSort on Iterable<ReminderPB> {
|
||||
List<ReminderPB> sortByScheduledAt() =>
|
||||
sorted((a, b) => b.scheduledAt.compareTo(a.scheduledAt));
|
||||
}
|
||||
|
||||
class NotificationDialog extends StatelessWidget {
|
||||
class NotificationDialog extends StatefulWidget {
|
||||
const NotificationDialog({
|
||||
super.key,
|
||||
required this.views,
|
||||
@@ -29,94 +33,278 @@ class NotificationDialog extends StatelessWidget {
|
||||
final List<ViewPB> views;
|
||||
final PopoverMutex mutex;
|
||||
|
||||
@override
|
||||
State<NotificationDialog> createState() => _NotificationDialogState();
|
||||
}
|
||||
|
||||
class _NotificationDialogState extends State<NotificationDialog>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final TabController _controller = TabController(length: 2, vsync: this);
|
||||
final PopoverMutex _mutex = PopoverMutex();
|
||||
final ReminderBloc _reminderBloc = getIt<ReminderBloc>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller.addListener(_updateState);
|
||||
}
|
||||
|
||||
void _updateState() => setState(() {});
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_mutex.close();
|
||||
_controller.removeListener(_updateState);
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final reminderBloc = getIt<ReminderBloc>();
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider<ReminderBloc>.value(value: _reminderBloc),
|
||||
BlocProvider<NotificationFilterBloc>(
|
||||
create: (_) => NotificationFilterBloc(),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<NotificationFilterBloc, NotificationFilterState>(
|
||||
builder: (context, filterState) =>
|
||||
BlocBuilder<ReminderBloc, ReminderState>(
|
||||
builder: (context, state) {
|
||||
final List<ReminderPB> pastReminders = state.pastReminders
|
||||
.where((r) => filterState.showUnreadsOnly ? !r.isRead : true)
|
||||
.sortByScheduledAt();
|
||||
|
||||
return BlocProvider<ReminderBloc>.value(
|
||||
value: reminderBloc,
|
||||
child: BlocBuilder<ReminderBloc, ReminderState>(
|
||||
builder: (context, state) {
|
||||
final shownReminders = state.reminders
|
||||
.where((reminder) => reminder.isBefore(DateTime.now()))
|
||||
.sorted((a, b) => b.scheduledAt.compareTo(a.scheduledAt));
|
||||
final List<ReminderPB> upcomingReminders =
|
||||
state.upcomingReminders.sortByScheduledAt();
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 4,
|
||||
horizontal: 10,
|
||||
),
|
||||
child: FlowyText.semibold(
|
||||
LocaleKeys.notificationHub_title.tr(),
|
||||
fontSize: 16,
|
||||
),
|
||||
const NotificationHubTitle(),
|
||||
NotificationTabBar(tabController: _controller),
|
||||
// TODO(Xazin): Resolve issue with taking up
|
||||
// max amount of vertical space
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
controller: _controller,
|
||||
children: [
|
||||
NotificationsView(
|
||||
shownReminders: pastReminders,
|
||||
reminderBloc: _reminderBloc,
|
||||
views: widget.views,
|
||||
onDelete: _onDelete,
|
||||
onAction: _onAction,
|
||||
onReadChanged: _onReadChanged,
|
||||
actionBar: _InboxActionBar(
|
||||
hasUnreads: state.hasUnreads,
|
||||
showUnreadsOnly: filterState.showUnreadsOnly,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
NotificationsView(
|
||||
shownReminders: upcomingReminders,
|
||||
reminderBloc: _reminderBloc,
|
||||
views: widget.views,
|
||||
isUpcoming: true,
|
||||
onAction: _onAction,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const VSpace(4),
|
||||
if (shownReminders.isEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12),
|
||||
child: Center(
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.notificationHub_empty.tr(),
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
...shownReminders.map((reminder) {
|
||||
return NotificationItem(
|
||||
reminderId: reminder.id,
|
||||
key: ValueKey(reminder.id),
|
||||
title: reminder.title,
|
||||
scheduled: reminder.scheduledAt,
|
||||
body: reminder.message,
|
||||
isRead: reminder.isRead,
|
||||
onReadChanged: (isRead) => reminderBloc.add(
|
||||
ReminderEvent.update(
|
||||
ReminderUpdate(id: reminder.id, isRead: isRead),
|
||||
),
|
||||
),
|
||||
onDelete: () => reminderBloc
|
||||
.add(ReminderEvent.remove(reminderId: reminder.id)),
|
||||
onAction: () {
|
||||
final view = views.firstWhereOrNull(
|
||||
(view) => view.id == reminder.objectId,
|
||||
);
|
||||
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
reminderBloc.add(
|
||||
ReminderEvent.pressReminder(reminderId: reminder.id),
|
||||
);
|
||||
|
||||
mutex.close();
|
||||
},
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _onAction(ReminderPB reminder) {
|
||||
final view = widget.views.firstWhereOrNull(
|
||||
(view) => view.id == reminder.objectId,
|
||||
);
|
||||
|
||||
if (view == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
_reminderBloc.add(
|
||||
ReminderEvent.pressReminder(reminderId: reminder.id),
|
||||
);
|
||||
|
||||
widget.mutex.close();
|
||||
}
|
||||
|
||||
void _onDelete(ReminderPB reminder) {
|
||||
_reminderBloc.add(ReminderEvent.remove(reminder: reminder));
|
||||
}
|
||||
|
||||
void _onReadChanged(ReminderPB reminder, bool isRead) {
|
||||
_reminderBloc.add(
|
||||
ReminderEvent.update(ReminderUpdate(id: reminder.id, isRead: isRead)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InboxActionBar extends StatelessWidget {
|
||||
const _InboxActionBar({
|
||||
required this.hasUnreads,
|
||||
required this.showUnreadsOnly,
|
||||
});
|
||||
|
||||
final bool hasUnreads;
|
||||
final bool showUnreadsOnly;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
_MarkAsReadButton(
|
||||
onMarkAllRead: !hasUnreads
|
||||
? null
|
||||
: () => context
|
||||
.read<ReminderBloc>()
|
||||
.add(const ReminderEvent.markAllRead()),
|
||||
),
|
||||
);
|
||||
},
|
||||
_ToggleUnreadsButton(
|
||||
showUnreadsOnly: showUnreadsOnly,
|
||||
onToggled: (showUnreadsOnly) => context
|
||||
.read<NotificationFilterBloc>()
|
||||
.add(const NotificationFilterEvent.toggleShowUnreadsOnly()),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ToggleUnreadsButton extends StatefulWidget {
|
||||
const _ToggleUnreadsButton({
|
||||
required this.onToggled,
|
||||
this.showUnreadsOnly = false,
|
||||
});
|
||||
|
||||
final Function(bool) onToggled;
|
||||
final bool showUnreadsOnly;
|
||||
|
||||
@override
|
||||
State<_ToggleUnreadsButton> createState() => _ToggleUnreadsButtonState();
|
||||
}
|
||||
|
||||
class _ToggleUnreadsButtonState extends State<_ToggleUnreadsButton> {
|
||||
late bool showUnreadsOnly = widget.showUnreadsOnly;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SegmentedButton<bool>(
|
||||
onSelectionChanged: (Set<bool> newSelection) {
|
||||
setState(() => showUnreadsOnly = newSelection.first);
|
||||
widget.onToggled(showUnreadsOnly);
|
||||
},
|
||||
showSelectedIcon: false,
|
||||
style: ButtonStyle(
|
||||
side: MaterialStatePropertyAll(
|
||||
BorderSide(color: Theme.of(context).dividerColor),
|
||||
),
|
||||
shape: const MaterialStatePropertyAll(
|
||||
RoundedRectangleBorder(borderRadius: Corners.s6Border),
|
||||
),
|
||||
foregroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(state) {
|
||||
if (state.contains(MaterialState.hovered) ||
|
||||
state.contains(MaterialState.selected) ||
|
||||
state.contains(MaterialState.pressed)) {
|
||||
return Theme.of(context).colorScheme.onSurface;
|
||||
}
|
||||
|
||||
return AFThemeExtension.of(context).textColor;
|
||||
},
|
||||
),
|
||||
backgroundColor: MaterialStateProperty.resolveWith<Color>(
|
||||
(state) {
|
||||
if (state.contains(MaterialState.hovered) ||
|
||||
state.contains(MaterialState.selected) ||
|
||||
state.contains(MaterialState.pressed)) {
|
||||
return Theme.of(context).colorScheme.primary;
|
||||
}
|
||||
|
||||
return Theme.of(context).cardColor;
|
||||
},
|
||||
),
|
||||
),
|
||||
segments: [
|
||||
ButtonSegment<bool>(
|
||||
value: false,
|
||||
label: Text(
|
||||
LocaleKeys.notificationHub_actions_showAll.tr(),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
ButtonSegment<bool>(
|
||||
value: true,
|
||||
label: Text(
|
||||
LocaleKeys.notificationHub_actions_showUnreads.tr(),
|
||||
style: const TextStyle(fontSize: 12),
|
||||
),
|
||||
),
|
||||
],
|
||||
selected: <bool>{showUnreadsOnly},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _MarkAsReadButton extends StatefulWidget {
|
||||
final VoidCallback? onMarkAllRead;
|
||||
|
||||
const _MarkAsReadButton({this.onMarkAllRead});
|
||||
|
||||
@override
|
||||
State<_MarkAsReadButton> createState() => _MarkAsReadButtonState();
|
||||
}
|
||||
|
||||
class _MarkAsReadButtonState extends State<_MarkAsReadButton> {
|
||||
bool _isHovering = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Opacity(
|
||||
opacity: widget.onMarkAllRead != null ? 1 : 0.5,
|
||||
child: FlowyHover(
|
||||
onHover: (isHovering) => setState(() => _isHovering = isHovering),
|
||||
resetHoverOnRebuild: false,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.notificationHub_actions_markAllRead.tr(),
|
||||
fontColor: widget.onMarkAllRead != null && _isHovering
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: AFThemeExtension.of(context).textColor,
|
||||
heading: FlowySvg(
|
||||
FlowySvgs.checklist_s,
|
||||
color: widget.onMarkAllRead != null && _isHovering
|
||||
? Theme.of(context).colorScheme.onSurface
|
||||
: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
hoverColor: widget.onMarkAllRead != null && _isHovering
|
||||
? Theme.of(context).colorScheme.primary
|
||||
: null,
|
||||
onPressed: widget.onMarkAllRead,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+4
-2
@@ -24,14 +24,16 @@ class NotificationButton extends StatelessWidget {
|
||||
return BlocProvider<ReminderBloc>.value(
|
||||
value: getIt<ReminderBloc>(),
|
||||
child: BlocBuilder<ReminderBloc, ReminderState>(
|
||||
builder: (context, state) => FlowyTooltip.delayed(
|
||||
builder: (context, state) => FlowyTooltip(
|
||||
message: LocaleKeys.notificationHub_title.tr(),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: AppFlowyPopover(
|
||||
mutex: mutex,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
constraints: const BoxConstraints(maxHeight: 250, maxWidth: 300),
|
||||
constraints: const BoxConstraints(maxHeight: 250, maxWidth: 400),
|
||||
windowPadding: EdgeInsets.zero,
|
||||
margin: EdgeInsets.zero,
|
||||
popupBuilder: (_) =>
|
||||
NotificationDialog(views: views, mutex: mutex),
|
||||
child: _buildNotificationIcon(context, state.hasUnreads),
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotificationHubTitle extends StatelessWidget {
|
||||
const NotificationHubTitle({
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16) +
|
||||
const EdgeInsets.only(top: 12, bottom: 4),
|
||||
child: FlowyText.semibold(
|
||||
LocaleKeys.notificationHub_title.tr(),
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
fontSize: 16,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+64
-54
@@ -1,13 +1,14 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/date_time/date_format_ext.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
DateFormat _dateFormat(BuildContext context) => DateFormat('MMM d, y');
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class NotificationItem extends StatefulWidget {
|
||||
const NotificationItem({
|
||||
@@ -17,6 +18,8 @@ class NotificationItem extends StatefulWidget {
|
||||
required this.scheduled,
|
||||
required this.body,
|
||||
required this.isRead,
|
||||
this.includeTime = false,
|
||||
this.readOnly = false,
|
||||
this.onAction,
|
||||
this.onDelete,
|
||||
this.onReadChanged,
|
||||
@@ -26,6 +29,8 @@ class NotificationItem extends StatefulWidget {
|
||||
final String title;
|
||||
final Int64 scheduled;
|
||||
final String body;
|
||||
final bool includeTime;
|
||||
final bool readOnly;
|
||||
final bool isRead;
|
||||
|
||||
final VoidCallback? onAction;
|
||||
@@ -53,64 +58,65 @@ class _NotificationItemState extends State<NotificationItem> {
|
||||
GestureDetector(
|
||||
onTap: widget.onAction,
|
||||
child: Opacity(
|
||||
opacity: widget.isRead ? 0.5 : 1,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(10),
|
||||
opacity: widget.isRead && !widget.readOnly ? 0.5 : 1,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
color: _isHovering && widget.onAction != null
|
||||
? AFThemeExtension.of(context).lightGreyHover
|
||||
: Colors.transparent,
|
||||
border: widget.isRead || widget.readOnly
|
||||
? null
|
||||
: Border(
|
||||
left: BorderSide(
|
||||
width: 2,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
const FlowySvg(FlowySvgs.time_s, size: Size.square(20)),
|
||||
if (!widget.isRead)
|
||||
Positioned(
|
||||
bottom: 1,
|
||||
right: 1,
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: AFThemeExtension.of(context).warning,
|
||||
),
|
||||
child: const SizedBox(height: 8, width: 8),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const HSpace(10),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
child: FlowyText.semibold(widget.title),
|
||||
),
|
||||
FlowyText.regular(
|
||||
_scheduledString(widget.scheduled),
|
||||
fontSize: 10,
|
||||
),
|
||||
],
|
||||
),
|
||||
const VSpace(5),
|
||||
FlowyText.regular(widget.body, maxLines: 4),
|
||||
],
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
vertical: 10,
|
||||
horizontal: 16,
|
||||
),
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
FlowySvg(
|
||||
FlowySvgs.time_s,
|
||||
size: const Size.square(20),
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
],
|
||||
const HSpace(16),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
FlowyText.semibold(
|
||||
widget.title,
|
||||
fontSize: 14,
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
// TODO(Xazin): Relative time + View Name
|
||||
FlowyText.regular(
|
||||
_scheduledString(
|
||||
widget.scheduled,
|
||||
widget.includeTime,
|
||||
),
|
||||
fontSize: 10,
|
||||
),
|
||||
const VSpace(5),
|
||||
FlowyText.regular(widget.body, maxLines: 4),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_isHovering)
|
||||
if (_isHovering && !widget.readOnly)
|
||||
Positioned(
|
||||
right: 4,
|
||||
top: 4,
|
||||
@@ -125,10 +131,14 @@ class _NotificationItemState extends State<NotificationItem> {
|
||||
);
|
||||
}
|
||||
|
||||
String _scheduledString(Int64 secondsSinceEpoch) =>
|
||||
_dateFormat(context).format(
|
||||
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch.toInt() * 1000),
|
||||
);
|
||||
String _scheduledString(Int64 secondsSinceEpoch, bool includeTime) {
|
||||
final appearance = context.read<AppearanceSettingsCubit>().state;
|
||||
return appearance.dateFormat.formatDate(
|
||||
DateTime.fromMillisecondsSinceEpoch(secondsSinceEpoch.toInt() * 1000),
|
||||
includeTime,
|
||||
appearance.timeFormat,
|
||||
);
|
||||
}
|
||||
|
||||
void _onHover(bool isHovering) => setState(() => _isHovering = isHovering);
|
||||
}
|
||||
+78
@@ -0,0 +1,78 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotificationTabBar extends StatelessWidget {
|
||||
final TabController tabController;
|
||||
|
||||
const NotificationTabBar({
|
||||
super.key,
|
||||
required this.tabController,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
border: Border(
|
||||
bottom: BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: TabBar(
|
||||
controller: tabController,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
labelPadding: EdgeInsets.zero,
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
indicator: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
isScrollable: true,
|
||||
tabs: [
|
||||
_FlowyTab(
|
||||
label: LocaleKeys.notificationHub_tabs_inbox.tr(),
|
||||
isSelected: tabController.index == 0,
|
||||
),
|
||||
_FlowyTab(
|
||||
label: LocaleKeys.notificationHub_tabs_upcoming.tr(),
|
||||
isSelected: tabController.index == 1,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _FlowyTab extends StatelessWidget {
|
||||
final String label;
|
||||
final bool isSelected;
|
||||
|
||||
const _FlowyTab({
|
||||
required this.label,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Tab(
|
||||
height: 26,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: FlowyText.regular(
|
||||
label,
|
||||
color: isSelected ? Theme.of(context).colorScheme.tertiary : null,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
import 'package:appflowy/user/application/reminder/reminder_extension.dart';
|
||||
import 'package:appflowy/user/application/reminder/reminder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notification_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/notifications/widgets/notifications_hub_empty.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/reminder.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotificationsView extends StatelessWidget {
|
||||
const NotificationsView({
|
||||
super.key,
|
||||
required this.shownReminders,
|
||||
required this.reminderBloc,
|
||||
required this.views,
|
||||
this.isUpcoming = false,
|
||||
this.onAction,
|
||||
this.onDelete,
|
||||
this.onReadChanged,
|
||||
this.actionBar,
|
||||
});
|
||||
|
||||
final List<ReminderPB> shownReminders;
|
||||
final ReminderBloc reminderBloc;
|
||||
final List<ViewPB> views;
|
||||
final bool isUpcoming;
|
||||
final Function(ReminderPB reminder)? onAction;
|
||||
final Function(ReminderPB reminder)? onDelete;
|
||||
final Function(ReminderPB reminder, bool isRead)? onReadChanged;
|
||||
final Widget? actionBar;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (shownReminders.isEmpty) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.max,
|
||||
children: [
|
||||
if (actionBar != null) actionBar!,
|
||||
const Expanded(child: NotificationsHubEmpty()),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (actionBar != null) actionBar!,
|
||||
...shownReminders.map(
|
||||
(ReminderPB reminder) {
|
||||
return NotificationItem(
|
||||
reminderId: reminder.id,
|
||||
key: ValueKey(reminder.id),
|
||||
title: reminder.title,
|
||||
scheduled: reminder.scheduledAt,
|
||||
body: reminder.message,
|
||||
isRead: reminder.isRead,
|
||||
includeTime: reminder.includeTime ?? false,
|
||||
readOnly: isUpcoming,
|
||||
onReadChanged: (isRead) =>
|
||||
onReadChanged?.call(reminder, isRead),
|
||||
onDelete: () => onDelete?.call(reminder),
|
||||
onAction: () => onAction?.call(reminder),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class NotificationsHubEmpty extends StatelessWidget {
|
||||
const NotificationsHubEmpty({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText(
|
||||
LocaleKeys.notificationHub_emptyTitle.tr(),
|
||||
fontWeight: FontWeight.w700,
|
||||
fontSize: 14,
|
||||
),
|
||||
const VSpace(8),
|
||||
FlowyText.regular(
|
||||
LocaleKeys.notificationHub_emptyBody.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_notifications_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/sync_setting_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_customize_shortcuts_view.dart';
|
||||
@@ -101,6 +102,8 @@ class SettingsDialog extends StatelessWidget {
|
||||
didLogout: didLogout,
|
||||
didOpenUser: didOpenUser,
|
||||
);
|
||||
case SettingsPage.notifications:
|
||||
return const SettingsNotificationsView();
|
||||
case SettingsPage.syncSetting:
|
||||
return SyncSettingView(userId: user.id.toString());
|
||||
case SettingsPage.shortcuts:
|
||||
|
||||
+2
-2
@@ -36,12 +36,12 @@ void showEmojiPickerMenu(
|
||||
final top = alignment == Alignment.topLeft ? offset.dy : null;
|
||||
final bottom = alignment == Alignment.bottomLeft ? offset.dy : null;
|
||||
|
||||
keepEditorFocusNotifier.value += 1;
|
||||
keepEditorFocusNotifier.increase();
|
||||
final emojiPickerMenuEntry = FullScreenOverlayEntry(
|
||||
top: top,
|
||||
bottom: bottom,
|
||||
left: offset.dx,
|
||||
dismissCallback: () => keepEditorFocusNotifier.value -= 1,
|
||||
dismissCallback: () => keepEditorFocusNotifier.decrease(),
|
||||
builder: (context) => Material(
|
||||
type: MaterialType.transparency,
|
||||
child: Container(
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/theme_setting_entry_template.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/theme_upload/theme_upload_view.dart';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/util/google_font_family_extension.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/appearance_defaults.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/date_time.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/create_file_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/date_format_setting.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/time_format_setting.dart';
|
||||
|
||||
Loaded 100 of 278 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user