fix: mobile launch review (#4214)

* fix: text for changing field type

* fix: field name text field autofocus

* fix: don't allow insert left on primary field

* fix: edit field from view setting

* fix: edit non-current database view

* fix: row detail toggle hidden fields button

* fix: database view name autofocus

* fix: mobile date picker

* fix: deleting select option not deleting column

* fix: duplicate and delete view

* fix: mobile tab bar header left  padding
This commit is contained in:
Richard Shiue 2023-12-27 11:56:06 +08:00 committed by GitHub
parent df8409178b
commit 1cde5a0df6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 169 additions and 84 deletions

View File

@ -402,7 +402,6 @@ class MobileRowDetailPageContentState
children: [ children: [
if (rowDetailState.numHiddenFields != 0) ...[ if (rowDetailState.numHiddenFields != 0) ...[
const ToggleHiddenFieldsVisibilityButton(), const ToggleHiddenFieldsVisibilityButton(),
const VSpace(12),
], ],
MobileRowDetailCreateFieldButton( MobileRowDetailCreateFieldButton(
viewId: viewId, viewId: viewId,

View File

@ -20,6 +20,7 @@ class OptionTextField extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FlowyOptionTile.textField( return FlowyOptionTile.textField(
controller: controller, controller: controller,
autofocus: true,
textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0), textFieldPadding: const EdgeInsets.symmetric(horizontal: 12.0),
onTextChanged: onTextChanged, onTextChanged: onTextChanged,
leftIcon: FlowySvg( leftIcon: FlowySvg(

View File

@ -72,10 +72,8 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
child: const Center(child: DragHandler()), child: const Center(child: DragHandler()),
), ),
_buildHeader(), _buildHeader(),
Expanded( _DateCellEditBody(
child: _DateCellEditBody( dateCellController: widget.controller,
dateCellController: widget.controller,
),
), ),
], ],
), ),

View File

@ -24,7 +24,8 @@ void showCreateFieldBottomSheet(BuildContext context, String viewId) {
minChildSize: 0.7, minChildSize: 0.7,
builder: (context, controller) => FieldOptions( builder: (context, controller) => FieldOptions(
scrollController: controller, scrollController: controller,
onAddField: (type) async { mode: FieldOptionMode.add,
onSelectFieldType: (type) async {
final optionValues = await context.push<FieldOptionValues>( final optionValues = await context.push<FieldOptionValues>(
Uri( Uri(
path: MobileNewPropertyScreen.routeName, path: MobileNewPropertyScreen.routeName,

View File

@ -8,6 +8,8 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'mobile_field_type_option_editor.dart';
const _supportedFieldTypes = [ const _supportedFieldTypes = [
FieldType.RichText, FieldType.RichText,
FieldType.Number, FieldType.Number,
@ -22,18 +24,20 @@ const _supportedFieldTypes = [
class FieldOptions extends StatelessWidget { class FieldOptions extends StatelessWidget {
const FieldOptions({ const FieldOptions({
super.key, super.key,
required this.onAddField, required this.mode,
required this.onSelectFieldType,
this.scrollController, this.scrollController,
}); });
final void Function(FieldType) onAddField; final FieldOptionMode mode;
final void Function(FieldType) onSelectFieldType;
final ScrollController? scrollController; final ScrollController? scrollController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
children: [ children: [
const _FieldHeader(), _FieldHeader(mode: mode),
const VSpace(12.0), const VSpace(12.0),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
@ -46,7 +50,7 @@ class FieldOptions extends StatelessWidget {
.map( .map(
(e) => _Field( (e) => _Field(
type: e, type: e,
onTap: () => onAddField(e), onTap: () => onSelectFieldType(e),
), ),
) )
.toList(), .toList(),
@ -59,7 +63,9 @@ class FieldOptions extends StatelessWidget {
} }
class _FieldHeader extends StatelessWidget { class _FieldHeader extends StatelessWidget {
const _FieldHeader(); const _FieldHeader({required this.mode});
final FieldOptionMode mode;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -76,7 +82,10 @@ class _FieldHeader extends StatelessWidget {
), ),
), ),
FlowyText.medium( FlowyText.medium(
LocaleKeys.titleBar_addField.tr(), switch (mode) {
FieldOptionMode.add => LocaleKeys.grid_field_newProperty.tr(),
FieldOptionMode.edit => LocaleKeys.grid_field_editProperty.tr(),
},
fontSize: 17.0, fontSize: 17.0,
), ),
const HSpace(120), const HSpace(120),

View File

@ -378,7 +378,8 @@ class _PropertyType extends StatelessWidget {
minChildSize: 0.7, minChildSize: 0.7,
builder: (context, controller) => FieldOptions( builder: (context, controller) => FieldOptions(
scrollController: controller, scrollController: controller,
onAddField: (type) { mode: FieldOptionMode.edit,
onSelectFieldType: (type) {
onSelected(type); onSelected(type);
context.pop(); context.pop();
}, },

View File

@ -92,15 +92,16 @@ class _QuickEditFieldState extends State<QuickEditField> {
await service.hide(); await service.hide();
}, },
), ),
FlowyOptionTile.text( if (!widget.fieldInfo.isPrimary)
showTopBorder: false, FlowyOptionTile.text(
text: LocaleKeys.grid_field_insertLeft.tr(), showTopBorder: false,
leftIcon: const FlowySvg(FlowySvgs.insert_left_s), text: LocaleKeys.grid_field_insertLeft.tr(),
onTap: () async { leftIcon: const FlowySvg(FlowySvgs.insert_left_s),
context.pop(); onTap: () async {
await service.insertLeft(); context.pop();
}, await service.insertLeft();
), },
),
FlowyOptionTile.text( FlowyOptionTile.text(
showTopBorder: false, showTopBorder: false,
text: LocaleKeys.grid_field_insertRight.tr(), text: LocaleKeys.grid_field_insertRight.tr(),

View File

@ -209,6 +209,9 @@ class DatabaseFieldListTile extends StatelessWidget {
size: const Size.square(20), size: const Size.square(20),
), ),
showTopBorder: showTopBorder, showTopBorder: showTopBorder,
onTap: () {
showEditFieldScreen(context, viewId, fieldInfo);
},
onValueChanged: (value) { onValueChanged: (value) {
final newVisibility = fieldInfo.visibility!.toggle(); final newVisibility = fieldInfo.visibility!.toggle();
context.read<DatabasePropertyBloc>().add( context.read<DatabasePropertyBloc>().add(

View File

@ -22,9 +22,7 @@ import 'database_view_quick_actions.dart';
/// [MobileDatabaseViewList] shows a list of all the views in the database and /// [MobileDatabaseViewList] shows a list of all the views in the database and
/// adds a button to create a new database view. /// adds a button to create a new database view.
class MobileDatabaseViewList extends StatelessWidget { class MobileDatabaseViewList extends StatelessWidget {
const MobileDatabaseViewList({super.key, required this.databaseController}); const MobileDatabaseViewList({super.key});
final DatabaseController databaseController;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -37,7 +35,6 @@ class MobileDatabaseViewList extends StatelessWidget {
...views.mapIndexed( ...views.mapIndexed(
(index, view) => MobileDatabaseViewListButton( (index, view) => MobileDatabaseViewListButton(
view: view, view: view,
databaseController: databaseController,
showTopBorder: index == 0, showTopBorder: index == 0,
), ),
), ),
@ -93,12 +90,10 @@ class MobileDatabaseViewListButton extends StatelessWidget {
const MobileDatabaseViewListButton({ const MobileDatabaseViewListButton({
super.key, super.key,
required this.view, required this.view,
required this.databaseController,
required this.showTopBorder, required this.showTopBorder,
}); });
final ViewPB view; final ViewPB view;
final DatabaseController databaseController;
final bool showTopBorder; final bool showTopBorder;
@override @override
@ -116,7 +111,11 @@ class MobileDatabaseViewListButton extends StatelessWidget {
.add(DatabaseTabBarEvent.selectView(view.id)); .add(DatabaseTabBarEvent.selectView(view.id));
}, },
leftIcon: _buildViewIconButton(context, view), leftIcon: _buildViewIconButton(context, view),
trailing: _trailing(context, isSelected), trailing: _trailing(
context,
state.tabBarControllerByViewId[view.id]!.controller,
isSelected,
),
showTopBorder: showTopBorder, showTopBorder: showTopBorder,
); );
}, },
@ -130,7 +129,11 @@ class MobileDatabaseViewListButton extends StatelessWidget {
); );
} }
Widget _trailing(BuildContext context, bool isSelected) { Widget _trailing(
BuildContext context,
DatabaseController databaseController,
bool isSelected,
) {
final more = FlowyIconButton( final more = FlowyIconButton(
icon: FlowySvg( icon: FlowySvg(
FlowySvgs.three_dots_s, FlowySvgs.three_dots_s,
@ -240,6 +243,7 @@ class _MobileCreateDatabaseViewState extends State<MobileCreateDatabaseView> {
selectedLayout: layoutType, selectedLayout: layoutType,
), ),
FlowyOptionTile.textField( FlowyOptionTile.textField(
autofocus: true,
controller: controller, controller: controller,
), ),
const VSpace(20), const VSpace(20),

View File

@ -2,9 +2,12 @@ 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:appflowy/plugins/database_view/application/database_controller.dart'; import 'package:appflowy/plugins/database_view/application/database_controller.dart';
import 'package:appflowy/workspace/application/view/view_bloc.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
import 'edit_database_view_screen.dart'; import 'edit_database_view_screen.dart';
@ -35,34 +38,49 @@ class _MobileDatabaseViewQuickActionsState
? MobileEditDatabaseViewScreen( ? MobileEditDatabaseViewScreen(
databaseController: widget.databaseController, databaseController: widget.databaseController,
) )
: Padding( : _quickActions(context, widget.view);
padding: const EdgeInsets.only(top: 8, bottom: 38),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_actionButton(context, _Action.edit),
_divider(),
_actionButton(context, _Action.duplicate),
_divider(),
_actionButton(context, _Action.delete),
_divider(),
],
),
);
} }
Widget _actionButton(BuildContext context, _Action action) { Widget _quickActions(BuildContext context, ViewPB view) {
final isInline = view.childViews.isNotEmpty;
return Padding(
padding: const EdgeInsets.only(top: 8, bottom: 38),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
_actionButton(context, _Action.edit, () {
setState(() => isEditing = true);
}),
if (!isInline) ...[
_divider(),
_actionButton(context, _Action.duplicate, () {
context.read<ViewBloc>().add(const ViewEvent.duplicate());
context.pop();
}),
_divider(),
_actionButton(context, _Action.delete, () {
context.read<ViewBloc>().add(const ViewEvent.delete());
context.pop();
}),
_divider(),
],
],
),
);
}
Widget _actionButton(
BuildContext context,
_Action action,
VoidCallback onTap,
) {
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: MobileQuickActionButton( child: MobileQuickActionButton(
icon: action.icon, icon: action.icon,
text: action.label, text: action.label,
color: action.color(context), color: action.color(context),
onTap: () { onTap: onTap,
if (action == _Action.edit) {
setState(() => isEditing = true);
}
},
), ),
); );
} }

View File

@ -209,6 +209,7 @@ class _NameAndIconState extends State<_NameAndIcon> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FlowyOptionTile.textField( return FlowyOptionTile.textField(
autofocus: true,
controller: textEditingController, controller: textEditingController,
onTextChanged: (text) { onTextChanged: (text) {
context.read<ViewBloc>().add(ViewEvent.rename(text)); context.read<ViewBloc>().add(ViewEvent.rename(text));

View File

@ -27,9 +27,11 @@ class FlowyOptionTile extends StatelessWidget {
vertical: 2.0, vertical: 2.0,
), ),
this.isSelected = false, this.isSelected = false,
this.onValueChanged,
this.textFieldHintText, this.textFieldHintText,
this.onTextChanged, this.onTextChanged,
this.onTextSubmitted, this.onTextSubmitted,
this.autofocus,
}); });
factory FlowyOptionTile.text({ factory FlowyOptionTile.text({
@ -67,6 +69,7 @@ class FlowyOptionTile extends StatelessWidget {
Widget? leftIcon, Widget? leftIcon,
Widget? trailing, Widget? trailing,
String? textFieldHintText, String? textFieldHintText,
bool autofocus = false,
}) { }) {
return FlowyOptionTile._( return FlowyOptionTile._(
type: FlowyOptionTileType.textField, type: FlowyOptionTileType.textField,
@ -81,6 +84,7 @@ class FlowyOptionTile extends StatelessWidget {
textFieldHintText: textFieldHintText, textFieldHintText: textFieldHintText,
onTextChanged: onTextChanged, onTextChanged: onTextChanged,
onTextSubmitted: onTextSubmitted, onTextSubmitted: onTextSubmitted,
autofocus: autofocus,
); );
} }
@ -114,6 +118,7 @@ class FlowyOptionTile extends StatelessWidget {
required String text, required String text,
required bool isSelected, required bool isSelected,
required void Function(bool value) onValueChanged, required void Function(bool value) onValueChanged,
void Function()? onTap,
bool showTopBorder = true, bool showTopBorder = true,
bool showBottomBorder = true, bool showBottomBorder = true,
Widget? leftIcon, Widget? leftIcon,
@ -122,7 +127,8 @@ class FlowyOptionTile extends StatelessWidget {
type: FlowyOptionTileType.toggle, type: FlowyOptionTileType.toggle,
text: text, text: text,
controller: null, controller: null,
onTap: () => onValueChanged(!isSelected), onTap: onTap ?? () => onValueChanged(!isSelected),
onValueChanged: onValueChanged,
showTopBorder: showTopBorder, showTopBorder: showTopBorder,
showBottomBorder: showBottomBorder, showBottomBorder: showBottomBorder,
leading: leftIcon, leading: leftIcon,
@ -143,10 +149,14 @@ class FlowyOptionTile extends StatelessWidget {
// only used in checkbox or switcher // only used in checkbox or switcher
final bool isSelected; final bool isSelected;
// only used in switcher
final void Function(bool value)? onValueChanged;
// only used in textfield // only used in textfield
final String? textFieldHintText; final String? textFieldHintText;
final void Function(String value)? onTextChanged; final void Function(String value)? onTextChanged;
final void Function(String value)? onTextSubmitted; final void Function(String value)? onTextSubmitted;
final bool? autofocus;
final FlowyOptionTileType type; final FlowyOptionTileType type;
@ -229,6 +239,7 @@ class FlowyOptionTile extends StatelessWidget {
alignment: Alignment.center, alignment: Alignment.center,
child: TextField( child: TextField(
controller: controller, controller: controller,
autofocus: autofocus ?? false,
textInputAction: TextInputAction.done, textInputAction: TextInputAction.done,
decoration: InputDecoration( decoration: InputDecoration(
border: InputBorder.none, border: InputBorder.none,

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/base/emoji/emoji_text.dart'; import 'package:appflowy/plugins/base/emoji/emoji_text.dart';
import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart'; import 'package:appflowy/plugins/database_view/application/tab_bar_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_controls.dart'; import 'package:appflowy/plugins/database_view/widgets/setting/mobile_database_controls.dart';
import 'package:appflowy/workspace/application/view/view_ext.dart'; import 'package:appflowy/workspace/application/view/view_ext.dart';
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
@ -65,7 +66,7 @@ class _DatabaseViewList extends StatelessWidget {
return const SizedBox.shrink(); return const SizedBox.shrink();
} }
final children = state.tabBars.mapIndexed((index, tabBar) { final children = state.tabBars.mapIndexed<Widget>((index, tabBar) {
return Padding( return Padding(
padding: EdgeInsetsDirectional.only( padding: EdgeInsetsDirectional.only(
start: index == 0 ? 0 : 2, start: index == 0 ? 0 : 2,
@ -78,6 +79,8 @@ class _DatabaseViewList extends StatelessWidget {
); );
}).toList(); }).toList();
children.insert(0, HSpace(GridSize.leadingHeaderPadding));
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Row(children: children), child: Row(children: children),

View File

@ -110,12 +110,9 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
child: const TabBarHeader(), child: const TabBarHeader(),
); );
} else { } else {
return Padding( return const Padding(
padding: EdgeInsets.only( padding: EdgeInsets.only(right: 8),
left: GridSize.leadingHeaderPadding, child: MobileTabBarHeader(),
right: 8,
),
child: const MobileTabBarHeader(),
); );
} }
}, },

View File

@ -12,6 +12,7 @@ import 'package:appflowy/plugins/database_view/widgets/row/cells/cells.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart'; import 'package:appflowy/plugins/document/presentation/editor_plugins/actions/block_action_button.dart';
import 'package:appflowy_backend/log.dart'; import 'package:appflowy_backend/log.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart'; import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:appflowy_popover/appflowy_popover.dart'; import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
@ -314,24 +315,63 @@ class ToggleHiddenFieldsVisibilityButton extends StatelessWidget {
), ),
}; };
return SizedBox( if (PlatformExtension.isDesktop) {
height: 30, return SizedBox(
child: FlowyButton( height: 30,
text: FlowyText.medium(text, color: Theme.of(context).hintColor), child: FlowyButton(
hoverColor: AFThemeExtension.of(context).lightGreyHover, text: FlowyText.medium(text, color: Theme.of(context).hintColor),
leftIcon: RotatedBox( hoverColor: AFThemeExtension.of(context).lightGreyHover,
quarterTurns: state.showHiddenFields ? 1 : 3, leftIcon: RotatedBox(
child: FlowySvg( quarterTurns: state.showHiddenFields ? 1 : 3,
FlowySvgs.arrow_left_s, child: FlowySvg(
FlowySvgs.arrow_left_s,
color: Theme.of(context).hintColor,
),
),
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6),
onTap: () => context.read<RowDetailBloc>().add(
const RowDetailEvent.toggleHiddenFieldVisibility(),
),
),
);
} else {
return ConstrainedBox(
constraints: const BoxConstraints(minWidth: double.infinity),
child: TextButton.icon(
style: Theme.of(context).textButtonTheme.style?.copyWith(
shape: MaterialStateProperty.all<RoundedRectangleBorder>(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.0),
side: BorderSide.none,
),
),
overlayColor: MaterialStateProperty.all<Color>(
Theme.of(context).hoverColor,
),
alignment: AlignmentDirectional.centerStart,
splashFactory: NoSplash.splashFactory,
padding: const MaterialStatePropertyAll(
EdgeInsets.symmetric(vertical: 14, horizontal: 6),
),
),
label: FlowyText.medium(
text,
fontSize: 15,
color: Theme.of(context).hintColor, color: Theme.of(context).hintColor,
), ),
), onPressed: () => context
margin: const EdgeInsets.symmetric(horizontal: 4, vertical: 6), .read<RowDetailBloc>()
onTap: () => context.read<RowDetailBloc>().add( .add(const RowDetailEvent.toggleHiddenFieldVisibility()),
const RowDetailEvent.toggleHiddenFieldVisibility(), icon: RotatedBox(
quarterTurns: state.showHiddenFields ? 1 : 3,
child: FlowySvg(
FlowySvgs.arrow_left_s,
color: Theme.of(context).hintColor,
), ),
), ),
); ),
);
}
}, },
); );
} }

View File

@ -99,9 +99,7 @@ class MobileDatabaseControls extends StatelessWidget {
value: context.read<DatabaseTabBarBloc>(), value: context.read<DatabaseTabBarBloc>(),
), ),
], ],
child: MobileDatabaseViewList( child: const MobileDatabaseViewList(),
databaseController: controller,
),
); );
}, },
); );

View File

@ -842,14 +842,14 @@ impl DatabaseEditor {
type_option.delete_option(&option.id); type_option.delete_option(&option.id);
} }
notify_did_update_database_field(&self.database, field_id)?; let view_editor = self.database_views.get_view_editor(view_id).await?;
self update_field_type_option_fn(
.database &self.database,
.lock() &view_editor,
.fields type_option.to_type_option_data(),
.update_field(field_id, |update| { field.clone(),
update.set_type_option(field.field_type, Some(type_option.to_type_option_data())); )
}); .await?;
self self
.update_cell_with_changeset(view_id, row_id, field_id, cell_changeset) .update_cell_with_changeset(view_id, row_id, field_id, cell_changeset)