Compare commits

...
Author SHA1 Message Date
appflowy b090aae280 chore: update version number 2022-09-14 16:42:47 +08:00
appflowy f86e9b25e8 feat: enable delete field in edit row detail page 2022-09-14 16:35:29 +08:00
appflowy 9f845adc74 fix: widget errors 2022-09-08 18:18:36 +08:00
Lucas.Xu 79cb3ea25c chore: delete the windows_release.yml 2022-09-08 14:54:41 +08:00
Lucas.Xu fd07a87f45 feat: integrate github action for windows x86_64 release 2022-09-08 14:54:36 +08:00
Nathan.fooo e037e11504 Merge pull request #1011 from AppFlowy-IO/fix/grid_scroll_bar_listener
fix: listen on the scroll position changed after widget was  dpated
2022-09-08 12:02:24 +08:00
appflowy fa7390066c fix: listen on the scroll position changed after widget was dpated 2022-09-08 11:59:56 +08:00
Nathan.fooo d770003ed0 Merge pull request #1009 from AppFlowy-IO/fix/default_localization
chore: fallback to en localization if the key is not found in the loc…
2022-09-08 11:30:52 +08:00
appflowy e03024f22c chore: fallback to en localization if the key is not found in the locale file 2022-09-08 11:29:43 +08:00
Nathan.fooo 043de6d03a Merge pull request #1008 from AppFlowy-IO/fix/grid_row_count
fix: refactor grid row count UI layout
2022-09-08 11:20:39 +08:00
appflowy 61727ace59 fix: refactor grid row count UI layout 2022-09-08 11:17:37 +08:00
appflowy db7845722a fix: adjust board dark mode UI 2022-09-07 20:52:25 +08:00
appflowy e93bf3ddc2 fix: default name when create a new field 2022-09-07 20:32:30 +08:00
Nathan.fooo 2fdc5e25f6 Merge pull request #1003 from AppFlowy-IO/fix/005_2
Fix/005 2
2022-09-07 19:06:37 +08:00
appflowy 1b600055fd feat: auto scroll to bottom after crating a new field 2022-09-07 19:04:08 +08:00
appflowy 5275bb67c2 feat: enable select checkbox directly 2022-09-07 16:38:34 +08:00
appflowy f65558116f fix: autofocus after creating a new option 2022-09-07 16:25:09 +08:00
appflowy 529ede5596 fix: duplicate key 2022-09-07 15:51:40 +08:00
Nathan.fooo a7dd6c6944 Merge pull request #1000 from AppFlowy-IO/fix/005_warnings
chore: remove unuse code
2022-09-07 14:24:45 +08:00
appflowy 3c77899a34 chore: remove unuse code 2022-09-07 14:16:38 +08:00
34 changed files with 466 additions and 194 deletions

No files matched your search

+51
View File
@@ -129,3 +129,54 @@ jobs:
asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_X86_ZIP_NAME }}
asset_name: ${{ env.MACOS_X86_ZIP_NAME }}
asset_content_type: application/octet-stream
build-windows-x86_64:
runs-on: windows-latest
needs: create-release
env:
WINDOWS_APP_RELEASE_PATH: frontend\app_flowy\product\${{ github.ref_name }}\windows
WINDOWS_ZIP_NAME: AppFlowy-windows-x86_64.zip
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup environment - Rust and Cargo
uses: actions-rs/toolchain@v1
with:
toolchain: 'stable-2022-01-20'
- name: Setup environment - Flutter
uses: subosito/flutter-action@v2
with:
channel: 'stable'
flutter-version: '3.0.5'
- name: Pre build
working-directory: frontend
run: |
vcpkg integrate install
cargo install --force cargo-make
cargo install --force duckscript_cli
cargo make flowy_dev
- name: Build Windows app
working-directory: frontend
run: |
flutter config --enable-windows-desktop
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-windows-x86 appflowy
- uses: vimtor/action-zip@v1
with:
files: ${{ env.WINDOWS_APP_RELEASE_PATH }}\
dest: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
- name: Upload Release Asset
id: upload-release-asset
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ needs.create-release.outputs.upload_url }}
asset_path: ${{ env.WINDOWS_APP_RELEASE_PATH }}\${{ env.WINDOWS_ZIP_NAME }}
asset_name: ${{ env.WINDOWS_ZIP_NAME }}
asset_content_type: application/octet-stream
+22
View File
@@ -1,5 +1,27 @@
# Release Notes
## Version 0.0.5.1 - 09/14/2022
New features
- Enable deleting a field in board
- Fix some bugs
## Version 0.0.5 - 09/08/2022
New Features - Kanban Board like Notion and Trello beta
Boards are the best way to manage projects & tasks. Use them to group your databases by select, multiselect, and checkbox.
<p align="left"><img src="https://user-images.githubusercontent.com/12026239/190055984-6efa2d7a-ee38-4551-859e-ee56388e1859.gif" width="1000px" /></p>
- Set up columns that represent a specific phase of the project cycle and use cards to represent each project / task
- Drag and drop a card from one phase / column to another phase / column
- Update database properties in the Board view by clicking on a property and making edits on the card
### Other Features & Improvements
- Settings allow users to change avatars
- Click and drag the right edge to resize your sidebar
- And many user interface improvements (link)
## Version 0.0.5 - beta.2 - beta.1 - 09/01/2022
New features
+1 -1
View File
@@ -22,7 +22,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
CARGO_MAKE_CRATE_NAME = "dart-ffi"
LIB_NAME = "dart_ffi"
CURRENT_APP_VERSION = "0.0.5"
CURRENT_APP_VERSION = "0.0.5.1"
FEATURES = "flutter"
PRODUCT_NAME = "AppFlowy"
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
@@ -191,13 +191,15 @@
"optionTitle": "Options",
"addOption": "Add option",
"editProperty": "Edit property",
"newColumn": "New column"
"newColumn": "New column",
"deleteFieldPromptMessage": "Are you sure? This property will be deleted"
},
"row": {
"duplicate": "Duplicate",
"delete": "Delete",
"textPlaceholder": "Empty",
"copyProperty": "Copied property to clipboard"
"copyProperty": "Copied property to clipboard",
"count": "Count"
},
"selectOption": {
"create": "Create",
@@ -21,6 +21,9 @@ class BoardCheckboxCellBloc
didReceiveCellUpdate: (cellData) {
emit(state.copyWith(isSelected: _isSelected(cellData)));
},
select: () async {
cellController.saveCellData(!state.isSelected ? "Yes" : "No");
},
);
},
);
@@ -50,6 +53,7 @@ class BoardCheckboxCellBloc
@freezed
class BoardCheckboxCellEvent with _$BoardCheckboxCellEvent {
const factory BoardCheckboxCellEvent.initial() = _InitialCell;
const factory BoardCheckboxCellEvent.select() = _Selected;
const factory BoardCheckboxCellEvent.didReceiveCellUpdate(
String cellContent) = _DidReceiveCellUpdate;
}
@@ -9,6 +9,7 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/cell_builder.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_detail.dart';
import 'package:app_flowy/workspace/application/appearance.dart';
import 'package:appflowy_board/appflowy_board.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
@@ -20,6 +21,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/block_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:provider/provider.dart';
import '../../grid/application/row/row_cache.dart';
import '../application/board_bloc.dart';
import 'card/card.dart';
@@ -87,34 +89,37 @@ class _BoardContentState extends State<BoardContent> {
children: [const _ToolbarBlocAdaptor(), _buildBoard(context)],
);
return Container(
color: context.read<AppTheme>().surface,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: column,
),
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: column,
);
},
),
);
}
Expanded _buildBoard(BuildContext context) {
return Expanded(
child: AppFlowyBoard(
boardScrollController: scrollManager,
scrollController: ScrollController(),
controller: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footerBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
groupConstraints: const BoxConstraints.tightFor(width: 300),
config: AppFlowyBoardConfig(
groupBackgroundColor: HexColor.fromHex('#F7F8FC'),
Widget _buildBoard(BuildContext context) {
return ChangeNotifierProvider.value(
value: Provider.of<AppearanceSettingModel>(context, listen: true),
child: Selector<AppearanceSettingModel, AppTheme>(
selector: (ctx, notifier) => notifier.theme,
builder: (ctx, theme, child) => Expanded(
child: AppFlowyBoard(
boardScrollController: scrollManager,
scrollController: ScrollController(),
controller: context.read<BoardBloc>().boardController,
headerBuilder: _buildHeader,
footerBuilder: _buildFooter,
cardBuilder: (_, column, columnItem) => _buildCard(
context,
column,
columnItem,
),
groupConstraints: const BoxConstraints.tightFor(width: 300),
config: AppFlowyBoardConfig(
groupBackgroundColor: theme.bg1,
),
),
),
),
);
@@ -214,7 +219,7 @@ class _BoardContentState extends State<BoardContent> {
Widget _buildCard(
BuildContext context,
AppFlowyGroupData column,
AppFlowyGroupData group,
AppFlowyGroupItem columnItem,
) {
final boardColumnItem = columnItem as BoardColumnItem;
@@ -241,10 +246,11 @@ class _BoardContentState extends State<BoardContent> {
},
);
ValueKey? key = cardKeysCache[columnItem.id];
final groupItemId = columnItem.id + group.id;
ValueKey? key = cardKeysCache[groupItemId];
if (key == null) {
final newKey = ValueKey(columnItem.id);
cardKeysCache[columnItem.id] = newKey;
final newKey = ValueKey(groupItemId);
cardKeysCache[groupItemId] = newKey;
key = newKey;
}
@@ -254,7 +260,7 @@ class _BoardContentState extends State<BoardContent> {
decoration: _makeBoxDecoration(context),
child: BoardCard(
gridId: gridId,
groupId: column.id,
groupId: group.id,
fieldId: boardColumnItem.fieldContext.id,
isEditing: isEditing,
cellBuilder: cellBuilder,
@@ -280,8 +286,13 @@ class _BoardContentState extends State<BoardContent> {
);
}
void _openCard(String gridId, GridFieldController fieldController,
RowPB rowPB, GridRowCache rowCache, BuildContext context) {
void _openCard(
String gridId,
GridFieldController fieldController,
RowPB rowPB,
GridRowCache rowCache,
BuildContext context,
) {
final rowInfo = RowInfo(
gridId: gridId,
fields: UnmodifiableListView(fieldController.fieldContexts),
@@ -314,7 +325,15 @@ class _ToolbarBlocAdaptor extends StatelessWidget {
fieldController: bloc.fieldController,
);
return BoardToolbar(toolbarContext: toolbarContext);
return ChangeNotifierProvider.value(
value: Provider.of<AppearanceSettingModel>(context, listen: true),
child: Selector<AppearanceSettingModel, AppTheme>(
selector: (ctx, notifier) => notifier.theme,
builder: (ctx, theme, child) {
return BoardToolbar(toolbarContext: toolbarContext);
},
),
);
},
);
}
@@ -48,6 +48,9 @@ class _BoardCheckboxCellState extends State<BoardCheckboxCell> {
iconPadding: EdgeInsets.zero,
icon: icon,
width: 20,
onPressed: () => context
.read<BoardCheckboxCellBloc>()
.add(const BoardCheckboxCellEvent.select()),
),
);
},
@@ -87,24 +87,3 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
super.dispose();
}
}
class _SelectOptionDialog extends StatelessWidget {
final GridSelectOptionCellController _controller;
const _SelectOptionDialog({
Key? key,
required IGridCellController controller,
}) : _controller = controller as GridSelectOptionCellController,
super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
child: InkWell(onTap: () {
SelectOptionCellEditor.show(
context: context,
cellController: _controller,
);
}),
);
}
}
@@ -53,7 +53,7 @@ class _SettingButton extends StatelessWidget {
onPressed: () => BoardSettingList.show(context, settingContext),
icon: Padding(
padding: const EdgeInsets.symmetric(vertical: 3.0, horizontal: 3.0),
child: svgWidget("grid/setting/setting"),
child: svgWidget("grid/setting/setting", color: theme.iconColor),
),
);
}
@@ -20,7 +20,7 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
_startListening();
},
select: () async {
_updateCellData();
cellController.saveCellData(!state.isSelected ? "Yes" : "No");
},
didReceiveCellUpdate: (cellData) {
emit(state.copyWith(isSelected: _isSelected(cellData)));
@@ -49,10 +49,6 @@ class CheckboxCellBloc extends Bloc<CheckboxCellEvent, CheckboxCellState> {
}
}));
}
void _updateCellData() {
cellController.saveCellData(!state.isSelected ? "Yes" : "No");
}
}
@freezed
@@ -103,11 +103,19 @@ class SelectOptionCellEditorBloc
void _filterOption(String optionName, Emitter<SelectOptionEditorState> emit) {
final _MakeOptionResult result =
_makeOptions(Some(optionName), state.allOptions);
emit(state.copyWith(
filter: Some(optionName),
options: result.options,
createOption: result.createOption,
));
if (optionName.isEmpty) {
emit(state.copyWith(
filter: Some(optionName),
options: result.options,
createOption: none(),
));
} else {
emit(state.copyWith(
filter: Some(optionName),
options: result.options,
createOption: result.createOption,
));
}
}
void _loadOptions() {
@@ -2,6 +2,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'dart:async';
import 'package:dartz/dartz.dart';
import 'field_service.dart';
import 'type_option/type_option_context.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@@ -15,10 +16,11 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
FieldEditorBloc({
required String gridId,
required String fieldName,
required bool isGroupField,
required IFieldTypeOptionLoader loader,
}) : dataController =
TypeOptionDataController(gridId: gridId, loader: loader),
super(FieldEditorState.initial(gridId, fieldName)) {
super(FieldEditorState.initial(gridId, fieldName, isGroupField)) {
on<FieldEditorEvent>(
(event, emit) async {
await event.when(
@@ -35,7 +37,23 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
emit(state.copyWith(name: name));
},
didReceiveFieldChanged: (FieldPB field) {
emit(state.copyWith(field: Some(field)));
emit(state.copyWith(
field: Some(field),
name: field.name,
canDelete: field.isPrimary,
));
},
deleteField: () {
state.field.fold(
() => null,
(field) {
final fieldService = FieldService(
gridId: gridId,
fieldId: field.id,
);
fieldService.deleteField();
},
);
},
);
},
@@ -52,6 +70,7 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
class FieldEditorEvent with _$FieldEditorEvent {
const factory FieldEditorEvent.initial() = _InitialField;
const factory FieldEditorEvent.updateName(String name) = _UpdateName;
const factory FieldEditorEvent.deleteField() = _DeleteField;
const factory FieldEditorEvent.didReceiveFieldChanged(FieldPB field) =
_DidReceiveFieldChanged;
}
@@ -63,16 +82,21 @@ class FieldEditorState with _$FieldEditorState {
required String errorText,
required String name,
required Option<FieldPB> field,
required bool canDelete,
required bool isGroupField,
}) = _FieldEditorState;
factory FieldEditorState.initial(
String gridId,
String fieldName,
bool isGroupField,
) =>
FieldEditorState(
gridId: gridId,
errorText: '',
field: none(),
canDelete: false,
name: fieldName,
isGroupField: isGroupField,
);
}
@@ -41,6 +41,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
didReceiveRowUpdate: (newRowInfos, reason) {
emit(state.copyWith(
rowInfos: newRowInfos,
rowCount: newRowInfos.length,
reason: reason,
));
},
@@ -117,6 +118,7 @@ class GridState with _$GridState {
required Option<GridPB> grid,
required GridFieldEquatable fields,
required List<RowInfo> rowInfos,
required int rowCount,
required GridLoadingState loadingState,
required RowsChangedReason reason,
}) = _GridState;
@@ -124,6 +126,7 @@ class GridState with _$GridState {
factory GridState.initial(String gridId) => GridState(
fields: GridFieldEquatable(UnmodifiableListView([])),
rowInfos: [],
rowCount: 0,
grid: none(),
gridId: gridId,
loadingState: const _Loading(),
@@ -1,7 +1,9 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
@@ -118,6 +120,7 @@ class _FlowyGridState extends State<FlowyGrid> {
const _GridToolbarAdaptor(),
_gridHeader(context, state.gridId),
Flexible(child: child),
const RowCountBadge(),
],
);
},
@@ -302,48 +305,46 @@ class _GridFooter extends StatelessWidget {
@override
Widget build(BuildContext context) {
final rowCount = context.watch<GridBloc>().state.rowInfos.length;
final theme = context.watch<AppTheme>();
return SliverPadding(
padding: const EdgeInsets.only(bottom: 200),
sliver: SliverToBoxAdapter(
child: SizedBox(
height: GridSize.footerHeight,
child: Padding(
padding: GridSize.headerContentInsets,
child: Row(
children: [
SizedBox(width: GridSize.leadingHeaderPadding),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(width: 120, child: GridAddRowButton()),
const SizedBox(height: 30),
_rowCountTextWidget(theme: theme, count: rowCount)
],
),
],
),
padding: GridSize.footerContentInsets,
child: const SizedBox(height: 40, child: GridAddRowButton()),
),
),
),
);
}
}
Widget _rowCountTextWidget({required AppTheme theme, required int count}) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FlowyText.regular(
'Count : ',
fontSize: 13,
color: theme.shader3,
),
FlowyText.regular(
count.toString(),
fontSize: 13,
),
],
class RowCountBadge extends StatelessWidget {
const RowCountBadge({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocSelector<GridBloc, GridState, int>(
selector: (state) => state.rowCount,
builder: (context, rowCount) {
return Padding(
padding: GridSize.footerContentInsets,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
FlowyText.regular(
'${LocaleKeys.grid_row_count.tr()} : ',
fontSize: 13,
color: theme.shader3,
),
FlowyText.regular(rowCount.toString(), fontSize: 13),
],
),
);
},
);
}
}
@@ -5,7 +5,7 @@ class GridSize {
static double get scrollBarSize => 12 * scale;
static double get headerHeight => 40 * scale;
static double get footerHeight => 100 * scale;
static double get footerHeight => 40 * scale;
static double get leadingHeaderPadding => 50 * scale;
static double get trailHeaderPadding => 140 * scale;
static double get headerContainerPadding => 0 * scale;
@@ -35,7 +35,7 @@ class GridSize {
);
static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB(
0,
GridSize.leadingHeaderPadding,
GridSize.headerContainerPadding,
GridSize.headerContainerPadding,
GridSize.headerContainerPadding,
@@ -100,11 +100,7 @@ class SelectOptionTag extends StatelessWidget {
backgroundColor: color,
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
selected: true,
onSelected: (_) {
if (onSelected != null) {
onSelected!();
}
},
onSelected: (_) => onSelected?.call(),
);
}
}
@@ -22,7 +22,7 @@ import '../../header/type_option/select_option_editor.dart';
import 'extension.dart';
import 'text_field.dart';
const double _editorPannelWidth = 300;
const double _editorPanelWidth = 300;
class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
final GridSelectOptionCellController cellController;
@@ -72,8 +72,8 @@ class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
//
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(_editorPannelWidth, 300)),
child: SizedBox(width: _editorPannelWidth, child: editor),
constraints: BoxConstraints.loose(const Size(_editorPanelWidth, 300)),
child: SizedBox(width: _editorPanelWidth, child: editor),
),
identifier: SelectOptionCellEditor.identifier(),
anchorContext: context,
@@ -158,7 +158,7 @@ class _TextField extends StatelessWidget {
child: SelectOptionTextField(
options: state.options,
selectedOptionMap: optionMap,
distanceToText: _editorPannelWidth * 0.7,
distanceToText: _editorPanelWidth * 0.7,
tagController: _tagController,
onClick: () => FlowyOverlay.of(context)
.remove(SelectOptionTypeOptionEditor.identifier),
@@ -32,10 +32,10 @@ class SelectOptionTextField extends StatelessWidget {
required this.onNewTag,
required this.newText,
this.onClick,
TextEditingController? controller,
TextEditingController? textController,
FocusNode? focusNode,
Key? key,
}) : _controller = controller ?? TextEditingController(),
}) : _controller = textController ?? TextEditingController(),
_focusNode = focusNode ?? FocusNode(),
super(key: key);
@@ -48,7 +48,7 @@ class SelectOptionTextField extends StatelessWidget {
textfieldTagsController: tagController,
initialTags: selectedOptionMap.keys.toList(),
focusNode: _focusNode,
textSeparators: const [' ', ','],
textSeparators: const [','],
inputfieldBuilder: (BuildContext context, editController, focusNode,
error, onChanged, onSubmitted) {
return ((context, sc, tags, onTagDelegate) {
@@ -70,6 +70,7 @@ class SelectOptionTextField extends StatelessWidget {
if (text.isNotEmpty) {
onNewTag(text);
focusNode.requestFocus();
}
},
maxLines: 1,
@@ -16,7 +16,7 @@ class GridAddRowButton extends StatelessWidget {
text: const FlowyText.medium('New row', fontSize: 12),
hoverColor: theme.shader6,
onTap: () => context.read<GridBloc>().add(const GridEvent.createRow()),
leftIcon: svgWidget("home/add"),
leftIcon: svgWidget("home/add", color: theme.iconColor),
);
}
}
@@ -1,5 +1,6 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@@ -206,9 +207,15 @@ extension _FieldActionExtension on FieldAction {
.add(const FieldActionSheetEvent.duplicateField());
break;
case FieldAction.delete:
context
.read<FieldActionSheetBloc>()
.add(const FieldActionSheetEvent.deleteField());
FlowyAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {
context
.read<FieldActionSheetBloc>()
.add(const FieldActionSheetEvent.deleteField());
},
).show(context);
break;
}
}
@@ -1,7 +1,12 @@
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flutter/material.dart';
@@ -13,12 +18,16 @@ import 'field_type_option_editor.dart';
class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
final String gridId;
final String fieldName;
final bool isGroupField;
final VoidCallback? onRemoved;
final IFieldTypeOptionLoader typeOptionLoader;
const FieldEditor({
required this.gridId,
required this.fieldName,
this.fieldName = "",
required this.typeOptionLoader,
this.isGroupField = false,
this.onRemoved,
Key? key,
}) : super(key: key);
@@ -28,6 +37,7 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
create: (context) => FieldEditorBloc(
gridId: gridId,
fieldName: fieldName,
isGroupField: isGroupField,
loader: typeOptionLoader,
)..add(const FieldEditorEvent.initial()),
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
@@ -41,6 +51,8 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
const VSpace(10),
const _FieldNameCell(),
const VSpace(10),
const _DeleteFieldButton(),
const VSpace(10),
const _FieldTypeOptionCell(),
],
);
@@ -73,6 +85,9 @@ class FieldEditor extends StatelessWidget with FlowyOverlayDelegate {
@override
bool asBarrier() => true;
@override
void didRemove() => onRemoved?.call();
}
class _FieldTypeOptionCell extends StatelessWidget {
@@ -116,3 +131,47 @@ class _FieldNameCell extends StatelessWidget {
);
}
}
class _DeleteFieldButton extends StatelessWidget {
const _DeleteFieldButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) {
final enable = !state.canDelete && !state.isGroupField;
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_delete.tr(),
fontSize: 12,
color: enable ? null : theme.shader4,
),
hoverColor: theme.hover,
onTap: () {
if (enable) {
FlowyAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
cancel: () {
FlowyOverlay.of(context).remove(FieldEditor.identifier());
},
confirm: () {
context
.read<FieldEditorBloc>()
.add(const FieldEditorEvent.deleteField());
FlowyOverlay.of(context).remove(FieldEditor.identifier());
},
).show(context);
}
},
leftIcon: svgWidget('grid/delete', color: theme.iconColor),
),
);
},
);
}
void so() {}
}
@@ -33,6 +33,7 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
final theme = context.watch<AppTheme>();
return RoundedInputField(
height: 36,
autoFocus: true,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
controller: controller,
normalBorderColor: theme.shader4,
@@ -47,7 +48,8 @@ class _FieldNameTextFieldState extends State<FieldNameTextField> {
@override
void didUpdateWidget(covariant FieldNameTextField oldWidget) {
controller.text = widget.name;
controller.selection = TextSelection.fromPosition(TextPosition(offset: controller.text.length));
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
super.didUpdateWidget(oldWidget);
}
@@ -1,4 +1,5 @@
import 'package:app_flowy/plugins/grid/application/row/row_action_sheet_bloc.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';
@@ -150,9 +151,15 @@ extension _RowActionExtension on _RowAction {
.add(const RowActionSheetEvent.duplicateRow());
break;
case _RowAction.delete:
context
.read<RowActionSheetBloc>()
.add(const RowActionSheetEvent.deleteRow());
FlowyAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {
context
.read<RowActionSheetBloc>()
.add(const RowActionSheetEvent.deleteRow());
},
).show(context);
break;
}
}
@@ -130,6 +130,7 @@ class _PropertyList extends StatelessWidget {
axis: Axis.vertical,
controller: _scrollController,
barSize: GridSize.scrollBarSize,
autoHideScrollbar: false,
child: ListView.separated(
controller: _scrollController,
itemCount: state.gridCells.length,
@@ -145,7 +146,26 @@ class _PropertyList extends StatelessWidget {
),
),
),
_CreateFieldButton(viewId: viewId),
const VSpace(10),
_CreateFieldButton(
viewId: viewId,
onCreated: () {
// _scrollController.
FieldEditor(
gridId: viewId,
typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
onRemoved: () {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
});
},
).show(context);
},
),
],
);
},
@@ -155,29 +175,40 @@ class _PropertyList extends StatelessWidget {
class _CreateFieldButton extends StatelessWidget {
final String viewId;
const _CreateFieldButton({required this.viewId, Key? key}) : super(key: key);
final VoidCallback onCreated;
const _CreateFieldButton({
required this.viewId,
required this.onCreated,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
return SizedBox(
return Container(
height: 40,
decoration: _makeBoxDecoration(context),
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_newColumn.tr(),
fontSize: 12,
),
hoverColor: theme.shader6,
onTap: () => FieldEditor(
gridId: viewId,
fieldName: "",
typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
).show(context),
onTap: () => onCreated(),
leftIcon: svgWidget("home/add"),
),
);
}
BoxDecoration _makeBoxDecoration(BuildContext context) {
final theme = context.read<AppTheme>();
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
return BoxDecoration(
color: theme.surface,
border: Border(top: borderSide),
);
}
}
class _RowDetailCell extends StatelessWidget {
@@ -231,6 +262,7 @@ class _RowDetailCell extends StatelessWidget {
FieldEditor(
gridId: cellId.gridId,
fieldName: cellId.fieldContext.name,
isGroupField: cellId.fieldContext.isGroupField,
typeOptionLoader: FieldTypeOptionLoader(
gridId: cellId.gridId,
field: cellId.fieldContext.field,
@@ -46,6 +46,7 @@ class InitAppWidgetTask extends LaunchTask {
],
path: 'assets/translations',
fallbackLocale: const Locale('en'),
useFallbackTranslations: true,
saveLocale: false,
child: app,
),
@@ -56,7 +56,8 @@ class _CreateTextFieldDialog extends State<TextFieldDialog> {
FlowyFormTextInput(
hintText: LocaleKeys.dialogCreatePageNameHint.tr(),
initialValue: widget.value,
textStyle: const TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
textStyle:
const TextStyle(fontSize: 24, fontWeight: FontWeight.w400),
autoFocus: true,
onChanged: (text) {
newValue = text;
@@ -120,7 +121,7 @@ class _CreateFlowyAlertDialog extends State<FlowyAlertDialog> {
const VSpace(20),
OkCancelButton(
onOkPressed: widget.confirm!,
onCancelPressed: widget.confirm,
onCancelPressed: widget.cancel,
)
]
],
@@ -158,7 +159,7 @@ class OkCancelDialog extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
if (title != null) ...[
Text(title!.toUpperCase(), style: TextStyles.T1.textColor(theme.shader1)),
FlowyText.medium(title!.toUpperCase(), color: theme.shader1),
VSpace(Insets.sm * 1.5),
Container(color: theme.bg1, height: 1),
VSpace(Insets.m * 1.5),
@@ -185,7 +186,12 @@ class OkCancelButton extends StatelessWidget {
final double? minHeight;
const OkCancelButton(
{Key? key, this.onOkPressed, this.onCancelPressed, this.okTitle, this.cancelTitle, this.minHeight})
{Key? key,
this.onOkPressed,
this.onCancelPressed,
this.okTitle,
this.cancelTitle,
this.minHeight})
: super(key: key);
@override
@@ -200,7 +206,7 @@ class OkCancelButton extends StatelessWidget {
cancelTitle ?? LocaleKeys.button_Cancel.tr(),
onPressed: () {
onCancelPressed!();
AppGlobals.nav.pop();
Navigator.of(context).pop();
},
bigMode: true,
),
@@ -210,7 +216,7 @@ class OkCancelButton extends StatelessWidget {
okTitle ?? LocaleKeys.button_OK.tr(),
onPressed: () {
onOkPressed!();
AppGlobals.nav.pop();
Navigator.of(context).pop();
},
bigMode: true,
),
@@ -14,12 +14,9 @@ class StyledScrollbar extends StatefulWidget {
final ScrollController controller;
final Function(double)? onDrag;
final bool showTrack;
final bool autoHideScrollbar;
final Color? handleColor;
final Color? trackColor;
// ignore: todo
// TODO: Remove contentHeight if we can fix this issue
// https://stackoverflow.com/questions/60855712/flutter-how-to-force-scrollcontroller-to-recalculate-position-maxextents
final double? contentSize;
const StyledScrollbar(
@@ -30,6 +27,7 @@ class StyledScrollbar extends StatefulWidget {
this.onDrag,
this.contentSize,
this.showTrack = false,
this.autoHideScrollbar = true,
this.handleColor,
this.trackColor})
: super(key: key);
@@ -46,28 +44,7 @@ class ScrollbarState extends State<StyledScrollbar> {
@override
void initState() {
widget.controller.addListener(() => setState(() {}));
widget.controller.position.isScrollingNotifier.addListener(
() {
if (!mounted) {
return;
}
_hideScrollbarOperation?.cancel();
if (!widget.controller.position.isScrollingNotifier.value) {
// scroll is stopped
_hideScrollbarOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(seconds: 2), () {}),
).then((_) {
// Opti: hide with animation
hideHandler = true;
if (mounted) {
setState(() {});
}
});
} else {
hideHandler = false;
}
},
);
_listenOnScrollPositionChanged();
super.initState();
}
@@ -78,6 +55,7 @@ class ScrollbarState extends State<StyledScrollbar> {
@override
void didUpdateWidget(StyledScrollbar oldWidget) {
_listenOnScrollPositionChanged();
if (oldWidget.contentSize != widget.contentSize) setState(() {});
super.didUpdateWidget(oldWidget);
}
@@ -103,7 +81,6 @@ class ScrollbarState extends State<StyledScrollbar> {
break;
case Axis.horizontal:
// Use supplied contentSize if we have it, otherwise just fallback to maxScrollExtents
if (contentSize != null && contentSize > 0) {
maxExtent = contentSize - constraints.maxWidth;
} else {
@@ -118,7 +95,8 @@ class ScrollbarState extends State<StyledScrollbar> {
// Calculate the alignment for the handle, this is a value between 0 and 1,
// it automatically takes the handle size into acct
// ignore: omit_local_variable_types
double handleAlignment = maxExtent == 0 ? 0 : widget.controller.offset / maxExtent;
double handleAlignment =
maxExtent == 0 ? 0 : widget.controller.offset / maxExtent;
// Convert handle alignment from [0, 1] to [-1, 1]
handleAlignment *= 2.0;
@@ -127,19 +105,25 @@ class ScrollbarState extends State<StyledScrollbar> {
// Calculate handleSize by comparing the total content size to our viewport
var handleExtent = _viewExtent;
if (contentExtent > _viewExtent) {
//Make sure handle is never small than the minSize
// Make sure handle is never small than the minSize
handleExtent = max(60, _viewExtent * _viewExtent / contentExtent);
}
// Hide the handle if content is < the viewExtent
var showHandle = contentExtent > _viewExtent && contentExtent > 0;
if (hideHandler) {
showHandle = false;
}
// Handle color
var handleColor = widget.handleColor ?? (theme.isDark ? theme.bg2.withOpacity(.2) : theme.bg2);
var handleColor = widget.handleColor ??
(theme.isDark ? theme.bg2.withOpacity(.2) : theme.bg2);
// Track color
var trackColor = widget.trackColor ?? (theme.isDark ? theme.bg2.withOpacity(.1) : theme.bg2.withOpacity(.3));
var trackColor = widget.trackColor ??
(theme.isDark
? theme.bg2.withOpacity(.1)
: theme.bg2.withOpacity(.3));
//Layout the stack, it just contains a child, and
return Stack(children: <Widget>[
@@ -149,8 +133,12 @@ class ScrollbarState extends State<StyledScrollbar> {
alignment: const Alignment(1, 1),
child: Container(
color: trackColor,
width: widget.axis == Axis.vertical ? widget.size : double.infinity,
height: widget.axis == Axis.horizontal ? widget.size : double.infinity,
width: widget.axis == Axis.vertical
? widget.size
: double.infinity,
height: widget.axis == Axis.horizontal
? widget.size
: double.infinity,
),
),
@@ -167,10 +155,14 @@ class ScrollbarState extends State<StyledScrollbar> {
// HANDLE SHAPE
child: MouseHoverBuilder(
builder: (_, isHovered) => Container(
width: widget.axis == Axis.vertical ? widget.size : handleExtent,
height: widget.axis == Axis.horizontal ? widget.size : handleExtent,
width:
widget.axis == Axis.vertical ? widget.size : handleExtent,
height: widget.axis == Axis.horizontal
? widget.size
: handleExtent,
decoration: BoxDecoration(
color: handleColor.withOpacity(isHovered ? 1 : .85), borderRadius: Corners.s3Border),
color: handleColor.withOpacity(isHovered ? 1 : .85),
borderRadius: Corners.s3Border),
),
),
),
@@ -182,17 +174,42 @@ class ScrollbarState extends State<StyledScrollbar> {
void _handleHorizontalDrag(DragUpdateDetails details) {
var pos = widget.controller.offset;
var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) / _viewExtent;
widget.controller.jumpTo((pos + details.delta.dx * pxRatio).clamp(0.0, widget.controller.position.maxScrollExtent));
var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) /
_viewExtent;
widget.controller.jumpTo((pos + details.delta.dx * pxRatio)
.clamp(0.0, widget.controller.position.maxScrollExtent));
widget.onDrag?.call(details.delta.dx);
}
void _handleVerticalDrag(DragUpdateDetails details) {
var pos = widget.controller.offset;
var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) / _viewExtent;
widget.controller.jumpTo((pos + details.delta.dy * pxRatio).clamp(0.0, widget.controller.position.maxScrollExtent));
var pxRatio = (widget.controller.position.maxScrollExtent + _viewExtent) /
_viewExtent;
widget.controller.jumpTo((pos + details.delta.dy * pxRatio)
.clamp(0.0, widget.controller.position.maxScrollExtent));
widget.onDrag?.call(details.delta.dy);
}
void _listenOnScrollPositionChanged() {
widget.controller.position.isScrollingNotifier.addListener(
() {
if (!mounted) return;
if (!widget.autoHideScrollbar) return;
_hideScrollbarOperation?.cancel();
if (!widget.controller.position.isScrollingNotifier.value) {
_hideScrollbarOperation = CancelableOperation.fromFuture(
Future.delayed(const Duration(seconds: 2), () {}),
).then((_) {
hideHandler = true;
if (mounted) setState(() {});
});
} else {
hideHandler = false;
}
},
);
}
}
class ScrollbarListStack extends StatelessWidget {
@@ -204,6 +221,7 @@ class ScrollbarListStack extends StatelessWidget {
final EdgeInsets? scrollbarPadding;
final Color? handleColor;
final Color? trackColor;
final bool autoHideScrollbar;
const ScrollbarListStack(
{Key? key,
@@ -214,6 +232,7 @@ class ScrollbarListStack extends StatelessWidget {
this.contentSize,
this.scrollbarPadding,
this.handleColor,
this.autoHideScrollbar = true,
this.trackColor})
: super(key: key);
@@ -238,6 +257,7 @@ class ScrollbarListStack extends StatelessWidget {
contentSize: contentSize,
trackColor: trackColor,
handleColor: handleColor,
autoHideScrollbar: autoHideScrollbar,
),
)
// The animate will be used by the children that using styled_widget.
@@ -43,7 +43,9 @@ class StyledSingleChildScrollViewState
@override
void dispose() {
// scrollController.dispose();
if (widget.controller == null) {
scrollController.dispose();
}
super.dispose();
}
@@ -109,6 +111,9 @@ class StyledCustomScrollViewState extends State<StyledCustomScrollView> {
@override
void dispose() {
if (widget.verticalController == null) {
controller.dispose();
}
super.dispose();
}
@@ -1,22 +1,30 @@
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/text_style.dart';
import 'package:flowy_infra/theme.dart';
import 'base_styled_button.dart';
import 'package:textstyle_extensions/textstyle_extensions.dart';
class PrimaryTextButton extends StatelessWidget {
final String label;
final VoidCallback? onPressed;
final bool bigMode;
const PrimaryTextButton(this.label, {Key? key, this.onPressed, this.bigMode = false}) : super(key: key);
const PrimaryTextButton(this.label,
{Key? key, this.onPressed, this.bigMode = false})
: super(key: key);
@override
Widget build(BuildContext context) {
TextStyle txtStyle = TextStyles.Btn.textColor(Colors.white);
return PrimaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
final theme = context.watch<AppTheme>();
return PrimaryButton(
bigMode: bigMode,
onPressed: onPressed,
child: FlowyText.regular(
label,
color: theme.surface,
),
);
}
}
@@ -25,14 +33,16 @@ class PrimaryButton extends StatelessWidget {
final VoidCallback? onPressed;
final bool bigMode;
const PrimaryButton({Key? key, required this.child, this.onPressed, this.bigMode = false}) : super(key: key);
const PrimaryButton(
{Key? key, required this.child, this.onPressed, this.bigMode = false})
: super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BaseStyledButton(
minWidth: bigMode ? 170 : 78,
minHeight: bigMode ? 48 : 28,
minWidth: bigMode ? 100 : 80,
minHeight: bigMode ? 40 : 38,
contentPadding: EdgeInsets.zero,
bgColor: theme.main1,
hoverColor: theme.main1,
@@ -1,9 +1,8 @@
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// ignore: import_of_legacy_library_into_null_safe
import 'package:textstyle_extensions/textstyle_extensions.dart';
import 'package:flowy_infra/size.dart';
import 'package:flowy_infra/text_style.dart';
import 'package:flowy_infra/theme.dart';
import 'base_styled_button.dart';
@@ -12,13 +11,21 @@ class SecondaryTextButton extends StatelessWidget {
final VoidCallback? onPressed;
final bool bigMode;
const SecondaryTextButton(this.label, {Key? key, this.onPressed, this.bigMode = false}) : super(key: key);
const SecondaryTextButton(this.label,
{Key? key, this.onPressed, this.bigMode = false})
: super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
TextStyle txtStyle = TextStyles.Btn.textColor(theme.main1);
return SecondaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
return SecondaryButton(
bigMode: bigMode,
onPressed: onPressed,
child: FlowyText.regular(
label,
color: theme.main1,
),
);
}
}
@@ -27,14 +34,16 @@ class SecondaryButton extends StatelessWidget {
final VoidCallback? onPressed;
final bool bigMode;
const SecondaryButton({Key? key, required this.child, this.onPressed, this.bigMode = false}) : super(key: key);
const SecondaryButton(
{Key? key, required this.child, this.onPressed, this.bigMode = false})
: super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return BaseStyledButton(
minWidth: bigMode ? 170 : 78,
minHeight: bigMode ? 48 : 28,
minWidth: bigMode ? 100 : 80,
minHeight: bigMode ? 40 : 38,
contentPadding: EdgeInsets.zero,
bgColor: theme.shader7,
hoverColor: theme.hover,
@@ -1,3 +1,3 @@
class DialogSize {
static double get minDialogWidth => 480;
static double get minDialogWidth => 400;
}
@@ -133,7 +133,9 @@ class StyledDialogRoute<T> extends PopupRoute<T> {
super(settings: settings, filter: barrier.filter);
@override
bool get barrierDismissible => barrier.dismissible;
bool get barrierDismissible {
return barrier.dismissible;
}
@override
String get barrierLabel => barrier.label;
+1 -1
View File
@@ -186,7 +186,7 @@ script = [
"""
cd ./app_flowy/
exec cmd.exe /c flutter clean
exec cmd.exe /c flutter pub get
exec cmd.exe /c flutter packages pub get
exec cmd.exe /c flutter packages pub run easy_localization:generate -S assets/translations/ -f keys -o locale_keys.g.dart -S assets/translations -s en.json
exec cmd.exe /c flutter packages pub run build_runner build --delete-conflicting-outputs
"""
+3
View File
@@ -29,6 +29,9 @@ end
ret = which protoc-gen-dart
if is_empty ${ret}
exec cmd.exe /c dart pub global activate protoc_plugin
end
ret = which protoc-gen-dart
if is_empty ${ret}
home_dir = get_home_dir
echo Please add '${home_dir}\\\\AppData\\\\Local\\\\Pub\\\\Cache\\\\bin' into PATH env var
exit -1