chore: Merge main 521 (#2574)

* chore: merge main branch

* chore: remove document plugins

* chore: add color generator

* ci: tests
This commit is contained in:
Nathan.fooo
2023-05-21 15:08:50 +08:00
committed by GitHub
parent eaa1dcdeb9
commit 5cc8340e05
27 changed files with 416 additions and 240 deletions

View File

@ -45,7 +45,8 @@
"medium": "medium", "medium": "medium",
"large": "large", "large": "large",
"fontSize": "Font Size", "fontSize": "Font Size",
"import": "Import" "import": "Import",
"moreOptions": "More options"
}, },
"disclosureAction": { "disclosureAction": {
"rename": "Rename", "rename": "Rename",
@ -108,6 +109,7 @@
"openAsPage": "Open as a Page", "openAsPage": "Open as a Page",
"addNewRow": "Add a new row", "addNewRow": "Add a new row",
"openMenu": "Click to open menu", "openMenu": "Click to open menu",
"dragRow": "Long press to reorder the row",
"viewDataBase": "View database", "viewDataBase": "View database",
"referencePage": "This {name} is referenced" "referencePage": "This {name} is referenced"
}, },
@ -358,12 +360,14 @@
"autoGeneratorGenerate": "Generate", "autoGeneratorGenerate": "Generate",
"autoGeneratorHintText": "Ask OpenAI ...", "autoGeneratorHintText": "Ask OpenAI ...",
"autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key", "autoGeneratorCantGetOpenAIKey": "Can't get OpenAI key",
"autoGeneratorRewrite": "Rewrite",
"smartEdit": "AI Assistants", "smartEdit": "AI Assistants",
"openAI": "OpenAI", "openAI": "OpenAI",
"smartEditFixSpelling": "Fix spelling", "smartEditFixSpelling": "Fix spelling",
"warning": "⚠️ AI responses can be inaccurate or misleading.", "warning": "⚠️ AI responses can be inaccurate or misleading.",
"smartEditSummarize": "Summarize", "smartEditSummarize": "Summarize",
"smartEditImproveWriting": "Improve Writing", "smartEditImproveWriting": "Improve writing",
"smartEditMakeLonger": "Make longer",
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI", "smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key", "smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
"smartEditDisabled": "Connect OpenAI in Settings", "smartEditDisabled": "Connect OpenAI in Settings",

View File

@ -44,7 +44,6 @@ class DateCellDataPersistence implements CellDataPersistence<DateCellData> {
@override @override
Future<Option<FlowyError>> save(DateCellData data) { Future<Option<FlowyError>> save(DateCellData data) {
var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId); var payload = DateChangesetPB.create()..cellPath = _makeCellPath(cellId);
if (data.dateTime != null) { if (data.dateTime != null) {
final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString(); final date = (data.dateTime!.millisecondsSinceEpoch ~/ 1000).toString();
payload.date = date; payload.date = date;

View File

@ -176,18 +176,28 @@ class DatabaseController {
); );
} }
Future<Either<Unit, FlowyError>> moveRow({ Future<Either<Unit, FlowyError>> moveGroupRow({
required RowPB fromRow, required RowPB fromRow,
required String groupId, required String groupId,
RowPB? toRow, RowPB? toRow,
}) { }) {
return _databaseViewBackendSvc.moveRow( return _databaseViewBackendSvc.moveGroupRow(
fromRowId: fromRow.id, fromRowId: fromRow.id,
toGroupId: groupId, toGroupId: groupId,
toRowId: toRow?.id, toRowId: toRow?.id,
); );
} }
Future<Either<Unit, FlowyError>> moveRow({
required RowPB fromRow,
required RowPB toRow,
}) {
return _databaseViewBackendSvc.moveRow(
fromRowId: fromRow.id,
toRowId: toRow.id,
);
}
Future<Either<Unit, FlowyError>> moveGroup({ Future<Either<Unit, FlowyError>> moveGroup({
required String fromGroupId, required String fromGroupId,
required String toGroupId, required String toGroupId,

View File

@ -43,7 +43,7 @@ class DatabaseViewBackendService {
return DatabaseEventCreateRow(payload).send(); return DatabaseEventCreateRow(payload).send();
} }
Future<Either<Unit, FlowyError>> moveRow({ Future<Either<Unit, FlowyError>> moveGroupRow({
required RowId fromRowId, required RowId fromRowId,
required String toGroupId, required String toGroupId,
RowId? toRowId, RowId? toRowId,
@ -60,6 +60,18 @@ class DatabaseViewBackendService {
return DatabaseEventMoveGroupRow(payload).send(); return DatabaseEventMoveGroupRow(payload).send();
} }
Future<Either<Unit, FlowyError>> moveRow({
required String fromRowId,
required String toRowId,
}) {
var payload = MoveRowPayloadPB.create()
..viewId = viewId
..fromRowId = fromRowId
..toRowId = toRowId;
return DatabaseEventMoveRow(payload).send();
}
Future<Either<Unit, FlowyError>> moveGroup({ Future<Either<Unit, FlowyError>> moveGroup({
required String fromGroupId, required String fromGroupId,
required String toGroupId, required String toGroupId,

View File

@ -30,7 +30,7 @@ abstract class RowCacheDelegate {
class RowCache { class RowCache {
final String viewId; final String viewId;
/// _rows containers the current block's rows /// _rows contains the current block's rows
/// Use List to reverse the order of the GridRow. /// Use List to reverse the order of the GridRow.
final RowList _rowList = RowList(); final RowList _rowList = RowList();

View File

@ -54,7 +54,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex); final fromRow = groupControllers[groupId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[groupId]?.rowAtIndex(toIndex); final toRow = groupControllers[groupId]?.rowAtIndex(toIndex);
if (fromRow != null) { if (fromRow != null) {
_databaseController.moveRow( _databaseController.moveGroupRow(
fromRow: fromRow, fromRow: fromRow,
toRow: toRow, toRow: toRow,
groupId: groupId, groupId: groupId,
@ -70,7 +70,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex); final fromRow = groupControllers[fromGroupId]?.rowAtIndex(fromIndex);
final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex); final toRow = groupControllers[toGroupId]?.rowAtIndex(toIndex);
if (fromRow != null) { if (fromRow != null) {
_databaseController.moveRow( _databaseController.moveGroupRow(
fromRow: fromRow, fromRow: fromRow,
toRow: toRow, toRow: toRow,
groupId: toGroupId, groupId: toGroupId,

View File

@ -161,6 +161,9 @@ class CalendarDayCard extends StatelessWidget {
}); });
renderHook.addSelectOptionHook((selectedOptions, cardData, _) { renderHook.addSelectOptionHook((selectedOptions, cardData, _) {
if (selectedOptions.isEmpty) {
return const SizedBox.shrink();
}
final children = selectedOptions.map( final children = selectedOptions.map(
(option) { (option) {
return SelectOptionTag.fromOption( return SelectOptionTag.fromOption(

View File

@ -35,6 +35,17 @@ class GridBloc extends Bloc<GridEvent, GridState> {
); );
await rowService.deleteRow(rowInfo.rowPB.id); await rowService.deleteRow(rowInfo.rowPB.id);
}, },
moveRow: (int from, int to) {
final List<RowInfo> rows = [...state.rowInfos];
final fromRow = rows[from].rowPB;
final toRow = rows[to].rowPB;
rows.insert(to, rows.removeAt(from));
emit(state.copyWith(rowInfos: rows));
databaseController.moveRow(fromRow: fromRow, toRow: toRow);
},
didReceiveGridUpdate: (grid) { didReceiveGridUpdate: (grid) {
emit(state.copyWith(grid: Some(grid))); emit(state.copyWith(grid: Some(grid)));
}, },
@ -110,6 +121,7 @@ class GridEvent with _$GridEvent {
const factory GridEvent.initial() = InitialGrid; const factory GridEvent.initial() = InitialGrid;
const factory GridEvent.createRow() = _CreateRow; const factory GridEvent.createRow() = _CreateRow;
const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow; const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
const factory GridEvent.moveRow(int from, int to) = _MoveRow;
const factory GridEvent.didReceiveRowUpdate( const factory GridEvent.didReceiveRowUpdate(
List<RowInfo> rows, List<RowInfo> rows,
RowsChangedReason listState, RowsChangedReason listState,

View File

@ -91,21 +91,6 @@ class _GridPageState extends State<GridPage> {
), ),
); );
} }
@override
void dispose() {
super.dispose();
}
@override
void deactivate() {
super.deactivate();
}
@override
void didUpdateWidget(covariant GridPage oldWidget) {
super.didUpdateWidget(oldWidget);
}
} }
class FlowyGrid extends StatefulWidget { class FlowyGrid extends StatefulWidget {
@ -119,12 +104,12 @@ class _FlowyGridState extends State<FlowyGrid> {
final _scrollController = GridScrollController( final _scrollController = GridScrollController(
scrollGroupController: LinkedScrollControllerGroup(), scrollGroupController: LinkedScrollControllerGroup(),
); );
late ScrollController headerScrollController; late final ScrollController headerScrollController;
@override @override
void initState() { void initState() {
headerScrollController = _scrollController.linkHorizontalController();
super.initState(); super.initState();
headerScrollController = _scrollController.linkHorizontalController();
} }
@override @override
@ -216,6 +201,11 @@ class _GridRowsState extends State<_GridRows> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Builder(
builder: (context) {
final filterState = context.watch<GridFilterMenuBloc>().state;
final sortState = context.watch<SortMenuBloc>().state;
return BlocConsumer<GridBloc, GridState>( return BlocConsumer<GridBloc, GridState>(
listenWhen: (previous, current) => previous.reason != current.reason, listenWhen: (previous, current) => previous.reason != current.reason,
listener: (context, state) { listener: (context, state) {
@ -226,39 +216,63 @@ class _GridRowsState extends State<_GridRows> {
delete: (item) { delete: (item) {
_key.currentState?.removeItem( _key.currentState?.removeItem(
item.index, item.index,
(context, animation) => (context, animation) => _renderRow(
_renderRow(context, item.rowInfo, animation), context,
item.rowInfo,
animation: animation,
),
); );
}, },
reorderSingleRow: (reorderRow, rowInfo) {
// _key.currentState?.removeItem(
// reorderRow.oldIndex,
// (context, animation) => _renderRow(context, rowInfo, animation),
// );
// _key.currentState?.insertItem(reorderRow.newIndex);
},
); );
}, },
buildWhen: (previous, current) { buildWhen: (previous, current) {
return current.reason.whenOrNull( return current.reason.maybeWhen(
reorderRows: () => true, reorderRows: () => true,
reorderSingleRow: (reorderRow, rowInfo) => true, reorderSingleRow: (reorderRow, rowInfo) => true,
) ?? delete: (item) => true,
false; insert: (item) => true,
orElse: () => false,
);
}, },
builder: (context, state) { builder: (context, state) {
return SliverAnimatedList( final rowInfos = context.watch<GridBloc>().state.rowInfos;
return SliverFillRemaining(
child: ReorderableListView.builder(
key: _key, key: _key,
initialItemCount: context.read<GridBloc>().state.rowInfos.length, buildDefaultDragHandles: false,
itemBuilder: proxyDecorator: (child, index, animation) => Material(
(BuildContext context, int index, Animation<double> animation) { color: Colors.white.withOpacity(.1),
final rowInfos = context.read<GridBloc>().state.rowInfos; child: Opacity(
if (index >= rowInfos.length) { opacity: .5,
return const SizedBox(); child: child,
} else { ),
final RowInfo rowInfo = rowInfos[index]; ),
return _renderRow(context, rowInfo, animation); onReorder: (fromIndex, newIndex) {
final toIndex =
newIndex > fromIndex ? newIndex - 1 : newIndex;
if (fromIndex == toIndex) {
return;
} }
context
.read<GridBloc>()
.add(GridEvent.moveRow(fromIndex, toIndex));
},
itemCount: rowInfos.length,
itemBuilder: (BuildContext context, int index) {
final RowInfo rowInfo = rowInfos[index];
return _renderRow(
context,
rowInfo,
index: index,
isSortEnabled: sortState.sortInfos.isNotEmpty,
isFilterEnabled: filterState.filters.isNotEmpty,
);
},
),
);
}, },
); );
}, },
@ -267,15 +281,18 @@ class _GridRowsState extends State<_GridRows> {
Widget _renderRow( Widget _renderRow(
BuildContext context, BuildContext context,
RowInfo rowInfo, RowInfo rowInfo, {
Animation<double> animation, int? index,
) { bool isSortEnabled = false,
bool isFilterEnabled = false,
Animation<double>? animation,
}) {
final rowCache = context.read<GridBloc>().getRowCache( final rowCache = context.read<GridBloc>().getRowCache(
rowInfo.rowPB.id, rowInfo.rowPB.id,
); );
/// Return placeholder widget if the rowCache is null. /// Return placeholder widget if the rowCache is null.
if (rowCache == null) return const SizedBox(); if (rowCache == null) return const SizedBox.shrink();
final fieldController = final fieldController =
context.read<GridBloc>().databaseController.fieldController; context.read<GridBloc>().databaseController.fieldController;
@ -285,9 +302,10 @@ class _GridRowsState extends State<_GridRows> {
rowCache: rowCache, rowCache: rowCache,
); );
return SizeTransition( final child = GridRow(
sizeFactor: animation, key: ValueKey(rowInfo.rowPB.id),
child: GridRow( index: index,
isDraggable: !isSortEnabled && !isFilterEnabled,
rowInfo: rowInfo, rowInfo: rowInfo,
dataController: dataController, dataController: dataController,
cellBuilder: GridCellBuilder(cellCache: dataController.cellCache), cellBuilder: GridCellBuilder(cellCache: dataController.cellCache),
@ -300,9 +318,16 @@ class _GridRowsState extends State<_GridRows> {
cellBuilder, cellBuilder,
); );
}, },
key: ValueKey(rowInfo.rowPB.id),
),
); );
if (animation != null) {
return SizeTransition(
sizeFactor: animation,
child: child,
);
}
return child;
} }
void _openRowDetailPage( void _openRowDetailPage(

View File

@ -25,29 +25,34 @@ class GridRow extends StatefulWidget {
final GridCellBuilder cellBuilder; final GridCellBuilder cellBuilder;
final void Function(BuildContext, GridCellBuilder) openDetailPage; final void Function(BuildContext, GridCellBuilder) openDetailPage;
final int? index;
final bool isDraggable;
const GridRow({ const GridRow({
super.key,
required this.rowInfo, required this.rowInfo,
required this.dataController, required this.dataController,
required this.cellBuilder, required this.cellBuilder,
required this.openDetailPage, required this.openDetailPage,
Key? key, this.index,
}) : super(key: key); this.isDraggable = false,
});
@override @override
State<GridRow> createState() => _GridRowState(); State<GridRow> createState() => _GridRowState();
} }
class _GridRowState extends State<GridRow> { class _GridRowState extends State<GridRow> {
late RowBloc _rowBloc; late final RowBloc _rowBloc;
@override @override
void initState() { void initState() {
super.initState();
_rowBloc = RowBloc( _rowBloc = RowBloc(
rowInfo: widget.rowInfo, rowInfo: widget.rowInfo,
dataController: widget.dataController, dataController: widget.dataController,
); );
_rowBloc.add(const RowEvent.initial()); _rowBloc.add(const RowEvent.initial());
super.initState();
} }
@override @override
@ -70,9 +75,11 @@ class _GridRowState extends State<GridRow> {
return Row( return Row(
children: [ children: [
const _RowLeading(), _RowLeading(
index: widget.index,
isDraggable: widget.isDraggable,
),
content, content,
const _RowTrailing(),
], ],
); );
}, },
@ -89,19 +96,25 @@ class _GridRowState extends State<GridRow> {
} }
class _RowLeading extends StatefulWidget { class _RowLeading extends StatefulWidget {
const _RowLeading({Key? key}) : super(key: key); final int? index;
final bool isDraggable;
const _RowLeading({
this.index,
this.isDraggable = false,
});
@override @override
State<_RowLeading> createState() => _RowLeadingState(); State<_RowLeading> createState() => _RowLeadingState();
} }
class _RowLeadingState extends State<_RowLeading> { class _RowLeadingState extends State<_RowLeading> {
late PopoverController popoverController; late final PopoverController popoverController;
@override @override
void initState() { void initState() {
popoverController = PopoverController();
super.initState(); super.initState();
popoverController = PopoverController();
} }
@override @override
@ -131,23 +144,28 @@ class _RowLeadingState extends State<_RowLeading> {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
const _InsertButton(), const _InsertButton(),
if (isDraggable) ...[
ReorderableDragStartListener(
index: widget.index!,
child: _MenuButton(
isDragEnabled: isDraggable,
openMenu: () {
popoverController.show();
},
),
),
] else ...[
_MenuButton( _MenuButton(
openMenu: () { openMenu: () {
popoverController.show(); popoverController.show();
}, },
), ),
], ],
],
); );
} }
}
class _RowTrailing extends StatelessWidget { bool get isDraggable => widget.index != null && widget.isDraggable;
const _RowTrailing({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const SizedBox();
}
} }
class _InsertButton extends StatelessWidget { class _InsertButton extends StatelessWidget {
@ -172,25 +190,31 @@ class _InsertButton extends StatelessWidget {
class _MenuButton extends StatefulWidget { class _MenuButton extends StatefulWidget {
final VoidCallback openMenu; final VoidCallback openMenu;
final bool isDragEnabled;
const _MenuButton({ const _MenuButton({
required this.openMenu, required this.openMenu,
Key? key, this.isDragEnabled = false,
}) : super(key: key); });
@override @override
State<_MenuButton> createState() => _MenuButtonState(); State<_MenuButton> createState() => _MenuButtonState();
} }
class _MenuButtonState extends State<_MenuButton> { class _MenuButtonState extends State<_MenuButton> {
@override
void initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return FlowyIconButton( return FlowyIconButton(
tooltipText: LocaleKeys.tooltip_openMenu.tr(), tooltipText:
widget.isDragEnabled ? null : LocaleKeys.tooltip_openMenu.tr(),
richTooltipText: widget.isDragEnabled
? TextSpan(
children: [
TextSpan(text: '${LocaleKeys.tooltip_dragRow.tr()}\n'),
TextSpan(text: LocaleKeys.tooltip_openMenu.tr()),
],
)
: null,
hoverColor: AFThemeExtension.of(context).lightGreyHover, hoverColor: AFThemeExtension.of(context).lightGreyHover,
width: 20, width: 20,
height: 30, height: 30,
@ -258,6 +282,7 @@ class RowContent extends StatelessWidget {
if (builder != null) { if (builder != null) {
accessories.addAll(builder(buildContext)); accessories.addAll(builder(buildContext));
} }
return accessories; return accessories;
}, },
child: child, child: child,
@ -289,12 +314,12 @@ class _RowEnterRegion extends StatefulWidget {
} }
class _RowEnterRegionState extends State<_RowEnterRegion> { class _RowEnterRegionState extends State<_RowEnterRegion> {
late RegionStateNotifier _rowStateNotifier; late final RegionStateNotifier _rowStateNotifier;
@override @override
void initState() { void initState() {
_rowStateNotifier = RegionStateNotifier();
super.initState(); super.initState();
_rowStateNotifier = RegionStateNotifier();
} }
@override @override

View File

@ -74,6 +74,7 @@ class GridCellBuilder {
key: key, key: key,
); );
} }
throw UnimplementedError; throw UnimplementedError;
} }
} }
@ -83,7 +84,7 @@ class BlankCell extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container(); return const SizedBox.shrink();
} }
} }

View File

@ -68,20 +68,17 @@ class CellContainer extends StatelessWidget {
if (isFocus) { if (isFocus) {
final borderSide = BorderSide( final borderSide = BorderSide(
color: Theme.of(context).colorScheme.primary, color: Theme.of(context).colorScheme.primary,
width: 1.0,
); );
return BoxDecoration(border: Border.fromBorderSide(borderSide)); return BoxDecoration(border: Border.fromBorderSide(borderSide));
} else { }
final borderSide = BorderSide(
color: Theme.of(context).dividerColor, final borderSide = BorderSide(color: Theme.of(context).dividerColor);
width: 1.0,
);
return BoxDecoration( return BoxDecoration(
border: Border(right: borderSide, bottom: borderSide), border: Border(right: borderSide, bottom: borderSide),
); );
} }
} }
}
class _GridCellEnterRegion extends StatelessWidget { class _GridCellEnterRegion extends StatelessWidget {
final Widget child; final Widget child;

View File

@ -82,7 +82,6 @@ class DateCellCalendarBloc
date != null && time == null, date != null && time == null,
); );
String? newTime = time ?? state.time; String? newTime = time ?? state.time;
DateTime? newDate = _utcToLocalAddTime(date); DateTime? newDate = _utcToLocalAddTime(date);
if (time != null && time.isNotEmpty) { if (time != null && time.isNotEmpty) {
newDate = state.dateTime ?? DateTime.now(); newDate = state.dateTime ?? DateTime.now();

View File

@ -6,7 +6,8 @@ import 'package:easy_localization/easy_localization.dart';
enum SmartEditAction { enum SmartEditAction {
summarize, summarize,
fixSpelling, fixSpelling,
improveWriting; improveWriting,
makeItLonger;
String get toInstruction { String get toInstruction {
switch (this) { switch (this) {
@ -16,6 +17,8 @@ enum SmartEditAction {
return 'Correct this to standard English:'; return 'Correct this to standard English:';
case SmartEditAction.improveWriting: case SmartEditAction.improveWriting:
return 'Rewrite this in your own words:'; return 'Rewrite this in your own words:';
case SmartEditAction.makeItLonger:
return 'Make this text longer:';
} }
} }
@ -27,6 +30,8 @@ enum SmartEditAction {
return 'Correct this to standard English:\n\n$input'; return 'Correct this to standard English:\n\n$input';
case SmartEditAction.improveWriting: case SmartEditAction.improveWriting:
return 'Rewrite this:\n\n$input'; return 'Rewrite this:\n\n$input';
case SmartEditAction.makeItLonger:
return 'Make this text longer:\n\n$input';
} }
} }
@ -38,6 +43,8 @@ enum SmartEditAction {
return SmartEditAction.fixSpelling; return SmartEditAction.fixSpelling;
case 2: case 2:
return SmartEditAction.improveWriting; return SmartEditAction.improveWriting;
case 3:
return SmartEditAction.makeItLonger;
} }
return SmartEditAction.fixSpelling; return SmartEditAction.fixSpelling;
} }
@ -50,6 +57,8 @@ enum SmartEditAction {
return LocaleKeys.document_plugins_smartEditFixSpelling.tr(); return LocaleKeys.document_plugins_smartEditFixSpelling.tr();
case SmartEditAction.improveWriting: case SmartEditAction.improveWriting:
return LocaleKeys.document_plugins_smartEditImproveWriting.tr(); return LocaleKeys.document_plugins_smartEditImproveWriting.tr();
case SmartEditAction.makeItLonger:
return LocaleKeys.document_plugins_smartEditMakeLonger.tr();
} }
} }
} }

View File

@ -257,15 +257,15 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
} }
Widget _buildResultWidget(BuildContext context) { Widget _buildResultWidget(BuildContext context) {
final loading = Padding( final loadingWidget = Padding(
padding: const EdgeInsets.symmetric(horizontal: 4.0), padding: const EdgeInsets.symmetric(horizontal: 4.0),
child: SizedBox.fromSize( child: SizedBox.fromSize(
size: const Size.square(14), size: const Size.square(14),
child: const CircularProgressIndicator(), child: const CircularProgressIndicator(),
), ),
); );
if (result.isEmpty) { if (result.isEmpty || loading) {
return loading; return loadingWidget;
} }
return Flexible( return Flexible(
child: Text( child: Text(
@ -277,6 +277,18 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
Widget _buildInputFooterWidget(BuildContext context) { Widget _buildInputFooterWidget(BuildContext context) {
return Row( return Row(
children: [ children: [
FlowyRichTextButton(
TextSpan(
children: [
TextSpan(
text: LocaleKeys.document_plugins_autoGeneratorRewrite.tr(),
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
onPressed: () => _requestCompletions(rewrite: true),
),
const Space(10, 0),
FlowyRichTextButton( FlowyRichTextButton(
TextSpan( TextSpan(
children: [ children: [
@ -402,7 +414,13 @@ class _SmartEditInputWidgetState extends State<SmartEditInputWidget> {
); );
} }
Future<void> _requestCompletions() async { Future<void> _requestCompletions({bool rewrite = false}) async {
if (rewrite) {
setState(() {
loading = true;
result = "";
});
}
final openAIRepository = await getIt.getAsync<OpenAIRepository>(); final openAIRepository = await getIt.getAsync<OpenAIRepository>();
var lines = content.split('\n\n'); var lines = content.split('\n\n');

View File

@ -1,5 +1,7 @@
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart'; import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
import 'package:appflowy/plugins/document/presentation/more/font_size_switcher.dart'; import 'package:appflowy/plugins/document/presentation/more/font_size_switcher.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart'; import 'package:flowy_infra/image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
@ -14,6 +16,7 @@ class DocumentMoreButton extends StatelessWidget {
return PopupMenuButton<int>( return PopupMenuButton<int>(
color: Theme.of(context).colorScheme.surfaceVariant, color: Theme.of(context).colorScheme.surfaceVariant,
offset: const Offset(0, 30), offset: const Offset(0, 30),
tooltip: LocaleKeys.moreAction_moreOptions.tr(),
itemBuilder: (context) { itemBuilder: (context) {
return [ return [
PopupMenuItem( PopupMenuItem(

View File

@ -0,0 +1,11 @@
import 'dart:ui';
class ColorGenerator {
Color generateColorFromString(String string) {
final hash = string.hashCode;
int r = (hash & 0xFF0000) >> 16;
int g = (hash & 0x00FF00) >> 8;
int b = hash & 0x0000FF;
return Color.fromRGBO(r, g, b, 0.5);
}
}

View File

@ -1,10 +1,10 @@
class HomeSizes { class HomeSizes {
static double get menuAddButtonHeight => 60; static const double menuAddButtonHeight = 60;
static double get topBarHeight => 60; static const double topBarHeight = 60;
static double get editPanelTopBarHeight => 60; static const double editPanelTopBarHeight = 60;
static double get editPanelWidth => 400; static const double editPanelWidth = 400;
} }
class HomeInsets { class HomeInsets {
static double get topBarTitlePadding => 12; static const double topBarTitlePadding = 12;
} }

View File

@ -170,23 +170,24 @@ class HomeStackManager {
return MultiProvider( return MultiProvider(
providers: [ChangeNotifierProvider.value(value: _notifier)], providers: [ChangeNotifierProvider.value(value: _notifier)],
child: Consumer( child: Consumer(
builder: (ctx, HomeStackNotifier notifier, child) { builder: (_, HomeStackNotifier notifier, __) {
return FadingIndexedStack( return FadingIndexedStack(
index: getIt<PluginSandbox>().indexOf(notifier.plugin.ty), index: getIt<PluginSandbox>().indexOf(notifier.plugin.ty),
children: children: getIt<PluginSandbox>().supportPluginTypes.map(
getIt<PluginSandbox>().supportPluginTypes.map((pluginType) { (pluginType) {
if (pluginType == notifier.plugin.ty) { if (pluginType == notifier.plugin.ty) {
final pluginWidget = notifier.plugin.display final pluginWidget = notifier.plugin.display
.buildWidget(PluginContext(onDeleted: onDeleted)); .buildWidget(PluginContext(onDeleted: onDeleted));
if (pluginType == PluginType.editor) { if (pluginType == PluginType.editor) {
return pluginWidget; return pluginWidget;
} else { }
return pluginWidget.padding(horizontal: 40, vertical: 28); return pluginWidget.padding(horizontal: 40, vertical: 28);
} }
} else {
return const BlankPage(); return const BlankPage();
} },
}).toList(), ).toList(),
); );
}, },
), ),
@ -204,6 +205,10 @@ class HomeTopBar extends StatelessWidget {
return Container( return Container(
color: Theme.of(context).colorScheme.onSecondaryContainer, color: Theme.of(context).colorScheme.onSecondaryContainer,
height: HomeSizes.topBarHeight, height: HomeSizes.topBarHeight,
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: HomeInsets.topBarTitlePadding,
),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -213,21 +218,14 @@ class HomeTopBar extends StatelessWidget {
ChangeNotifierProvider.value( ChangeNotifierProvider.value(
value: Provider.of<HomeStackNotifier>(context, listen: false), value: Provider.of<HomeStackNotifier>(context, listen: false),
child: Consumer( child: Consumer(
builder: ( builder: (_, HomeStackNotifier notifier, __) =>
BuildContext context, notifier.plugin.display.rightBarItem ??
HomeStackNotifier notifier, const SizedBox.shrink(),
Widget? child, ),
) {
return notifier.plugin.display.rightBarItem ?? const SizedBox();
},
), ),
) // _renderMoreButton(),
], ],
) ).bottomBorder(color: Theme.of(context).dividerColor),
.padding( ),
horizontal: HomeInsets.topBarTitlePadding,
)
.bottomBorder(color: Theme.of(context).dividerColor),
); );
} }
} }

View File

@ -1,4 +1,5 @@
import 'package:appflowy/startup/startup.dart'; import 'package:appflowy/startup/startup.dart';
import 'package:appflowy/util/color_generator/color_generator.dart';
import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart'; import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart'; import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart'; import 'package:appflowy/workspace/presentation/settings/widgets/settings_user_view.dart';
@ -45,8 +46,31 @@ class MenuUser extends StatelessWidget {
String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl; String iconUrl = context.read<MenuUserBloc>().state.userProfile.iconUrl;
if (iconUrl.isEmpty) { if (iconUrl.isEmpty) {
iconUrl = defaultUserAvatar; iconUrl = defaultUserAvatar;
final String name = context.read<MenuUserBloc>().state.userProfile.name;
final Color color = ColorGenerator().generateColorFromString(name);
const initialsCount = 2;
// Taking the first letters of the name components and limiting to 2 elements
final nameInitials = name
.split(' ')
.where((element) => element.isNotEmpty)
.take(initialsCount)
.map((element) => element[0].toUpperCase())
.join('');
return Container(
width: 28,
height: 28,
alignment: Alignment.center,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
child: FlowyText.semibold(
nameInitials,
color: Colors.white,
fontSize: nameInitials.length == initialsCount ? 12 : 14,
),
);
} }
return SizedBox( return SizedBox(
width: 25, width: 25,
height: 25, height: 25,

View File

@ -17,12 +17,13 @@ class ViewLeftBarItem extends StatefulWidget {
class _ViewLeftBarItemState extends State<ViewLeftBarItem> { class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
final _controller = TextEditingController(); final _controller = TextEditingController();
final _focusNode = FocusNode(); final _focusNode = FocusNode();
late ViewService _viewService; late final ViewService _viewService;
late ViewListener _viewListener; late final ViewListener _viewListener;
late ViewPB view; late ViewPB view;
@override @override
void initState() { void initState() {
super.initState();
view = widget.view; view = widget.view;
_viewService = ViewService(); _viewService = ViewService();
_focusNode.addListener(_handleFocusChanged); _focusNode.addListener(_handleFocusChanged);
@ -39,7 +40,8 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
); );
}, },
); );
super.initState();
_controller.text = view.name;
} }
@override @override
@ -53,11 +55,8 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
_controller.text = view.name; return GestureDetector(
return IntrinsicWidth(
key: ValueKey(_controller.text), key: ValueKey(_controller.text),
child: GestureDetector(
onDoubleTap: () { onDoubleTap: () {
_controller.selection = TextSelection( _controller.selection = TextSelection(
baseOffset: 0, baseOffset: 0,
@ -74,9 +73,6 @@ class _ViewLeftBarItemState extends State<ViewLeftBarItem> {
isDense: true, isDense: true,
), ),
style: Theme.of(context).textTheme.bodyMedium, style: Theme.of(context).textTheme.bodyMedium,
// cursorColor: widget.cursorColor,
// obscureText: widget.enableObscure,
),
), ),
); );
} }

View File

@ -38,7 +38,7 @@ String languageFromLocale(Locale locale) {
case "hu": case "hu":
return "Magyar"; return "Magyar";
case "id": case "id":
return "Bahasa"; return "Bahasa Indonesia";
case "it": case "it":
return "Italiano"; return "Italiano";
case "ja": case "ja":

View File

@ -3,7 +3,7 @@ export 'package:styled_widget/styled_widget.dart';
extension FlowyStyledWidget on Widget { extension FlowyStyledWidget on Widget {
Widget bottomBorder({double width = 1.0, Color color = Colors.grey}) { Widget bottomBorder({double width = 1.0, Color color = Colors.grey}) {
return Container( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
bottom: BorderSide(width: width, color: color), bottom: BorderSide(width: width, color: color),
@ -14,7 +14,7 @@ extension FlowyStyledWidget on Widget {
} }
Widget topBorder({double width = 1.0, Color color = Colors.grey}) { Widget topBorder({double width = 1.0, Color color = Colors.grey}) {
return Container( return DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide(width: width, color: color), top: BorderSide(width: width, color: color),

View File

@ -16,6 +16,7 @@ class FlowyIconButton extends StatelessWidget {
final EdgeInsets iconPadding; final EdgeInsets iconPadding;
final BorderRadius? radius; final BorderRadius? radius;
final String? tooltipText; final String? tooltipText;
final InlineSpan? richTooltipText;
final bool preferBelow; final bool preferBelow;
const FlowyIconButton({ const FlowyIconButton({
@ -29,15 +30,22 @@ class FlowyIconButton extends StatelessWidget {
this.iconPadding = EdgeInsets.zero, this.iconPadding = EdgeInsets.zero,
this.radius, this.radius,
this.tooltipText, this.tooltipText,
this.richTooltipText,
this.preferBelow = true, this.preferBelow = true,
required this.icon, required this.icon,
}) : super(key: key); }) : assert((richTooltipText != null && tooltipText == null) ||
(richTooltipText == null && tooltipText != null) ||
(richTooltipText == null && tooltipText == null)),
super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget child = icon; Widget child = icon;
final size = Size(width, height ?? width); final size = Size(width, height ?? width);
final tooltipMessage =
tooltipText == null && richTooltipText == null ? '' : tooltipText;
assert(size.width > iconPadding.horizontal); assert(size.width > iconPadding.horizontal);
assert(size.height > iconPadding.vertical); assert(size.height > iconPadding.vertical);
@ -46,11 +54,14 @@ class FlowyIconButton extends StatelessWidget {
final childSize = Size(childWidth, childWidth); final childSize = Size(childWidth, childWidth);
return ConstrainedBox( return ConstrainedBox(
constraints: constraints: BoxConstraints.tightFor(
BoxConstraints.tightFor(width: size.width, height: size.height), width: size.width,
height: size.height,
),
child: Tooltip( child: Tooltip(
preferBelow: preferBelow, preferBelow: preferBelow,
message: tooltipText ?? '', message: tooltipMessage,
richMessage: richTooltipText,
showDuration: Duration.zero, showDuration: Duration.zero,
child: RawMaterialButton( child: RawMaterialButton(
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,

View File

@ -13,9 +13,11 @@ void main() {
group('Edit Grid:', () { group('Edit Grid:', () {
late GridTestContext context; late GridTestContext context;
setUp(() async { setUp(() async {
context = await gridTest.createTestGrid(); context = await gridTest.createTestGrid();
}); });
// The initial number of rows is 3 for each grid. // The initial number of rows is 3 for each grid.
blocTest<GridBloc, GridState>( blocTest<GridBloc, GridState>(
"create a row", "create a row",
@ -54,5 +56,34 @@ void main() {
); );
}, },
); );
String? firstId;
String? secondId;
String? thirdId;
blocTest(
'reorder rows',
build: () => GridBloc(
view: context.gridView,
databaseController: DatabaseController(
view: context.gridView,
layoutType: DatabaseLayoutPB.Grid,
),
)..add(const GridEvent.initial()),
act: (bloc) async {
await gridResponseFuture();
firstId = bloc.state.rowInfos[0].rowPB.id;
secondId = bloc.state.rowInfos[1].rowPB.id;
thirdId = bloc.state.rowInfos[2].rowPB.id;
bloc.add(const GridEvent.moveRow(0, 2));
},
verify: (bloc) {
expect(secondId, bloc.state.rowInfos[0].rowPB.id);
expect(thirdId, bloc.state.rowInfos[1].rowPB.id);
expect(firstId, bloc.state.rowInfos[2].rowPB.id);
},
);
}); });
} }

View File

@ -159,7 +159,7 @@ mod tests {
date: Some("1653609600".to_owned()), date: Some("1653609600".to_owned()),
time: Some("9:00 AM".to_owned()), time: Some("9:00 AM".to_owned()),
include_time: Some(true), include_time: Some(true),
timezone_id: None, timezone_id: Some("Etc/UTC".to_owned()),
}, },
None, None,
"May 27, 2022 09:00 AM", "May 27, 2022 09:00 AM",
@ -213,7 +213,7 @@ mod tests {
date: Some("1653609600".to_owned()), date: Some("1653609600".to_owned()),
time: Some("1:".to_owned()), time: Some("1:".to_owned()),
include_time: Some(true), include_time: Some(true),
timezone_id: None, timezone_id: Some("Etc/UTC".to_owned()),
}, },
None, None,
"May 27, 2022 01:00", "May 27, 2022 01:00",
@ -252,7 +252,7 @@ mod tests {
date: Some("1653609600".to_owned()), date: Some("1653609600".to_owned()),
time: Some("00:00".to_owned()), time: Some("00:00".to_owned()),
include_time: Some(true), include_time: Some(true),
timezone_id: None, timezone_id: Some("Etc/UTC".to_owned()),
}, },
None, None,
"May 27, 2022 00:00", "May 27, 2022 00:00",
@ -273,7 +273,7 @@ mod tests {
date: Some("1653609600".to_owned()), date: Some("1653609600".to_owned()),
time: Some("1:00 am".to_owned()), time: Some("1:00 am".to_owned()),
include_time: Some(true), include_time: Some(true),
timezone_id: None, timezone_id: Some("Etc/UTC".to_owned()),
}, },
None, None,
"May 27, 2022 01:00 AM", "May 27, 2022 01:00 AM",
@ -372,7 +372,7 @@ mod tests {
date: Some("1700006400".to_owned()), date: Some("1700006400".to_owned()),
time: Some("08:00".to_owned()), time: Some("08:00".to_owned()),
include_time: Some(true), include_time: Some(true),
timezone_id: None, timezone_id: Some("Etc/UTC".to_owned()),
}, },
); );
assert_date( assert_date(
@ -382,7 +382,7 @@ mod tests {
date: None, date: None,
time: Some("14:00".to_owned()), time: Some("14:00".to_owned()),
include_time: None, include_time: None,
timezone_id: None, timezone_id: Some("Etc/UTC".to_owned()),
}, },
Some(old_cell_data), Some(old_cell_data),
"Nov 15, 2023 14:00", "Nov 15, 2023 14:00",

View File

@ -1,29 +1,17 @@
// @ts-check
/**
* @type { import("@inlang/core/config").DefineConfig }
*/
export async function defineConfig(env) { export async function defineConfig(env) {
const plugin = await env.$import( const { default: pluginJson } = await env.$import(
"https://cdn.jsdelivr.net/gh/samuelstroschein/inlang-plugin-json@1.1.1/dist/index.js" 'https://cdn.jsdelivr.net/gh/samuelstroschein/inlang-plugin-json@2/dist/index.js'
); );
const { default: standardLintRules } = await env.$import( const { default: standardLintRules } = await env.$import(
"https://cdn.jsdelivr.net/gh/inlang/standard-lint-rules@1.1.1/dist/index.js" 'https://cdn.jsdelivr.net/gh/inlang/standard-lint-rules@2/dist/index.js'
); );
const pluginConfig = {
pathPattern: "./frontend/appflowy_flutter/assets/translations/{language}.json",
};
return { return {
referenceLanguage: "en", referenceLanguage: 'en',
languages: await plugin.getLanguages({ ...env, pluginConfig }), plugins: [pluginJson({
readResources: (args) => plugin.readResources({ ...args, ...env, pluginConfig }), pathPattern: './frontend/appflowy_flutter/assets/translations/{language}.json',
writeResources: (args) => plugin.writeResources({ ...args, ...env, pluginConfig }), variableReferencePattern: ["@:"]
lint: { }), standardLintRules()]
rules: [standardLintRules()],
},
}; };
} }