mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
fix: can't input url in Grid (#2737)
* fix: launch url in url cell * fix: can't input url in Grid * feat: support selecting or deselecting all items in export page * fix: remove the circle shape * fix: light mode toolbar color * chore: update language and adjust the launch page
This commit is contained in:
parent
37547a6485
commit
02b7149514
@ -196,6 +196,8 @@
|
||||
"restartApp": "Please restart app for the changes to take effect.",
|
||||
"exportDatabase": "Export database",
|
||||
"selectFiles": "Select the files that need to be export",
|
||||
"selectAll": "Select all",
|
||||
"deselectAll": "Deselect all",
|
||||
"createNewFolder": "Create a new folder",
|
||||
"createNewFolderDesc": "Tell us where you want to store your data",
|
||||
"defineWhereYourDataIsStored": "Define where your data is stored",
|
||||
@ -211,12 +213,13 @@
|
||||
"folderPath": "Path to store your folder",
|
||||
"locationCannotBeEmpty": "Path cannot be empty",
|
||||
"pathCopiedSnackbar": "File storage path copied to clipboard!",
|
||||
"changeLocationTooltips": "Change the files read the data directory",
|
||||
"changeLocationTooltips": "Change the data directory",
|
||||
"change": "Change",
|
||||
"openLocationTooltips": "Open the files read the data directory",
|
||||
"recoverLocationTooltips": "Recover the files read the data directory",
|
||||
"openLocationTooltips": "Open another data directory",
|
||||
"recoverLocationTooltips": "Reset to AppFlowy's default data directory",
|
||||
"exportFileSuccess": "Export file successfully!",
|
||||
"exportFileFail": "Export file failed!"
|
||||
"exportFileFail": "Export file failed!",
|
||||
"export": "Export"
|
||||
},
|
||||
"user": {
|
||||
"name": "Name",
|
||||
|
@ -10,7 +10,7 @@ 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:url_launcher/url_launcher.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import '../../../../grid/presentation/layout/sizes.dart';
|
||||
import '../../accessory/cell_accessory.dart';
|
||||
import '../../cell_builder.dart';
|
||||
@ -36,13 +36,11 @@ enum GridURLCellAccessoryType {
|
||||
}
|
||||
|
||||
class GridURLCell extends GridCellWidget {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
late final GridURLCellStyle? cellStyle;
|
||||
GridURLCell({
|
||||
super.key,
|
||||
required this.cellControllerBuilder,
|
||||
GridCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key) {
|
||||
}) {
|
||||
if (style != null) {
|
||||
cellStyle = (style as GridURLCellStyle);
|
||||
} else {
|
||||
@ -50,6 +48,9 @@ class GridURLCell extends GridCellWidget {
|
||||
}
|
||||
}
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
late final GridURLCellStyle? cellStyle;
|
||||
|
||||
@override
|
||||
GridCellState<GridURLCell> createState() => _GridURLCellState();
|
||||
|
||||
@ -104,28 +105,35 @@ class GridURLCell extends GridCellWidget {
|
||||
};
|
||||
}
|
||||
|
||||
class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
class _GridURLCellState extends GridFocusNodeCellState<GridURLCell> {
|
||||
final _popoverController = PopoverController();
|
||||
late URLCellBloc _cellBloc;
|
||||
late TextEditingController _controller;
|
||||
late FocusNode _focusNode;
|
||||
late final URLCellBloc _cellBloc;
|
||||
late final TextEditingController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
final cellController =
|
||||
widget.cellControllerBuilder.build() as URLCellController;
|
||||
_cellBloc = URLCellBloc(cellController: cellController);
|
||||
_cellBloc.add(const URLCellEvent.initial());
|
||||
_cellBloc = URLCellBloc(cellController: cellController)
|
||||
..add(const URLCellEvent.initial());
|
||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||
_focusNode = FocusNode();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cellBloc,
|
||||
child: BlocBuilder<URLCellBloc, URLCellState>(
|
||||
child: BlocConsumer<URLCellBloc, URLCellState>(
|
||||
listenWhen: (previous, current) => previous.content != current.content,
|
||||
listener: (context, state) => _controller.text = state.content,
|
||||
builder: (context, state) {
|
||||
final urlEditor = Padding(
|
||||
padding: EdgeInsets.only(
|
||||
@ -134,7 +142,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
),
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
focusNode: focusNode,
|
||||
maxLines: 1,
|
||||
style: (widget.cellStyle?.textStyle ??
|
||||
Theme.of(context).textTheme.bodyMedium)
|
||||
@ -143,8 +151,6 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
autofocus: false,
|
||||
onEditingComplete: focusChanged,
|
||||
onSubmitted: (value) => focusChanged(isUrlSubmitted: true),
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: GridSize.cellContentInsets.top,
|
||||
@ -162,24 +168,10 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
);
|
||||
}
|
||||
|
||||
void focusChanged({
|
||||
bool isUrlSubmitted = false,
|
||||
}) {
|
||||
if (mounted) {
|
||||
if (_cellBloc.isClosed == false &&
|
||||
_controller.text != _cellBloc.state.content) {
|
||||
_cellBloc.add(URLCellEvent.updateURL(_controller.text));
|
||||
}
|
||||
if (isUrlSubmitted) {
|
||||
_focusNode.unfocus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
_cellBloc.close();
|
||||
super.dispose();
|
||||
Future<void> focusChanged() async {
|
||||
_cellBloc.add(URLCellEvent.updateURL(_controller.text));
|
||||
return super.focusChanged();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -192,19 +184,17 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||
String? onCopy() => _cellBloc.state.content;
|
||||
|
||||
@override
|
||||
void onInsert(String value) {
|
||||
_cellBloc.add(URLCellEvent.updateURL(value));
|
||||
}
|
||||
void onInsert(String value) => _cellBloc.add(URLCellEvent.updateURL(value));
|
||||
}
|
||||
|
||||
class _EditURLAccessory extends StatefulWidget {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final BuildContext anchorContext;
|
||||
const _EditURLAccessory({
|
||||
required this.cellControllerBuilder,
|
||||
required this.anchorContext,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
});
|
||||
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final BuildContext anchorContext;
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _EditURLAccessoryState();
|
||||
@ -212,20 +202,14 @@ class _EditURLAccessory extends StatefulWidget {
|
||||
|
||||
class _EditURLAccessoryState extends State<_EditURLAccessory>
|
||||
with GridCellAccessoryState {
|
||||
late PopoverController _popoverController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_popoverController = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
final popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppFlowyPopover(
|
||||
margin: EdgeInsets.zero,
|
||||
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||
controller: _popoverController,
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
offset: const Offset(0, 8),
|
||||
child: svgWidget(
|
||||
@ -236,7 +220,7 @@ class _EditURLAccessoryState extends State<_EditURLAccessory>
|
||||
return URLEditorPopover(
|
||||
cellController:
|
||||
widget.cellControllerBuilder.build() as URLCellController,
|
||||
onExit: () => _popoverController.close(),
|
||||
onExit: () => popoverController.close(),
|
||||
);
|
||||
},
|
||||
);
|
||||
@ -244,14 +228,17 @@ class _EditURLAccessoryState extends State<_EditURLAccessory>
|
||||
|
||||
@override
|
||||
void onTap() {
|
||||
_popoverController.show();
|
||||
popoverController.show();
|
||||
}
|
||||
}
|
||||
|
||||
class _CopyURLAccessory extends StatefulWidget {
|
||||
const _CopyURLAccessory({
|
||||
super.key,
|
||||
required this.cellContext,
|
||||
});
|
||||
|
||||
final URLCellController cellContext;
|
||||
const _CopyURLAccessory({required this.cellContext, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _CopyURLAccessoryState();
|
||||
@ -270,16 +257,22 @@ class _CopyURLAccessoryState extends State<_CopyURLAccessory>
|
||||
@override
|
||||
void onTap() {
|
||||
final content =
|
||||
widget.cellContext.getCellData(loadIfNotExist: false)?.content ?? "";
|
||||
widget.cellContext.getCellData(loadIfNotExist: false)?.content;
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
Clipboard.setData(ClipboardData(text: content));
|
||||
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||
}
|
||||
}
|
||||
|
||||
class _VisitURLAccessory extends StatefulWidget {
|
||||
const _VisitURLAccessory({
|
||||
super.key,
|
||||
required this.cellContext,
|
||||
});
|
||||
|
||||
final URLCellController cellContext;
|
||||
const _VisitURLAccessory({required this.cellContext, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _VisitURLAccessoryState();
|
||||
@ -297,14 +290,14 @@ class _VisitURLAccessoryState extends State<_VisitURLAccessory>
|
||||
|
||||
@override
|
||||
void onTap() {
|
||||
var content =
|
||||
widget.cellContext.getCellData(loadIfNotExist: false)?.content ?? "";
|
||||
if (!content.startsWith('http://') && !content.startsWith('https://')) {
|
||||
content = 'http://$content';
|
||||
}
|
||||
final uri = Uri.parse(content);
|
||||
if (content.isNotEmpty) {
|
||||
canLaunchUrl(uri).then((value) => launchUrl(uri));
|
||||
final content =
|
||||
widget.cellContext.getCellData(loadIfNotExist: false)?.content;
|
||||
if (content == null) {
|
||||
return;
|
||||
}
|
||||
final shouldAddScheme =
|
||||
!['http', 'https'].any((pattern) => content.startsWith(pattern));
|
||||
final url = shouldAddScheme ? 'http://$content' : content;
|
||||
canLaunchUrlString(url).then((value) => launchUrlString(url));
|
||||
}
|
||||
}
|
||||
|
@ -57,24 +57,24 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
|
||||
late final Map<String, BlockComponentBuilder> blockComponentBuilders =
|
||||
_customAppFlowyBlockComponentBuilders();
|
||||
late final List<CharacterShortcutEvent> characterShortcutEvents = [
|
||||
// code block
|
||||
...codeBlockCharacterEvents,
|
||||
List<CharacterShortcutEvent> get characterShortcutEvents => [
|
||||
// code block
|
||||
...codeBlockCharacterEvents,
|
||||
|
||||
// toggle list
|
||||
// formatGreaterToToggleList,
|
||||
// toggle list
|
||||
// formatGreaterToToggleList,
|
||||
|
||||
// customize the slash menu command
|
||||
customSlashCommand(
|
||||
slashMenuItems,
|
||||
style: styleCustomizer.selectionMenuStyleBuilder(),
|
||||
),
|
||||
// customize the slash menu command
|
||||
customSlashCommand(
|
||||
slashMenuItems,
|
||||
style: styleCustomizer.selectionMenuStyleBuilder(),
|
||||
),
|
||||
|
||||
...standardCharacterShortcutEvents
|
||||
..removeWhere(
|
||||
(element) => element == slashCommand,
|
||||
), // remove the default slash command.
|
||||
];
|
||||
...standardCharacterShortcutEvents
|
||||
..removeWhere(
|
||||
(element) => element == slashCommand,
|
||||
), // remove the default slash command.
|
||||
];
|
||||
|
||||
late final showSlashMenu = customSlashCommand(
|
||||
slashMenuItems,
|
||||
@ -82,7 +82,9 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
style: styleCustomizer.selectionMenuStyleBuilder(),
|
||||
).handler;
|
||||
|
||||
late final styleCustomizer = EditorStyleCustomizer(context: context);
|
||||
EditorStyleCustomizer get styleCustomizer => EditorStyleCustomizer(
|
||||
context: context,
|
||||
);
|
||||
DocumentBloc get documentBloc => context.read<DocumentBloc>();
|
||||
|
||||
@override
|
||||
|
@ -140,7 +140,7 @@ class EditorStyleCustomizer {
|
||||
FloatingToolbarStyle floatingToolbarStyleBuilder() {
|
||||
final theme = Theme.of(context);
|
||||
return FloatingToolbarStyle(
|
||||
backgroundColor: theme.cardColor,
|
||||
backgroundColor: theme.colorScheme.onTertiary,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -218,10 +218,13 @@ Widget _buildTextButton(
|
||||
String title,
|
||||
VoidCallback onPressed,
|
||||
) {
|
||||
return SecondaryTextButton(
|
||||
title,
|
||||
mode: SecondaryTextButtonMode.small,
|
||||
onPressed: onPressed,
|
||||
return SizedBox(
|
||||
width: 60,
|
||||
child: SecondaryTextButton(
|
||||
title,
|
||||
mode: SecondaryTextButtonMode.small,
|
||||
onPressed: onPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -53,6 +53,7 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
const Spacer(),
|
||||
FlowyLogoTitle(
|
||||
title: LocaleKeys.welcomeText.tr(),
|
||||
logoSize: const Size.square(40),
|
||||
@ -68,8 +69,6 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
||||
},
|
||||
),
|
||||
const VSpace(32),
|
||||
_buildSubscribeButtons(context),
|
||||
const VSpace(32),
|
||||
SizedBox(
|
||||
width: MediaQuery.of(context).size.width * 0.5,
|
||||
child: FolderWidget(
|
||||
@ -78,7 +77,10 @@ class _SkipLogInScreenState extends State<SkipLogInScreen> {
|
||||
},
|
||||
),
|
||||
),
|
||||
const VSpace(64),
|
||||
const Spacer(),
|
||||
const VSpace(48),
|
||||
_buildSubscribeButtons(context),
|
||||
const VSpace(24),
|
||||
],
|
||||
);
|
||||
}
|
||||
@ -182,6 +184,7 @@ class GoButton extends StatelessWidget {
|
||||
maxWidth: 340,
|
||||
maxHeight: 48,
|
||||
),
|
||||
radius: BorderRadius.circular(12),
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
fontSize: FontSizes.s14,
|
||||
fontFamily: GoogleFonts.poppins(fontWeight: FontWeight.w500).fontFamily,
|
||||
|
@ -55,6 +55,18 @@ class SettingsFileExporterCubit extends Cubit<SettingsFileExportState> {
|
||||
required List<ViewPB> views,
|
||||
}) : super(SettingsFileExportState(views: views));
|
||||
|
||||
void selectOrDeselectAllItems() {
|
||||
final List<List<bool>> selectedItems = state.selectedItems;
|
||||
final isSelectAll =
|
||||
selectedItems.expand((element) => element).every((element) => element);
|
||||
for (var i = 0; i < selectedItems.length; i++) {
|
||||
for (var j = 0; j < selectedItems[i].length; j++) {
|
||||
selectedItems[i][j] = !isSelectAll;
|
||||
}
|
||||
}
|
||||
emit(state.copyWith(selectedItems: selectedItems));
|
||||
}
|
||||
|
||||
void selectOrDeselectItem(int outerIndex, int innerIndex) {
|
||||
final selectedItems = state.selectedItems;
|
||||
selectedItems[outerIndex][innerIndex] =
|
||||
@ -69,7 +81,7 @@ class SettingsFileExporterCubit extends Cubit<SettingsFileExportState> {
|
||||
}
|
||||
|
||||
Map<String, List<String>> fetchSelectedPages() {
|
||||
final apps = state.views;
|
||||
final views = state.views;
|
||||
final selectedItems = state.selectedItems;
|
||||
final Map<String, List<String>> result = {};
|
||||
for (var i = 0; i < selectedItems.length; i++) {
|
||||
@ -77,11 +89,11 @@ class SettingsFileExporterCubit extends Cubit<SettingsFileExportState> {
|
||||
final ids = <String>[];
|
||||
for (var j = 0; j < selectedItem.length; j++) {
|
||||
if (selectedItem[j]) {
|
||||
ids.add(apps[i].childViews[j].id);
|
||||
ids.add(views[i].childViews[j].id);
|
||||
}
|
||||
}
|
||||
if (ids.isNotEmpty) {
|
||||
result[apps[i].id] = ids;
|
||||
result[views[i].id] = ids;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -3,6 +3,7 @@ import 'package:flowy_infra/image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import '../../../../generated/locale_keys.g.dart';
|
||||
|
||||
@ -25,8 +26,9 @@ class SettingsExportFileWidgetState extends State<SettingsExportFileWidget> {
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.settings_files_exportData.tr(),
|
||||
fontSize: 13,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
).padding(horizontal: 5.0),
|
||||
const Spacer(),
|
||||
_OpenExportedDirectoryButton(
|
||||
onTap: () async {
|
||||
@ -62,7 +64,7 @@ class _OpenExportedDirectoryButton extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyIconButton(
|
||||
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
tooltipText: LocaleKeys.settings_files_open.tr(),
|
||||
tooltipText: LocaleKeys.settings_files_export.tr(),
|
||||
icon: svgWidget(
|
||||
'common/open_folder',
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
|
@ -129,12 +129,14 @@ class _CopyableText extends StatelessWidget {
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
if (onHover)
|
||||
if (onHover) ...[
|
||||
const HSpace(5),
|
||||
FlowyText.regular(
|
||||
LocaleKeys.settings_files_copy.tr(),
|
||||
fontSize: 12,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
)
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -49,9 +49,29 @@ class _FileExporterWidgetState extends State<FileExporterWidget> {
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.settings_files_selectFiles.tr(),
|
||||
fontSize: 16.0,
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
FlowyText.medium(
|
||||
LocaleKeys.settings_files_selectFiles.tr(),
|
||||
fontSize: 16.0,
|
||||
),
|
||||
BlocBuilder<SettingsFileExporterCubit,
|
||||
SettingsFileExportState>(
|
||||
builder: (context, state) => FlowyTextButton(
|
||||
state.selectedItems
|
||||
.expand((element) => element)
|
||||
.every((element) => element)
|
||||
? LocaleKeys.settings_files_deselectAll.tr()
|
||||
: LocaleKeys.settings_files_selectAll.tr(),
|
||||
onPressed: () {
|
||||
context
|
||||
.read<SettingsFileExporterCubit>()
|
||||
.selectOrDeselectAllItems();
|
||||
},
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
const VSpace(8),
|
||||
const Expanded(child: _ExpandedList()),
|
||||
|
@ -60,11 +60,12 @@ class FlowyColorPicker extends StatelessWidget {
|
||||
final colorIcon = SizedBox.square(
|
||||
dimension: iconSize,
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: option.color,
|
||||
shape: BoxShape.circle,
|
||||
border: border,
|
||||
)),
|
||||
decoration: BoxDecoration(
|
||||
color: option.color,
|
||||
shape: BoxShape.circle,
|
||||
// border: border,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
return SizedBox(
|
||||
|
@ -11,7 +11,7 @@ use serde::Serialize;
|
||||
use crate::kv::schema::{kv_table, kv_table::dsl, KV_SQL};
|
||||
use crate::sqlite::{DBConnection, Database, PoolConfig};
|
||||
|
||||
const DB_NAME: &str = "kv.db";
|
||||
const DB_NAME: &str = "cache.db";
|
||||
lazy_static! {
|
||||
static ref KV_HOLDER: RwLock<KV> = RwLock::new(KV::new());
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user