feat: edit property (#4065)

* feat: edit property

* feat: disable actions if field is primary

* feat: remove include time

* chore: set title to medium

* fix: flutter analyze

* chore: update built in text style

* chore: update text style

* fix: unable to click the calendar day
This commit is contained in:
Lucas.Xu 2023-12-02 20:39:03 +08:00 committed by GitHub
parent 9824d5980a
commit 415f121720
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 447 additions and 253 deletions

View File

@ -202,4 +202,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 8c681999c7764593c94846b2a64b44d86f7a27ac
COCOAPODS: 1.11.3
COCOAPODS: 1.12.1

View File

@ -515,7 +515,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = "";
DEVELOPMENT_TEAM = VHB67HRSZG;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = AppFlowy;

View File

@ -34,7 +34,6 @@ class AppBarCancelButton extends StatelessWidget {
onTap: onTap,
child: FlowyText(
LocaleKeys.button_cancel.tr(),
fontSize: 16.0,
),
);
}

View File

@ -17,7 +17,6 @@ class MobileViewItemBottomSheetHeader extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
@ -44,9 +43,14 @@ class MobileViewItemBottomSheetHeader extends StatelessWidget {
},
),
// title
Text(
view.name,
style: theme.textTheme.labelSmall,
ConstrainedBox(
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width * 0.6,
),
child: FlowyText.medium(
view.name,
overflow: TextOverflow.ellipsis,
),
),
const HSpace(24.0),
],

View File

@ -64,7 +64,6 @@ Future<T?> showMobileBottomSheet<T>(
: const SizedBox.shrink(),
FlowyText(
title,
fontSize: 16.0,
),
showCloseButton
? HSpace(padding.right + 24)

View File

@ -1,6 +1,6 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_new_field_option.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options_eidtor.dart';
import 'package:appflowy/util/field_type_extension.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
import 'package:easy_localization/easy_localization.dart';
@ -46,9 +46,8 @@ class _MobileNewPropertyScreenState extends State<MobileNewPropertyScreen> {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: FlowyText(
title: FlowyText.medium(
LocaleKeys.grid_field_newProperty.tr(),
fontSize: 16.0,
),
leading: AppBarCancelButton(
onTap: () => context.pop(),
@ -62,7 +61,7 @@ class _MobileNewPropertyScreenState extends State<MobileNewPropertyScreen> {
),
],
),
body: FieldOption(
body: FieldOptionEditor(
mode: FieldOptionMode.add,
defaultValues: optionValues,
onOptionValuesChanged: (optionValues) {
@ -88,10 +87,9 @@ class _SaveButton extends StatelessWidget {
alignment: Alignment.center,
child: GestureDetector(
onTap: onSave,
child: FlowyText(
child: FlowyText.medium(
LocaleKeys.button_save.tr(),
color: const Color(0xFF00ADDC),
fontSize: 16.0,
),
),
),

View File

@ -0,0 +1,139 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/base/app_bar_actions.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options_eidtor.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field_settings/field_settings_service.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
class MobileEditPropertyScreen extends StatefulWidget {
static const routeName = '/edit_property';
static const argViewId = 'view_id';
static const argField = 'field';
static const argIsPrimary = 'is_primary';
const MobileEditPropertyScreen({
super.key,
required this.viewId,
required this.field,
this.isPrimary = false,
});
final String viewId;
final FieldPB field;
final bool isPrimary;
@override
State<MobileEditPropertyScreen> createState() =>
_MobileEditPropertyScreenState();
}
class _MobileEditPropertyScreenState extends State<MobileEditPropertyScreen> {
late Future<FieldOptionValues?> future;
FieldOptionValues? optionValues;
@override
void initState() {
super.initState();
future = FieldOptionValues.get(
viewId: widget.viewId,
fieldId: widget.field.id,
fieldType: widget.field.fieldType,
);
}
@override
Widget build(BuildContext context) {
final viewId = widget.viewId;
final fieldId = widget.field.id;
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: FlowyText.medium(
LocaleKeys.grid_field_editProperty.tr(),
),
leading: AppBarCancelButton(
onTap: () => context.pop(),
),
leadingWidth: 120,
actions: [
_SaveButton(
onSave: () {
context.pop(optionValues);
},
),
],
),
body: FutureBuilder<FieldOptionValues?>(
future: future,
builder: (context, snapshot) {
final optionValues = snapshot.data;
if (optionValues == null) {
return const Center(child: CircularProgressIndicator.adaptive());
}
return FieldOptionEditor(
mode: FieldOptionMode.edit,
isPrimary: widget.isPrimary,
defaultValues: optionValues,
onOptionValuesChanged: (optionValues) {
this.optionValues = optionValues;
},
onAction: (action) {
final service = FieldBackendService(
viewId: viewId,
fieldId: fieldId,
);
switch (action) {
case FieldOptionAction.delete:
service.deleteField();
break;
case FieldOptionAction.duplicate:
service.duplicateField();
break;
case FieldOptionAction.hide:
FieldSettingsBackendService(viewId: viewId)
.updateFieldSettings(
fieldId: fieldId,
fieldVisibility: FieldVisibility.AlwaysHidden,
);
break;
}
context.pop();
},
);
},
),
);
}
}
class _SaveButton extends StatelessWidget {
const _SaveButton({
required this.onSave,
});
final VoidCallback onSave;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(right: 16.0),
child: Align(
alignment: Alignment.center,
child: GestureDetector(
onTap: onSave,
child: FlowyText.medium(
LocaleKeys.button_save.tr(),
color: const Color(0xFF00ADDC),
),
),
),
);
}
}

View File

@ -75,7 +75,7 @@ class _FieldHeader extends StatelessWidget {
),
FlowyText.medium(
LocaleKeys.titleBar_addField.tr(),
fontSize: 16.0,
fontSize: 17.0,
),
const HSpace(120),
],
@ -106,7 +106,9 @@ class _Field extends StatelessWidget {
size: Size.square(width / 4.0),
),
const VSpace(6.0),
FlowyText(type.i18n),
FlowyText(
type.i18n,
),
],
),
);

View File

@ -7,6 +7,7 @@ import 'package:appflowy/mobile/presentation/base/option_color_list.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options.dart';
import 'package:appflowy/mobile/presentation/widgets/widgets.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/number_format_bloc.dart';
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_service.dart';
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/type_option/date.dart';
@ -32,7 +33,6 @@ class FieldOptionValues {
required this.type,
required this.name,
this.dateFormate,
this.includeTime = false,
this.timeFormat,
this.numberFormat,
this.selectOption = const [],
@ -43,7 +43,6 @@ class FieldOptionValues {
// FieldType.Date
DateFormatPB? dateFormate;
bool includeTime;
TimeFormatPB? timeFormat;
// FieldType.Num
@ -56,65 +55,110 @@ class FieldOptionValues {
Future<void> create({
required String viewId,
}) async {
Uint8List? typeOptionData;
switch (type) {
case FieldType.RichText:
break;
case FieldType.URL:
break;
case FieldType.Checkbox:
break;
case FieldType.Number:
typeOptionData = NumberTypeOptionPB(
format: numberFormat,
).writeToBuffer();
break;
case FieldType.DateTime:
typeOptionData = DateTypeOptionPB(
dateFormat: dateFormate,
timeFormat: timeFormat,
).writeToBuffer();
break;
case FieldType.SingleSelect:
typeOptionData = SingleSelectTypeOptionPB(
options: selectOption,
).writeToBuffer();
case FieldType.MultiSelect:
typeOptionData = MultiSelectTypeOptionPB(
options: selectOption,
).writeToBuffer();
break;
default:
throw UnimplementedError();
}
await TypeOptionBackendService.createFieldTypeOption(
viewId: viewId,
fieldType: type,
fieldName: name,
typeOptionData: typeOptionData,
typeOptionData: toTypeOptionBuffer(),
);
}
Uint8List? toTypeOptionBuffer() {
switch (type) {
case FieldType.RichText:
case FieldType.URL:
case FieldType.Checkbox:
return null;
case FieldType.Number:
return NumberTypeOptionPB(
format: numberFormat,
).writeToBuffer();
case FieldType.DateTime:
return DateTypeOptionPB(
dateFormat: dateFormate,
timeFormat: timeFormat,
).writeToBuffer();
case FieldType.SingleSelect:
return SingleSelectTypeOptionPB(
options: selectOption,
).writeToBuffer();
case FieldType.MultiSelect:
return MultiSelectTypeOptionPB(
options: selectOption,
).writeToBuffer();
default:
throw UnimplementedError();
}
}
static Future<FieldOptionValues?> get({
required String viewId,
required String fieldId,
required FieldType fieldType,
}) async {
final service = FieldBackendService(viewId: viewId, fieldId: fieldId);
final result = await service.getFieldTypeOptionData(fieldType: fieldType);
return result.fold(
(option) {
final type = option.field_2.fieldType;
final buffer = option.typeOptionData;
return FieldOptionValues(
type: type,
name: option.field_2.name,
numberFormat: type == FieldType.Number
? NumberTypeOptionPB.fromBuffer(buffer).format
: null,
dateFormate: type == FieldType.DateTime
? DateTypeOptionPB.fromBuffer(buffer).dateFormat
: null,
timeFormat: type == FieldType.DateTime
? DateTypeOptionPB.fromBuffer(buffer).timeFormat
: null,
selectOption: switch (type) {
FieldType.SingleSelect =>
SingleSelectTypeOptionPB.fromBuffer(buffer).options,
FieldType.MultiSelect =>
MultiSelectTypeOptionPB.fromBuffer(buffer).options,
_ => [],
},
);
},
(error) => null,
);
}
}
class FieldOption extends StatefulWidget {
const FieldOption({
enum FieldOptionAction {
hide,
duplicate,
delete,
}
class FieldOptionEditor extends StatefulWidget {
const FieldOptionEditor({
super.key,
required this.mode,
required this.defaultValues,
required this.onOptionValuesChanged,
this.onAction,
this.isPrimary = false,
});
final FieldOptionMode mode;
final FieldOptionValues defaultValues;
final void Function(FieldOptionValues values) onOptionValuesChanged;
// only used in edit mode
final void Function(FieldOptionAction action)? onAction;
// the primary field can't be deleted, duplicated, and changed type
final bool isPrimary;
@override
State<FieldOption> createState() => _FieldOptionState();
State<FieldOptionEditor> createState() => _FieldOptionEditorState();
}
class _FieldOptionState extends State<FieldOption> {
class _FieldOptionEditorState extends State<FieldOptionEditor> {
final controller = TextEditingController();
late FieldOptionValues values;
@ -124,7 +168,7 @@ class _FieldOptionState extends State<FieldOption> {
super.initState();
values = widget.defaultValues;
controller.text = values.type.i18n;
controller.text = values.name;
}
@override
@ -136,6 +180,7 @@ class _FieldOptionState extends State<FieldOption> {
@override
Widget build(BuildContext context) {
final option = _buildOption();
return Container(
color: Theme.of(context).colorScheme.secondaryContainer,
height: MediaQuery.of(context).size.height,
@ -151,18 +196,26 @@ class _FieldOptionState extends State<FieldOption> {
},
),
const _Divider(),
_PropertyType(
type: values.type,
onSelected: (type) => setState(
() {
controller.text = type.i18n;
_updateOptionValues(type: type, name: type.i18n);
},
if (!widget.isPrimary) ...[
_PropertyType(
type: values.type,
onSelected: (type) => setState(
() {
if (widget.mode == FieldOptionMode.add) {
controller.text = type.i18n;
}
_updateOptionValues(type: type, name: type.i18n);
},
),
),
),
const _Divider(),
..._buildOption(),
const _Divider(),
if (option.isNotEmpty) ...[
...option,
const _Divider(),
],
],
..._buildOptionActions(),
const _Divider(),
],
),
),
@ -171,18 +224,6 @@ class _FieldOptionState extends State<FieldOption> {
List<Widget> _buildOption() {
switch (values.type) {
case FieldType.RichText:
return [
const _TextOption(),
];
case FieldType.URL:
return [
const _URLOption(),
];
case FieldType.Checkbox:
return [
const _CheckboxOption(),
];
case FieldType.Number:
return [
_NumberOption(
@ -204,10 +245,8 @@ class _FieldOptionState extends State<FieldOption> {
),
const _Divider(),
_TimeOption(
includeTime: values.includeTime,
selectedFormat: values.timeFormat ?? TimeFormatPB.TwelveHour,
onSelected: (includeTime, format) => _updateOptionValues(
includeTime: includeTime,
onSelected: (format) => _updateOptionValues(
timeFormat: format,
),
),
@ -244,19 +283,24 @@ class _FieldOptionState extends State<FieldOption> {
FieldOptionMode.add => [],
FieldOptionMode.edit => [
FlowyOptionTile.text(
text: LocaleKeys.button_delete.tr(),
leftIcon: const FlowySvg(FlowySvgs.delete_s),
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_duplicate.tr(),
leftIcon: const FlowySvg(FlowySvgs.copy_s),
),
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.grid_field_hide.tr(),
leftIcon: const FlowySvg(FlowySvgs.hide_s),
onTap: () => widget.onAction?.call(FieldOptionAction.hide),
),
if (!widget.isPrimary)
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_duplicate.tr(),
leftIcon: const FlowySvg(FlowySvgs.copy_s),
onTap: () => widget.onAction?.call(FieldOptionAction.duplicate),
),
if (!widget.isPrimary)
FlowyOptionTile.text(
showTopBorder: false,
text: LocaleKeys.button_delete.tr(),
leftIcon: const FlowySvg(FlowySvgs.delete_s),
onTap: () => widget.onAction?.call(FieldOptionAction.delete),
),
]
};
}
@ -265,7 +309,6 @@ class _FieldOptionState extends State<FieldOption> {
FieldType? type,
String? name,
DateFormatPB? dateFormate,
bool? includeTime,
TimeFormatPB? timeFormat,
NumberFormatPB? numberFormat,
List<SelectOptionPB>? selectOption,
@ -279,9 +322,6 @@ class _FieldOptionState extends State<FieldOption> {
if (dateFormate != null) {
values.dateFormate = dateFormate;
}
if (includeTime != null) {
values.includeTime = includeTime;
}
if (timeFormat != null) {
values.timeFormat = timeFormat;
}
@ -348,7 +388,6 @@ class _PropertyType extends StatelessWidget {
FlowyText(
type.i18n,
color: Theme.of(context).hintColor,
fontSize: 16.0,
),
const HSpace(4.0),
FlowySvg(
@ -394,33 +433,6 @@ class _Divider extends StatelessWidget {
}
}
class _TextOption extends StatelessWidget {
const _TextOption();
@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}
class _URLOption extends StatelessWidget {
const _URLOption();
@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}
class _CheckboxOption extends StatelessWidget {
const _CheckboxOption();
@override
Widget build(BuildContext context) {
return const SizedBox.shrink();
}
}
class _DateOption extends StatefulWidget {
const _DateOption({
required this.selectedFormat,
@ -456,7 +468,6 @@ class _DateOptionState extends State<_DateOption> {
),
child: FlowyText(
LocaleKeys.grid_field_dateFormat.tr(),
fontSize: 16.0,
color: Theme.of(context).hintColor,
),
),
@ -480,14 +491,12 @@ class _DateOptionState extends State<_DateOption> {
class _TimeOption extends StatefulWidget {
const _TimeOption({
required this.includeTime,
required this.selectedFormat,
required this.onSelected,
});
final bool includeTime;
final TimeFormatPB selectedFormat;
final Function(bool includeTime, TimeFormatPB format) onSelected;
final Function(TimeFormatPB format) onSelected;
@override
State<_TimeOption> createState() => _TimeOptionState();
@ -495,14 +504,12 @@ class _TimeOption extends StatefulWidget {
class _TimeOptionState extends State<_TimeOption> {
TimeFormatPB selectedFormat = TimeFormatPB.TwelveHour;
bool includeTime = false;
@override
void initState() {
super.initState();
selectedFormat = widget.selectedFormat;
includeTime = widget.includeTime;
}
@override
@ -517,34 +524,22 @@ class _TimeOptionState extends State<_TimeOption> {
),
child: FlowyText(
LocaleKeys.grid_field_timeFormat.tr(),
fontSize: 16.0,
color: Theme.of(context).hintColor,
),
),
FlowyOptionTile.switcher(
text: LocaleKeys.grid_field_includeTime.tr(),
isSelected: includeTime,
onValueChanged: (includeTime) {
widget.onSelected(includeTime, selectedFormat);
setState(() {
this.includeTime = includeTime;
});
},
),
if (includeTime)
...TimeFormatPB.values.mapIndexed((index, format) {
return FlowyOptionTile.checkbox(
text: format.title(),
isSelected: selectedFormat == format,
showTopBorder: false,
onTap: () {
widget.onSelected(includeTime, format);
setState(() {
selectedFormat = format;
});
},
);
}),
...TimeFormatPB.values.mapIndexed((index, format) {
return FlowyOptionTile.checkbox(
text: format.title(),
isSelected: selectedFormat == format,
showTopBorder: false,
onTap: () {
widget.onSelected(format);
setState(() {
selectedFormat = format;
});
},
);
}),
],
);
}
@ -569,7 +564,6 @@ class _NumberOption extends StatelessWidget {
FlowyText(
selectedFormat.title(),
color: Theme.of(context).hintColor,
fontSize: 16.0,
),
const HSpace(4.0),
FlowySvg(
@ -663,7 +657,6 @@ class _SelectOption extends StatelessWidget {
),
child: FlowyText(
LocaleKeys.grid_field_optionTitle.tr(),
fontSize: 16.0,
color: Theme.of(context).hintColor,
),
),

View File

@ -58,7 +58,9 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
Widget _buildFullScreen() {
return Scaffold(
appBar: AppBar(
title: Text(LocaleKeys.titleBar_date.tr()),
title: FlowyText.medium(
LocaleKeys.titleBar_date.tr(),
),
),
body: _buildBody(),
);
@ -117,7 +119,7 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
}
Widget _buildHeader() {
const iconWidth = 36.0;
const iconWidth = 30.0;
const height = 44.0;
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
@ -139,7 +141,7 @@ class _MobileDateCellEditScreenState extends State<MobileDateCellEditScreen> {
alignment: Alignment.center,
child: FlowyText.medium(
LocaleKeys.grid_field_dateFieldName.tr(),
fontSize: 18,
fontSize: 16,
),
),
].map((e) => SizedBox(height: height, child: e)).toList(),

View File

@ -9,14 +9,14 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class MobileCalendarEventsScreen extends StatefulWidget {
static const routeName = "/calendar-events";
static const routeName = '/calendar_events';
// GoRouter Arguments
static const calendarBlocKey = "calendar_bloc";
static const calendarDateKey = "date";
static const calendarEventsKey = "events";
static const calendarRowCacheKey = "row_cache";
static const calendarViewIdKey = "view_id";
static const calendarBlocKey = 'calendar_bloc';
static const calendarDateKey = 'date';
static const calendarEventsKey = 'events';
static const calendarRowCacheKey = 'row_cache';
static const calendarViewIdKey = 'view_id';
const MobileCalendarEventsScreen({
super.key,

View File

@ -137,7 +137,7 @@ class _TrashButton extends StatelessWidget {
color: Theme.of(context).colorScheme.onSurface,
),
leftIconSize: const Size.square(24),
text: FlowyText(
text: FlowyText.medium(
LocaleKeys.trash_text.tr(),
fontSize: 18.0,
),

View File

@ -80,8 +80,8 @@ class _RecentViews extends StatelessWidget {
separatorBuilder: () => const HSpace(8),
children: recentViews
.map(
(view) => SizedBox(
width: 150,
(view) => SizedBox.square(
dimension: 148,
child: MobileRecentView(view: view),
),
)

View File

@ -96,8 +96,8 @@ class _MobileRecentViewState extends State<MobileRecentView> {
),
Expanded(
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 16, 8, 2),
child: FlowyText(
padding: const EdgeInsets.fromLTRB(8, 18, 8, 2),
child: FlowyText.medium(
view.name,
maxLines: 2,
overflow: TextOverflow.ellipsis,
@ -110,7 +110,7 @@ class _MobileRecentViewState extends State<MobileRecentView> {
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.only(left: 4),
padding: const EdgeInsets.only(left: 8.0),
child: icon.isNotEmpty
? EmojiText(
emoji: icon,

View File

@ -307,7 +307,7 @@ class _SingleMobileInnerViewItemState extends State<SingleMobileInnerViewItem> {
const HSpace(8),
// title
Expanded(
child: FlowyText.regular(
child: FlowyText.medium(
widget.view.name,
fontSize: 18.0,
overflow: TextOverflow.ellipsis,

View File

@ -1,5 +1,5 @@
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
/// Widget for the root/initial pages in the bottom navigation bar.
class RootPlaceholderScreen extends StatelessWidget {
@ -24,31 +24,10 @@ class RootPlaceholderScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Root of section $label'),
),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('$label Page', style: Theme.of(context).textTheme.titleLarge),
const Padding(padding: EdgeInsets.all(4)),
TextButton(
onPressed: () {
context.go(detailsPath, extra: '$label-XYZ');
},
child: const Text('View details'),
),
const Padding(padding: EdgeInsets.all(4)),
if (secondDetailsPath != null)
TextButton(
onPressed: () {
context.go(secondDetailsPath!);
},
child: const Text('View more details'),
),
],
),
centerTitle: true,
title: FlowyText.medium(label),
),
body: const SizedBox.shrink(),
);
}
}

View File

@ -22,7 +22,8 @@ class FlowyOptionTile extends StatelessWidget {
this.onTap,
this.trailing,
this.textFieldPadding = const EdgeInsets.symmetric(
horizontal: 16.0,
horizontal: 12.0,
vertical: 2.0,
),
this.isSelected = false,
this.textFieldHintText,
@ -55,7 +56,8 @@ class FlowyOptionTile extends StatelessWidget {
void Function(String value)? onTextChanged,
void Function(String value)? onTextSubmitted,
EdgeInsets textFieldPadding = const EdgeInsets.symmetric(
horizontal: 16.0,
horizontal: 12.0,
vertical: 2.0,
),
bool showTopBorder = true,
bool showBottomBorder = true,
@ -150,6 +152,7 @@ class FlowyOptionTile extends StatelessWidget {
showTopBorder: showTopBorder,
showBottomBorder: showBottomBorder,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildText(),
..._buildTextField(),
@ -185,7 +188,6 @@ class FlowyOptionTile extends StatelessWidget {
useIntrinsicWidth: true,
text: FlowyText(
text!,
fontSize: 16.0,
),
margin: const EdgeInsets.symmetric(
horizontal: 16.0,
@ -206,7 +208,6 @@ class FlowyOptionTile extends StatelessWidget {
),
child: FlowyText(
text!,
fontSize: 16.0,
),
);
}
@ -222,11 +223,12 @@ class FlowyOptionTile extends StatelessWidget {
return [
if (leading != null) leading!,
Expanded(
child: ConstrainedBox(
child: Container(
constraints: const BoxConstraints.tightFor(
height: 52.0,
height: 54.0,
width: double.infinity,
),
alignment: Alignment.center,
child: TextField(
controller: controller,
textInputAction: TextInputAction.done,

View File

@ -1,8 +1,8 @@
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
import 'package:dartz/dartz.dart';
import 'package:appflowy_backend/dispatch/dispatch.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
import 'package:dartz/dartz.dart';
/// FieldService consists of lots of event functions. We define the events in the backend(Rust),
/// you can find the corresponding event implementation in event_map.rs of the corresponding crate.
@ -56,6 +56,17 @@ class FieldBackendService {
return DatabaseEventUpdateFieldTypeOption(payload).send();
}
Future<Either<Unit, FlowyError>> updateFieldType({
required FieldType fieldType,
}) {
final payload = UpdateFieldTypePayloadPB.create()
..viewId = viewId
..fieldId = fieldId
..fieldType = fieldType;
return DatabaseEventUpdateFieldType(payload).send();
}
Future<Either<Unit, FlowyError>> deleteField() {
final payload = DeleteFieldPayloadPB.create()
..viewId = viewId

View File

@ -3,7 +3,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_new_field_option.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options_eidtor.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_bloc.dart';
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';

View File

@ -1,12 +1,12 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/mobile/presentation/bottom_sheet/bottom_sheet_database_field_editor.dart';
import 'package:appflowy/mobile/presentation/widgets/flowy_paginated_bottom_sheet.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_edit_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/widgets/_field_options_eidtor.dart';
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'field_type_extension.dart';
@ -41,19 +41,41 @@ class MobileFieldButton extends StatelessWidget {
),
),
child: FlowyButton(
onTap: () {
showPaginatedBottomSheet(
context,
page: SheetPage(
title: LocaleKeys.grid_field_editProperty.tr(),
body: MobileDBBottomSheetFieldEditor(
viewId: viewId,
field: fieldInfo.field,
fieldController: fieldController,
),
),
onTap: () async {
final optionValues = await context.push<FieldOptionValues>(
MobileEditPropertyScreen.routeName,
extra: {
MobileEditPropertyScreen.argViewId: viewId,
MobileEditPropertyScreen.argField: fieldInfo.field,
MobileEditPropertyScreen.argIsPrimary: fieldInfo.isPrimary,
},
);
if (optionValues != null) {
final fieldId = fieldInfo.field.id;
final service = FieldBackendService(
viewId: viewId,
fieldId: fieldId,
);
if (optionValues.name != fieldInfo.name) {
await service.updateField(name: optionValues.name);
}
if (optionValues.type != fieldInfo.fieldType) {
await service.updateFieldType(fieldType: optionValues.type);
}
final data = optionValues.toTypeOptionBuffer();
if (data != null) {
await FieldBackendService.updateFieldTypeOption(
viewId: viewId,
fieldId: fieldId,
typeOptionData: data,
);
}
}
},
radius: BorderRadius.zero,
margin: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
leftIcon: FlowySvg(
fieldInfo.fieldType.icon(),

View File

@ -41,9 +41,7 @@ class _MobileDatePickerState extends State<MobileDatePicker> {
Widget _buildCalendar(BuildContext context) {
const selectedColor = Color(0xFF00BCF0);
final textStyle = Theme.of(context).textTheme.bodyMedium!.copyWith(
fontSize: 16.0,
);
final textStyle = Theme.of(context).textTheme.bodyMedium!.copyWith();
const boxDecoration = BoxDecoration(
shape: BoxShape.circle,
);
@ -156,7 +154,6 @@ class _MobileDatePickerState extends State<MobileDatePicker> {
builder: (_, value, ___) {
return FlowyText(
DateFormat.yMMMM(value.$2).format(value.$1),
fontSize: 16.0,
);
},
),

View File

@ -426,6 +426,7 @@ class DocumentCoverState extends State<DocumentCover> {
children: [
IntrinsicWidth(
child: RoundedTextButton(
fontSize: 14,
onPressed: () {
showMobileBottomSheet(
context,

View File

@ -6,6 +6,7 @@ import 'package:appflowy/plugins/document/presentation/editor_plugins/mobile_too
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/inline_actions/inline_actions_menu.dart';
import 'package:appflowy/util/google_font_family_extension.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
import 'package:collection/collection.dart';
import 'package:flutter/gestures.dart';
@ -160,7 +161,7 @@ class EditorStyleCustomizer {
final theme = Theme.of(context);
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
return TextStyle(
fontFamily: 'poppins',
fontFamily: builtInFontFamily,
fontSize: fontSize,
height: 1.5,
color: theme.colorScheme.onBackground.withOpacity(0.6),
@ -207,7 +208,7 @@ class EditorStyleCustomizer {
fontWeight: fontWeight,
);
} on Exception {
return GoogleFonts.getFont('Poppins');
return GoogleFonts.getFont(builtInFontFamily);
}
}

View File

@ -1,4 +1,5 @@
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:bloc/bloc.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -28,14 +29,19 @@ class DocumentAppearance {
class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
DocumentAppearanceCubit()
: super(const DocumentAppearance(fontSize: 16.0, fontFamily: 'Poppins'));
: super(
const DocumentAppearance(
fontSize: 16.0,
fontFamily: builtInFontFamily,
),
);
Future<void> fetch() async {
final prefs = await SharedPreferences.getInstance();
final fontSize =
prefs.getDouble(KVKeys.kDocumentAppearanceFontSize) ?? 16.0;
final fontFamily =
prefs.getString(KVKeys.kDocumentAppearanceFontFamily) ?? 'Poppins';
final fontFamily = prefs.getString(KVKeys.kDocumentAppearanceFontFamily) ??
builtInFontFamily;
final defaultTextDirection =
prefs.getString(KVKeys.kDocumentAppearanceDefaultTextDirection);

View File

@ -2,6 +2,7 @@ import 'package:appflowy/mobile/presentation/database/board/mobile_board_screen.
import 'package:appflowy/mobile/presentation/database/card/card.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_create_row_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_detail/mobile_edit_field_screen.dart';
import 'package:appflowy/mobile/presentation/database/card/card_property_edit/card_property_edit_screen.dart';
import 'package:appflowy/mobile/presentation/database/date_picker/mobile_date_picker_screen.dart';
import 'package:appflowy/mobile/presentation/database/mobile_calendar_events_screen.dart';
@ -64,6 +65,7 @@ GoRouter generateRouter(Widget child) {
_mobileDateCellEditScreenRoute(),
_mobileCreateRowFieldScreenRoute(),
_mobileNewPropertyPageRoute(),
_mobileEditPropertyPageRoute(),
// home
// MobileHomeSettingPage is outside the bottom navigation bar, thus it is not in the StatefulShellRoute.
@ -373,9 +375,28 @@ GoRoute _mobileNewPropertyPageRoute() {
);
}
GoRoute _mobileEditPropertyPageRoute() {
return GoRoute(
parentNavigatorKey: AppGlobals.rootNavKey,
path: MobileEditPropertyScreen.routeName,
pageBuilder: (context, state) {
final args = state.extra as Map<String, dynamic>;
return MaterialPage(
fullscreenDialog: true,
child: MobileEditPropertyScreen(
viewId: args[MobileEditPropertyScreen.argViewId],
field: args[MobileEditPropertyScreen.argField],
isPrimary: args[MobileEditPropertyScreen.argIsPrimary],
),
);
},
);
}
GoRoute _mobileCalendarEventsPageRoute() {
return GoRoute(
path: MobileCalendarEventsScreen.routeName,
parentNavigatorKey: AppGlobals.rootNavKey,
pageBuilder: (context, state) {
final args = state.extra as Map<String, dynamic>;

View File

@ -3,6 +3,8 @@ import 'package:flowy_infra/theme.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
const builtInFontFamily = 'Poppins';
abstract class BaseAppearance {
final white = const Color(0xFFFFFFFF);
@ -20,25 +22,36 @@ abstract class BaseAppearance {
double? letterSpacing,
double? lineHeight,
}) {
fontSize = fontSize ?? FontSizes.s12;
fontWeight = fontWeight ?? FontWeight.w400;
letterSpacing = fontSize * (letterSpacing ?? 0.005);
final textStyle = TextStyle(
fontFamily: fontFamily,
fontSize: fontSize,
color: fontColor,
fontWeight: fontWeight,
fontFamilyFallback: const [builtInFontFamily],
letterSpacing: letterSpacing,
height: lineHeight,
);
// we embed Poppins font in the app, so we can use it without GoogleFonts
if (fontFamily == builtInFontFamily) {
return textStyle;
}
try {
return GoogleFonts.getFont(
fontFamily,
fontSize: fontSize ?? FontSizes.s12,
fontSize: fontSize,
color: fontColor,
fontWeight: fontWeight ?? FontWeight.w500,
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
fontWeight: fontWeight,
letterSpacing: letterSpacing,
height: lineHeight,
);
} catch (e) {
return TextStyle(
fontFamily: fontFamily,
fontSize: fontSize ?? FontSizes.s12,
color: fontColor,
fontWeight: fontWeight ?? FontWeight.w500,
fontFamilyFallback: const ['Poppins'],
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
height: lineHeight,
);
return textStyle;
}
}

View File

@ -24,7 +24,10 @@ class MobileAppearance extends BaseAppearance {
final fontStyle = getFontStyle(
fontFamily: fontFamily,
fontSize: 16.0,
fontWeight: FontWeight.w400,
);
final codeFontStyle = getFontStyle(
fontFamily: codeFontFamily,
);
@ -196,9 +199,7 @@ class MobileAppearance extends BaseAppearance {
// body2 14 Regular
bodyMedium: fontStyle.copyWith(
color: colorTheme.onBackground,
fontSize: 14,
fontWeight: FontWeight.w400,
// height: 1.2,
letterSpacing: 0.07,
),
// Trash empty title

View File

@ -1,7 +1,8 @@
import 'package:appflowy/user/application/user_settings_service.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy_backend/protobuf/flowy-user/user_setting.pb.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
@ -35,7 +36,7 @@ void main() {
AppTheme.fallback,
),
verify: (bloc) {
expect(bloc.state.font, 'Poppins');
expect(bloc.state.font, builtInFontFamily);
expect(bloc.state.monospaceFont, 'SF Mono');
expect(bloc.state.themeMode, ThemeMode.system);
},

View File

@ -1,5 +1,6 @@
import 'package:appflowy/core/config/kv_keys.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
@ -26,7 +27,7 @@ void main() {
test('Initial state', () {
expect(cubit.state.fontSize, 16.0);
expect(cubit.state.fontFamily, 'Poppins');
expect(cubit.state.fontFamily, builtInFontFamily);
});
test('Fetch document appearance from SharedPreferences', () async {

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:google_fonts/google_fonts.dart';
@ -39,7 +40,7 @@ void main() {
expect(result, isA<TextStyle>());
expect(
result.fontFamily,
GoogleFonts.getFont('Poppins').fontFamily,
GoogleFonts.getFont(builtInFontFamily).fontFamily,
);
});
});

View File

@ -1,5 +1,6 @@
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/appearance_cubit.dart';
import 'package:appflowy/workspace/application/settings/appearance/base_appearance.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_appearance/font_family_setting.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
@ -57,7 +58,7 @@ void main() {
],
child: const Scaffold(
body: ThemeFontFamilySetting(
currentFontFamily: 'Poppins',
currentFontFamily: builtInFontFamily,
),
),
),
@ -70,7 +71,7 @@ void main() {
await tester.pumpAndSettle();
// Verify the initial font family
expect(find.text('Poppins'), findsAtLeastNWidgets(1));
expect(find.text(builtInFontFamily), findsAtLeastNWidgets(1));
when(() => appearanceSettingsCubit.setFontFamily(any<String>()))
.thenAnswer((_) async {});
verifyNever(() => appearanceSettingsCubit.setFontFamily(any<String>()));