From 99ee60a60dce6139adecf829825315b1dd4ec5b3 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Fri, 22 Mar 2024 16:50:01 +0800 Subject: [PATCH] fix: launch review issues (#4960) * fix: i18n for multi select condition list * fix: lookup url cell content to assert validity * fix: compromise checkmark color --- .../application/cell/bloc/url_cell_bloc.dart | 44 ++++++++++++++++--- .../select_option/condition_list.dart | 4 +- .../widgets/header/type_option/relation.dart | 3 +- .../cell/editable_cell_skeleton/url.dart | 34 ++++++++------ .../cell_editor/relation_cell_editor.dart | 3 +- .../lib/style_widget/text_field.dart | 3 ++ 6 files changed, 66 insertions(+), 25 deletions(-) diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart index dbd2258cc1..299e8ced61 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/cell/bloc/url_cell_bloc.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:appflowy/plugins/database/application/cell/cell_controller_builder.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart'; @@ -29,15 +30,17 @@ class URLCellBloc extends Bloc { void _dispatch() { on( (event, emit) async { - event.when( + await event.when( initial: () { _startListening(); }, - didReceiveCellUpdate: (cellData) { + didReceiveCellUpdate: (cellData) async { + final content = cellData?.content ?? ""; + final isValid = await isUrlValid(content); emit( state.copyWith( - content: cellData?.content ?? "", - url: cellData?.url ?? "", + content: content, + isValid: isValid, ), ); }, @@ -58,6 +61,35 @@ class URLCellBloc extends Bloc { }, ); } + + Future isUrlValid(String content) async { + if (content.isEmpty) { + return true; + } + + try { + // check protocol is provided + const linkPrefix = [ + 'http://', + 'https://', + 'file://', + 'ftp://', + 'ftps://', + 'mailto:', + ]; + final shouldAddScheme = + !linkPrefix.any((pattern) => content.startsWith(pattern)); + final url = shouldAddScheme ? 'http://$content' : content; + + // get hostname and check validity + final uri = Uri.parse(url); + final hostName = uri.host; + await InternetAddress.lookup(hostName); + } catch (_) { + return false; + } + return true; + } } @freezed @@ -72,14 +104,14 @@ class URLCellEvent with _$URLCellEvent { class URLCellState with _$URLCellState { const factory URLCellState({ required String content, - required String url, + required bool isValid, }) = _URLCellState; factory URLCellState.initial(URLCellController context) { final cellData = context.getCellData(); return URLCellState( content: cellData?.content ?? "", - url: cellData?.url ?? "", + isValid: true, ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart index bb0a85ac17..d33dba6293 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/condition_list.dart @@ -96,9 +96,9 @@ extension SelectOptionFilterConditionPBExtension SelectOptionFilterConditionPB.OptionIsNot => LocaleKeys.grid_selectOptionFilter_isNot.tr(), SelectOptionFilterConditionPB.OptionContains => - LocaleKeys.grid_selectOptionFilter_isNot.tr(), + LocaleKeys.grid_selectOptionFilter_contains.tr(), SelectOptionFilterConditionPB.OptionDoesNotContain => - LocaleKeys.grid_selectOptionFilter_isNot.tr(), + LocaleKeys.grid_selectOptionFilter_doesNotContain.tr(), SelectOptionFilterConditionPB.OptionIsEmpty => LocaleKeys.grid_selectOptionFilter_isEmpty.tr(), SelectOptionFilterConditionPB.OptionIsNotEmpty => diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/type_option/relation.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/type_option/relation.dart index c408cb69de..9ca2729cb6 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/type_option/relation.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/header/type_option/relation.dart @@ -138,9 +138,8 @@ class _DatabaseList extends StatelessWidget { overflow: TextOverflow.ellipsis, ), rightIcon: meta.databaseId == currentDatabaseId - ? FlowySvg( + ? const FlowySvg( FlowySvgs.check_s, - color: Theme.of(context).colorScheme.primary, ) : null, ), diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart index 6502fb1469..3628f6c511 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell/editable_cell_skeleton/url.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:io'; import 'package:appflowy/generated/flowy_svgs.g.dart'; import 'package:appflowy/generated/locale_keys.g.dart'; @@ -24,9 +25,6 @@ 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(); @@ -134,8 +132,8 @@ class _GridURLCellState extends GridEditableTextCell { Future focusChanged() async { if (mounted && !cellBloc.isClosed && - cellBloc.state.content != _textEditingController.text.trim()) { - cellBloc.add(URLCellEvent.updateURL(_textEditingController.text.trim())); + cellBloc.state.content != _textEditingController.text) { + cellBloc.add(URLCellEvent.updateURL(_textEditingController.text)); } return super.focusChanged(); } @@ -169,6 +167,9 @@ class MobileURLEditor extends StatelessWidget { textStyle: Theme.of(context).textTheme.bodyMedium, keyboardType: TextInputType.url, hintTextConstraints: const BoxConstraints(maxHeight: 52), + error: context.watch().state.isValid + ? null + : const SizedBox.shrink(), onChanged: (_) { if (textEditingController.value.composing.isCollapsed) { context @@ -212,8 +213,11 @@ class MobileURLEditor extends StatelessWidget { } } -void openUrlCellLink(String content) { - if (RegExp(regexUrl).hasMatch(content)) { +void openUrlCellLink(String content) async { + String url = ""; + + try { + // check protocol is provided const linkPrefix = [ 'http://', 'https://', @@ -224,11 +228,15 @@ void openUrlCellLink(String content) { ]; 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)}", - ); + url = shouldAddScheme ? 'http://$content' : content; + + // get hostname and check validity + final uri = Uri.parse(url); + final hostName = uri.host; + await InternetAddress.lookup(hostName); + } catch (_) { + url = "https://www.google.com/search?q=${Uri.encodeComponent(content)}"; + } finally { + await afLaunchUrlString(url); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart index bece54ccc8..d3bf428ed8 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/widgets/cell_editor/relation_cell_editor.dart @@ -50,9 +50,8 @@ class RelationCellEditor extends StatelessWidget { rightIcon: cellState.rows .map((e) => e.rowId) .contains(row.rowId) - ? FlowySvg( + ? const FlowySvg( FlowySvgs.check_s, - color: Theme.of(context).primaryColor, ) : null, onTap: () => context diff --git a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart index dd6e4d56e1..1fce5c0714 100644 --- a/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart +++ b/frontend/appflowy_flutter/packages/flowy_infra_ui/lib/style_widget/text_field.dart @@ -20,6 +20,7 @@ class FlowyTextField extends StatefulWidget { final bool submitOnLeave; final Duration? debounceDuration; final String? errorText; + final Widget? error; final int? maxLines; final bool showCounter; final Widget? prefixIcon; @@ -50,6 +51,7 @@ class FlowyTextField extends StatefulWidget { this.submitOnLeave = false, this.debounceDuration, this.errorText, + this.error, this.maxLines = 1, this.showCounter = true, this.prefixIcon, @@ -176,6 +178,7 @@ class FlowyTextFieldState extends State { isDense: false, hintText: widget.hintText, errorText: widget.errorText, + error: widget.error, errorStyle: Theme.of(context) .textTheme .bodySmall!