Merge branch 'main' into workspace-invite

This commit is contained in:
Zack Fu Zi Xiang 2024-03-08 13:44:38 +08:00
commit 0aa4aa956d
No known key found for this signature in database
25 changed files with 225 additions and 131 deletions

View File

@ -257,7 +257,7 @@
"label": "AF: Tauri UI Dev", "label": "AF: Tauri UI Dev",
"type": "shell", "type": "shell",
"isBackground": true, "isBackground": true,
"command": "pnpm run tauri:dev", "command": "pnpm run sync:i18n && pnpm run dev",
"options": { "options": {
"cwd": "${workspaceFolder}/appflowy_tauri" "cwd": "${workspaceFolder}/appflowy_tauri"
} }

View File

@ -148,7 +148,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
context, context,
showDragHandle: true, showDragHandle: true,
showDivider: false, showDivider: false,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) => _buildViewPageBottomSheet(context), builder: (_) => _buildViewPageBottomSheet(context),
); );
}, },

View File

@ -2,7 +2,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/widget/separated_flex.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
enum MobileViewItemBottomSheetBodyAction { enum MobileViewItemBottomSheetBodyAction {
@ -26,12 +25,8 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SeparatedColumn( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
separatorBuilder: () => const Divider(
height: 8.5,
thickness: 0.5,
),
children: [ children: [
MobileQuickActionButton( MobileQuickActionButton(
text: LocaleKeys.button_rename.tr(), text: LocaleKeys.button_rename.tr(),
@ -40,6 +35,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
MobileViewItemBottomSheetBodyAction.rename, MobileViewItemBottomSheetBodyAction.rename,
), ),
), ),
_divider(),
MobileQuickActionButton( MobileQuickActionButton(
text: isFavorite text: isFavorite
? LocaleKeys.button_removeFromFavorites.tr() ? LocaleKeys.button_removeFromFavorites.tr()
@ -54,6 +50,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
: MobileViewItemBottomSheetBodyAction.addToFavorites, : MobileViewItemBottomSheetBodyAction.addToFavorites,
), ),
), ),
_divider(),
MobileQuickActionButton( MobileQuickActionButton(
text: LocaleKeys.button_duplicate.tr(), text: LocaleKeys.button_duplicate.tr(),
icon: FlowySvgs.m_duplicate_s, icon: FlowySvgs.m_duplicate_s,
@ -61,6 +58,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
MobileViewItemBottomSheetBodyAction.duplicate, MobileViewItemBottomSheetBodyAction.duplicate,
), ),
), ),
_divider(),
MobileQuickActionButton( MobileQuickActionButton(
text: LocaleKeys.button_delete.tr(), text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error, textColor: Theme.of(context).colorScheme.error,
@ -70,7 +68,13 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
MobileViewItemBottomSheetBodyAction.delete, MobileViewItemBottomSheetBodyAction.delete,
), ),
), ),
_divider(),
], ],
); );
} }
Widget _divider() => const Divider(
height: 8.5,
thickness: 0.5,
);
} }

View File

@ -4,7 +4,6 @@ import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart'; import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
enum MobileViewBottomSheetBodyAction { enum MobileViewBottomSheetBodyAction {
@ -85,12 +84,8 @@ class MobileViewBottomSheetBody extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isFavorite = view.isFavorite; final isFavorite = view.isFavorite;
return SeparatedColumn( return Column(
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
separatorBuilder: () => const Divider(
height: 8.5,
thickness: 0.5,
),
children: [ children: [
MobileQuickActionButton( MobileQuickActionButton(
text: LocaleKeys.button_rename.tr(), text: LocaleKeys.button_rename.tr(),
@ -99,6 +94,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
MobileViewBottomSheetBodyAction.rename, MobileViewBottomSheetBodyAction.rename,
), ),
), ),
_divider(),
MobileQuickActionButton( MobileQuickActionButton(
text: isFavorite text: isFavorite
? LocaleKeys.button_removeFromFavorites.tr() ? LocaleKeys.button_removeFromFavorites.tr()
@ -113,6 +109,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
: MobileViewBottomSheetBodyAction.addToFavorites, : MobileViewBottomSheetBodyAction.addToFavorites,
), ),
), ),
_divider(),
MobileQuickActionButton( MobileQuickActionButton(
text: LocaleKeys.button_duplicate.tr(), text: LocaleKeys.button_duplicate.tr(),
icon: FlowySvgs.m_duplicate_s, icon: FlowySvgs.m_duplicate_s,
@ -120,6 +117,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
MobileViewBottomSheetBodyAction.duplicate, MobileViewBottomSheetBodyAction.duplicate,
), ),
), ),
_divider(),
MobileQuickActionButton( MobileQuickActionButton(
text: LocaleKeys.button_delete.tr(), text: LocaleKeys.button_delete.tr(),
textColor: Theme.of(context).colorScheme.error, textColor: Theme.of(context).colorScheme.error,
@ -129,7 +127,13 @@ class MobileViewBottomSheetBody extends StatelessWidget {
MobileViewBottomSheetBodyAction.delete, MobileViewBottomSheetBodyAction.delete,
), ),
), ),
_divider(),
], ],
); );
} }
Widget _divider() => const Divider(
height: 8.5,
thickness: 0.5,
);
} }

View File

@ -54,7 +54,7 @@ enum MobilePaneActionType {
context, context,
showDragHandle: true, showDragHandle: true,
showDivider: false, showDivider: false,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.background,
useRootNavigator: true, useRootNavigator: true,
builder: (context) { builder: (context) {
return MultiBlocProvider( return MultiBlocProvider(

View File

@ -150,6 +150,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
icon: FlowySvgs.m_delete_m, icon: FlowySvgs.m_delete_m,
iconColor: Theme.of(context).colorScheme.error, iconColor: Theme.of(context).colorScheme.error,
), ),
const Divider(height: 8.5, thickness: 0.5),
], ],
), ),
); );

View File

@ -40,7 +40,7 @@ Future<FieldType?> showFieldTypeGridBottomSheet(
showCloseButton: true, showCloseButton: true,
elevation: 20, elevation: 20,
title: title, title: title,
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.background,
enableDraggableScrollable: true, enableDraggableScrollable: true,
builder: (context) { builder: (context) {
final typeOptionMenuItemValue = mobileSupportedFieldTypes final typeOptionMenuItemValue = mobileSupportedFieldTypes
@ -94,9 +94,6 @@ void mobileCreateFieldWorkflow(
if (optionValues != null) { if (optionValues != null) {
await optionValues.create(viewId: viewId, position: position); await optionValues.create(viewId: viewId, position: position);
} }
if (context.mounted) {
context.pop();
}
} }
/// Used to edit a field. /// Used to edit a field.

View File

@ -262,13 +262,11 @@ class _SortItem extends StatelessWidget {
children: [ children: [
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0), padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Expanded(
child: FlowyText.medium( child: FlowyText.medium(
LocaleKeys.grid_sort_by.tr(), LocaleKeys.grid_sort_by.tr(),
fontSize: 15, fontSize: 15,
), ),
), ),
),
const VSpace(10), const VSpace(10),
Row( Row(
children: [ children: [
@ -407,6 +405,8 @@ class _SortDetailContent extends StatelessWidget {
final SortInfo? sortInfo; final SortInfo? sortInfo;
bool get isCreatingNewSort => sortInfo == null;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
@ -417,7 +417,7 @@ class _SortDetailContent extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.symmetric(horizontal: 16),
child: DefaultTabController( child: DefaultTabController(
length: 2, length: 2,
initialIndex: sortInfo == null initialIndex: isCreatingNewSort
? 0 ? 0
: sortInfo!.sortPB.condition == SortConditionPB.Ascending : sortInfo!.sortPB.condition == SortConditionPB.Ascending
? 0 ? 0
@ -489,30 +489,40 @@ class _SortDetailContent extends StatelessWidget {
child: BlocBuilder<SortEditorBloc, SortEditorState>( child: BlocBuilder<SortEditorBloc, SortEditorState>(
builder: (context, state) { builder: (context, state) {
final fields = state.allFields final fields = state.allFields
.where( .where((field) => field.canCreateSort || field.hasSort)
(field) =>
field.canCreateSort ||
sortInfo != null && sortInfo!.fieldId == field.id,
)
.toList(); .toList();
return ListView.builder( return ListView.builder(
itemCount: fields.length, itemCount: fields.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
final fieldInfo = fields[index]; final fieldInfo = fields[index];
final isSelected = sortInfo == null final isSelected = isCreatingNewSort
? context ? context
.watch<MobileSortEditorCubit>() .watch<MobileSortEditorCubit>()
.state .state
.newSortFieldId == .newSortFieldId ==
fieldInfo.id fieldInfo.id
: sortInfo!.fieldId == fieldInfo.id; : sortInfo!.fieldId == fieldInfo.id;
final enabled = fieldInfo.canCreateSort ||
isCreatingNewSort && !fieldInfo.hasSort ||
!isCreatingNewSort && sortInfo!.fieldId == fieldInfo.id;
return FlowyOptionTile.checkbox( return FlowyOptionTile.checkbox(
text: fieldInfo.field.name, text: fieldInfo.field.name,
isSelected: isSelected, isSelected: isSelected,
textColor: enabled ? null : Theme.of(context).disabledColor,
showTopBorder: false, showTopBorder: false,
onTap: () { onTap: () {
if (!isSelected) { if (isSelected) {
return;
}
if (enabled) {
_changeFieldId(context, fieldInfo.id); _changeFieldId(context, fieldInfo.id);
} else {
Fluttertoast.showToast(
msg: LocaleKeys.grid_sort_fieldInUse.tr(),
gravity: ToastGravity.BOTTOM,
);
} }
}, },
); );
@ -526,7 +536,7 @@ class _SortDetailContent extends StatelessWidget {
} }
void _changeCondition(BuildContext context, SortConditionPB newCondition) { void _changeCondition(BuildContext context, SortConditionPB newCondition) {
if (sortInfo == null) { if (isCreatingNewSort) {
context.read<MobileSortEditorCubit>().changeSortCondition(newCondition); context.read<MobileSortEditorCubit>().changeSortCondition(newCondition);
} else { } else {
context.read<SortEditorBloc>().add( context.read<SortEditorBloc>().add(
@ -539,7 +549,7 @@ class _SortDetailContent extends StatelessWidget {
} }
void _changeFieldId(BuildContext context, String newFieldId) { void _changeFieldId(BuildContext context, String newFieldId) {
if (sortInfo == null) { if (isCreatingNewSort) {
context.read<MobileSortEditorCubit>().changeFieldId(newFieldId); context.read<MobileSortEditorCubit>().changeFieldId(newFieldId);
} else { } else {
context.read<SortEditorBloc>().add( context.read<SortEditorBloc>().add(

View File

@ -183,6 +183,7 @@ class MobileDatabaseViewListButton extends StatelessWidget {
showMobileBottomSheet( showMobileBottomSheet(
context, context,
showDragHandle: true, showDragHandle: true,
backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) { builder: (_) {
return BlocProvider<ViewBloc>( return BlocProvider<ViewBloc>(
create: (_) => create: (_) =>

View File

@ -7,6 +7,7 @@ import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/shared/appflowy_cache_manager.dart'; import 'package:appflowy/shared/appflowy_cache_manager.dart';
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/share_log_files.dart'; import 'package:appflowy/util/share_log_files.dart';
import 'package:appflowy/workspace/presentation/home/toast.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -71,9 +72,14 @@ class SupportSettingGroup extends StatelessWidget {
maxLines: 4, maxLines: 4,
), ),
actionButtonTitle: LocaleKeys.button_yes.tr(), actionButtonTitle: LocaleKeys.button_yes.tr(),
actionButtonColor: Theme.of(context).colorScheme.error,
onActionButtonPressed: () async { onActionButtonPressed: () async {
await getIt<FlowyCacheManager>().clearAllCache(); await getIt<FlowyCacheManager>().clearAllCache();
if (context.mounted) {
showSnackBarMessage(
context,
LocaleKeys.settings_files_clearCacheSuccess.tr(),
);
}
}, },
); );
}, },

View File

@ -13,6 +13,7 @@ enum FlowyOptionTileType {
class FlowyOptionTile extends StatelessWidget { class FlowyOptionTile extends StatelessWidget {
const FlowyOptionTile._({ const FlowyOptionTile._({
super.key,
required this.type, required this.type,
this.showTopBorder = true, this.showTopBorder = true,
this.showBottomBorder = true, this.showBottomBorder = true,
@ -88,9 +89,11 @@ class FlowyOptionTile extends StatelessWidget {
} }
factory FlowyOptionTile.checkbox({ factory FlowyOptionTile.checkbox({
Key? key,
required String text, required String text,
required bool isSelected, required bool isSelected,
required VoidCallback? onTap, required VoidCallback? onTap,
Color? textColor,
Widget? leftIcon, Widget? leftIcon,
Widget? content, Widget? content,
bool showTopBorder = true, bool showTopBorder = true,
@ -99,9 +102,11 @@ class FlowyOptionTile extends StatelessWidget {
Color? backgroundColor, Color? backgroundColor,
}) { }) {
return FlowyOptionTile._( return FlowyOptionTile._(
key: key,
type: FlowyOptionTileType.checkbox, type: FlowyOptionTileType.checkbox,
isSelected: isSelected, isSelected: isSelected,
text: text, text: text,
textColor: textColor,
content: content, content: content,
onTap: onTap, onTap: onTap,
fontFamily: fontFamily, fontFamily: fontFamily,

View File

@ -337,6 +337,7 @@ class FieldController {
...changeset.insertSorts.map((sort) => sort.sort.fieldId), ...changeset.insertSorts.map((sort) => sort.sort.fieldId),
...changeset.updateSorts.map((sort) => sort.fieldId), ...changeset.updateSorts.map((sort) => sort.fieldId),
...changeset.deleteSorts.map((sort) => sort.fieldId), ...changeset.deleteSorts.map((sort) => sort.fieldId),
...?_sortNotifier?.sorts.map((sort) => sort.fieldId),
]); ]);
final newFieldInfos = [...fieldInfos]; final newFieldInfos = [...fieldInfos];
@ -367,8 +368,8 @@ class FieldController {
insertSortFromChangeset(newSortInfos, changeset); insertSortFromChangeset(newSortInfos, changeset);
updateSortFromChangeset(newSortInfos, changeset); updateSortFromChangeset(newSortInfos, changeset);
_sortNotifier?.sorts = newSortInfos;
updateFieldInfos(newSortInfos, changeset); updateFieldInfos(newSortInfos, changeset);
_sortNotifier?.sorts = newSortInfos;
}, },
(err) => Log.error(err), (err) => Log.error(err),
); );

View File

@ -32,7 +32,7 @@ class _CalculationSelectorState extends State<CalculationSelector> {
onEnter: (_) => _setHovering(true), onEnter: (_) => _setHovering(true),
onExit: (_) => _setHovering(false), onExit: (_) => _setHovering(false),
child: AnimatedOpacity( child: AnimatedOpacity(
duration: const Duration(milliseconds: 200), duration: const Duration(milliseconds: 100),
opacity: widget.isSelected || _isHovering ? 1 : 0, opacity: widget.isSelected || _isHovering ? 1 : 0,
child: FlowyButton( child: FlowyButton(
radius: BorderRadius.zero, radius: BorderRadius.zero,

View File

@ -370,9 +370,6 @@ class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
Widget _addDeleteFieldButton() { Widget _addDeleteFieldButton() {
return BlocBuilder<FieldEditorBloc, FieldEditorState>( return BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) { builder: (context, state) {
if (state.field.isPrimary) {
return const SizedBox.shrink();
}
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0), padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
child: FieldActionCell( child: FieldActionCell(
@ -389,9 +386,6 @@ class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
Widget _addDuplicateFieldButton() { Widget _addDuplicateFieldButton() {
return BlocBuilder<FieldEditorBloc, FieldEditorState>( return BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) { builder: (context, state) {
if (state.field.isPrimary) {
return const SizedBox.shrink();
}
return Padding( return Padding(
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0), padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
child: FieldActionCell( child: FieldActionCell(
@ -533,11 +527,14 @@ class _SwitchFieldButtonState extends State<SwitchFieldButton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) {
final bool isPrimary = state.field.isPrimary;
return SizedBox( return SizedBox(
height: GridSize.popoverItemHeight, height: GridSize.popoverItemHeight,
child: AppFlowyPopover( child: AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(460, 540)), constraints: BoxConstraints.loose(const Size(460, 540)),
triggerActions: PopoverTriggerFlags.hover, triggerActions: isPrimary ? 0 : PopoverTriggerFlags.hover,
mutex: widget.popoverMutex, mutex: widget.popoverMutex,
controller: _popoverController, controller: _popoverController,
offset: const Offset(8, 0), offset: const Offset(8, 0),
@ -553,21 +550,29 @@ class _SwitchFieldButtonState extends State<SwitchFieldButton> {
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: _buildMoreButton(context), child: FlowyButton(
onTap: () {
if (!isPrimary) {
_popoverController.show();
}
},
text: FlowyText.medium(
state.field.fieldType.i18n,
color: isPrimary ? Theme.of(context).disabledColor : null,
),
leftIcon: FlowySvg(
state.field.fieldType.svgData,
color: isPrimary ? Theme.of(context).disabledColor : null,
),
rightIcon: FlowySvg(
FlowySvgs.more_s,
color: isPrimary ? Theme.of(context).disabledColor : null,
),
),
), ),
), ),
); );
} },
Widget _buildMoreButton(BuildContext context) {
final bloc = context.read<FieldEditorBloc>();
return FlowyButton(
onTap: () => _popoverController.show(),
text: FlowyText.medium(
bloc.state.field.fieldType.i18n,
),
leftIcon: FlowySvg(bloc.state.field.fieldType.svgData),
rightIcon: const FlowySvg(FlowySvgs.more_s),
); );
} }
} }

View File

@ -1,11 +1,12 @@
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart'; import 'package:flutter/material.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/checklist_cell_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_cell_editor.dart';
import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart'; import 'package:appflowy/plugins/database/widgets/cell_editor/checklist_progress_bar.dart';
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../editable_cell_skeleton/checklist.dart'; import '../editable_cell_skeleton/checklist.dart';
@ -25,6 +26,7 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin {
constraints: BoxConstraints.loose(const Size(360, 400)), constraints: BoxConstraints.loose(const Size(360, 400)),
direction: PopoverDirection.bottomWithLeftAligned, direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerFlags.none, triggerActions: PopoverTriggerFlags.none,
skipTraversal: true,
popupBuilder: (BuildContext popoverContext) { popupBuilder: (BuildContext popoverContext) {
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
cellContainerNotifier.isFocus = true; cellContainerNotifier.isFocus = true;

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart'; import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
@ -177,21 +176,11 @@ class _VisitURLAccessoryState extends State<_VisitURLAccessory>
} }
@override @override
bool enable() { bool enable() => widget.cellDataNotifier.value.isNotEmpty;
return widget.cellDataNotifier.value.isNotEmpty;
}
@override @override
void onTap() { void onTap() =>
final content = widget.cellDataNotifier.value; openUrlCellLink(widget.cellDataNotifier.value);
if (content.isEmpty) {
return;
}
final shouldAddScheme =
!['http', 'https'].any((pattern) => content.startsWith(pattern));
final url = shouldAddScheme ? 'http://$content' : content;
afLaunchUrlString(url);
}
} }
class _URLAccessoryIconContainer extends StatelessWidget { class _URLAccessoryIconContainer extends StatelessWidget {

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:appflowy/core/helpers/url_launcher.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
import 'package:appflowy/plugins/database/application/database_controller.dart'; import 'package:appflowy/plugins/database/application/database_controller.dart';
@ -15,6 +16,9 @@ import '../desktop_row_detail/desktop_row_detail_url_cell.dart';
import '../mobile_grid/mobile_grid_url_cell.dart'; import '../mobile_grid/mobile_grid_url_cell.dart';
import '../mobile_row_detail/mobile_row_detail_url_cell.dart'; import '../mobile_row_detail/mobile_row_detail_url_cell.dart';
const regexUrl =
r"[(http(s)?):\/\/(www\.)?a-zA-Z0-9@:._\+-~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:_\+.~#?&\/\/=]*)";
abstract class IEditableURLCellSkin { abstract class IEditableURLCellSkin {
const IEditableURLCellSkin(); const IEditableURLCellSkin();
@ -130,3 +134,24 @@ class _GridURLCellState extends GridEditableTextCell<EditableURLCell> {
@override @override
String? onCopy() => cellBloc.state.content; String? onCopy() => cellBloc.state.content;
} }
void openUrlCellLink(String content) {
if (RegExp(regexUrl).hasMatch(content)) {
const linkPrefix = [
'http://',
'https://',
'file://',
'ftp://',
'ftps://',
'mailto:',
];
final shouldAddScheme =
!linkPrefix.any((pattern) => content.startsWith(pattern));
final url = shouldAddScheme ? 'https://$content' : content;
afLaunchUrlString(url);
} else {
afLaunchUrlString(
"https://www.google.com/search?q=${Uri.encodeComponent(content)}",
);
}
}

View File

@ -1,6 +1,9 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart';
@ -11,11 +14,10 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/theme_extension.dart'; import 'package:flowy_infra/theme_extension.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart'; import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import '../../application/cell/bloc/checklist_cell_bloc.dart'; import '../../application/cell/bloc/checklist_cell_bloc.dart';
import 'checklist_progress_bar.dart'; import 'checklist_progress_bar.dart';
class ChecklistCellEditor extends StatefulWidget { class ChecklistCellEditor extends StatefulWidget {
@ -189,7 +191,9 @@ class _ChecklistItemState extends State<ChecklistItem> {
void didUpdateWidget(ChecklistItem oldWidget) { void didUpdateWidget(ChecklistItem oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.task.data.name != oldWidget.task.data.name) { if (widget.task.data.name != oldWidget.task.data.name) {
final selection = _textController.selection;
_textController.text = widget.task.data.name; _textController.text = widget.task.data.name;
_textController.selection = selection;
} }
} }
@ -234,6 +238,10 @@ class _ChecklistItemState extends State<ChecklistItem> {
else else
const SingleActivator(LogicalKeyboardKey.enter, control: true): const SingleActivator(LogicalKeyboardKey.enter, control: true):
const _SelectTaskIntent(), const _SelectTaskIntent(),
const SingleActivator(LogicalKeyboardKey.arrowUp):
const PreviousFocusIntent(),
const SingleActivator(LogicalKeyboardKey.arrowDown):
const NextFocusIntent(),
}, },
descendantsAreTraversable: false, descendantsAreTraversable: false,
child: Container( child: Container(
@ -261,15 +269,24 @@ class _ChecklistItemState extends State<ChecklistItem> {
), ),
Expanded( Expanded(
child: Shortcuts( child: Shortcuts(
shortcuts: const { shortcuts: {
SingleActivator(LogicalKeyboardKey.space): const SingleActivator(LogicalKeyboardKey.space):
DoNothingAndStopPropagationIntent(), const DoNothingAndStopPropagationIntent(),
SingleActivator(LogicalKeyboardKey.delete): const SingleActivator(LogicalKeyboardKey.delete):
DoNothingAndStopPropagationIntent(), const DoNothingAndStopPropagationIntent(),
SingleActivator(LogicalKeyboardKey.enter): if (Platform.isMacOS)
DoNothingAndStopPropagationIntent(), LogicalKeySet(
SingleActivator(LogicalKeyboardKey.escape): LogicalKeyboardKey.fn,
_EndEditingTaskIntent(), LogicalKeyboardKey.backspace,
): const DoNothingAndStopPropagationIntent(),
const SingleActivator(LogicalKeyboardKey.enter):
const DoNothingAndStopPropagationIntent(),
const SingleActivator(LogicalKeyboardKey.escape):
const _EndEditingTaskIntent(),
const SingleActivator(LogicalKeyboardKey.arrowUp):
const DoNothingAndStopPropagationIntent(),
const SingleActivator(LogicalKeyboardKey.arrowDown):
const DoNothingAndStopPropagationIntent(),
}, },
child: TextField( child: TextField(
controller: _textController, controller: _textController,
@ -345,12 +362,11 @@ class NewTaskItem extends StatefulWidget {
} }
class _NewTaskItemState extends State<NewTaskItem> { class _NewTaskItemState extends State<NewTaskItem> {
late final TextEditingController _textEditingController; final _textEditingController = TextEditingController();
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_textEditingController = TextEditingController();
if (widget.focusNode.canRequestFocus) { if (widget.focusNode.canRequestFocus) {
widget.focusNode.requestFocus(); widget.focusNode.requestFocus();
} }
@ -385,15 +401,13 @@ class _NewTaskItemState extends State<NewTaskItem> {
hintText: LocaleKeys.grid_checklist_addNew.tr(), hintText: LocaleKeys.grid_checklist_addNew.tr(),
), ),
onSubmitted: (taskDescription) { onSubmitted: (taskDescription) {
if (taskDescription.trim().isNotEmpty) { if (taskDescription.isNotEmpty) {
context.read<ChecklistCellBloc>().add( context
ChecklistCellEvent.createNewTask( .read<ChecklistCellBloc>()
taskDescription.trim(), .add(ChecklistCellEvent.createNewTask(taskDescription));
), _textEditingController.clear();
);
} }
widget.focusNode.requestFocus(); widget.focusNode.requestFocus();
_textEditingController.clear();
}, },
onChanged: (value) => setState(() {}), onChanged: (value) => setState(() {}),
), ),
@ -409,13 +423,14 @@ class _NewTaskItemState extends State<NewTaskItem> {
: Theme.of(context).colorScheme.primaryContainer, : Theme.of(context).colorScheme.primaryContainer,
fontColor: Theme.of(context).colorScheme.onPrimary, fontColor: Theme.of(context).colorScheme.onPrimary,
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
onPressed: () { onPressed: _textEditingController.text.isEmpty
final text = _textEditingController.text.trim(); ? null
if (text.isNotEmpty) { : () {
context.read<ChecklistCellBloc>().add( context.read<ChecklistCellBloc>().add(
ChecklistCellEvent.createNewTask(text), ChecklistCellEvent.createNewTask(
_textEditingController.text,
),
); );
}
widget.focusNode.requestFocus(); widget.focusNode.requestFocus();
_textEditingController.clear(); _textEditingController.clear();
}, },

View File

@ -150,6 +150,7 @@ void _showEditSortPanelFromToolbar(
showDragHandle: true, showDragHandle: true,
showDivider: false, showDivider: false,
useSafeArea: false, useSafeArea: false,
backgroundColor: Theme.of(context).colorScheme.background,
builder: (_) { builder: (_) {
return BlocProvider.value( return BlocProvider.value(
value: context.read<SortEditorBloc>(), value: context.read<SortEditorBloc>(),

View File

@ -86,6 +86,13 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
popupBuilder: (context) { popupBuilder: (context) {
return UploadImageMenu( return UploadImageMenu(
limitMaximumImageSize: !_isLocalMode(), limitMaximumImageSize: !_isLocalMode(),
supportTypes: const [
UploadImageType.local,
UploadImageType.url,
UploadImageType.unsplash,
UploadImageType.openAI,
UploadImageType.stabilityAI,
],
onSelectedLocalImage: (path) { onSelectedLocalImage: (path) {
controller.close(); controller.close();
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async { WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {

View File

@ -1,7 +1,8 @@
import 'package:appflowy_popover/src/layout.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:appflowy_popover/src/layout.dart';
import 'mask.dart'; import 'mask.dart';
import 'mutex.dart'; import 'mutex.dart';
@ -90,6 +91,8 @@ class Popover extends StatefulWidget {
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive. /// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
final PopoverClickHandler clickHandler; final PopoverClickHandler clickHandler;
final bool skipTraversal;
/// The content area of the popover. /// The content area of the popover.
final Widget child; final Widget child;
@ -110,6 +113,7 @@ class Popover extends StatefulWidget {
this.canClose, this.canClose,
this.asBarrier = false, this.asBarrier = false,
this.clickHandler = PopoverClickHandler.listener, this.clickHandler = PopoverClickHandler.listener,
this.skipTraversal = false,
}); });
@override @override
@ -158,6 +162,7 @@ class PopoverState extends State<Popover> {
popupBuilder: widget.popupBuilder, popupBuilder: widget.popupBuilder,
onClose: () => close(), onClose: () => close(),
onCloseAll: () => _removeRootOverlay(), onCloseAll: () => _removeRootOverlay(),
skipTraversal: widget.skipTraversal,
), ),
); );
@ -263,6 +268,7 @@ class PopoverContainer extends StatefulWidget {
final EdgeInsets windowPadding; final EdgeInsets windowPadding;
final void Function() onClose; final void Function() onClose;
final void Function() onCloseAll; final void Function() onCloseAll;
final bool skipTraversal;
const PopoverContainer({ const PopoverContainer({
super.key, super.key,
@ -273,6 +279,7 @@ class PopoverContainer extends StatefulWidget {
required this.windowPadding, required this.windowPadding,
required this.onClose, required this.onClose,
required this.onCloseAll, required this.onCloseAll,
required this.skipTraversal,
}); });
@override @override
@ -293,6 +300,7 @@ class PopoverContainerState extends State<PopoverContainer> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Focus( return Focus(
autofocus: true, autofocus: true,
skipTraversal: widget.skipTraversal,
child: CustomSingleChildLayout( child: CustomSingleChildLayout(
delegate: PopoverLayoutDelegate( delegate: PopoverLayoutDelegate(
direction: widget.direction, direction: widget.direction,

View File

@ -26,6 +26,10 @@ class AppFlowyPopover extends StatelessWidget {
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive. /// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
final PopoverClickHandler clickHandler; final PopoverClickHandler clickHandler;
/// If true the popover will not participate in focus traversal.
///
final bool skipTraversal;
const AppFlowyPopover({ const AppFlowyPopover({
super.key, super.key,
required this.child, required this.child,
@ -43,6 +47,7 @@ class AppFlowyPopover extends StatelessWidget {
this.windowPadding = const EdgeInsets.all(8.0), this.windowPadding = const EdgeInsets.all(8.0),
this.decoration, this.decoration,
this.clickHandler = PopoverClickHandler.listener, this.clickHandler = PopoverClickHandler.listener,
this.skipTraversal = false,
}); });
@override @override
@ -58,6 +63,7 @@ class AppFlowyPopover extends StatelessWidget {
windowPadding: windowPadding, windowPadding: windowPadding,
offset: offset, offset: offset,
clickHandler: clickHandler, clickHandler: clickHandler,
skipTraversal: skipTraversal,
popupBuilder: (context) { popupBuilder: (context) {
return _PopoverContainer( return _PopoverContainer(
constraints: constraints, constraints: constraints,

View File

@ -1,12 +1,13 @@
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flowy_infra/size.dart'; import 'package:flowy_infra/size.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart'; import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart'; import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/flowy_tooltip.dart'; import 'package:flowy_infra_ui/widget/flowy_tooltip.dart';
import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart'; import 'package:flowy_infra_ui/widget/ignore_parent_gesture.dart';
import 'package:flowy_infra_ui/widget/spacing.dart'; import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
class FlowyButton extends StatelessWidget { class FlowyButton extends StatelessWidget {
final Widget text; final Widget text;
@ -213,6 +214,7 @@ class FlowyTextButton extends StatelessWidget {
); );
child = RawMaterialButton( child = RawMaterialButton(
focusNode: FocusNode(skipTraversal: onPressed == null),
hoverElevation: 0, hoverElevation: 0,
highlightElevation: 0, highlightElevation: 0,
shape: RoundedRectangleBorder(borderRadius: radius ?? Corners.s6Border), shape: RoundedRectangleBorder(borderRadius: radius ?? Corners.s6Border),
@ -237,6 +239,10 @@ class FlowyTextButton extends StatelessWidget {
); );
} }
if (onPressed == null) {
child = ExcludeFocus(child: child);
}
return child; return child;
} }
} }

View File

@ -1101,10 +1101,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: markdown name: markdown
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90" sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.2.1" version: "7.2.2"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:

View File

@ -463,7 +463,7 @@
"exportFileFail": "Export file failed!", "exportFileFail": "Export file failed!",
"export": "Export", "export": "Export",
"clearCache": "Clear cache", "clearCache": "Clear cache",
"clearCacheDesc": "Clear the cache including the images, fonts, and other temporary files. This will not delete your data.", "clearCacheDesc": "If you encounter issues with images not loading or fonts not displaying correctly, try clearing your cache. This action will not remove your user data.",
"areYouSureToClearCache": "Are you sure to clear the cache?", "areYouSureToClearCache": "Are you sure to clear the cache?",
"clearCacheSuccess": "Cache cleared successfully!" "clearCacheSuccess": "Cache cleared successfully!"
}, },
@ -679,7 +679,8 @@
"cannotFindCreatableField": "Cannot find a suitable field to sort by", "cannotFindCreatableField": "Cannot find a suitable field to sort by",
"deleteAllSorts": "Delete all sorts", "deleteAllSorts": "Delete all sorts",
"addSort": "Add new sort", "addSort": "Add new sort",
"removeSorting": "Would you like to remove sorting?" "removeSorting": "Would you like to remove sorting?",
"fieldInUse": "You are already sorting by this field"
}, },
"row": { "row": {
"duplicate": "Duplicate", "duplicate": "Duplicate",