mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge branch 'main' into workspace-invite
This commit is contained in:
commit
0aa4aa956d
2
frontend/.vscode/tasks.json
vendored
2
frontend/.vscode/tasks.json
vendored
@ -257,7 +257,7 @@
|
||||
"label": "AF: Tauri UI Dev",
|
||||
"type": "shell",
|
||||
"isBackground": true,
|
||||
"command": "pnpm run tauri:dev",
|
||||
"command": "pnpm run sync:i18n && pnpm run dev",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/appflowy_tauri"
|
||||
}
|
||||
|
@ -148,7 +148,7 @@ class _MobileViewPageState extends State<MobileViewPage> {
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
builder: (_) => _buildViewPageBottomSheet(context),
|
||||
);
|
||||
},
|
||||
|
@ -2,7 +2,6 @@ import 'package:appflowy/generated/flowy_svgs.g.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/mobile/presentation/widgets/flowy_mobile_quick_action_button.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/separated_flex.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum MobileViewItemBottomSheetBodyAction {
|
||||
@ -26,12 +25,8 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SeparatedColumn(
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
separatorBuilder: () => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
),
|
||||
children: [
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
@ -40,6 +35,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
MobileViewItemBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
@ -54,6 +50,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
: MobileViewItemBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
@ -61,6 +58,7 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
MobileViewItemBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
@ -70,7 +68,13 @@ class MobileViewItemBottomSheetBody extends StatelessWidget {
|
||||
MobileViewItemBottomSheetBodyAction.delete,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _divider() => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
);
|
||||
}
|
||||
|
@ -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_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum MobileViewBottomSheetBodyAction {
|
||||
@ -85,12 +84,8 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isFavorite = view.isFavorite;
|
||||
return SeparatedColumn(
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
separatorBuilder: () => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
),
|
||||
children: [
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_rename.tr(),
|
||||
@ -99,6 +94,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
MobileViewBottomSheetBodyAction.rename,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: isFavorite
|
||||
? LocaleKeys.button_removeFromFavorites.tr()
|
||||
@ -113,6 +109,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
: MobileViewBottomSheetBodyAction.addToFavorites,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_duplicate.tr(),
|
||||
icon: FlowySvgs.m_duplicate_s,
|
||||
@ -120,6 +117,7 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
MobileViewBottomSheetBodyAction.duplicate,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
MobileQuickActionButton(
|
||||
text: LocaleKeys.button_delete.tr(),
|
||||
textColor: Theme.of(context).colorScheme.error,
|
||||
@ -129,7 +127,13 @@ class MobileViewBottomSheetBody extends StatelessWidget {
|
||||
MobileViewBottomSheetBodyAction.delete,
|
||||
),
|
||||
),
|
||||
_divider(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _divider() => const Divider(
|
||||
height: 8.5,
|
||||
thickness: 0.5,
|
||||
);
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ enum MobilePaneActionType {
|
||||
context,
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
useRootNavigator: true,
|
||||
builder: (context) {
|
||||
return MultiBlocProvider(
|
||||
|
@ -150,6 +150,7 @@ class _MobileRowDetailPageState extends State<MobileRowDetailPage> {
|
||||
icon: FlowySvgs.m_delete_m,
|
||||
iconColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
const Divider(height: 8.5, thickness: 0.5),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -40,7 +40,7 @@ Future<FieldType?> showFieldTypeGridBottomSheet(
|
||||
showCloseButton: true,
|
||||
elevation: 20,
|
||||
title: title,
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
enableDraggableScrollable: true,
|
||||
builder: (context) {
|
||||
final typeOptionMenuItemValue = mobileSupportedFieldTypes
|
||||
@ -94,9 +94,6 @@ void mobileCreateFieldWorkflow(
|
||||
if (optionValues != null) {
|
||||
await optionValues.create(viewId: viewId, position: position);
|
||||
}
|
||||
if (context.mounted) {
|
||||
context.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// Used to edit a field.
|
||||
|
@ -262,11 +262,9 @@ class _SortItem extends StatelessWidget {
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: Expanded(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_sort_by.tr(),
|
||||
fontSize: 15,
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_sort_by.tr(),
|
||||
fontSize: 15,
|
||||
),
|
||||
),
|
||||
const VSpace(10),
|
||||
@ -407,6 +405,8 @@ class _SortDetailContent extends StatelessWidget {
|
||||
|
||||
final SortInfo? sortInfo;
|
||||
|
||||
bool get isCreatingNewSort => sortInfo == null;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
@ -417,7 +417,7 @@ class _SortDetailContent extends StatelessWidget {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: DefaultTabController(
|
||||
length: 2,
|
||||
initialIndex: sortInfo == null
|
||||
initialIndex: isCreatingNewSort
|
||||
? 0
|
||||
: sortInfo!.sortPB.condition == SortConditionPB.Ascending
|
||||
? 0
|
||||
@ -489,30 +489,40 @@ class _SortDetailContent extends StatelessWidget {
|
||||
child: BlocBuilder<SortEditorBloc, SortEditorState>(
|
||||
builder: (context, state) {
|
||||
final fields = state.allFields
|
||||
.where(
|
||||
(field) =>
|
||||
field.canCreateSort ||
|
||||
sortInfo != null && sortInfo!.fieldId == field.id,
|
||||
)
|
||||
.where((field) => field.canCreateSort || field.hasSort)
|
||||
.toList();
|
||||
return ListView.builder(
|
||||
itemCount: fields.length,
|
||||
itemBuilder: (context, index) {
|
||||
final fieldInfo = fields[index];
|
||||
final isSelected = sortInfo == null
|
||||
final isSelected = isCreatingNewSort
|
||||
? context
|
||||
.watch<MobileSortEditorCubit>()
|
||||
.state
|
||||
.newSortFieldId ==
|
||||
fieldInfo.id
|
||||
: sortInfo!.fieldId == fieldInfo.id;
|
||||
|
||||
final enabled = fieldInfo.canCreateSort ||
|
||||
isCreatingNewSort && !fieldInfo.hasSort ||
|
||||
!isCreatingNewSort && sortInfo!.fieldId == fieldInfo.id;
|
||||
|
||||
return FlowyOptionTile.checkbox(
|
||||
text: fieldInfo.field.name,
|
||||
isSelected: isSelected,
|
||||
textColor: enabled ? null : Theme.of(context).disabledColor,
|
||||
showTopBorder: false,
|
||||
onTap: () {
|
||||
if (!isSelected) {
|
||||
if (isSelected) {
|
||||
return;
|
||||
}
|
||||
if (enabled) {
|
||||
_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) {
|
||||
if (sortInfo == null) {
|
||||
if (isCreatingNewSort) {
|
||||
context.read<MobileSortEditorCubit>().changeSortCondition(newCondition);
|
||||
} else {
|
||||
context.read<SortEditorBloc>().add(
|
||||
@ -539,7 +549,7 @@ class _SortDetailContent extends StatelessWidget {
|
||||
}
|
||||
|
||||
void _changeFieldId(BuildContext context, String newFieldId) {
|
||||
if (sortInfo == null) {
|
||||
if (isCreatingNewSort) {
|
||||
context.read<MobileSortEditorCubit>().changeFieldId(newFieldId);
|
||||
} else {
|
||||
context.read<SortEditorBloc>().add(
|
||||
|
@ -183,6 +183,7 @@ class MobileDatabaseViewListButton extends StatelessWidget {
|
||||
showMobileBottomSheet(
|
||||
context,
|
||||
showDragHandle: true,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
builder: (_) {
|
||||
return BlocProvider<ViewBloc>(
|
||||
create: (_) =>
|
||||
|
@ -7,6 +7,7 @@ import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
|
||||
import 'package:appflowy/shared/appflowy_cache_manager.dart';
|
||||
import 'package:appflowy/startup/startup.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:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -71,9 +72,14 @@ class SupportSettingGroup extends StatelessWidget {
|
||||
maxLines: 4,
|
||||
),
|
||||
actionButtonTitle: LocaleKeys.button_yes.tr(),
|
||||
actionButtonColor: Theme.of(context).colorScheme.error,
|
||||
onActionButtonPressed: () async {
|
||||
await getIt<FlowyCacheManager>().clearAllCache();
|
||||
if (context.mounted) {
|
||||
showSnackBarMessage(
|
||||
context,
|
||||
LocaleKeys.settings_files_clearCacheSuccess.tr(),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -13,6 +13,7 @@ enum FlowyOptionTileType {
|
||||
|
||||
class FlowyOptionTile extends StatelessWidget {
|
||||
const FlowyOptionTile._({
|
||||
super.key,
|
||||
required this.type,
|
||||
this.showTopBorder = true,
|
||||
this.showBottomBorder = true,
|
||||
@ -88,9 +89,11 @@ class FlowyOptionTile extends StatelessWidget {
|
||||
}
|
||||
|
||||
factory FlowyOptionTile.checkbox({
|
||||
Key? key,
|
||||
required String text,
|
||||
required bool isSelected,
|
||||
required VoidCallback? onTap,
|
||||
Color? textColor,
|
||||
Widget? leftIcon,
|
||||
Widget? content,
|
||||
bool showTopBorder = true,
|
||||
@ -99,9 +102,11 @@ class FlowyOptionTile extends StatelessWidget {
|
||||
Color? backgroundColor,
|
||||
}) {
|
||||
return FlowyOptionTile._(
|
||||
key: key,
|
||||
type: FlowyOptionTileType.checkbox,
|
||||
isSelected: isSelected,
|
||||
text: text,
|
||||
textColor: textColor,
|
||||
content: content,
|
||||
onTap: onTap,
|
||||
fontFamily: fontFamily,
|
||||
|
@ -337,6 +337,7 @@ class FieldController {
|
||||
...changeset.insertSorts.map((sort) => sort.sort.fieldId),
|
||||
...changeset.updateSorts.map((sort) => sort.fieldId),
|
||||
...changeset.deleteSorts.map((sort) => sort.fieldId),
|
||||
...?_sortNotifier?.sorts.map((sort) => sort.fieldId),
|
||||
]);
|
||||
|
||||
final newFieldInfos = [...fieldInfos];
|
||||
@ -367,8 +368,8 @@ class FieldController {
|
||||
insertSortFromChangeset(newSortInfos, changeset);
|
||||
updateSortFromChangeset(newSortInfos, changeset);
|
||||
|
||||
_sortNotifier?.sorts = newSortInfos;
|
||||
updateFieldInfos(newSortInfos, changeset);
|
||||
_sortNotifier?.sorts = newSortInfos;
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
|
@ -32,7 +32,7 @@ class _CalculationSelectorState extends State<CalculationSelector> {
|
||||
onEnter: (_) => _setHovering(true),
|
||||
onExit: (_) => _setHovering(false),
|
||||
child: AnimatedOpacity(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
duration: const Duration(milliseconds: 100),
|
||||
opacity: widget.isSelected || _isHovering ? 1 : 0,
|
||||
child: FlowyButton(
|
||||
radius: BorderRadius.zero,
|
||||
|
@ -370,9 +370,6 @@ class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
|
||||
Widget _addDeleteFieldButton() {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
if (state.field.isPrimary) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
|
||||
child: FieldActionCell(
|
||||
@ -389,9 +386,6 @@ class _FieldDetailsEditorState extends State<FieldDetailsEditor> {
|
||||
Widget _addDuplicateFieldButton() {
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
if (state.field.isPrimary) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 4.0, 8.0, 0),
|
||||
child: FieldActionCell(
|
||||
@ -533,41 +527,52 @@ class _SwitchFieldButtonState extends State<SwitchFieldButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(460, 540)),
|
||||
triggerActions: PopoverTriggerFlags.hover,
|
||||
mutex: widget.popoverMutex,
|
||||
controller: _popoverController,
|
||||
offset: const Offset(8, 0),
|
||||
margin: const EdgeInsets.all(8),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return FieldTypeList(
|
||||
onSelectField: (newFieldType) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.switchFieldType(newFieldType));
|
||||
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
|
||||
builder: (context, state) {
|
||||
final bool isPrimary = state.field.isPrimary;
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: AppFlowyPopover(
|
||||
constraints: BoxConstraints.loose(const Size(460, 540)),
|
||||
triggerActions: isPrimary ? 0 : PopoverTriggerFlags.hover,
|
||||
mutex: widget.popoverMutex,
|
||||
controller: _popoverController,
|
||||
offset: const Offset(8, 0),
|
||||
margin: const EdgeInsets.all(8),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return FieldTypeList(
|
||||
onSelectField: (newFieldType) {
|
||||
context
|
||||
.read<FieldEditorBloc>()
|
||||
.add(FieldEditorEvent.switchFieldType(newFieldType));
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: _buildMoreButton(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
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),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:flutter/material.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_progress_bar.dart';
|
||||
import 'package:appflowy/plugins/database/widgets/row/cells/cell_container.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../editable_cell_skeleton/checklist.dart';
|
||||
@ -25,6 +26,7 @@ class DesktopGridChecklistCellSkin extends IEditableChecklistCellSkin {
|
||||
constraints: BoxConstraints.loose(const Size(360, 400)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
skipTraversal: true,
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
cellContainerNotifier.isFocus = true;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:flutter/material.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/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database/application/cell/bloc/url_cell_bloc.dart';
|
||||
@ -177,21 +176,11 @@ class _VisitURLAccessoryState extends State<_VisitURLAccessory>
|
||||
}
|
||||
|
||||
@override
|
||||
bool enable() {
|
||||
return widget.cellDataNotifier.value.isNotEmpty;
|
||||
}
|
||||
bool enable() => widget.cellDataNotifier.value.isNotEmpty;
|
||||
|
||||
@override
|
||||
void onTap() {
|
||||
final content = widget.cellDataNotifier.value;
|
||||
if (content.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final shouldAddScheme =
|
||||
!['http', 'https'].any((pattern) => content.startsWith(pattern));
|
||||
final url = shouldAddScheme ? 'http://$content' : content;
|
||||
afLaunchUrlString(url);
|
||||
}
|
||||
void onTap() =>
|
||||
openUrlCellLink(widget.cellDataNotifier.value);
|
||||
}
|
||||
|
||||
class _URLAccessoryIconContainer extends StatelessWidget {
|
||||
|
@ -1,5 +1,6 @@
|
||||
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_builder.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_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 {
|
||||
const IEditableURLCellSkin();
|
||||
|
||||
@ -130,3 +134,24 @@ class _GridURLCellState extends GridEditableTextCell<EditableURLCell> {
|
||||
@override
|
||||
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)}",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,9 @@
|
||||
import 'dart:async';
|
||||
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/locale_keys.g.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/theme_extension.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 '../../application/cell/bloc/checklist_cell_bloc.dart';
|
||||
|
||||
import 'checklist_progress_bar.dart';
|
||||
|
||||
class ChecklistCellEditor extends StatefulWidget {
|
||||
@ -189,7 +191,9 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
||||
void didUpdateWidget(ChecklistItem oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.task.data.name != oldWidget.task.data.name) {
|
||||
final selection = _textController.selection;
|
||||
_textController.text = widget.task.data.name;
|
||||
_textController.selection = selection;
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,6 +238,10 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
||||
else
|
||||
const SingleActivator(LogicalKeyboardKey.enter, control: true):
|
||||
const _SelectTaskIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp):
|
||||
const PreviousFocusIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown):
|
||||
const NextFocusIntent(),
|
||||
},
|
||||
descendantsAreTraversable: false,
|
||||
child: Container(
|
||||
@ -261,15 +269,24 @@ class _ChecklistItemState extends State<ChecklistItem> {
|
||||
),
|
||||
Expanded(
|
||||
child: Shortcuts(
|
||||
shortcuts: const {
|
||||
SingleActivator(LogicalKeyboardKey.space):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.delete):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.enter):
|
||||
DoNothingAndStopPropagationIntent(),
|
||||
SingleActivator(LogicalKeyboardKey.escape):
|
||||
_EndEditingTaskIntent(),
|
||||
shortcuts: {
|
||||
const SingleActivator(LogicalKeyboardKey.space):
|
||||
const DoNothingAndStopPropagationIntent(),
|
||||
const SingleActivator(LogicalKeyboardKey.delete):
|
||||
const DoNothingAndStopPropagationIntent(),
|
||||
if (Platform.isMacOS)
|
||||
LogicalKeySet(
|
||||
LogicalKeyboardKey.fn,
|
||||
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(
|
||||
controller: _textController,
|
||||
@ -345,12 +362,11 @@ class NewTaskItem extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _NewTaskItemState extends State<NewTaskItem> {
|
||||
late final TextEditingController _textEditingController;
|
||||
final _textEditingController = TextEditingController();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_textEditingController = TextEditingController();
|
||||
if (widget.focusNode.canRequestFocus) {
|
||||
widget.focusNode.requestFocus();
|
||||
}
|
||||
@ -385,15 +401,13 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
||||
hintText: LocaleKeys.grid_checklist_addNew.tr(),
|
||||
),
|
||||
onSubmitted: (taskDescription) {
|
||||
if (taskDescription.trim().isNotEmpty) {
|
||||
context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.createNewTask(
|
||||
taskDescription.trim(),
|
||||
),
|
||||
);
|
||||
if (taskDescription.isNotEmpty) {
|
||||
context
|
||||
.read<ChecklistCellBloc>()
|
||||
.add(ChecklistCellEvent.createNewTask(taskDescription));
|
||||
_textEditingController.clear();
|
||||
}
|
||||
widget.focusNode.requestFocus();
|
||||
_textEditingController.clear();
|
||||
},
|
||||
onChanged: (value) => setState(() {}),
|
||||
),
|
||||
@ -409,16 +423,17 @@ class _NewTaskItemState extends State<NewTaskItem> {
|
||||
: Theme.of(context).colorScheme.primaryContainer,
|
||||
fontColor: Theme.of(context).colorScheme.onPrimary,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
onPressed: () {
|
||||
final text = _textEditingController.text.trim();
|
||||
if (text.isNotEmpty) {
|
||||
context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.createNewTask(text),
|
||||
);
|
||||
}
|
||||
widget.focusNode.requestFocus();
|
||||
_textEditingController.clear();
|
||||
},
|
||||
onPressed: _textEditingController.text.isEmpty
|
||||
? null
|
||||
: () {
|
||||
context.read<ChecklistCellBloc>().add(
|
||||
ChecklistCellEvent.createNewTask(
|
||||
_textEditingController.text,
|
||||
),
|
||||
);
|
||||
widget.focusNode.requestFocus();
|
||||
_textEditingController.clear();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -150,6 +150,7 @@ void _showEditSortPanelFromToolbar(
|
||||
showDragHandle: true,
|
||||
showDivider: false,
|
||||
useSafeArea: false,
|
||||
backgroundColor: Theme.of(context).colorScheme.background,
|
||||
builder: (_) {
|
||||
return BlocProvider.value(
|
||||
value: context.read<SortEditorBloc>(),
|
||||
|
@ -86,6 +86,13 @@ class ImagePlaceholderState extends State<ImagePlaceholder> {
|
||||
popupBuilder: (context) {
|
||||
return UploadImageMenu(
|
||||
limitMaximumImageSize: !_isLocalMode(),
|
||||
supportTypes: const [
|
||||
UploadImageType.local,
|
||||
UploadImageType.url,
|
||||
UploadImageType.unsplash,
|
||||
UploadImageType.openAI,
|
||||
UploadImageType.stabilityAI,
|
||||
],
|
||||
onSelectedLocalImage: (path) {
|
||||
controller.close();
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) async {
|
||||
|
@ -1,7 +1,8 @@
|
||||
import 'package:appflowy_popover/src/layout.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:appflowy_popover/src/layout.dart';
|
||||
|
||||
import 'mask.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.
|
||||
final PopoverClickHandler clickHandler;
|
||||
|
||||
final bool skipTraversal;
|
||||
|
||||
/// The content area of the popover.
|
||||
final Widget child;
|
||||
|
||||
@ -110,6 +113,7 @@ class Popover extends StatefulWidget {
|
||||
this.canClose,
|
||||
this.asBarrier = false,
|
||||
this.clickHandler = PopoverClickHandler.listener,
|
||||
this.skipTraversal = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -158,6 +162,7 @@ class PopoverState extends State<Popover> {
|
||||
popupBuilder: widget.popupBuilder,
|
||||
onClose: () => close(),
|
||||
onCloseAll: () => _removeRootOverlay(),
|
||||
skipTraversal: widget.skipTraversal,
|
||||
),
|
||||
);
|
||||
|
||||
@ -263,6 +268,7 @@ class PopoverContainer extends StatefulWidget {
|
||||
final EdgeInsets windowPadding;
|
||||
final void Function() onClose;
|
||||
final void Function() onCloseAll;
|
||||
final bool skipTraversal;
|
||||
|
||||
const PopoverContainer({
|
||||
super.key,
|
||||
@ -273,6 +279,7 @@ class PopoverContainer extends StatefulWidget {
|
||||
required this.windowPadding,
|
||||
required this.onClose,
|
||||
required this.onCloseAll,
|
||||
required this.skipTraversal,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -293,6 +300,7 @@ class PopoverContainerState extends State<PopoverContainer> {
|
||||
Widget build(BuildContext context) {
|
||||
return Focus(
|
||||
autofocus: true,
|
||||
skipTraversal: widget.skipTraversal,
|
||||
child: CustomSingleChildLayout(
|
||||
delegate: PopoverLayoutDelegate(
|
||||
direction: widget.direction,
|
||||
|
@ -26,6 +26,10 @@ class AppFlowyPopover extends StatelessWidget {
|
||||
/// the conflict won't be resolve by using Listener, we want these two gestures exclusive.
|
||||
final PopoverClickHandler clickHandler;
|
||||
|
||||
/// If true the popover will not participate in focus traversal.
|
||||
///
|
||||
final bool skipTraversal;
|
||||
|
||||
const AppFlowyPopover({
|
||||
super.key,
|
||||
required this.child,
|
||||
@ -43,6 +47,7 @@ class AppFlowyPopover extends StatelessWidget {
|
||||
this.windowPadding = const EdgeInsets.all(8.0),
|
||||
this.decoration,
|
||||
this.clickHandler = PopoverClickHandler.listener,
|
||||
this.skipTraversal = false,
|
||||
});
|
||||
|
||||
@override
|
||||
@ -58,6 +63,7 @@ class AppFlowyPopover extends StatelessWidget {
|
||||
windowPadding: windowPadding,
|
||||
offset: offset,
|
||||
clickHandler: clickHandler,
|
||||
skipTraversal: skipTraversal,
|
||||
popupBuilder: (context) {
|
||||
return _PopoverContainer(
|
||||
constraints: constraints,
|
||||
|
@ -1,12 +1,13 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.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/ignore_parent_gesture.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowyButton extends StatelessWidget {
|
||||
final Widget text;
|
||||
@ -213,6 +214,7 @@ class FlowyTextButton extends StatelessWidget {
|
||||
);
|
||||
|
||||
child = RawMaterialButton(
|
||||
focusNode: FocusNode(skipTraversal: onPressed == null),
|
||||
hoverElevation: 0,
|
||||
highlightElevation: 0,
|
||||
shape: RoundedRectangleBorder(borderRadius: radius ?? Corners.s6Border),
|
||||
@ -237,6 +239,10 @@ class FlowyTextButton extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
if (onPressed == null) {
|
||||
child = ExcludeFocus(child: child);
|
||||
}
|
||||
|
||||
return child;
|
||||
}
|
||||
}
|
||||
|
@ -1101,10 +1101,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: markdown
|
||||
sha256: "1b134d9f8ff2da15cb298efe6cd8b7d2a78958c1b00384ebcbdf13fe340a6c90"
|
||||
sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.2.1"
|
||||
version: "7.2.2"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -463,7 +463,7 @@
|
||||
"exportFileFail": "Export file failed!",
|
||||
"export": "Export",
|
||||
"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?",
|
||||
"clearCacheSuccess": "Cache cleared successfully!"
|
||||
},
|
||||
@ -679,7 +679,8 @@
|
||||
"cannotFindCreatableField": "Cannot find a suitable field to sort by",
|
||||
"deleteAllSorts": "Delete all sorts",
|
||||
"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": {
|
||||
"duplicate": "Duplicate",
|
||||
@ -1355,4 +1356,4 @@
|
||||
"userIcon": "User icon"
|
||||
},
|
||||
"noLogFiles": "There're no log files"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user