fix: launch review issues (#4960)

* fix: i18n for multi select condition list

* fix: lookup url cell content to assert validity

* fix: compromise checkmark color
This commit is contained in:
Richard Shiue 2024-03-22 16:50:01 +08:00 committed by GitHub
parent 27ff5f07ab
commit 99ee60a60d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 66 additions and 25 deletions

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
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_backend/protobuf/flowy-database2/url_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/url_entities.pb.dart';
@ -29,15 +30,17 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
void _dispatch() { void _dispatch() {
on<URLCellEvent>( on<URLCellEvent>(
(event, emit) async { (event, emit) async {
event.when( await event.when(
initial: () { initial: () {
_startListening(); _startListening();
}, },
didReceiveCellUpdate: (cellData) { didReceiveCellUpdate: (cellData) async {
final content = cellData?.content ?? "";
final isValid = await isUrlValid(content);
emit( emit(
state.copyWith( state.copyWith(
content: cellData?.content ?? "", content: content,
url: cellData?.url ?? "", isValid: isValid,
), ),
); );
}, },
@ -58,6 +61,35 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
}, },
); );
} }
Future<bool> 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 @freezed
@ -72,14 +104,14 @@ class URLCellEvent with _$URLCellEvent {
class URLCellState with _$URLCellState { class URLCellState with _$URLCellState {
const factory URLCellState({ const factory URLCellState({
required String content, required String content,
required String url, required bool isValid,
}) = _URLCellState; }) = _URLCellState;
factory URLCellState.initial(URLCellController context) { factory URLCellState.initial(URLCellController context) {
final cellData = context.getCellData(); final cellData = context.getCellData();
return URLCellState( return URLCellState(
content: cellData?.content ?? "", content: cellData?.content ?? "",
url: cellData?.url ?? "", isValid: true,
); );
} }
} }

View File

@ -96,9 +96,9 @@ extension SelectOptionFilterConditionPBExtension
SelectOptionFilterConditionPB.OptionIsNot => SelectOptionFilterConditionPB.OptionIsNot =>
LocaleKeys.grid_selectOptionFilter_isNot.tr(), LocaleKeys.grid_selectOptionFilter_isNot.tr(),
SelectOptionFilterConditionPB.OptionContains => SelectOptionFilterConditionPB.OptionContains =>
LocaleKeys.grid_selectOptionFilter_isNot.tr(), LocaleKeys.grid_selectOptionFilter_contains.tr(),
SelectOptionFilterConditionPB.OptionDoesNotContain => SelectOptionFilterConditionPB.OptionDoesNotContain =>
LocaleKeys.grid_selectOptionFilter_isNot.tr(), LocaleKeys.grid_selectOptionFilter_doesNotContain.tr(),
SelectOptionFilterConditionPB.OptionIsEmpty => SelectOptionFilterConditionPB.OptionIsEmpty =>
LocaleKeys.grid_selectOptionFilter_isEmpty.tr(), LocaleKeys.grid_selectOptionFilter_isEmpty.tr(),
SelectOptionFilterConditionPB.OptionIsNotEmpty => SelectOptionFilterConditionPB.OptionIsNotEmpty =>

View File

@ -138,9 +138,8 @@ class _DatabaseList extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
rightIcon: meta.databaseId == currentDatabaseId rightIcon: meta.databaseId == currentDatabaseId
? FlowySvg( ? const FlowySvg(
FlowySvgs.check_s, FlowySvgs.check_s,
color: Theme.of(context).colorScheme.primary,
) )
: null, : null,
), ),

View File

@ -1,4 +1,5 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
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';
@ -24,9 +25,6 @@ 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();
@ -134,8 +132,8 @@ class _GridURLCellState extends GridEditableTextCell<EditableURLCell> {
Future<void> focusChanged() async { Future<void> focusChanged() async {
if (mounted && if (mounted &&
!cellBloc.isClosed && !cellBloc.isClosed &&
cellBloc.state.content != _textEditingController.text.trim()) { cellBloc.state.content != _textEditingController.text) {
cellBloc.add(URLCellEvent.updateURL(_textEditingController.text.trim())); cellBloc.add(URLCellEvent.updateURL(_textEditingController.text));
} }
return super.focusChanged(); return super.focusChanged();
} }
@ -169,6 +167,9 @@ class MobileURLEditor extends StatelessWidget {
textStyle: Theme.of(context).textTheme.bodyMedium, textStyle: Theme.of(context).textTheme.bodyMedium,
keyboardType: TextInputType.url, keyboardType: TextInputType.url,
hintTextConstraints: const BoxConstraints(maxHeight: 52), hintTextConstraints: const BoxConstraints(maxHeight: 52),
error: context.watch<URLCellBloc>().state.isValid
? null
: const SizedBox.shrink(),
onChanged: (_) { onChanged: (_) {
if (textEditingController.value.composing.isCollapsed) { if (textEditingController.value.composing.isCollapsed) {
context context
@ -212,8 +213,11 @@ class MobileURLEditor extends StatelessWidget {
} }
} }
void openUrlCellLink(String content) { void openUrlCellLink(String content) async {
if (RegExp(regexUrl).hasMatch(content)) { String url = "";
try {
// check protocol is provided
const linkPrefix = [ const linkPrefix = [
'http://', 'http://',
'https://', 'https://',
@ -224,11 +228,15 @@ void openUrlCellLink(String content) {
]; ];
final shouldAddScheme = final shouldAddScheme =
!linkPrefix.any((pattern) => content.startsWith(pattern)); !linkPrefix.any((pattern) => content.startsWith(pattern));
final url = shouldAddScheme ? 'https://$content' : content; url = shouldAddScheme ? 'http://$content' : content;
afLaunchUrlString(url);
} else { // get hostname and check validity
afLaunchUrlString( final uri = Uri.parse(url);
"https://www.google.com/search?q=${Uri.encodeComponent(content)}", final hostName = uri.host;
); await InternetAddress.lookup(hostName);
} catch (_) {
url = "https://www.google.com/search?q=${Uri.encodeComponent(content)}";
} finally {
await afLaunchUrlString(url);
} }
} }

View File

@ -50,9 +50,8 @@ class RelationCellEditor extends StatelessWidget {
rightIcon: cellState.rows rightIcon: cellState.rows
.map((e) => e.rowId) .map((e) => e.rowId)
.contains(row.rowId) .contains(row.rowId)
? FlowySvg( ? const FlowySvg(
FlowySvgs.check_s, FlowySvgs.check_s,
color: Theme.of(context).primaryColor,
) )
: null, : null,
onTap: () => context onTap: () => context

View File

@ -20,6 +20,7 @@ class FlowyTextField extends StatefulWidget {
final bool submitOnLeave; final bool submitOnLeave;
final Duration? debounceDuration; final Duration? debounceDuration;
final String? errorText; final String? errorText;
final Widget? error;
final int? maxLines; final int? maxLines;
final bool showCounter; final bool showCounter;
final Widget? prefixIcon; final Widget? prefixIcon;
@ -50,6 +51,7 @@ class FlowyTextField extends StatefulWidget {
this.submitOnLeave = false, this.submitOnLeave = false,
this.debounceDuration, this.debounceDuration,
this.errorText, this.errorText,
this.error,
this.maxLines = 1, this.maxLines = 1,
this.showCounter = true, this.showCounter = true,
this.prefixIcon, this.prefixIcon,
@ -176,6 +178,7 @@ class FlowyTextFieldState extends State<FlowyTextField> {
isDense: false, isDense: false,
hintText: widget.hintText, hintText: widget.hintText,
errorText: widget.errorText, errorText: widget.errorText,
error: widget.error,
errorStyle: Theme.of(context) errorStyle: Theme.of(context)
.textTheme .textTheme
.bodySmall! .bodySmall!