mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge remote-tracking branch 'origin/main' into tekdel/main
# Conflicts: # frontend/app_flowy/packages/appflowy_editor/test/infra/test_raw_key_event.dart
This commit is contained in:
commit
e9c0956c51
@ -1,4 +1,4 @@
|
||||
name: FlowyEditor test
|
||||
name: AppFlowyEditor test
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -37,6 +37,8 @@ jobs:
|
||||
working-directory: frontend/app_flowy/packages/appflowy_editor
|
||||
run: |
|
||||
flutter pub get
|
||||
flutter format --set-exit-if-changed .
|
||||
flutter analyze .
|
||||
flutter test --coverage
|
||||
|
||||
- uses: codecov/codecov-action@v3
|
@ -94,7 +94,20 @@
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "Passer en mode clair",
|
||||
"darkMode": "Passer en mode sombre"
|
||||
"darkMode": "Passer en mode sombre",
|
||||
"openAsPage": "Ouvrir en tant que page",
|
||||
"addNewRow": "Ajouter une ligne",
|
||||
"openMenu": "Cliquer pour ouvrir le menu"
|
||||
},
|
||||
"sideBar": {
|
||||
"closeSidebar": "Fermer le menu latéral",
|
||||
"openSidebar": "Ouvrir le menu latéral"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "Note exportée en Markdown",
|
||||
"path": "Documents/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "Contacts",
|
||||
@ -123,7 +136,7 @@
|
||||
"failedMsg": "Assurez-vous d'avoir terminé le processus de connexion dans votre navigateur."
|
||||
},
|
||||
"google": {
|
||||
"title": "CONNEXION GOOGLE",
|
||||
"title": "CONNEXION VIA GOOGLE",
|
||||
"instruction1": "Pour importer vos contacts Google, vous devez autoriser cette application à l'aide de votre navigateur web.",
|
||||
"instruction2": "Copiez ce code dans votre presse-papiers en cliquant sur l'icône ou en sélectionnant le texte:",
|
||||
"instruction3": "Accédez au lien suivant dans votre navigateur web et saisissez le code ci-dessus:",
|
||||
@ -135,6 +148,7 @@
|
||||
"menu": {
|
||||
"appearance": "Apparence",
|
||||
"language": "Langue",
|
||||
"user": "Utilisateur",
|
||||
"open": "Ouvrir les paramètres"
|
||||
},
|
||||
"appearance": {
|
||||
@ -142,15 +156,12 @@
|
||||
"darkLabel": "Mode sombre"
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "Filtrer",
|
||||
"sortBy": "Filtrer par",
|
||||
"Properties": "Propriétés"
|
||||
"Properties": "Propriétés",
|
||||
"group": "Groupe"
|
||||
},
|
||||
"field": {
|
||||
"hide": "Cacher",
|
||||
@ -179,13 +190,17 @@
|
||||
"addSelectOption": "Ajouter une option",
|
||||
"optionTitle": "Options",
|
||||
"addOption": "Ajouter une option",
|
||||
"editProperty": "Modifier la propriété"
|
||||
"editProperty": "Modifier la propriété",
|
||||
"newColumn": "Nouvelle colonne",
|
||||
"deleteFieldPromptMessage": "Vous voulez supprimer cette propriété ?"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "Dupliquer",
|
||||
"delete": "Supprimer",
|
||||
"textPlaceholder": "Vide",
|
||||
"copyProperty": "Copie de la propriété dans le presse-papiers"
|
||||
"copyProperty": "Copie de la propriété dans le presse-papiers",
|
||||
"count": "Nombre",
|
||||
"newRow": "Nouvelle ligne"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Créer",
|
||||
@ -211,5 +226,10 @@
|
||||
"timeHintTextInTwelveHour": "01:00 PM",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "Nouveau"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -45,5 +45,7 @@
|
||||
<array>
|
||||
<string>en</string>
|
||||
</array>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
@ -89,18 +89,30 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
didCreateRow: (String groupId, RowPB row, int? index) {
|
||||
didCreateRow: (group, row, int? index) {
|
||||
emit(state.copyWith(
|
||||
editingRow: Some(BoardEditingRow(
|
||||
columnId: groupId,
|
||||
group: group,
|
||||
row: row,
|
||||
index: index,
|
||||
)),
|
||||
));
|
||||
_groupItemStartEditing(group, row, true);
|
||||
},
|
||||
endEditRow: (rowId) {
|
||||
startEditingRow: (group, row) {
|
||||
emit(state.copyWith(
|
||||
editingRow: Some(BoardEditingRow(
|
||||
group: group,
|
||||
row: row,
|
||||
index: null,
|
||||
)),
|
||||
));
|
||||
_groupItemStartEditing(group, row, true);
|
||||
},
|
||||
endEditingRow: (rowId) {
|
||||
state.editingRow.fold(() => null, (editingRow) {
|
||||
assert(editingRow.row.id == rowId);
|
||||
_groupItemStartEditing(editingRow.group, editingRow.row, false);
|
||||
emit(state.copyWith(editingRow: none()));
|
||||
});
|
||||
},
|
||||
@ -122,6 +134,24 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
void _groupItemStartEditing(GroupPB group, RowPB row, bool isEdit) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
if (fieldContext == null) {
|
||||
Log.warn("FieldContext should not be null");
|
||||
return;
|
||||
}
|
||||
|
||||
boardController.enableGroupDragging(!isEdit);
|
||||
// boardController.updateGroupItem(
|
||||
// group.groupId,
|
||||
// GroupItem(
|
||||
// row: row,
|
||||
// fieldContext: fieldContext,
|
||||
// isDraggable: !isEdit,
|
||||
// ),
|
||||
// );
|
||||
}
|
||||
|
||||
void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
|
||||
if (fromRow != null) {
|
||||
_rowService
|
||||
@ -136,11 +166,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
}
|
||||
}
|
||||
|
||||
void _moveGroup(String fromColumnId, String toColumnId) {
|
||||
void _moveGroup(String fromGroupId, String toGroupId) {
|
||||
_rowService
|
||||
.moveGroup(
|
||||
fromGroupId: fromColumnId,
|
||||
toGroupId: toColumnId,
|
||||
fromGroupId: fromGroupId,
|
||||
toGroupId: toGroupId,
|
||||
)
|
||||
.then((result) {
|
||||
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
|
||||
@ -156,7 +186,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
return super.close();
|
||||
}
|
||||
|
||||
void initializeGroups(List<GroupPB> groups) {
|
||||
void initializeGroups(List<GroupPB> groupsData) {
|
||||
for (var controller in groupControllers.values) {
|
||||
controller.dispose();
|
||||
}
|
||||
@ -164,27 +194,27 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
boardController.clear();
|
||||
|
||||
//
|
||||
List<AppFlowyGroupData> columns = groups
|
||||
List<AppFlowyGroupData> groups = groupsData
|
||||
.where((group) => fieldController.getField(group.fieldId) != null)
|
||||
.map((group) {
|
||||
return AppFlowyGroupData(
|
||||
id: group.groupId,
|
||||
name: group.desc,
|
||||
items: _buildRows(group),
|
||||
customData: BoardCustomData(
|
||||
items: _buildGroupItems(group),
|
||||
customData: GroupData(
|
||||
group: group,
|
||||
fieldContext: fieldController.getField(group.fieldId)!,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
boardController.addGroups(columns);
|
||||
boardController.addGroups(groups);
|
||||
|
||||
for (final group in groups) {
|
||||
for (final group in groupsData) {
|
||||
final delegate = GroupControllerDelegateImpl(
|
||||
controller: boardController,
|
||||
fieldController: fieldController,
|
||||
onNewColumnItem: (groupId, row, index) {
|
||||
add(BoardEvent.didCreateRow(groupId, row, index));
|
||||
add(BoardEvent.didCreateRow(group, row, index));
|
||||
},
|
||||
);
|
||||
final controller = GroupController(
|
||||
@ -242,10 +272,13 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
);
|
||||
}
|
||||
|
||||
List<AppFlowyGroupItem> _buildRows(GroupPB group) {
|
||||
List<AppFlowyGroupItem> _buildGroupItems(GroupPB group) {
|
||||
final items = group.rows.map((row) {
|
||||
final fieldContext = fieldController.getField(group.fieldId);
|
||||
return BoardColumnItem(row: row, fieldContext: fieldContext!);
|
||||
return GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext!,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return <AppFlowyGroupItem>[...items];
|
||||
@ -270,11 +303,15 @@ class BoardEvent with _$BoardEvent {
|
||||
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
||||
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
||||
const factory BoardEvent.didCreateRow(
|
||||
String groupId,
|
||||
GroupPB group,
|
||||
RowPB row,
|
||||
int? index,
|
||||
) = _DidCreateRow;
|
||||
const factory BoardEvent.endEditRow(String rowId) = _EndEditRow;
|
||||
const factory BoardEvent.startEditingRow(
|
||||
GroupPB group,
|
||||
RowPB row,
|
||||
) = _StartEditRow;
|
||||
const factory BoardEvent.endEditingRow(String rowId) = _EndEditRow;
|
||||
const factory BoardEvent.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||
const factory BoardEvent.didReceiveGridUpdate(
|
||||
GridPB grid,
|
||||
@ -334,14 +371,17 @@ class GridFieldEquatable extends Equatable {
|
||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||
}
|
||||
|
||||
class BoardColumnItem extends AppFlowyGroupItem {
|
||||
class GroupItem extends AppFlowyGroupItem {
|
||||
final RowPB row;
|
||||
final GridFieldContext fieldContext;
|
||||
|
||||
BoardColumnItem({
|
||||
GroupItem({
|
||||
required this.row,
|
||||
required this.fieldContext,
|
||||
});
|
||||
bool draggable = true,
|
||||
}) {
|
||||
super.draggable = draggable;
|
||||
}
|
||||
|
||||
@override
|
||||
String get id => row.id;
|
||||
@ -367,10 +407,16 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
}
|
||||
|
||||
if (index != null) {
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
);
|
||||
controller.insertGroupItem(group.groupId, index, item);
|
||||
} else {
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
);
|
||||
controller.addGroupItem(group.groupId, item);
|
||||
}
|
||||
}
|
||||
@ -389,7 +435,10 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
}
|
||||
controller.updateGroupItem(
|
||||
group.groupId,
|
||||
BoardColumnItem(row: row, fieldContext: fieldContext),
|
||||
GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -400,7 +449,11 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
Log.warn("FieldContext should not be null");
|
||||
return;
|
||||
}
|
||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
||||
final item = GroupItem(
|
||||
row: row,
|
||||
fieldContext: fieldContext,
|
||||
draggable: false,
|
||||
);
|
||||
|
||||
if (index != null) {
|
||||
controller.insertGroupItem(group.groupId, index, item);
|
||||
@ -412,21 +465,21 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
||||
}
|
||||
|
||||
class BoardEditingRow {
|
||||
String columnId;
|
||||
GroupPB group;
|
||||
RowPB row;
|
||||
int? index;
|
||||
|
||||
BoardEditingRow({
|
||||
required this.columnId,
|
||||
required this.group,
|
||||
required this.row,
|
||||
required this.index,
|
||||
});
|
||||
}
|
||||
|
||||
class BoardCustomData {
|
||||
class GroupData {
|
||||
final GroupPB group;
|
||||
final GridFieldContext fieldContext;
|
||||
BoardCustomData({
|
||||
GroupData({
|
||||
required this.group,
|
||||
required this.fieldContext,
|
||||
});
|
||||
|
@ -87,13 +87,13 @@ class BoardDataController {
|
||||
onUpdatedGroup.call(changeset.updateGroups);
|
||||
}
|
||||
|
||||
if (changeset.insertedGroups.isNotEmpty) {
|
||||
onInsertedGroup.call(changeset.insertedGroups);
|
||||
}
|
||||
|
||||
if (changeset.deletedGroups.isNotEmpty) {
|
||||
onDeletedGroup.call(changeset.deletedGroups);
|
||||
}
|
||||
|
||||
if (changeset.insertedGroups.isNotEmpty) {
|
||||
onInsertedGroup.call(changeset.insertedGroups);
|
||||
}
|
||||
},
|
||||
(e) => _onError?.call(e),
|
||||
);
|
||||
|
@ -83,7 +83,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocListener<BoardBloc, BoardState>(
|
||||
listener: (context, state) => _handleEditState(state, context),
|
||||
listener: (context, state) => _handleEditStateChanged(state, context),
|
||||
child: BlocBuilder<BoardBloc, BoardState>(
|
||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||
builder: (context, state) {
|
||||
@ -128,21 +128,14 @@ class _BoardContentState extends State<BoardContent> {
|
||||
);
|
||||
}
|
||||
|
||||
void _handleEditState(BoardState state, BuildContext context) {
|
||||
void _handleEditStateChanged(BoardState state, BuildContext context) {
|
||||
state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (editingRow.index != null) {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
} else {
|
||||
scrollManager.scrollToBottom(editingRow.columnId, (boardContext) {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
||||
});
|
||||
scrollManager.scrollToBottom(editingRow.group.groupId);
|
||||
}
|
||||
});
|
||||
},
|
||||
@ -156,14 +149,14 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
Widget _buildHeader(
|
||||
BuildContext context,
|
||||
AppFlowyGroupData columnData,
|
||||
AppFlowyGroupData groupData,
|
||||
) {
|
||||
final boardCustomData = columnData.customData as BoardCustomData;
|
||||
final boardCustomData = groupData.customData as GroupData;
|
||||
return AppFlowyGroupHeader(
|
||||
title: Flexible(
|
||||
fit: FlexFit.tight,
|
||||
child: FlowyText.medium(
|
||||
columnData.headerData.groupName,
|
||||
groupData.headerData.groupName,
|
||||
fontSize: 14,
|
||||
overflow: TextOverflow.clip,
|
||||
color: context.read<AppTheme>().textColor,
|
||||
@ -180,7 +173,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
),
|
||||
onAddButtonClick: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.createHeaderRow(columnData.id),
|
||||
BoardEvent.createHeaderRow(groupData.id),
|
||||
);
|
||||
},
|
||||
height: 50,
|
||||
@ -218,15 +211,16 @@ class _BoardContentState extends State<BoardContent> {
|
||||
|
||||
Widget _buildCard(
|
||||
BuildContext context,
|
||||
AppFlowyGroupData group,
|
||||
AppFlowyGroupItem columnItem,
|
||||
AppFlowyGroupData afGroupData,
|
||||
AppFlowyGroupItem afGroupItem,
|
||||
) {
|
||||
final boardColumnItem = columnItem as BoardColumnItem;
|
||||
final rowPB = boardColumnItem.row;
|
||||
final groupItem = afGroupItem as GroupItem;
|
||||
final groupData = afGroupData.customData as GroupData;
|
||||
final rowPB = groupItem.row;
|
||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||
|
||||
/// Return placeholder widget if the rowCache is null.
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(columnItem));
|
||||
if (rowCache == null) return SizedBox(key: ObjectKey(groupItem));
|
||||
|
||||
final fieldController = context.read<BoardBloc>().fieldController;
|
||||
final gridId = context.read<BoardBloc>().gridId;
|
||||
@ -241,19 +235,19 @@ class _BoardContentState extends State<BoardContent> {
|
||||
context.read<BoardBloc>().state.editingRow.fold(
|
||||
() => null,
|
||||
(editingRow) {
|
||||
isEditing = editingRow.row.id == columnItem.row.id;
|
||||
isEditing = editingRow.row.id == groupItem.row.id;
|
||||
},
|
||||
);
|
||||
|
||||
final groupItemId = columnItem.id + group.id;
|
||||
final groupItemId = groupItem.row.id + groupData.group.groupId;
|
||||
return AppFlowyGroupCard(
|
||||
key: ValueKey(groupItemId),
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: BoardCard(
|
||||
gridId: gridId,
|
||||
groupId: group.id,
|
||||
fieldId: boardColumnItem.fieldContext.id,
|
||||
groupId: groupData.group.groupId,
|
||||
fieldId: groupItem.fieldContext.id,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
dataController: cardController,
|
||||
@ -264,6 +258,19 @@ class _BoardContentState extends State<BoardContent> {
|
||||
rowCache,
|
||||
context,
|
||||
),
|
||||
onStartEditing: () {
|
||||
context.read<BoardBloc>().add(
|
||||
BoardEvent.startEditingRow(
|
||||
groupData.group,
|
||||
groupItem.row,
|
||||
),
|
||||
);
|
||||
},
|
||||
onEndEditing: () {
|
||||
context
|
||||
.read<BoardBloc>()
|
||||
.add(BoardEvent.endEditingRow(groupItem.row.id));
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -345,7 +352,7 @@ extension HexColor on Color {
|
||||
}
|
||||
}
|
||||
|
||||
Widget? _buildHeaderIcon(BoardCustomData customData) {
|
||||
Widget? _buildHeaderIcon(GroupData customData) {
|
||||
Widget? widget;
|
||||
switch (customData.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
|
@ -76,6 +76,10 @@ class EditableRowNotifier {
|
||||
}
|
||||
|
||||
abstract class EditableCell {
|
||||
// Each cell notifier will be bind to the [EditableRowNotifier], which enable
|
||||
// the row notifier receive its cells event. For example: begin editing the
|
||||
// cell or end editing the cell.
|
||||
//
|
||||
EditableCellNotifier? get editableNotifier;
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,9 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
|
||||
// If the focusNode lost its focus, the widget's editableNotifier will
|
||||
// set to false, which will cause the [EditableRowNotifier] to receive
|
||||
// end edit event.
|
||||
focusNode.addListener(() {
|
||||
if (!focusNode.hasFocus) {
|
||||
focusWhenInit = false;
|
||||
@ -131,7 +134,11 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
||||
padding: EdgeInsets.symmetric(
|
||||
vertical: BoardSizes.cardCellVPadding,
|
||||
),
|
||||
child: FlowyText.medium(state.content, fontSize: 14),
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
fontSize: 14,
|
||||
maxLines: null, // Enable multiple lines
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -21,6 +21,8 @@ class BoardCard extends StatefulWidget {
|
||||
final CardDataController dataController;
|
||||
final BoardCellBuilder cellBuilder;
|
||||
final void Function(BuildContext) openCard;
|
||||
final VoidCallback onStartEditing;
|
||||
final VoidCallback onEndEditing;
|
||||
|
||||
const BoardCard({
|
||||
required this.gridId,
|
||||
@ -30,6 +32,8 @@ class BoardCard extends StatefulWidget {
|
||||
required this.dataController,
|
||||
required this.cellBuilder,
|
||||
required this.openCard,
|
||||
required this.onStartEditing,
|
||||
required this.onEndEditing,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -56,6 +60,12 @@ class _BoardCardState extends State<BoardCard> {
|
||||
rowNotifier.isEditing.addListener(() {
|
||||
if (!mounted) return;
|
||||
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
||||
|
||||
if (rowNotifier.isEditing.value) {
|
||||
widget.onStartEditing();
|
||||
} else {
|
||||
widget.onEndEditing();
|
||||
}
|
||||
});
|
||||
|
||||
popoverController = PopoverController();
|
||||
|
@ -15,6 +15,7 @@ import 'package:clipboard/clipboard.dart';
|
||||
import 'package:dartz/dartz.dart' as dartz;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
@ -112,7 +113,6 @@ class DocumentShareButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
double buttonWidth = 60;
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<DocShareBloc>(param1: view),
|
||||
child: BlocListener<DocShareBloc, DocShareState>(
|
||||
@ -130,6 +130,7 @@ class DocumentShareButton extends StatelessWidget {
|
||||
},
|
||||
child: BlocBuilder<DocShareBloc, DocShareState>(
|
||||
builder: (context, state) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return ChangeNotifierProvider.value(
|
||||
value: Provider.of<AppearanceSetting>(context, listen: true),
|
||||
child: Selector<AppearanceSetting, Locale>(
|
||||
@ -137,16 +138,15 @@ class DocumentShareButton extends StatelessWidget {
|
||||
builder: (ctx, _, child) => ConstrainedBox(
|
||||
constraints: const BoxConstraints.expand(
|
||||
height: 30,
|
||||
// minWidth: buttonWidth,
|
||||
width: 100,
|
||||
),
|
||||
child: RoundedTextButton(
|
||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||
fontSize: 12,
|
||||
borderRadius: Corners.s6Border,
|
||||
color: Colors.lightBlue,
|
||||
onPressed: () => _showActionList(
|
||||
context, Offset(-(buttonWidth / 2), 10)),
|
||||
color: theme.main1,
|
||||
onPressed: () =>
|
||||
_showActionList(context, const Offset(0, 10)),
|
||||
),
|
||||
),
|
||||
),
|
||||
@ -193,7 +193,7 @@ class DocumentShareButton extends StatelessWidget {
|
||||
});
|
||||
actionList.show(
|
||||
context,
|
||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||
anchorDirection: AnchorDirection.bottomWithRightAligned,
|
||||
anchorOffset: offset,
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@ -157,6 +159,7 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
focusedDay: state.focusedDay,
|
||||
rowHeight: 40,
|
||||
calendarFormat: state.format,
|
||||
daysOfWeekHeight: 40,
|
||||
headerStyle: HeaderStyle(
|
||||
formatButtonVisible: false,
|
||||
titleCentered: true,
|
||||
@ -166,15 +169,46 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
||||
rightChevronPadding: EdgeInsets.zero,
|
||||
rightChevronMargin: EdgeInsets.zero,
|
||||
rightChevronIcon: svgWidget("home/arrow_right"),
|
||||
headerMargin: const EdgeInsets.only(bottom: 8.0),
|
||||
),
|
||||
daysOfWeekStyle: DaysOfWeekStyle(
|
||||
dowTextFormatter: (date, locale) =>
|
||||
DateFormat.E(locale).format(date).toUpperCase(),
|
||||
weekdayStyle: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.shader3,
|
||||
),
|
||||
weekendStyle: TextStyle(
|
||||
fontSize: 13,
|
||||
color: theme.shader3,
|
||||
),
|
||||
),
|
||||
calendarStyle: CalendarStyle(
|
||||
cellMargin: const EdgeInsets.all(3),
|
||||
defaultDecoration: BoxDecoration(
|
||||
color: theme.surface,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
selectedDecoration: BoxDecoration(
|
||||
color: theme.main1,
|
||||
shape: BoxShape.circle,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
todayDecoration: BoxDecoration(
|
||||
color: theme.shader4,
|
||||
shape: BoxShape.circle,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
weekendDecoration: BoxDecoration(
|
||||
color: theme.surface,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
outsideDecoration: BoxDecoration(
|
||||
color: theme.surface,
|
||||
shape: BoxShape.rectangle,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||
),
|
||||
selectedTextStyle: TextStyle(
|
||||
color: theme.surface,
|
||||
@ -230,11 +264,13 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(),
|
||||
fontSize: 14),
|
||||
const Spacer(),
|
||||
Switch(
|
||||
Toggle(
|
||||
value: includeTime,
|
||||
onChanged: (newValue) => context
|
||||
onChanged: (value) => context
|
||||
.read<DateCalBloc>()
|
||||
.add(DateCalEvent.setIncludeTime(newValue)),
|
||||
.add(DateCalEvent.setIncludeTime(!value)),
|
||||
style: ToggleStyle.big(theme),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
@ -350,7 +386,7 @@ class _DateTypeOptionButton extends StatelessWidget {
|
||||
offset: const Offset(20, 0),
|
||||
constraints: BoxConstraints.loose(const Size(140, 100)),
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(title, fontSize: 12),
|
||||
text: FlowyText.medium(title, fontSize: 14),
|
||||
hoverColor: theme.hover,
|
||||
margin: kMargin,
|
||||
rightIcon: svgWidget("grid/more", color: theme.iconColor),
|
||||
|
@ -130,14 +130,10 @@ class SelectOptionTagCell extends StatelessWidget {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
flex: 2,
|
||||
child: SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
onSelected: () => onSelected(option),
|
||||
),
|
||||
SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
onSelected: () => onSelected(option),
|
||||
),
|
||||
const Spacer(),
|
||||
...children,
|
||||
|
@ -142,11 +142,12 @@ class _TextField extends StatelessWidget {
|
||||
value: (option) => option);
|
||||
|
||||
return SizedBox(
|
||||
height: 42,
|
||||
height: 62,
|
||||
child: SelectOptionTextField(
|
||||
options: state.options,
|
||||
selectedOptionMap: optionMap,
|
||||
distanceToText: _editorPanelWidth * 0.7,
|
||||
maxLength: 30,
|
||||
tagController: _tagController,
|
||||
onClick: () => popoverMutex.close(),
|
||||
newText: (text) {
|
||||
@ -248,32 +249,25 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
|
||||
mutex: widget.popoverMutex,
|
||||
child: SizedBox(
|
||||
height: GridSize.typeOptionItemHeight,
|
||||
child: Row(
|
||||
child: SelectOptionTagCell(
|
||||
option: widget.option,
|
||||
onSelected: (option) {
|
||||
context
|
||||
.read<SelectOptionCellEditorBloc>()
|
||||
.add(SelectOptionEditorEvent.selectOption(option.id));
|
||||
},
|
||||
children: [
|
||||
Flexible(
|
||||
fit: FlexFit.loose,
|
||||
child: SelectOptionTagCell(
|
||||
option: widget.option,
|
||||
onSelected: (option) {
|
||||
context
|
||||
.read<SelectOptionCellEditorBloc>()
|
||||
.add(SelectOptionEditorEvent.selectOption(option.id));
|
||||
},
|
||||
children: [
|
||||
if (widget.isSelected)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: svgWidget("grid/checkmark"),
|
||||
),
|
||||
],
|
||||
if (widget.isSelected)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 6),
|
||||
child: svgWidget("grid/checkmark"),
|
||||
),
|
||||
),
|
||||
FlowyIconButton(
|
||||
width: 30,
|
||||
onPressed: () => _popoverController.show(),
|
||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||
icon: svgWidget("editor/details", color: theme.iconColor),
|
||||
)
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -6,6 +6,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/select_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:textfield_tags/textfield_tags.dart';
|
||||
|
||||
@ -20,6 +21,7 @@ class SelectOptionTextField extends StatefulWidget {
|
||||
final Function(String) onSubmitted;
|
||||
final Function(String) newText;
|
||||
final VoidCallback? onClick;
|
||||
final int? maxLength;
|
||||
|
||||
const SelectOptionTextField({
|
||||
required this.options,
|
||||
@ -29,6 +31,7 @@ class SelectOptionTextField extends StatefulWidget {
|
||||
required this.onSubmitted,
|
||||
required this.newText,
|
||||
this.onClick,
|
||||
this.maxLength,
|
||||
TextEditingController? textController,
|
||||
FocusNode? focusNode,
|
||||
Key? key,
|
||||
@ -93,6 +96,9 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
||||
}
|
||||
},
|
||||
maxLines: 1,
|
||||
maxLength: widget.maxLength,
|
||||
maxLengthEnforcement:
|
||||
MaxLengthEnforcement.truncateAfterCompositionEnds,
|
||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: OutlineInputBorder(
|
||||
|
@ -9,6 +9,7 @@ class InputTextField extends StatefulWidget {
|
||||
final void Function() onCanceled;
|
||||
final bool autoClearWhenDone;
|
||||
final String text;
|
||||
final int? maxLength;
|
||||
|
||||
const InputTextField({
|
||||
required this.text,
|
||||
@ -16,6 +17,7 @@ class InputTextField extends StatefulWidget {
|
||||
required this.onCanceled,
|
||||
this.onChanged,
|
||||
this.autoClearWhenDone = false,
|
||||
this.maxLength,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@ -41,11 +43,14 @@ class _InputTextFieldState extends State<InputTextField> {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
|
||||
final height = widget.maxLength == null ? 36.0 : 56.0;
|
||||
|
||||
return RoundedInputField(
|
||||
controller: _controller,
|
||||
focusNode: _focusNode,
|
||||
autoFocus: true,
|
||||
height: 36,
|
||||
height: height,
|
||||
maxLength: widget.maxLength,
|
||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||
normalBorderColor: theme.shader4,
|
||||
focusBorderColor: theme.main1,
|
||||
|
@ -1,5 +1,7 @@
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/date_bloc.dart';
|
||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:app_flowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide DateFormat;
|
||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@ -161,6 +163,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.watch<AppTheme>();
|
||||
return BlocSelector<DateTypeOptionBloc, DateTypeOptionState, bool>(
|
||||
selector: (state) => state.typeOption.includeTime,
|
||||
builder: (context, includeTime) {
|
||||
@ -173,13 +176,15 @@ class _IncludeTimeButton extends StatelessWidget {
|
||||
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(),
|
||||
fontSize: 12),
|
||||
const Spacer(),
|
||||
Switch(
|
||||
Toggle(
|
||||
value: includeTime,
|
||||
onChanged: (newValue) {
|
||||
onChanged: (value) {
|
||||
context
|
||||
.read<DateTypeOptionBloc>()
|
||||
.add(DateTypeOptionEvent.includeTime(newValue));
|
||||
.add(DateTypeOptionEvent.includeTime(!value));
|
||||
},
|
||||
style: ToggleStyle.big(theme),
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -256,6 +256,7 @@ class _CreateOptionTextField extends StatelessWidget {
|
||||
final text = state.newOptionName.foldRight("", (a, previous) => a);
|
||||
return InputTextField(
|
||||
autoClearWhenDone: true,
|
||||
maxLength: 30,
|
||||
text: text,
|
||||
onCanceled: () {
|
||||
context
|
||||
|
@ -106,6 +106,7 @@ class _OptionNameTextField extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
return InputTextField(
|
||||
text: name,
|
||||
maxLength: 30,
|
||||
onCanceled: () {},
|
||||
onDone: (optionName) {
|
||||
if (name != optionName) {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'package:app_flowy/user/application/user_listener.dart';
|
||||
import 'package:app_flowy/workspace/application/edit_panel/edit_context.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flowy_sdk/log.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
|
||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||
@ -50,13 +51,24 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
unauthorized: (_Unauthorized value) {
|
||||
emit(state.copyWith(unauthorized: true));
|
||||
},
|
||||
collapseMenu: (e) {
|
||||
collapseMenu: (_CollapseMenu e) {
|
||||
emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed));
|
||||
},
|
||||
editPanelResized: (e) {
|
||||
final newOffset =
|
||||
(state.resizeOffset + e.offset).clamp(-50, 200).toDouble();
|
||||
emit(state.copyWith(resizeOffset: newOffset));
|
||||
editPanelResizeStart: (_EditPanelResizeStart e) {
|
||||
emit(state.copyWith(
|
||||
resizeType: MenuResizeType.drag,
|
||||
resizeStart: state.resizeOffset,
|
||||
));
|
||||
},
|
||||
editPanelResized: (_EditPanelResized e) {
|
||||
final newPosition =
|
||||
(e.offset + state.resizeStart).clamp(-50, 200).toDouble();
|
||||
if (state.resizeOffset != newPosition) {
|
||||
emit(state.copyWith(resizeOffset: newPosition));
|
||||
}
|
||||
},
|
||||
editPanelResizeEnd: (_EditPanelResizeEnd e) {
|
||||
emit(state.copyWith(resizeType: MenuResizeType.slide));
|
||||
},
|
||||
);
|
||||
},
|
||||
@ -78,6 +90,22 @@ class HomeBloc extends Bloc<HomeEvent, HomeState> {
|
||||
}
|
||||
}
|
||||
|
||||
enum MenuResizeType {
|
||||
slide,
|
||||
drag,
|
||||
}
|
||||
|
||||
extension MenuResizeTypeExtension on MenuResizeType {
|
||||
Duration duration() {
|
||||
switch (this) {
|
||||
case MenuResizeType.drag:
|
||||
return 30.milliseconds;
|
||||
case MenuResizeType.slide:
|
||||
return 350.milliseconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class HomeEvent with _$HomeEvent {
|
||||
const factory HomeEvent.initial() = _Initial;
|
||||
@ -91,6 +119,8 @@ class HomeEvent with _$HomeEvent {
|
||||
const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
|
||||
const factory HomeEvent.collapseMenu() = _CollapseMenu;
|
||||
const factory HomeEvent.editPanelResized(double offset) = _EditPanelResized;
|
||||
const factory HomeEvent.editPanelResizeStart() = _EditPanelResizeStart;
|
||||
const factory HomeEvent.editPanelResizeEnd() = _EditPanelResizeEnd;
|
||||
}
|
||||
|
||||
@freezed
|
||||
@ -103,6 +133,8 @@ class HomeState with _$HomeState {
|
||||
required bool unauthorized,
|
||||
required bool isMenuCollapsed,
|
||||
required double resizeOffset,
|
||||
required double resizeStart,
|
||||
required MenuResizeType resizeType,
|
||||
}) = _HomeState;
|
||||
|
||||
factory HomeState.initial(CurrentWorkspaceSettingPB workspaceSetting) =>
|
||||
@ -114,5 +146,7 @@ class HomeState with _$HomeState {
|
||||
unauthorized: false,
|
||||
isMenuCollapsed: false,
|
||||
resizeOffset: 0,
|
||||
resizeStart: 0,
|
||||
resizeType: MenuResizeType.slide,
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,6 @@ import 'dart:io' show Platform;
|
||||
|
||||
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
// ignore: import_of_legacy_library_into_null_safe
|
||||
import 'package:sized_context/sized_context.dart';
|
||||
@ -44,7 +43,7 @@ class HomeLayout {
|
||||
homePageLOffset = (showMenu && !menuIsDrawer) ? menuWidth : 0.0;
|
||||
|
||||
menuSpacing = !showMenu && Platform.isMacOS ? 80.0 : 0.0;
|
||||
animDuration = .35.seconds;
|
||||
animDuration = homeBlocState.resizeType.duration();
|
||||
|
||||
editPanelWidth = HomeSizes.editPanelWidth;
|
||||
homePageROffset = showEditPanel ? editPanelWidth : 0;
|
||||
|
@ -176,11 +176,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
cursor: SystemMouseCursors.resizeLeftRight,
|
||||
child: GestureDetector(
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
onPanUpdate: ((details) {
|
||||
context
|
||||
.read<HomeBloc>()
|
||||
.add(HomeEvent.editPanelResized(details.delta.dx));
|
||||
}),
|
||||
onHorizontalDragStart: (details) => context
|
||||
.read<HomeBloc>()
|
||||
.add(const HomeEvent.editPanelResizeStart()),
|
||||
onHorizontalDragUpdate: (details) => context
|
||||
.read<HomeBloc>()
|
||||
.add(HomeEvent.editPanelResized(details.localPosition.dx)),
|
||||
onHorizontalDragEnd: (details) => context
|
||||
.read<HomeBloc>()
|
||||
.add(const HomeEvent.editPanelResizeEnd()),
|
||||
onHorizontalDragCancel: () => context
|
||||
.read<HomeBloc>()
|
||||
.add(const HomeEvent.editPanelResizeEnd()),
|
||||
behavior: HitTestBehavior.translucent,
|
||||
child: SizedBox(
|
||||
width: 10,
|
||||
@ -208,7 +215,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
top: 0,
|
||||
animate: true)
|
||||
.animate(layout.animDuration, Curves.easeOut),
|
||||
homeMenuResizer.positioned(left: layout.homePageLOffset - 5),
|
||||
bubble
|
||||
.positioned(
|
||||
right: 20,
|
||||
@ -236,6 +242,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
bottom: 0,
|
||||
animate: true)
|
||||
.animate(layout.animDuration, Curves.easeOut),
|
||||
homeMenuResizer
|
||||
.positioned(left: layout.homePageLOffset - 5)
|
||||
.animate(layout.animDuration, Curves.easeOut),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
@ -99,6 +99,7 @@ class MenuAppHeader extends StatelessWidget {
|
||||
app.name,
|
||||
fontSize: 12,
|
||||
color: theme.textColor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -82,7 +82,7 @@ class ViewSectionItem extends StatelessWidget {
|
||||
child: FlowyText.regular(
|
||||
state.view.name,
|
||||
fontSize: 12,
|
||||
overflow: TextOverflow.clip,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
];
|
||||
|
@ -28,8 +28,9 @@ class MenuUser extends StatelessWidget {
|
||||
children: [
|
||||
_renderAvatar(context),
|
||||
const HSpace(10),
|
||||
_renderUserName(context),
|
||||
const Spacer(),
|
||||
Expanded(
|
||||
child: _renderUserName(context),
|
||||
),
|
||||
_renderSettingsButton(context),
|
||||
//ToDo: when the user is allowed to create another workspace,
|
||||
//we get the below block back
|
||||
@ -63,7 +64,7 @@ class MenuUser extends StatelessWidget {
|
||||
if (name.isEmpty) {
|
||||
name = context.read<MenuUserBloc>().state.userProfile.email;
|
||||
}
|
||||
return FlowyText(name, fontSize: 12);
|
||||
return FlowyText(name, fontSize: 12, overflow: TextOverflow.ellipsis);
|
||||
}
|
||||
|
||||
Widget _renderSettingsButton(BuildContext context) {
|
||||
|
@ -13,7 +13,7 @@ class SettingsAppearanceView extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = context.read<AppTheme>();
|
||||
final theme = context.watch<AppTheme>();
|
||||
|
||||
return SingleChildScrollView(
|
||||
child: Column(
|
||||
|
@ -97,10 +97,8 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
|
||||
child: SizedBox(
|
||||
height: itemHeight,
|
||||
child: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
if (icon != null) icon,
|
||||
HSpace(ActionListSizes.itemHPadding),
|
||||
if (icon != null) ...[icon, HSpace(ActionListSizes.itemHPadding)],
|
||||
FlowyText.medium(action.name, fontSize: 12),
|
||||
],
|
||||
),
|
||||
|
@ -1,8 +1,14 @@
|
||||
# 0.0.9
|
||||
* Enable slide to select text in card
|
||||
* Fix some bugs
|
||||
|
||||
# 0.0.8
|
||||
* Enable drag and drop group
|
||||
|
||||
# 0.0.7
|
||||
* Rename some classes
|
||||
* Add documentation
|
||||
|
||||
# 0.0.6
|
||||
* Support scroll to bottom
|
||||
* Fix some bugs
|
||||
|
@ -78,7 +78,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
||||
height: 50,
|
||||
margin: config.groupItemPadding,
|
||||
onAddButtonClick: () {
|
||||
boardController.scrollToBottom(columnData.id, (p0) {});
|
||||
boardController.scrollToBottom(columnData.id);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -32,4 +32,8 @@ class Log {
|
||||
'AppFlowyBoard: ❗️[Trace] - ${DateTime.now().second}=> $message');
|
||||
}
|
||||
}
|
||||
|
||||
static void error(String? message) {
|
||||
debugPrint('AppFlowyBoard: ❌[Error] - ${DateTime.now().second}=> $message');
|
||||
}
|
||||
}
|
||||
|
@ -11,10 +11,11 @@ import 'reorder_phantom/phantom_controller.dart';
|
||||
import '../rendering/board_overlay.dart';
|
||||
|
||||
class AppFlowyBoardScrollController {
|
||||
AppFlowyBoardState? _groupState;
|
||||
AppFlowyBoardState? _boardState;
|
||||
|
||||
void scrollToBottom(String groupId, void Function(BuildContext)? completed) {
|
||||
_groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
||||
void scrollToBottom(String groupId,
|
||||
{void Function(BuildContext)? completed}) {
|
||||
_boardState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
||||
}
|
||||
}
|
||||
|
||||
@ -39,9 +40,6 @@ class AppFlowyBoardConfig {
|
||||
}
|
||||
|
||||
class AppFlowyBoard extends StatelessWidget {
|
||||
/// The direction to use as the main axis.
|
||||
final Axis direction = Axis.vertical;
|
||||
|
||||
/// The widget that will be rendered as the background of the board.
|
||||
final Widget? background;
|
||||
|
||||
@ -94,11 +92,7 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
///
|
||||
final AppFlowyBoardScrollController? boardScrollController;
|
||||
|
||||
final AppFlowyBoardState _groupState = AppFlowyBoardState();
|
||||
|
||||
late final BoardPhantomController _phantomController;
|
||||
|
||||
AppFlowyBoard({
|
||||
const AppFlowyBoard({
|
||||
required this.controller,
|
||||
required this.cardBuilder,
|
||||
this.background,
|
||||
@ -109,12 +103,7 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
this.groupConstraints = const BoxConstraints(maxWidth: 200),
|
||||
this.config = const AppFlowyBoardConfig(),
|
||||
Key? key,
|
||||
}) : super(key: key) {
|
||||
_phantomController = BoardPhantomController(
|
||||
delegate: controller,
|
||||
groupsState: _groupState,
|
||||
);
|
||||
}
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@ -122,8 +111,14 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
value: controller,
|
||||
child: Consumer<AppFlowyBoardController>(
|
||||
builder: (context, notifier, child) {
|
||||
final boardState = AppFlowyBoardState();
|
||||
BoardPhantomController phantomController = BoardPhantomController(
|
||||
delegate: controller,
|
||||
groupsState: boardState,
|
||||
);
|
||||
|
||||
if (boardScrollController != null) {
|
||||
boardScrollController!._groupState = _groupState;
|
||||
boardScrollController!._boardState = boardState;
|
||||
}
|
||||
|
||||
return _AppFlowyBoardContent(
|
||||
@ -131,14 +126,14 @@ class AppFlowyBoard extends StatelessWidget {
|
||||
dataController: controller,
|
||||
scrollController: scrollController,
|
||||
scrollManager: boardScrollController,
|
||||
groupState: _groupState,
|
||||
boardState: boardState,
|
||||
background: background,
|
||||
delegate: _phantomController,
|
||||
delegate: phantomController,
|
||||
groupConstraints: groupConstraints,
|
||||
cardBuilder: cardBuilder,
|
||||
footerBuilder: footerBuilder,
|
||||
headerBuilder: headerBuilder,
|
||||
phantomController: _phantomController,
|
||||
phantomController: phantomController,
|
||||
onReorder: controller.moveGroup,
|
||||
);
|
||||
},
|
||||
@ -156,7 +151,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
final ReorderFlexConfig reorderFlexConfig;
|
||||
final BoxConstraints groupConstraints;
|
||||
final AppFlowyBoardScrollController? scrollManager;
|
||||
final AppFlowyBoardState groupState;
|
||||
final AppFlowyBoardState boardState;
|
||||
final AppFlowyBoardCardBuilder cardBuilder;
|
||||
final AppFlowyBoardHeaderBuilder? headerBuilder;
|
||||
final AppFlowyBoardFooterBuilder? footerBuilder;
|
||||
@ -169,7 +164,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
required this.delegate,
|
||||
required this.dataController,
|
||||
required this.scrollManager,
|
||||
required this.groupState,
|
||||
required this.boardState,
|
||||
this.scrollController,
|
||||
this.background,
|
||||
required this.groupConstraints,
|
||||
@ -178,7 +173,10 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
||||
this.headerBuilder,
|
||||
required this.phantomController,
|
||||
Key? key,
|
||||
}) : reorderFlexConfig = const ReorderFlexConfig(),
|
||||
}) : reorderFlexConfig = const ReorderFlexConfig(
|
||||
direction: Axis.horizontal,
|
||||
dragDirection: Axis.horizontal,
|
||||
),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@ -198,7 +196,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
reorderFlexId: widget.dataController.identifier,
|
||||
acceptedReorderFlexId: widget.dataController.groupIds,
|
||||
delegate: widget.delegate,
|
||||
columnsState: widget.groupState,
|
||||
columnsState: widget.boardState,
|
||||
);
|
||||
|
||||
final reorderFlex = ReorderFlex(
|
||||
@ -206,9 +204,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
scrollController: widget.scrollController,
|
||||
onReorder: widget.onReorder,
|
||||
dataSource: widget.dataController,
|
||||
direction: Axis.horizontal,
|
||||
interceptor: interceptor,
|
||||
reorderable: true,
|
||||
children: _buildColumns(),
|
||||
);
|
||||
|
||||
@ -254,7 +250,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
);
|
||||
|
||||
final reorderFlexAction = ReorderFlexActionImpl();
|
||||
widget.groupState.reorderFlexActionMap[columnData.id] =
|
||||
widget.boardState.reorderFlexActionMap[columnData.id] =
|
||||
reorderFlexAction;
|
||||
|
||||
return ChangeNotifierProvider.value(
|
||||
@ -275,8 +271,8 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
||||
onReorder: widget.dataController.moveGroupItem,
|
||||
cornerRadius: widget.config.cornerRadius,
|
||||
backgroundColor: widget.config.groupBackgroundColor,
|
||||
dragStateStorage: widget.groupState,
|
||||
dragTargetKeys: widget.groupState,
|
||||
dragStateStorage: widget.boardState,
|
||||
dragTargetKeys: widget.boardState,
|
||||
reorderFlexAction: reorderFlexAction,
|
||||
);
|
||||
|
||||
|
@ -138,7 +138,11 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
/// groups or get ready to reinitialize the [AppFlowyBoard].
|
||||
void clear() {
|
||||
_groupDatas.clear();
|
||||
for (final group in _groupControllers.values) {
|
||||
group.dispose();
|
||||
}
|
||||
_groupControllers.clear();
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -202,6 +206,14 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
getGroupController(groupId)?.replaceOrInsertItem(item);
|
||||
}
|
||||
|
||||
void enableGroupDragging(bool isEnable) {
|
||||
for (var groupController in _groupControllers.values) {
|
||||
groupController.enableDragging(isEnable);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
/// Moves the item at [fromGroupIndex] in group with id [fromGroupId] to
|
||||
/// group with id [toGroupId] at [toGroupIndex]
|
||||
@override
|
||||
@ -215,6 +227,8 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
final fromGroupController = getGroupController(fromGroupId)!;
|
||||
final toGroupController = getGroupController(toGroupId)!;
|
||||
final fromGroupItem = fromGroupController.removeAt(fromGroupIndex);
|
||||
if (fromGroupItem == null) return;
|
||||
|
||||
if (toGroupController.items.length > toGroupIndex) {
|
||||
assert(toGroupController.items[toGroupIndex] is PhantomGroupItem);
|
||||
|
||||
@ -275,7 +289,9 @@ class AppFlowyBoardController extends ChangeNotifier
|
||||
Log.trace(
|
||||
'[$BoardPhantomController] update $groupId:$index to $groupId:$newIndex');
|
||||
final item = groupController.removeAt(index, notify: false);
|
||||
groupController.insert(newIndex, item, notify: false);
|
||||
if (item != null) {
|
||||
groupController.insert(newIndex, item, notify: false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +156,9 @@ class _AppFlowyBoardGroupState extends State<AppFlowyBoardGroup> {
|
||||
widget.onDragStarted?.call(index);
|
||||
},
|
||||
onReorder: ((fromIndex, toIndex) {
|
||||
if (widget.phantomController.isFromGroup(widget.groupId)) {
|
||||
if (widget.phantomController.shouldReorder(widget.groupId)) {
|
||||
widget.onReorder(widget.groupId, fromIndex, toIndex);
|
||||
widget.phantomController.transformIndex(fromIndex, toIndex);
|
||||
widget.phantomController.updateIndex(fromIndex, toIndex);
|
||||
}
|
||||
}),
|
||||
onDragEnded: () {
|
||||
|
@ -5,6 +5,8 @@ import 'package:appflowy_board/src/widgets/reorder_flex/reorder_flex.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef IsDraggable = bool;
|
||||
|
||||
/// A item represents the generic data model of each group card.
|
||||
///
|
||||
/// Each item displayed in the group required to implement this class.
|
||||
@ -50,8 +52,17 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
/// * [notify] the default value of [notify] is true, it will notify the
|
||||
/// listener. Set to false if you do not want to notify the listeners.
|
||||
///
|
||||
AppFlowyGroupItem removeAt(int index, {bool notify = true}) {
|
||||
assert(index >= 0);
|
||||
AppFlowyGroupItem? removeAt(int index, {bool notify = true}) {
|
||||
if (groupData._items.length <= index) {
|
||||
Log.error(
|
||||
'Fatal error, index is out of bounds. Index: $index, len: ${groupData._items.length}');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (index < 0) {
|
||||
Log.error('Invalid index:$index');
|
||||
return null;
|
||||
}
|
||||
|
||||
Log.debug('[$AppFlowyGroupController] $groupData remove item at $index');
|
||||
final item = groupData._items.removeAt(index);
|
||||
@ -71,12 +82,17 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
/// Move the item from [fromIndex] to [toIndex]. It will do nothing if the
|
||||
/// [fromIndex] equal to the [toIndex].
|
||||
bool move(int fromIndex, int toIndex) {
|
||||
assert(fromIndex >= 0);
|
||||
assert(toIndex >= 0);
|
||||
if (groupData._items.length < fromIndex) {
|
||||
Log.error(
|
||||
'Out of bounds error. index: $fromIndex should not greater than ${groupData._items.length}');
|
||||
return false;
|
||||
}
|
||||
|
||||
if (fromIndex == toIndex) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Log.debug(
|
||||
'[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex');
|
||||
final item = groupData._items.removeAt(fromIndex);
|
||||
@ -124,7 +140,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
Log.debug('[$AppFlowyGroupController] $groupData add $newItem');
|
||||
} else {
|
||||
if (index >= groupData._items.length) {
|
||||
Log.warn(
|
||||
Log.error(
|
||||
'[$AppFlowyGroupController] unexpected items length, index should less than the count of the items. Index: $index, items count: ${items.length}');
|
||||
return;
|
||||
}
|
||||
@ -155,6 +171,15 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
||||
-1;
|
||||
}
|
||||
|
||||
void enableDragging(bool isEnable) {
|
||||
groupData.draggable = isEnable;
|
||||
|
||||
for (var item in groupData._items) {
|
||||
item.draggable = isEnable;
|
||||
}
|
||||
_notify();
|
||||
}
|
||||
|
||||
void _notify() {
|
||||
notifyListeners();
|
||||
}
|
||||
|
@ -16,13 +16,13 @@ class FlexDragTargetData extends DragTargetData {
|
||||
@override
|
||||
final int draggingIndex;
|
||||
|
||||
final DraggingState _state;
|
||||
final DraggingState _draggingState;
|
||||
|
||||
Widget? get draggingWidget => _state.draggingWidget;
|
||||
Widget? get draggingWidget => _draggingState.draggingWidget;
|
||||
|
||||
Size? get feedbackSize => _state.feedbackSize;
|
||||
Size? get feedbackSize => _draggingState.feedbackSize;
|
||||
|
||||
bool get isDragging => _state.isDragging();
|
||||
bool get isDragging => _draggingState.isDragging();
|
||||
|
||||
final String dragTargetId;
|
||||
|
||||
@ -40,8 +40,8 @@ class FlexDragTargetData extends DragTargetData {
|
||||
required this.reorderFlexId,
|
||||
required this.reorderFlexItem,
|
||||
required this.dragTargetIndexKey,
|
||||
required DraggingState state,
|
||||
}) : _state = state;
|
||||
required DraggingState draggingState,
|
||||
}) : _draggingState = draggingState;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:appflowy_board/src/utils/log.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
@ -78,10 +79,12 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
|
||||
final bool useMoveAnimation;
|
||||
|
||||
final bool draggable;
|
||||
final IsDraggable draggable;
|
||||
|
||||
final double draggingOpacity;
|
||||
|
||||
final Axis? dragDirection;
|
||||
|
||||
const ReorderDragTarget({
|
||||
Key? key,
|
||||
required this.child,
|
||||
@ -99,6 +102,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
||||
this.onLeave,
|
||||
this.draggableTargetBuilder,
|
||||
this.draggingOpacity = 0.3,
|
||||
this.dragDirection,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -115,8 +119,10 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
Widget dragTarget = DragTarget<T>(
|
||||
builder: _buildDraggableWidget,
|
||||
onWillAccept: (dragTargetData) {
|
||||
assert(dragTargetData != null);
|
||||
if (dragTargetData == null) return false;
|
||||
if (dragTargetData == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return widget.onWillAccept(dragTargetData);
|
||||
},
|
||||
onAccept: widget.onAccept,
|
||||
@ -140,9 +146,6 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
List<T?> acceptedCandidates,
|
||||
List<dynamic> rejectedCandidates,
|
||||
) {
|
||||
if (!widget.draggable) {
|
||||
return widget.child;
|
||||
}
|
||||
Widget feedbackBuilder = Builder(builder: (BuildContext context) {
|
||||
BoxConstraints contentSizeConstraints =
|
||||
BoxConstraints.loose(_draggingFeedbackSize!);
|
||||
@ -163,7 +166,8 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
||||
widget.deleteAnimationController,
|
||||
) ??
|
||||
Draggable<DragTargetData>(
|
||||
maxSimultaneousDrags: 1,
|
||||
axis: widget.dragDirection,
|
||||
maxSimultaneousDrags: widget.draggable ? 1 : 0,
|
||||
data: widget.dragTargetData,
|
||||
ignoringFeedbackSemantics: false,
|
||||
feedback: feedbackBuilder,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import '../../utils/log.dart';
|
||||
@ -29,6 +30,8 @@ abstract class ReoderFlexDataSource {
|
||||
abstract class ReoderFlexItem {
|
||||
/// [id] is used to identify the item. It must be unique.
|
||||
String get id;
|
||||
|
||||
IsDraggable draggable = true;
|
||||
}
|
||||
|
||||
/// Cache each dragTarget's key.
|
||||
@ -73,8 +76,15 @@ class ReorderFlexConfig {
|
||||
|
||||
final bool useMovePlaceholder;
|
||||
|
||||
/// [direction] How to place the children, default is Axis.vertical
|
||||
final Axis direction;
|
||||
|
||||
final Axis? dragDirection;
|
||||
|
||||
const ReorderFlexConfig({
|
||||
this.useMoveAnimation = true,
|
||||
this.direction = Axis.vertical,
|
||||
this.dragDirection,
|
||||
}) : useMovePlaceholder = !useMoveAnimation;
|
||||
}
|
||||
|
||||
@ -82,8 +92,6 @@ class ReorderFlex extends StatefulWidget {
|
||||
final ReorderFlexConfig config;
|
||||
final List<Widget> children;
|
||||
|
||||
/// [direction] How to place the children, default is Axis.vertical
|
||||
final Axis direction;
|
||||
final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start;
|
||||
|
||||
final ScrollController? scrollController;
|
||||
@ -108,8 +116,6 @@ class ReorderFlex extends StatefulWidget {
|
||||
|
||||
final ReorderFlexAction? reorderFlexAction;
|
||||
|
||||
final bool reorderable;
|
||||
|
||||
ReorderFlex({
|
||||
Key? key,
|
||||
this.scrollController,
|
||||
@ -117,14 +123,12 @@ class ReorderFlex extends StatefulWidget {
|
||||
required this.children,
|
||||
required this.config,
|
||||
required this.onReorder,
|
||||
this.reorderable = true,
|
||||
this.dragStateStorage,
|
||||
this.dragTargetKeys,
|
||||
this.onDragStarted,
|
||||
this.onDragEnded,
|
||||
this.interceptor,
|
||||
this.reorderFlexAction,
|
||||
this.direction = Axis.vertical,
|
||||
}) : assert(children.every((Widget w) => w.key != null),
|
||||
'All child must have a key.'),
|
||||
super(key: key);
|
||||
@ -146,8 +150,8 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// Whether or not we are currently scrolling this view to show a widget.
|
||||
bool _scrolling = false;
|
||||
|
||||
/// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc.
|
||||
late DraggingState dragState;
|
||||
/// [draggingState] records the dragging state including dragStartIndex, and phantomIndex, etc.
|
||||
late DraggingState draggingState;
|
||||
|
||||
/// [_animation] controls the dragging animations
|
||||
late DragTargetAnimation _animation;
|
||||
@ -158,9 +162,9 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
void initState() {
|
||||
_notifier = ReorderFlexNotifier();
|
||||
final flexId = widget.reorderFlexId;
|
||||
dragState = widget.dragStateStorage?.readState(flexId) ??
|
||||
draggingState = widget.dragStateStorage?.readState(flexId) ??
|
||||
DraggingState(widget.reorderFlexId);
|
||||
Log.trace('[DragTarget] init dragState: $dragState');
|
||||
Log.trace('[DragTarget] init dragState: $draggingState');
|
||||
|
||||
widget.dragStateStorage?.removeState(flexId);
|
||||
|
||||
@ -168,7 +172,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
reorderAnimationDuration: widget.config.reorderAnimationDuration,
|
||||
entranceAnimateStatusChanged: (status) {
|
||||
if (status == AnimationStatus.completed) {
|
||||
if (dragState.nextIndex == -1) return;
|
||||
if (draggingState.nextIndex == -1) return;
|
||||
setState(() => _requestAnimationToNextIndex());
|
||||
}
|
||||
},
|
||||
@ -225,7 +229,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
indexKey,
|
||||
);
|
||||
|
||||
children.add(_wrap(child, i, indexKey));
|
||||
children.add(_wrap(child, i, indexKey, item.draggable));
|
||||
|
||||
// if (widget.config.useMovePlaceholder) {
|
||||
// children.add(DragTargeMovePlaceholder(
|
||||
@ -256,64 +260,70 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
/// when the animation finish.
|
||||
|
||||
if (_animation.entranceController.isCompleted) {
|
||||
dragState.removePhantom();
|
||||
draggingState.removePhantom();
|
||||
|
||||
if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) {
|
||||
if (!isAcceptingNewTarget && draggingState.didDragTargetMoveToNext()) {
|
||||
return;
|
||||
}
|
||||
|
||||
dragState.moveDragTargetToNext();
|
||||
draggingState.moveDragTargetToNext();
|
||||
_animation.animateToNext();
|
||||
}
|
||||
}
|
||||
|
||||
/// [child]: the child will be wrapped with dartTarget
|
||||
/// [childIndex]: the index of the child in a list
|
||||
Widget _wrap(Widget child, int childIndex, GlobalObjectKey indexKey) {
|
||||
Widget _wrap(
|
||||
Widget child,
|
||||
int childIndex,
|
||||
GlobalObjectKey indexKey,
|
||||
IsDraggable draggable,
|
||||
) {
|
||||
return Builder(builder: (context) {
|
||||
final ReorderDragTarget dragTarget = _buildDragTarget(
|
||||
context,
|
||||
child,
|
||||
childIndex,
|
||||
indexKey,
|
||||
draggable,
|
||||
);
|
||||
int shiftedIndex = childIndex;
|
||||
|
||||
if (dragState.isOverlapWithPhantom()) {
|
||||
shiftedIndex = dragState.calculateShiftedIndex(childIndex);
|
||||
if (draggingState.isOverlapWithPhantom()) {
|
||||
shiftedIndex = draggingState.calculateShiftedIndex(childIndex);
|
||||
}
|
||||
|
||||
Log.trace(
|
||||
'Rebuild: Group:[${dragState.reorderFlexId}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
||||
final currentIndex = dragState.currentIndex;
|
||||
final dragPhantomIndex = dragState.phantomIndex;
|
||||
'Rebuild: Group:[${draggingState.reorderFlexId}] ${draggingState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
||||
final currentIndex = draggingState.currentIndex;
|
||||
final dragPhantomIndex = draggingState.phantomIndex;
|
||||
|
||||
if (shiftedIndex == currentIndex || childIndex == dragPhantomIndex) {
|
||||
Widget dragSpace;
|
||||
if (dragState.draggingWidget != null) {
|
||||
if (dragState.draggingWidget is PhantomWidget) {
|
||||
dragSpace = dragState.draggingWidget!;
|
||||
if (draggingState.draggingWidget != null) {
|
||||
if (draggingState.draggingWidget is PhantomWidget) {
|
||||
dragSpace = draggingState.draggingWidget!;
|
||||
} else {
|
||||
dragSpace = PhantomWidget(
|
||||
opacity: widget.config.draggingWidgetOpacity,
|
||||
child: dragState.draggingWidget,
|
||||
child: draggingState.draggingWidget,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
dragSpace = SizedBox.fromSize(size: dragState.dropAreaSize);
|
||||
dragSpace = SizedBox.fromSize(size: draggingState.dropAreaSize);
|
||||
}
|
||||
|
||||
/// Returns the dragTarget it is not start dragging. The size of the
|
||||
/// dragTarget is the same as the the passed in child.
|
||||
///
|
||||
if (dragState.isNotDragging()) {
|
||||
if (draggingState.isNotDragging()) {
|
||||
return _buildDraggingContainer(children: [dragTarget]);
|
||||
}
|
||||
|
||||
/// Determine the size of the drop area to show under the dragging widget.
|
||||
Size? feedbackSize = Size.zero;
|
||||
if (widget.config.useMoveAnimation) {
|
||||
feedbackSize = dragState.feedbackSize;
|
||||
feedbackSize = draggingState.feedbackSize;
|
||||
}
|
||||
|
||||
Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize);
|
||||
@ -321,7 +331,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
|
||||
/// When start dragging, the dragTarget, [ReorderDragTarget], will
|
||||
/// return a [IgnorePointerWidget] which size is zero.
|
||||
if (dragState.isPhantomAboveDragTarget()) {
|
||||
if (draggingState.isPhantomAboveDragTarget()) {
|
||||
_notifier.updateDragTargetIndex(currentIndex);
|
||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||
return _buildDraggingContainer(children: [
|
||||
@ -343,7 +353,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
///
|
||||
if (dragState.isPhantomBelowDragTarget()) {
|
||||
if (draggingState.isPhantomBelowDragTarget()) {
|
||||
_notifier.updateDragTargetIndex(currentIndex);
|
||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||
return _buildDraggingContainer(children: [
|
||||
@ -364,10 +374,10 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
}
|
||||
|
||||
assert(!dragState.isOverlapWithPhantom());
|
||||
assert(!draggingState.isOverlapWithPhantom());
|
||||
|
||||
List<Widget> children = [];
|
||||
if (dragState.isDragTargetMovingDown()) {
|
||||
if (draggingState.isDragTargetMovingDown()) {
|
||||
children.addAll([dragTarget, appearSpace]);
|
||||
} else {
|
||||
children.addAll([appearSpace, dragTarget]);
|
||||
@ -395,15 +405,17 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
Widget child,
|
||||
int dragTargetIndex,
|
||||
GlobalObjectKey indexKey,
|
||||
IsDraggable draggable,
|
||||
) {
|
||||
final reorderFlexItem = widget.dataSource.items[dragTargetIndex];
|
||||
return ReorderDragTarget<FlexDragTargetData>(
|
||||
indexGlobalKey: indexKey,
|
||||
draggable: draggable,
|
||||
dragTargetData: FlexDragTargetData(
|
||||
draggingIndex: dragTargetIndex,
|
||||
reorderFlexId: widget.reorderFlexId,
|
||||
reorderFlexItem: reorderFlexItem,
|
||||
state: dragState,
|
||||
draggingState: draggingState,
|
||||
dragTargetId: reorderFlexItem.id,
|
||||
dragTargetIndexKey: indexKey,
|
||||
),
|
||||
@ -432,11 +444,11 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
setState(() {
|
||||
if (dragTargetData.reorderFlexId == widget.reorderFlexId) {
|
||||
_onReordered(
|
||||
dragState.dragStartIndex,
|
||||
dragState.currentIndex,
|
||||
draggingState.dragStartIndex,
|
||||
draggingState.currentIndex,
|
||||
);
|
||||
}
|
||||
dragState.endDragging();
|
||||
draggingState.endDragging();
|
||||
widget.onDragEnded?.call();
|
||||
});
|
||||
},
|
||||
@ -482,8 +494,8 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
deleteAnimationController: _animation.deleteController,
|
||||
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
||||
useMoveAnimation: widget.config.useMoveAnimation,
|
||||
draggable: widget.reorderable,
|
||||
draggingOpacity: widget.config.draggingWidgetOpacity,
|
||||
dragDirection: widget.config.dragDirection,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
@ -506,7 +518,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
child,
|
||||
_animation.entranceController,
|
||||
feedbackSize,
|
||||
widget.direction,
|
||||
widget.config.direction,
|
||||
);
|
||||
}
|
||||
|
||||
@ -515,7 +527,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
child,
|
||||
_animation.phantomController,
|
||||
feedbackSize,
|
||||
widget.direction,
|
||||
widget.config.direction,
|
||||
);
|
||||
}
|
||||
|
||||
@ -525,7 +537,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
Size? feedbackSize,
|
||||
) {
|
||||
setState(() {
|
||||
dragState.startDragging(draggingWidget, dragIndex, feedbackSize);
|
||||
draggingState.startDragging(draggingWidget, dragIndex, feedbackSize);
|
||||
_animation.startDragging();
|
||||
});
|
||||
}
|
||||
@ -535,34 +547,34 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
return;
|
||||
}
|
||||
|
||||
dragState.setStartDraggingIndex(dragTargetIndex);
|
||||
draggingState.setStartDraggingIndex(dragTargetIndex);
|
||||
widget.dragStateStorage?.insertState(
|
||||
widget.reorderFlexId,
|
||||
dragState,
|
||||
draggingState,
|
||||
);
|
||||
}
|
||||
|
||||
bool handleOnWillAccept(BuildContext context, int dragTargetIndex) {
|
||||
final dragIndex = dragState.dragStartIndex;
|
||||
final dragIndex = draggingState.dragStartIndex;
|
||||
|
||||
/// The [willAccept] will be true if the dargTarget is the widget that gets
|
||||
/// dragged and it is dragged on top of the other dragTargets.
|
||||
///
|
||||
|
||||
bool willAccept =
|
||||
dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex;
|
||||
bool willAccept = draggingState.dragStartIndex == dragIndex &&
|
||||
dragIndex != dragTargetIndex;
|
||||
setState(() {
|
||||
if (willAccept) {
|
||||
int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex);
|
||||
dragState.updateNextIndex(shiftedIndex);
|
||||
int shiftedIndex = draggingState.calculateShiftedIndex(dragTargetIndex);
|
||||
draggingState.updateNextIndex(shiftedIndex);
|
||||
} else {
|
||||
dragState.updateNextIndex(dragTargetIndex);
|
||||
draggingState.updateNextIndex(dragTargetIndex);
|
||||
}
|
||||
_requestAnimationToNextIndex(isAcceptingNewTarget: true);
|
||||
});
|
||||
|
||||
Log.trace(
|
||||
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $dragState}');
|
||||
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $draggingState}');
|
||||
|
||||
_scrollTo(context);
|
||||
|
||||
@ -587,7 +599,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
return child;
|
||||
} else {
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: widget.direction,
|
||||
scrollDirection: widget.config.direction,
|
||||
controller: _scrollController,
|
||||
child: child,
|
||||
);
|
||||
@ -595,7 +607,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
Widget _wrapContainer(List<Widget> children) {
|
||||
switch (widget.direction) {
|
||||
switch (widget.config.direction) {
|
||||
case Axis.horizontal:
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -613,7 +625,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
}
|
||||
|
||||
Widget _buildDraggingContainer({required List<Widget> children}) {
|
||||
switch (widget.direction) {
|
||||
switch (widget.config.direction) {
|
||||
case Axis.horizontal:
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@ -660,6 +672,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
.ensureVisible(
|
||||
dragTargetRenderObject,
|
||||
alignment: 0.5,
|
||||
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
|
||||
duration: const Duration(milliseconds: 120),
|
||||
)
|
||||
.then((value) {
|
||||
@ -683,9 +696,9 @@ class ReorderFlexState extends State<ReorderFlex>
|
||||
// If and only if the current scroll offset falls in-between the offsets
|
||||
// necessary to reveal the selected context at the top or bottom of the
|
||||
// screen, then it is already on-screen.
|
||||
final double margin = widget.direction == Axis.horizontal
|
||||
? dragState.dropAreaSize.width
|
||||
: dragState.dropAreaSize.height / 2.0;
|
||||
final double margin = widget.config.direction == Axis.horizontal
|
||||
? draggingState.dropAreaSize.width
|
||||
: draggingState.dropAreaSize.height / 2.0;
|
||||
if (_scrollController.hasClients) {
|
||||
final double scrollOffset = _scrollController.offset;
|
||||
final double topOffset = max(
|
||||
|
@ -46,15 +46,23 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
required this.groupsState,
|
||||
});
|
||||
|
||||
bool isFromGroup(String groupId) {
|
||||
/// Determines whether the group should perform reorder
|
||||
///
|
||||
/// Returns `true` if the fromGroupId and toGroupId of the phantomRecord
|
||||
/// equal to the passed in groupId.
|
||||
///
|
||||
/// Returns `true` if the phantomRecord is null
|
||||
///
|
||||
bool shouldReorder(String groupId) {
|
||||
if (phantomRecord != null) {
|
||||
return phantomRecord!.fromGroupId == groupId;
|
||||
return phantomRecord!.toGroupId == groupId &&
|
||||
phantomRecord!.fromGroupId == groupId;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void transformIndex(int fromIndex, int toIndex) {
|
||||
void updateIndex(int fromIndex, int toIndex) {
|
||||
if (phantomRecord == null) {
|
||||
return;
|
||||
}
|
||||
@ -69,7 +77,6 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
||||
/// Remove the phantom in the group when the group is end dragging.
|
||||
void groupEndDragging(String groupId) {
|
||||
phantomState.setGroupIsDragging(groupId, false);
|
||||
|
||||
if (phantomRecord == null) return;
|
||||
|
||||
final fromGroupId = phantomRecord!.fromGroupId;
|
||||
@ -246,10 +253,6 @@ class PhantomRecord {
|
||||
});
|
||||
|
||||
void updateFromGroupIndex(int index) {
|
||||
if (fromGroupIndex == index) {
|
||||
return;
|
||||
}
|
||||
|
||||
fromGroupIndex = index;
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
name: appflowy_board
|
||||
description: AppFlowyBoard is a board-style widget that consists of multi-groups. It supports drag and drop between different groups.
|
||||
version: 0.0.8
|
||||
version: 0.0.9
|
||||
homepage: https://github.com/AppFlowy-IO/AppFlowy
|
||||
repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board
|
||||
|
||||
|
@ -1,3 +1,13 @@
|
||||
## 0.0.6
|
||||
* Add three plugins: Code Block, LateX, and Horizontal rule.
|
||||
* Support web platform.
|
||||
* Support more markdown syntax conversions.
|
||||
* `~ ~` to format text as strikethrough
|
||||
* `_ _` to format text as italic
|
||||
* \` \` to format text as code
|
||||
* `[]()` to format text as link
|
||||
* Fix some bugs.
|
||||
|
||||
## 0.0.5
|
||||
* Support customize the hotkeys for a shortcut on different platforms.
|
||||
* Support customize a theme.
|
||||
|
@ -2,6 +2,8 @@ import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:example/plugin/code_block_node_widget.dart';
|
||||
import 'package:example/plugin/horizontal_rule_node_widget.dart';
|
||||
import 'package:example/plugin/tex_block_node_widget.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
@ -119,14 +121,19 @@ class _MyHomePageState extends State<MyHomePage> {
|
||||
editable: true,
|
||||
customBuilders: {
|
||||
'text/code_block': CodeBlockNodeWidgetBuilder(),
|
||||
'tex': TeXBlockNodeWidgetBuidler(),
|
||||
'horizontal_rule': HorizontalRuleWidgetBuilder(),
|
||||
},
|
||||
shortcutEvents: [
|
||||
enterInCodeBlock,
|
||||
ignoreKeysInCodeBlock,
|
||||
underscoreToItalic,
|
||||
insertHorizontalRule,
|
||||
],
|
||||
selectionMenuItems: [
|
||||
codeBlockItem,
|
||||
codeBlockMenuItem,
|
||||
teXBlockMenuItem,
|
||||
horizontalRuleMenuItem,
|
||||
],
|
||||
),
|
||||
);
|
||||
|
@ -44,9 +44,13 @@ ShortcutEventHandler _ignorekHandler = (editorState, event) {
|
||||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
||||
SelectionMenuItem codeBlockItem = SelectionMenuItem(
|
||||
name: 'Code Block',
|
||||
icon: const Icon(Icons.abc),
|
||||
SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
||||
name: () => 'Code Block',
|
||||
icon: const Icon(
|
||||
Icons.abc,
|
||||
color: Colors.black,
|
||||
size: 18.0,
|
||||
),
|
||||
keywords: ['code block'],
|
||||
handler: (editorState, _, __) {
|
||||
final selection =
|
||||
|
@ -0,0 +1,167 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
ShortcutEvent insertHorizontalRule = ShortcutEvent(
|
||||
key: 'Horizontal rule',
|
||||
command: 'Minus',
|
||||
handler: _insertHorzaontalRule,
|
||||
);
|
||||
|
||||
ShortcutEventHandler _insertHorzaontalRule = (editorState, event) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
final textNodes = editorState.service.selectionService.currentSelectedNodes
|
||||
.whereType<TextNode>();
|
||||
if (textNodes.length != 1 || selection == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
final textNode = textNodes.first;
|
||||
if (textNode.toRawString() == '--') {
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, 0, 2)
|
||||
..insertNode(
|
||||
textNode.path,
|
||||
Node(
|
||||
type: 'horizontal_rule',
|
||||
children: LinkedList(),
|
||||
attributes: {},
|
||||
),
|
||||
)
|
||||
..afterSelection =
|
||||
Selection.single(path: textNode.path.next, startOffset: 0)
|
||||
..commit();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
return KeyEventResult.ignored;
|
||||
};
|
||||
|
||||
SelectionMenuItem horizontalRuleMenuItem = SelectionMenuItem(
|
||||
name: () => 'Horizontal rule',
|
||||
icon: const Icon(
|
||||
Icons.horizontal_rule,
|
||||
color: Colors.black,
|
||||
size: 18.0,
|
||||
),
|
||||
keywords: ['horizontal rule'],
|
||||
handler: (editorState, _, __) {
|
||||
final selection =
|
||||
editorState.service.selectionService.currentSelection.value;
|
||||
final textNodes = editorState.service.selectionService.currentSelectedNodes
|
||||
.whereType<TextNode>();
|
||||
if (selection == null || textNodes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final textNode = textNodes.first;
|
||||
if (textNode.toRawString().isEmpty) {
|
||||
TransactionBuilder(editorState)
|
||||
..insertNode(
|
||||
textNode.path,
|
||||
Node(
|
||||
type: 'horizontal_rule',
|
||||
children: LinkedList(),
|
||||
attributes: {},
|
||||
),
|
||||
)
|
||||
..afterSelection =
|
||||
Selection.single(path: textNode.path.next, startOffset: 0)
|
||||
..commit();
|
||||
} else {
|
||||
TransactionBuilder(editorState)
|
||||
..insertNode(
|
||||
selection.end.path.next,
|
||||
TextNode(
|
||||
type: 'text',
|
||||
children: LinkedList(),
|
||||
attributes: {
|
||||
'subtype': 'horizontal_rule',
|
||||
},
|
||||
delta: Delta()..insert('---'),
|
||||
),
|
||||
)
|
||||
..afterSelection = selection
|
||||
..commit();
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
class HorizontalRuleWidgetBuilder extends NodeWidgetBuilder<Node> {
|
||||
@override
|
||||
Widget build(NodeWidgetContext<Node> context) {
|
||||
return _HorizontalRuleWidget(
|
||||
key: context.node.key,
|
||||
node: context.node,
|
||||
editorState: context.editorState,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
NodeValidator<Node> get nodeValidator => (node) {
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
class _HorizontalRuleWidget extends StatefulWidget {
|
||||
const _HorizontalRuleWidget({
|
||||
Key? key,
|
||||
required this.node,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
final Node node;
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
State<_HorizontalRuleWidget> createState() => __HorizontalRuleWidgetState();
|
||||
}
|
||||
|
||||
class __HorizontalRuleWidgetState extends State<_HorizontalRuleWidget>
|
||||
with SelectableMixin {
|
||||
RenderBox get _renderBox => context.findRenderObject() as RenderBox;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Container(
|
||||
height: 1,
|
||||
color: Colors.grey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Position start() => Position(path: widget.node.path, offset: 0);
|
||||
|
||||
@override
|
||||
Position end() => Position(path: widget.node.path, offset: 1);
|
||||
|
||||
@override
|
||||
Position getPositionInOffset(Offset start) => end();
|
||||
|
||||
@override
|
||||
bool get shouldCursorBlink => false;
|
||||
|
||||
@override
|
||||
CursorStyle get cursorStyle => CursorStyle.borderLine;
|
||||
|
||||
@override
|
||||
Rect? getCursorRectInPosition(Position position) {
|
||||
final size = _renderBox.size;
|
||||
return Rect.fromLTWH(-size.width / 2.0, 0, size.width, size.height);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Rect> getRectsInSelection(Selection selection) =>
|
||||
[Offset.zero & _renderBox.size];
|
||||
|
||||
@override
|
||||
Selection getSelectionInRange(Offset start, Offset end) => Selection.single(
|
||||
path: widget.node.path,
|
||||
startOffset: 0,
|
||||
endOffset: 1,
|
||||
);
|
||||
|
||||
@override
|
||||
Offset localToGlobal(Offset offset) => _renderBox.localToGlobal(offset);
|
||||
}
|
@ -0,0 +1,193 @@
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_math_fork/flutter_math.dart';
|
||||
|
||||
SelectionMenuItem teXBlockMenuItem = SelectionMenuItem(
|
||||
name: () => 'Tex',
|
||||
icon: const Icon(
|
||||
Icons.text_fields_rounded,
|
||||
color: Colors.black,
|
||||
size: 18.0,
|
||||
),
|
||||
keywords: ['tex, latex, katex'],
|
||||
handler: (editorState, _, __) {
|
||||
final selection =
|
||||
editorState.service.selectionService.currentSelection.value;
|
||||
final textNodes = editorState.service.selectionService.currentSelectedNodes
|
||||
.whereType<TextNode>();
|
||||
if (selection == null || !selection.isCollapsed || textNodes.isEmpty) {
|
||||
return;
|
||||
}
|
||||
final Path texNodePath;
|
||||
if (textNodes.first.toRawString().isEmpty) {
|
||||
texNodePath = selection.end.path;
|
||||
TransactionBuilder(editorState)
|
||||
..insertNode(
|
||||
selection.end.path,
|
||||
Node(
|
||||
type: 'tex',
|
||||
children: LinkedList(),
|
||||
attributes: {'tex': ''},
|
||||
),
|
||||
)
|
||||
..deleteNode(textNodes.first)
|
||||
..afterSelection = selection
|
||||
..commit();
|
||||
} else {
|
||||
texNodePath = selection.end.path.next;
|
||||
TransactionBuilder(editorState)
|
||||
..insertNode(
|
||||
selection.end.path.next,
|
||||
Node(
|
||||
type: 'tex',
|
||||
children: LinkedList(),
|
||||
attributes: {'tex': ''},
|
||||
),
|
||||
)
|
||||
..afterSelection = selection
|
||||
..commit();
|
||||
}
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
final texState =
|
||||
editorState.document.nodeAtPath(texNodePath)?.key?.currentState;
|
||||
if (texState != null && texState is __TeXBlockNodeWidgetState) {
|
||||
texState.showEditingDialog();
|
||||
}
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
class TeXBlockNodeWidgetBuidler extends NodeWidgetBuilder<Node> {
|
||||
@override
|
||||
Widget build(NodeWidgetContext<Node> context) {
|
||||
return _TeXBlockNodeWidget(
|
||||
key: context.node.key,
|
||||
node: context.node,
|
||||
editorState: context.editorState,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
NodeValidator<Node> get nodeValidator => (node) {
|
||||
return node.attributes['tex'] is String;
|
||||
};
|
||||
}
|
||||
|
||||
class _TeXBlockNodeWidget extends StatefulWidget {
|
||||
const _TeXBlockNodeWidget({
|
||||
Key? key,
|
||||
required this.node,
|
||||
required this.editorState,
|
||||
}) : super(key: key);
|
||||
|
||||
final Node node;
|
||||
final EditorState editorState;
|
||||
|
||||
@override
|
||||
State<_TeXBlockNodeWidget> createState() => __TeXBlockNodeWidgetState();
|
||||
}
|
||||
|
||||
class __TeXBlockNodeWidgetState extends State<_TeXBlockNodeWidget> {
|
||||
String get _tex => widget.node.attributes['tex'] as String;
|
||||
bool _isHover = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
onHover: (value) {
|
||||
setState(() {
|
||||
_isHover = value;
|
||||
});
|
||||
},
|
||||
onTap: () {
|
||||
showEditingDialog();
|
||||
},
|
||||
child: Stack(
|
||||
children: [
|
||||
_buildTex(context),
|
||||
if (_isHover) _buildDeleteButton(context),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTex(BuildContext context) {
|
||||
return Container(
|
||||
width: MediaQuery.of(context).size.width,
|
||||
padding: const EdgeInsets.symmetric(vertical: 20),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
|
||||
color: _isHover ? Colors.grey[200] : Colors.transparent,
|
||||
),
|
||||
child: Center(
|
||||
child: Math.tex(
|
||||
_tex,
|
||||
textStyle: const TextStyle(fontSize: 20),
|
||||
mathStyle: MathStyle.display,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeleteButton(BuildContext context) {
|
||||
return Positioned(
|
||||
top: -5,
|
||||
right: -5,
|
||||
child: IconButton(
|
||||
icon: Icon(
|
||||
Icons.delete_outline,
|
||||
color: Colors.blue[400],
|
||||
size: 16,
|
||||
),
|
||||
onPressed: () {
|
||||
TransactionBuilder(widget.editorState)
|
||||
..deleteNode(widget.node)
|
||||
..commit();
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void showEditingDialog() {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final controller = TextEditingController(text: _tex);
|
||||
return AlertDialog(
|
||||
title: const Text('Edit Katex'),
|
||||
content: TextField(
|
||||
controller: controller,
|
||||
maxLines: null,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
if (controller.text != _tex) {
|
||||
TransactionBuilder(widget.editorState)
|
||||
..updateNode(
|
||||
widget.node,
|
||||
{'tex': controller.text},
|
||||
)
|
||||
..commit();
|
||||
}
|
||||
},
|
||||
child: const Text('OK'),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
|
||||
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
|
||||
rich_clipboard_macos: 43364b66b9dc69d203eb8dd6d758e2d12e02723c
|
||||
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
|
||||
|
@ -44,6 +44,7 @@ dependencies:
|
||||
file_picker: ^5.0.1
|
||||
universal_html: ^2.0.8
|
||||
highlight: ^0.7.0
|
||||
flutter_math_fork: ^0.6.3+1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "cs-CZ",
|
||||
"bold": "Tučně",
|
||||
"@bold": {},
|
||||
"bulletedList": "Odrážkový seznam",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "Zaškrtávací políčko",
|
||||
"@checkbox": {},
|
||||
"embedCode": "Vložit kód",
|
||||
"@embedCode": {},
|
||||
"heading1": "Nadpis 1",
|
||||
"@heading1": {},
|
||||
"heading2": "Nadpis 2",
|
||||
"@heading2": {},
|
||||
"heading3": "Nadpis 3",
|
||||
"@heading3": {},
|
||||
"highlight": "Zvýraznění",
|
||||
"@highlight": {},
|
||||
"image": "Obrázek",
|
||||
"@image": {},
|
||||
"italic": "Kurzíva",
|
||||
"@italic": {},
|
||||
"link": "Odkaz",
|
||||
"@link": {},
|
||||
"numberedList": "Číslovaný seznam",
|
||||
"@numberedList": {},
|
||||
"quote": "Citace",
|
||||
"@quote": {},
|
||||
"strikethrough": "Přeškrtnutí",
|
||||
"@strikethrough": {},
|
||||
"text": "Text",
|
||||
"@text": {},
|
||||
"underline": "Podtržení",
|
||||
"@underline": {}
|
||||
}
|
@ -1,35 +1,35 @@
|
||||
{
|
||||
"@@locale": "fr-CA",
|
||||
"bold": "",
|
||||
"bold": "gras",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"bulletedList": "liste à puces",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"checkbox": "case à cocher",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"embedCode": "incorporer Code",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"heading1": "en-tête1",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"heading2": "en-tête2",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"heading3": "en-tête3",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"highlight": "mettre en évidence",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"image": "l’image",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"italic": "italique",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"link": "lien",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"numberedList": "liste numérotée",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"quote": "citation",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"strikethrough": "barré",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"text": "texte",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"underline": "souligner",
|
||||
"@underline": {}
|
||||
}
|
@ -1,35 +1,35 @@
|
||||
{
|
||||
"@@locale": "fr-FR",
|
||||
"bold": "",
|
||||
"bold": "Gras",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"bulletedList": "List à puces",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"checkbox": "Case à cocher",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"embedCode": "Incorporer code",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"heading1": "Titre 1",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"heading2": "Titre 2",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"heading3": "Titre 3",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"highlight": "Surligné",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"image": "Image",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"italic": "Italique",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"link": "Lien",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"numberedList": "Liste numérotée",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"quote": "Citation",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"strikethrough": "Barré",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"text": "Texte",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"underline": "Souligné",
|
||||
"@underline": {}
|
||||
}
|
@ -1,35 +1,35 @@
|
||||
{
|
||||
"@@locale": "hu-HU",
|
||||
"bold": "",
|
||||
"bold": "bátor",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"bulletedList": "pontozott lista",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"checkbox": "jelölőnégyzetet",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"embedCode": "Beágyazás",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"heading1": "címsor1",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"heading2": "címsor2",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"heading3": "címsor3",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"highlight": "Kiemel",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"image": "kép",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"italic": "dőlt",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"link": "link",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"numberedList": "számozottLista",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"quote": "idézet",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"strikethrough": "áthúzott",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"text": "szöveg",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"underline": "aláhúzás",
|
||||
"@underline": {}
|
||||
}
|
@ -1,35 +1,35 @@
|
||||
{
|
||||
"@@locale": "id-ID",
|
||||
"bold": "",
|
||||
"bold": "berani",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"bulletedList": "daftar berpoin",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"checkbox": "kotak centang",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"embedCode": "menyematkan Kode",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"heading1": "pos1",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"heading2": "pos2",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"heading3": "pos3",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"highlight": "menyorot",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"image": "gambar",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"italic": "miring",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"link": "tautan",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"numberedList": "daftar bernomor",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"quote": "mengutip",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"strikethrough": "coret",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"text": "teks",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"underline": "menggarisbawahi",
|
||||
"@underline": {}
|
||||
}
|
@ -1,35 +1,35 @@
|
||||
{
|
||||
"@@locale": "it-IT",
|
||||
"bold": "",
|
||||
"bold": "Grassetto",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"bulletedList": "Elenco puntato",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"checkbox": "Casella di spunta",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"embedCode": "Incorpora codice",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"heading1": "H1",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"heading2": "H2",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"heading3": "H3",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"highlight": "Evidenzia",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"image": "Immagine",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"italic": "Corsivo",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"link": "Collegamento",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"numberedList": "Elenco numerato",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"quote": "Cita",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"strikethrough": "Barrato",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"text": "Testo",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"underline": "Sottolineato",
|
||||
"@underline": {}
|
||||
}
|
@ -0,0 +1,35 @@
|
||||
{
|
||||
"@@locale": "ml_IN",
|
||||
"bold": "ബോൾഡ്",
|
||||
"@bold": {},
|
||||
"bulletedList": "ബുള്ളറ്റഡ് പട്ടിക",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "ചെക്ക്ബോക്സ്",
|
||||
"@checkbox": {},
|
||||
"embedCode": "എംബെഡഡ് കോഡ്",
|
||||
"@embedCode": {},
|
||||
"heading1": "തലക്കെട്ട് 1",
|
||||
"@heading1": {},
|
||||
"heading2": "തലക്കെട്ട് 2",
|
||||
"@heading2": {},
|
||||
"heading3": "തലക്കെട്ട് 3",
|
||||
"@heading3": {},
|
||||
"highlight": "പ്രമുഖമാക്കിക്കാട്ടുക",
|
||||
"@highlight": {},
|
||||
"image": "ചിത്രം",
|
||||
"@image": {},
|
||||
"italic": "ഇറ്റാലിക്",
|
||||
"@italic": {},
|
||||
"link": "ലിങ്ക്",
|
||||
"@link": {},
|
||||
"numberedList": "അക്കമിട്ട പട്ടിക",
|
||||
"@numberedList": {},
|
||||
"quote": "ഉദ്ധരണി",
|
||||
"@quote": {},
|
||||
"strikethrough": "സ്ട്രൈക്ക്ത്രൂ",
|
||||
"@strikethrough": {},
|
||||
"text": "വചനം",
|
||||
"@text": {},
|
||||
"underline": "അടിവരയിടുക",
|
||||
"@underline": {}
|
||||
}
|
@ -1,35 +1,35 @@
|
||||
{
|
||||
"@@locale": "pt-PT",
|
||||
"bold": "",
|
||||
"bold": "negrito",
|
||||
"@bold": {},
|
||||
"bulletedList": "",
|
||||
"bulletedList": "lista com marcadores",
|
||||
"@bulletedList": {},
|
||||
"checkbox": "",
|
||||
"checkbox": "caixa de seleção",
|
||||
"@checkbox": {},
|
||||
"embedCode": "",
|
||||
"embedCode": "Código embutido",
|
||||
"@embedCode": {},
|
||||
"heading1": "",
|
||||
"heading1": "Cabeçallho 1",
|
||||
"@heading1": {},
|
||||
"heading2": "",
|
||||
"heading2": "Cabeçallho 2",
|
||||
"@heading2": {},
|
||||
"heading3": "",
|
||||
"heading3": "Cabeçallho 3",
|
||||
"@heading3": {},
|
||||
"highlight": "",
|
||||
"highlight": "realçar",
|
||||
"@highlight": {},
|
||||
"image": "",
|
||||
"image": "imagem",
|
||||
"@image": {},
|
||||
"italic": "",
|
||||
"italic": "itálico",
|
||||
"@italic": {},
|
||||
"link": "",
|
||||
"link": "link",
|
||||
"@link": {},
|
||||
"numberedList": "",
|
||||
"numberedList": "lista numerada",
|
||||
"@numberedList": {},
|
||||
"quote": "",
|
||||
"quote": "citar",
|
||||
"@quote": {},
|
||||
"strikethrough": "",
|
||||
"strikethrough": "tachado",
|
||||
"@strikethrough": {},
|
||||
"text": "",
|
||||
"text": "texto",
|
||||
"@text": {},
|
||||
"underline": "",
|
||||
"underline": "sublinhado",
|
||||
"@underline": {}
|
||||
}
|
@ -17,9 +17,9 @@ extension NodeAttributesExtensions on Attributes {
|
||||
return containsKey(BuiltInAttributeKey.quote);
|
||||
}
|
||||
|
||||
int? get number {
|
||||
num? get number {
|
||||
if (containsKey(BuiltInAttributeKey.number) &&
|
||||
this[BuiltInAttributeKey.number] is int) {
|
||||
this[BuiltInAttributeKey.number] is num) {
|
||||
return this[BuiltInAttributeKey.number];
|
||||
}
|
||||
return null;
|
||||
@ -27,7 +27,7 @@ extension NodeAttributesExtensions on Attributes {
|
||||
|
||||
bool get code {
|
||||
if (containsKey(BuiltInAttributeKey.code) &&
|
||||
this[BuiltInAttributeKey.code] == true) {
|
||||
this[BuiltInAttributeKey.code] is bool) {
|
||||
return this[BuiltInAttributeKey.code];
|
||||
}
|
||||
return false;
|
||||
@ -63,11 +63,14 @@ extension DeltaAttributesExtensions on Attributes {
|
||||
this[BuiltInAttributeKey.strikethrough] == true);
|
||||
}
|
||||
|
||||
static const whiteInt = 0XFFFFFFFF;
|
||||
|
||||
Color? get color {
|
||||
if (containsKey(BuiltInAttributeKey.color) &&
|
||||
this[BuiltInAttributeKey.color] is String) {
|
||||
return Color(
|
||||
int.parse(this[BuiltInAttributeKey.color]),
|
||||
// If the parse fails returns white by default
|
||||
int.tryParse(this[BuiltInAttributeKey.color]) ?? whiteInt,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
@ -77,8 +80,7 @@ extension DeltaAttributesExtensions on Attributes {
|
||||
if (containsKey(BuiltInAttributeKey.backgroundColor) &&
|
||||
this[BuiltInAttributeKey.backgroundColor] is String) {
|
||||
return Color(
|
||||
int.parse(this[BuiltInAttributeKey.backgroundColor]),
|
||||
);
|
||||
int.tryParse(this[BuiltInAttributeKey.backgroundColor]) ?? whiteInt);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ import 'package:appflowy_editor/src/document/text_delta.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
extension TextNodeExtension on TextNode {
|
||||
dynamic getAttributeInSelection(Selection selection, String styleKey) {
|
||||
T? getAttributeInSelection<T>(Selection selection, String styleKey) {
|
||||
final ops = delta.whereType<TextInsert>();
|
||||
final startOffset =
|
||||
selection.isBackward ? selection.start.offset : selection.end.offset;
|
||||
@ -19,8 +19,9 @@ extension TextNodeExtension on TextNode {
|
||||
}
|
||||
final length = op.length;
|
||||
if (start < endOffset && start + length > startOffset) {
|
||||
if (op.attributes?.containsKey(styleKey) == true) {
|
||||
return op.attributes![styleKey];
|
||||
final attributes = op.attributes;
|
||||
if (attributes != null && attributes[styleKey] is T?) {
|
||||
return attributes[styleKey];
|
||||
}
|
||||
}
|
||||
start += length;
|
||||
|
@ -16,6 +16,7 @@ import 'package:intl/message_lookup_by_library.dart';
|
||||
import 'package:intl/src/intl_helpers.dart';
|
||||
|
||||
import 'messages_ca.dart' as messages_ca;
|
||||
import 'messages_cs-CZ.dart' as messages_cs_cz;
|
||||
import 'messages_de-DE.dart' as messages_de_de;
|
||||
import 'messages_en.dart' as messages_en;
|
||||
import 'messages_es-VE.dart' as messages_es_ve;
|
||||
@ -25,6 +26,8 @@ import 'messages_hu-HU.dart' as messages_hu_hu;
|
||||
import 'messages_id-ID.dart' as messages_id_id;
|
||||
import 'messages_it-IT.dart' as messages_it_it;
|
||||
import 'messages_ja-JP.dart' as messages_ja_jp;
|
||||
import 'messages_ml_IN.dart' as messages_ml_in;
|
||||
import 'messages_nl-NL.dart' as messages_nl_nl;
|
||||
import 'messages_pl-PL.dart' as messages_pl_pl;
|
||||
import 'messages_pt-BR.dart' as messages_pt_br;
|
||||
import 'messages_pt-PT.dart' as messages_pt_pt;
|
||||
@ -36,6 +39,7 @@ import 'messages_zh-TW.dart' as messages_zh_tw;
|
||||
typedef Future<dynamic> LibraryLoader();
|
||||
Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'ca': () => new Future.value(null),
|
||||
'cs_CZ': () => new Future.value(null),
|
||||
'de_DE': () => new Future.value(null),
|
||||
'en': () => new Future.value(null),
|
||||
'es_VE': () => new Future.value(null),
|
||||
@ -45,6 +49,8 @@ Map<String, LibraryLoader> _deferredLibraries = {
|
||||
'id_ID': () => new Future.value(null),
|
||||
'it_IT': () => new Future.value(null),
|
||||
'ja_JP': () => new Future.value(null),
|
||||
'ml_IN': () => new Future.value(null),
|
||||
'nl_NL': () => new Future.value(null),
|
||||
'pl_PL': () => new Future.value(null),
|
||||
'pt_BR': () => new Future.value(null),
|
||||
'pt_PT': () => new Future.value(null),
|
||||
@ -58,6 +64,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
||||
switch (localeName) {
|
||||
case 'ca':
|
||||
return messages_ca.messages;
|
||||
case 'cs_CZ':
|
||||
return messages_cs_cz.messages;
|
||||
case 'de_DE':
|
||||
return messages_de_de.messages;
|
||||
case 'en':
|
||||
@ -76,6 +84,10 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
||||
return messages_it_it.messages;
|
||||
case 'ja_JP':
|
||||
return messages_ja_jp.messages;
|
||||
case 'ml_IN':
|
||||
return messages_ml_in.messages;
|
||||
case 'nl_NL':
|
||||
return messages_nl_nl.messages;
|
||||
case 'pl_PL':
|
||||
return messages_pl_pl.messages;
|
||||
case 'pt_BR':
|
||||
|
@ -0,0 +1,44 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a cs_CZ locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'cs_CZ';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage("Tučně"),
|
||||
"bulletedList":
|
||||
MessageLookupByLibrary.simpleMessage("Odrážkový seznam"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("Zaškrtávací políčko"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Vložit kód"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("Nadpis 1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("Nadpis 2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("Nadpis 3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("Zvýraznění"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("Obrázek"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("Kurzíva"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("Odkaz"),
|
||||
"numberedList":
|
||||
MessageLookupByLibrary.simpleMessage("Číslovaný seznam"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("Citace"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("Přeškrtnutí"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("Text"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("Podtržení")
|
||||
};
|
||||
}
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
"bold": MessageLookupByLibrary.simpleMessage("gras"),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage("liste à puces"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("case à cocher"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("incorporer Code"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("en-tête1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("en-tête2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("en-tête3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("mettre en évidence"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("l’image"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("italique"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("lien"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("liste numérotée"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("citation"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("barré"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("texte"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("souligner")
|
||||
};
|
||||
}
|
||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
"bold": MessageLookupByLibrary.simpleMessage("Gras"),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage("List à puces"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("Case à cocher"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Incorporer code"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("Titre 1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("Titre 2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("Titre 3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("Surligné"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("Image"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("Italique"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("Lien"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("Liste numérotée"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("Citation"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("Barré"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("Texte"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("Souligné")
|
||||
};
|
||||
}
|
||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
"bold": MessageLookupByLibrary.simpleMessage("bátor"),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage("pontozott lista"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("jelölőnégyzetet"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Beágyazás"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("címsor1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("címsor2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("címsor3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("Kiemel"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("kép"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("dőlt"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("link"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("számozottLista"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("idézet"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("áthúzott"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("szöveg"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("aláhúzás")
|
||||
};
|
||||
}
|
||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
"bold": MessageLookupByLibrary.simpleMessage("berani"),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage("daftar berpoin"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("kotak centang"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("menyematkan Kode"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("pos1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("pos2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("pos3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("menyorot"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("gambar"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("miring"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("tautan"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("daftar bernomor"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("mengutip"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("coret"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("teks"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("menggarisbawahi")
|
||||
};
|
||||
}
|
||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
"bold": MessageLookupByLibrary.simpleMessage("Grassetto"),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage("Elenco puntato"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("Casella di spunta"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Incorpora codice"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("Evidenzia"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("Immagine"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("Corsivo"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("Collegamento"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("Elenco numerato"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("Cita"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("Barrato"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("Testo"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("Sottolineato")
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a ml_IN locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'ml_IN';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage("ബോൾഡ്"),
|
||||
"bulletedList":
|
||||
MessageLookupByLibrary.simpleMessage("ബുള്ളറ്റഡ് പട്ടിക"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("ചെക്ക്ബോക്സ്"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("എംബെഡഡ് കോഡ്"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("തലക്കെട്ട് 1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("തലക്കെട്ട് 2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("തലക്കെട്ട് 3"),
|
||||
"highlight":
|
||||
MessageLookupByLibrary.simpleMessage("പ്രമുഖമാക്കിക്കാട്ടുക"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("ചിത്രം"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("ഇറ്റാലിക്"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("ലിങ്ക്"),
|
||||
"numberedList":
|
||||
MessageLookupByLibrary.simpleMessage("അക്കമിട്ട പട്ടിക"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("ഉദ്ധരണി"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("സ്ട്രൈക്ക്ത്രൂ"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("വചനം"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("അടിവരയിടുക")
|
||||
};
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a nl_NL locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names, avoid_escaping_inner_quotes
|
||||
// ignore_for_file:unnecessary_string_interpolations, unnecessary_string_escapes
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'nl_NL';
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage("Vet"),
|
||||
"bulletedList":
|
||||
MessageLookupByLibrary.simpleMessage("Opsommingstekens"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("Selectievakje"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Invoegcode"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("Highlight"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("Afbeelding"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("Cursief"),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("Nummering"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("Quote"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("Doorhalen"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("Tekst"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("Onderstrepen")
|
||||
};
|
||||
}
|
@ -22,21 +22,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
"bold": MessageLookupByLibrary.simpleMessage("Negrito"),
|
||||
"bulletedList":
|
||||
MessageLookupByLibrary.simpleMessage("Lista de marcadores"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("Caixa de seleção"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Código incorporado"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("Destacar"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("Imagem"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("Itálico"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("Link"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("Lista numerada"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("Citar"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("Rasurar"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("Texto"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("Sublinhar")
|
||||
};
|
||||
}
|
||||
|
@ -22,21 +22,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
||||
"bold": MessageLookupByLibrary.simpleMessage("negrito"),
|
||||
"bulletedList":
|
||||
MessageLookupByLibrary.simpleMessage("lista com marcadores"),
|
||||
"checkbox": MessageLookupByLibrary.simpleMessage("caixa de seleção"),
|
||||
"embedCode": MessageLookupByLibrary.simpleMessage("Código embutido"),
|
||||
"heading1": MessageLookupByLibrary.simpleMessage("Cabeçallho 1"),
|
||||
"heading2": MessageLookupByLibrary.simpleMessage("Cabeçallho 2"),
|
||||
"heading3": MessageLookupByLibrary.simpleMessage("Cabeçallho 3"),
|
||||
"highlight": MessageLookupByLibrary.simpleMessage("realçar"),
|
||||
"image": MessageLookupByLibrary.simpleMessage("imagem"),
|
||||
"italic": MessageLookupByLibrary.simpleMessage("itálico"),
|
||||
"link": MessageLookupByLibrary.simpleMessage("link"),
|
||||
"numberedList": MessageLookupByLibrary.simpleMessage("lista numerada"),
|
||||
"quote": MessageLookupByLibrary.simpleMessage("citar"),
|
||||
"strikethrough": MessageLookupByLibrary.simpleMessage("tachado"),
|
||||
"text": MessageLookupByLibrary.simpleMessage("texto"),
|
||||
"underline": MessageLookupByLibrary.simpleMessage("sublinhado")
|
||||
};
|
||||
}
|
||||
|
@ -220,6 +220,7 @@ class AppLocalizationDelegate
|
||||
return const <Locale>[
|
||||
Locale.fromSubtags(languageCode: 'en'),
|
||||
Locale.fromSubtags(languageCode: 'ca'),
|
||||
Locale.fromSubtags(languageCode: 'cs', countryCode: 'CZ'),
|
||||
Locale.fromSubtags(languageCode: 'de', countryCode: 'DE'),
|
||||
Locale.fromSubtags(languageCode: 'es', countryCode: 'VE'),
|
||||
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CA'),
|
||||
@ -228,6 +229,8 @@ class AppLocalizationDelegate
|
||||
Locale.fromSubtags(languageCode: 'id', countryCode: 'ID'),
|
||||
Locale.fromSubtags(languageCode: 'it', countryCode: 'IT'),
|
||||
Locale.fromSubtags(languageCode: 'ja', countryCode: 'JP'),
|
||||
Locale.fromSubtags(languageCode: 'ml', countryCode: 'IN'),
|
||||
Locale.fromSubtags(languageCode: 'nl', countryCode: 'NL'),
|
||||
Locale.fromSubtags(languageCode: 'pl', countryCode: 'PL'),
|
||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'BR'),
|
||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'),
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
abstract class BuiltInTextWidget extends StatefulWidget {
|
||||
|
@ -1,5 +1,6 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class CursorWidget extends StatefulWidget {
|
||||
@ -9,9 +10,13 @@ class CursorWidget extends StatefulWidget {
|
||||
required this.rect,
|
||||
required this.color,
|
||||
this.blinkingInterval = 0.5,
|
||||
this.shouldBlink = true,
|
||||
this.cursorStyle = CursorStyle.verticalLine,
|
||||
}) : super(key: key);
|
||||
|
||||
final double blinkingInterval; // milliseconds
|
||||
final bool shouldBlink;
|
||||
final CursorStyle cursorStyle;
|
||||
final Color color;
|
||||
final Rect rect;
|
||||
final LayerLink layerLink;
|
||||
@ -67,11 +72,28 @@ class CursorWidgetState extends State<CursorWidget> {
|
||||
// Ignore the gestures in cursor
|
||||
// to solve the problem that cursor area cannot be selected.
|
||||
child: IgnorePointer(
|
||||
child: Container(
|
||||
color: showCursor ? widget.color : Colors.transparent,
|
||||
),
|
||||
child: _buildCursor(context),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCursor(BuildContext context) {
|
||||
var color = widget.color;
|
||||
if (widget.shouldBlink && !showCursor) {
|
||||
color = Colors.transparent;
|
||||
}
|
||||
switch (widget.cursorStyle) {
|
||||
case CursorStyle.verticalLine:
|
||||
return Container(
|
||||
color: color,
|
||||
);
|
||||
case CursorStyle.borderLine:
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
border: Border.all(color: color, width: 2),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,11 @@ import 'package:appflowy_editor/src/document/position.dart';
|
||||
import 'package:appflowy_editor/src/document/selection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum CursorStyle {
|
||||
verticalLine,
|
||||
borderLine,
|
||||
}
|
||||
|
||||
/// [SelectableMixin] is used for the editor to calculate the position
|
||||
/// and size of the selection.
|
||||
///
|
||||
@ -53,4 +58,8 @@ mixin SelectableMixin<T extends StatefulWidget> on State<T> {
|
||||
Selection? getWorldBoundaryInOffset(Offset start) {
|
||||
return null;
|
||||
}
|
||||
|
||||
bool get shouldCursorBlink => true;
|
||||
|
||||
CursorStyle get cursorStyle => CursorStyle.verticalLine;
|
||||
}
|
||||
|
@ -169,6 +169,14 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
||||
insertBulletedListAfterSelection(editorState);
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: () => AppFlowyEditorLocalizations.current.numberedList,
|
||||
icon: _selectionMenuIcon('number'),
|
||||
keywords: ['numbered list', 'list', 'ordered list'],
|
||||
handler: (editorState, _, __) {
|
||||
insertNumberedListAfterSelection(editorState);
|
||||
},
|
||||
),
|
||||
SelectionMenuItem(
|
||||
name: () => AppFlowyEditorLocalizations.current.checkbox,
|
||||
icon: _selectionMenuIcon('checkbox'),
|
||||
|
@ -7,10 +7,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
typedef SelectionMenuItemHandler = void Function(
|
||||
EditorState editorState,
|
||||
SelectionMenuService menuService,
|
||||
BuildContext context,
|
||||
);
|
||||
EditorState editorState,
|
||||
SelectionMenuService menuService,
|
||||
BuildContext context,
|
||||
);
|
||||
|
||||
/// Selection Menu Item
|
||||
class SelectionMenuItem {
|
||||
@ -23,7 +23,7 @@ class SelectionMenuItem {
|
||||
this.handler = (editorState, menuService, context) {
|
||||
_deleteToSlash(editorState);
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
handler(editorState, menuService, context);
|
||||
handler(editorState, menuService, context);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
@ -333,8 +333,10 @@ void showLinkMenu(
|
||||
final textNode = node.first as TextNode;
|
||||
String? linkText;
|
||||
if (textNode.allSatisfyLinkInSelection(selection)) {
|
||||
linkText =
|
||||
textNode.getAttributeInSelection(selection, BuiltInAttributeKey.href);
|
||||
linkText = textNode.getAttributeInSelection<String>(
|
||||
selection,
|
||||
BuiltInAttributeKey.href,
|
||||
);
|
||||
}
|
||||
_linkMenuOverlay = OverlayEntry(builder: (context) {
|
||||
return Positioned(
|
||||
|
@ -34,6 +34,13 @@ void insertBulletedListAfterSelection(EditorState editorState) {
|
||||
});
|
||||
}
|
||||
|
||||
void insertNumberedListAfterSelection(EditorState editorState) {
|
||||
insertTextNodeAfterSelection(editorState, {
|
||||
BuiltInAttributeKey.subtype: BuiltInAttributeKey.numberList,
|
||||
BuiltInAttributeKey.number: 1,
|
||||
});
|
||||
}
|
||||
|
||||
bool insertTextNodeAfterSelection(
|
||||
EditorState editorState, Attributes attributes) {
|
||||
final selection = editorState.service.selectionService.currentSelection.value;
|
||||
|
@ -3,7 +3,6 @@ import 'package:appflowy_editor/src/service/internal_key_event_handlers/number_l
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
||||
|
||||
// Handle delete text.
|
||||
ShortcutEventHandler deleteTextHandler = (editorState, event) {
|
||||
@ -84,6 +83,11 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
||||
}
|
||||
} else {
|
||||
if (textNodes.isEmpty) {
|
||||
if (nonTextNodes.isNotEmpty) {
|
||||
transactionBuilder.afterSelection =
|
||||
Selection.collapsed(selection.start);
|
||||
}
|
||||
transactionBuilder.commit();
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
final startPosition = selection.start;
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/infra/html_converter.dart';
|
||||
import 'package:appflowy_editor/src/document/node_iterator.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/service/internal_key_event_handlers/number_list_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:rich_clipboard/rich_clipboard.dart';
|
||||
|
@ -3,7 +3,6 @@ import 'dart:collection';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
||||
import './number_list_helper.dart';
|
||||
|
||||
/// Handle some cases where enter is pressed and shift is not pressed.
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "dart:math";
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:appflowy_editor/src/service/default_text_operations/format_rich_text_style.dart';
|
||||
@ -49,7 +48,7 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
|
||||
.substring(selection.start.offset, selection.end.offset);
|
||||
|
||||
// toggle code style when selected some text
|
||||
if (selectionText.length > 0) {
|
||||
if (selectionText.isNotEmpty) {
|
||||
formatEmbedCode(editorState);
|
||||
return KeyEventResult.handled;
|
||||
}
|
||||
@ -124,3 +123,121 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
|
||||
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
||||
// convert ~~abc~~ to strikethrough abc.
|
||||
ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) {
|
||||
final selectionService = editorState.service.selectionService;
|
||||
final selection = selectionService.currentSelection.value;
|
||||
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
||||
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
final textNode = textNodes.first;
|
||||
final text = textNode.toRawString().substring(0, selection.end.offset);
|
||||
|
||||
// make sure the last two characters are ~~.
|
||||
if (text.length < 2 || text[selection.end.offset - 1] != '~') {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
// find all the index of `~`.
|
||||
final tildeIndexes = <int>[];
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
if (text[i] == '~') {
|
||||
tildeIndexes.add(i);
|
||||
}
|
||||
}
|
||||
|
||||
if (tildeIndexes.length < 3) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
// make sure the second to last and third to last tildes are connected.
|
||||
final thirdToLastTildeIndex = tildeIndexes[tildeIndexes.length - 3];
|
||||
final secondToLastTildeIndex = tildeIndexes[tildeIndexes.length - 2];
|
||||
final lastTildeIndex = tildeIndexes[tildeIndexes.length - 1];
|
||||
if (secondToLastTildeIndex != thirdToLastTildeIndex + 1 ||
|
||||
lastTildeIndex == secondToLastTildeIndex + 1) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
// delete the last three tildes.
|
||||
// update the style of the text surround by `~~ ~~` to strikethrough.
|
||||
// and update the cursor position.
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, lastTildeIndex, 1)
|
||||
..deleteText(textNode, thirdToLastTildeIndex, 2)
|
||||
..formatText(
|
||||
textNode,
|
||||
thirdToLastTildeIndex,
|
||||
selection.end.offset - thirdToLastTildeIndex - 2,
|
||||
{
|
||||
BuiltInAttributeKey.strikethrough: true,
|
||||
},
|
||||
)
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: textNode.path,
|
||||
offset: selection.end.offset - 3,
|
||||
),
|
||||
)
|
||||
..commit();
|
||||
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
||||
/// To create a link, enclose the link text in brackets (e.g., [link text]).
|
||||
/// Then, immediately follow it with the URL in parentheses (e.g., (https://example.com)).
|
||||
ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
|
||||
final selectionService = editorState.service.selectionService;
|
||||
final selection = selectionService.currentSelection.value;
|
||||
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
||||
if (selection == null || !selection.isSingle || textNodes.length != 1) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
// find all of the indexs for important characters
|
||||
final textNode = textNodes.first;
|
||||
final text = textNode.toRawString();
|
||||
final firstOpeningBracket = text.indexOf('[');
|
||||
final firstClosingBracket = text.indexOf(']');
|
||||
|
||||
// use regex to validate the format of the link
|
||||
// note: this enforces that the link has http or https
|
||||
final regexp = RegExp(r'\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d./?=#]+)$');
|
||||
final match = regexp.firstMatch(text);
|
||||
if (match == null) {
|
||||
return KeyEventResult.ignored;
|
||||
}
|
||||
|
||||
// extract the text and the url of the link
|
||||
final linkText = match.group(1);
|
||||
final linkUrl = match.group(2);
|
||||
|
||||
// Delete the initial opening bracket,
|
||||
// update the href attribute of the text surrounded by [ ] to the url,
|
||||
// delete everything after the text,
|
||||
// and update the cursor position.
|
||||
TransactionBuilder(editorState)
|
||||
..deleteText(textNode, firstOpeningBracket, 1)
|
||||
..formatText(
|
||||
textNode,
|
||||
firstOpeningBracket,
|
||||
firstClosingBracket - firstOpeningBracket - 1,
|
||||
{
|
||||
BuiltInAttributeKey.href: linkUrl,
|
||||
},
|
||||
)
|
||||
..deleteText(textNode, firstClosingBracket - 1,
|
||||
selection.end.offset - firstClosingBracket)
|
||||
..afterSelection = Selection.collapsed(
|
||||
Position(
|
||||
path: textNode.path,
|
||||
offset: firstOpeningBracket + linkText!.length,
|
||||
),
|
||||
)
|
||||
..commit();
|
||||
|
||||
return KeyEventResult.handled;
|
||||
};
|
||||
|
@ -457,6 +457,8 @@ class _AppFlowySelectionState extends State<AppFlowySelection>
|
||||
rect: cursorRect,
|
||||
color: widget.cursorColor,
|
||||
layerLink: node.layerLink,
|
||||
shouldBlink: selectable.shouldCursorBlink,
|
||||
cursorStyle: selectable.cursorStyle,
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -263,10 +263,21 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
||||
command: 'shift+underscore',
|
||||
handler: doubleUnderscoresToBold,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Backquote to code',
|
||||
command: 'backquote',
|
||||
handler: backquoteToCodeHandler,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Double tilde to strikethrough',
|
||||
command: 'shift+tilde',
|
||||
handler: doubleTildeToStrikethrough,
|
||||
),
|
||||
ShortcutEvent(
|
||||
key: 'Markdown link to link',
|
||||
command: 'shift+parenthesis right',
|
||||
handler: markdownLinkToLinkHandler,
|
||||
),
|
||||
// https://github.com/flutter/flutter/issues/104944
|
||||
// Workaround: Using space editing on the web platform often results in errors,
|
||||
// so adding a shortcut event to handle the space input instead of using the
|
||||
|
@ -1,12 +1,13 @@
|
||||
name: appflowy_editor
|
||||
description: A highly customizable rich-text editor for Flutter
|
||||
version: 0.0.5
|
||||
version: 0.0.6
|
||||
homepage: https://github.com/AppFlowy-IO/AppFlowy
|
||||
|
||||
platforms:
|
||||
linux:
|
||||
macos:
|
||||
windows:
|
||||
web:
|
||||
|
||||
environment:
|
||||
sdk: ">=2.17.0 <3.0.0"
|
||||
@ -32,6 +33,7 @@ dev_dependencies:
|
||||
sdk: flutter
|
||||
flutter_lints: ^2.0.1
|
||||
network_image_mock: ^2.1.1
|
||||
mockito: ^5.3.2
|
||||
|
||||
# For information on the generic Dart part of this file, see the
|
||||
# following page: https://dart.dev/tools/pub/pubspec
|
||||
|
@ -0,0 +1,201 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('NodeAttributesExtensions::', () {
|
||||
test('heading', () {
|
||||
final Attributes attribute = {
|
||||
'subtype': 'heading',
|
||||
'heading': 'AppFlowy',
|
||||
};
|
||||
expect(attribute.heading, 'AppFlowy');
|
||||
});
|
||||
|
||||
test('heading - text is not String return null', () {
|
||||
final Attributes attribute = {
|
||||
'subtype': 'heading',
|
||||
'heading': 123,
|
||||
};
|
||||
expect(attribute.heading, null);
|
||||
});
|
||||
|
||||
test('heading - subtype is not "heading" return null', () {
|
||||
final Attributes attribute = {
|
||||
'subtype': 'code',
|
||||
'heading': 'Hello World!',
|
||||
};
|
||||
expect(attribute.heading, null);
|
||||
});
|
||||
|
||||
test('quote', () {
|
||||
final Attributes attribute = {
|
||||
'quote': 'quote text',
|
||||
};
|
||||
expect(attribute.quote, true);
|
||||
});
|
||||
|
||||
test('number - int', () {
|
||||
final Attributes attribute = {
|
||||
'number': 99,
|
||||
};
|
||||
expect(attribute.number, 99);
|
||||
});
|
||||
|
||||
test('number - double', () {
|
||||
final Attributes attribute = {
|
||||
'number': 12.34,
|
||||
};
|
||||
expect(attribute.number, 12.34);
|
||||
});
|
||||
|
||||
test('number - return null', () {
|
||||
final Attributes attribute = {
|
||||
'code': 12.34,
|
||||
};
|
||||
expect(attribute.number, null);
|
||||
});
|
||||
|
||||
test('code', () {
|
||||
final Attributes attribute = {
|
||||
'code': true,
|
||||
};
|
||||
expect(attribute.code, true);
|
||||
});
|
||||
|
||||
test('code - return false', () {
|
||||
final Attributes attribute = {
|
||||
'quote': true,
|
||||
};
|
||||
expect(attribute.code, false);
|
||||
});
|
||||
|
||||
test('check', () {
|
||||
final Attributes attribute = {
|
||||
'checkbox': true,
|
||||
};
|
||||
expect(attribute.check, true);
|
||||
});
|
||||
|
||||
test('check - return false', () {
|
||||
final Attributes attribute = {
|
||||
'quote': true,
|
||||
};
|
||||
expect(attribute.check, false);
|
||||
});
|
||||
});
|
||||
|
||||
group('DeltaAttributesExtensions::', () {
|
||||
test('bold', () {
|
||||
final Attributes attribute = {
|
||||
'bold': true,
|
||||
};
|
||||
expect(attribute.bold, true);
|
||||
});
|
||||
|
||||
test('bold - return false', () {
|
||||
final Attributes attribute = {
|
||||
'bold': 123,
|
||||
};
|
||||
expect(attribute.bold, false);
|
||||
});
|
||||
|
||||
test('italic', () {
|
||||
final Attributes attribute = {
|
||||
'italic': true,
|
||||
};
|
||||
expect(attribute.italic, true);
|
||||
});
|
||||
|
||||
test('italic - return false', () {
|
||||
final Attributes attribute = {
|
||||
'italic': 123,
|
||||
};
|
||||
expect(attribute.italic, false);
|
||||
});
|
||||
|
||||
test('underline', () {
|
||||
final Attributes attribute = {
|
||||
'underline': true,
|
||||
};
|
||||
expect(attribute.underline, true);
|
||||
});
|
||||
|
||||
test('underline - return false', () {
|
||||
final Attributes attribute = {
|
||||
'underline': 123,
|
||||
};
|
||||
expect(attribute.underline, false);
|
||||
});
|
||||
|
||||
test('strikethrough', () {
|
||||
final Attributes attribute = {
|
||||
'strikethrough': true,
|
||||
};
|
||||
expect(attribute.strikethrough, true);
|
||||
});
|
||||
|
||||
test('strikethrough - return false', () {
|
||||
final Attributes attribute = {
|
||||
'strikethrough': 123,
|
||||
};
|
||||
expect(attribute.strikethrough, false);
|
||||
});
|
||||
|
||||
test('color', () {
|
||||
final Attributes attribute = {
|
||||
'color': '0xff212fff',
|
||||
};
|
||||
expect(attribute.color, const Color(0XFF212FFF));
|
||||
});
|
||||
|
||||
test('color - return null', () {
|
||||
final Attributes attribute = {
|
||||
'color': 123,
|
||||
};
|
||||
expect(attribute.color, null);
|
||||
});
|
||||
|
||||
test('color - parse failure return white', () {
|
||||
final Attributes attribute = {
|
||||
'color': 'hello123',
|
||||
};
|
||||
expect(attribute.color, const Color(0XFFFFFFFF));
|
||||
});
|
||||
|
||||
test('backgroundColor', () {
|
||||
final Attributes attribute = {
|
||||
'backgroundColor': '0xff678fff',
|
||||
};
|
||||
expect(attribute.backgroundColor, const Color(0XFF678FFF));
|
||||
});
|
||||
|
||||
test('backgroundColor - return null', () {
|
||||
final Attributes attribute = {
|
||||
'backgroundColor': 123,
|
||||
};
|
||||
expect(attribute.backgroundColor, null);
|
||||
});
|
||||
|
||||
test('backgroundColor - parse failure return white', () {
|
||||
final Attributes attribute = {
|
||||
'backgroundColor': 'hello123',
|
||||
};
|
||||
expect(attribute.backgroundColor, const Color(0XFFFFFFFF));
|
||||
});
|
||||
|
||||
test('href', () {
|
||||
final Attributes attribute = {
|
||||
'href': '/app/flowy',
|
||||
};
|
||||
expect(attribute.href, '/app/flowy');
|
||||
});
|
||||
|
||||
test('href - return null', () {
|
||||
final Attributes attribute = {
|
||||
'href': 123,
|
||||
};
|
||||
expect(attribute.href, null);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
import 'package:appflowy_editor/src/extensions/color_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('ColorExtension::', () {
|
||||
const white = Color(0XFFFFFFFF);
|
||||
const black = Color(0XFF000000);
|
||||
const blue = Color(0XFF000FFF);
|
||||
const blueRgba = 'rgba(0, 15, 255, 255)';
|
||||
test('ToRgbaString', () {
|
||||
expect(blue.toRgbaString(), 'rgba(0, 15, 255, 255)');
|
||||
expect(white.toRgbaString(), 'rgba(255, 255, 255, 255)');
|
||||
expect(black.toRgbaString(), 'rgba(0, 0, 0, 255)');
|
||||
});
|
||||
|
||||
test('tryFromRgbaString', () {
|
||||
final color = ColorExtension.tryFromRgbaString(blueRgba);
|
||||
expect(color, const Color.fromARGB(255, 0, 15, 255));
|
||||
});
|
||||
|
||||
test('tryFromRgbaString - wrong rgba format return null', () {
|
||||
const wrongRgba = 'abc(1,2,3,4)';
|
||||
final color = ColorExtension.tryFromRgbaString(wrongRgba);
|
||||
expect(color, null);
|
||||
});
|
||||
|
||||
test('tryFromRgbaString - wrong length return null', () {
|
||||
const wrongRgba = 'rgba(0, 15, 255)';
|
||||
final color = ColorExtension.tryFromRgbaString(wrongRgba);
|
||||
expect(color, null);
|
||||
});
|
||||
|
||||
test('tryFromRgbaString - wrong values return null', () {
|
||||
const wrongRgba = 'rgba(-12, 999, 1234, 619)';
|
||||
final color = ColorExtension.tryFromRgbaString(wrongRgba);
|
||||
expect(color, null);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
import 'dart:collection';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:appflowy_editor/src/extensions/node_extensions.dart';
|
||||
|
||||
class MockNode extends Mock implements Node {}
|
||||
|
||||
void main() {
|
||||
final mockNode = MockNode();
|
||||
|
||||
group('NodeExtensions::', () {
|
||||
final selection = Selection(
|
||||
start: Position(path: [0]),
|
||||
end: Position(path: [1]),
|
||||
);
|
||||
|
||||
test('rect - renderBox is null', () {
|
||||
when(mockNode.renderBox).thenReturn(null);
|
||||
final result = mockNode.rect;
|
||||
expect(result, Rect.zero);
|
||||
});
|
||||
|
||||
test('inSelection', () {
|
||||
// I use an empty implementation instead of mock, because the mocked
|
||||
// version throws error trying to access the path.
|
||||
|
||||
final subLinkedList = LinkedList<Node>()
|
||||
..addAll([
|
||||
Node(type: 'type', children: LinkedList(), attributes: {}),
|
||||
Node(type: 'type', children: LinkedList(), attributes: {}),
|
||||
Node(type: 'type', children: LinkedList(), attributes: {}),
|
||||
Node(type: 'type', children: LinkedList(), attributes: {}),
|
||||
Node(type: 'type', children: LinkedList(), attributes: {}),
|
||||
]);
|
||||
|
||||
final linkedList = LinkedList<Node>()
|
||||
..addAll([
|
||||
Node(
|
||||
type: 'type',
|
||||
children: subLinkedList,
|
||||
attributes: {},
|
||||
),
|
||||
]);
|
||||
|
||||
final node = Node(
|
||||
type: 'type',
|
||||
children: linkedList,
|
||||
attributes: {},
|
||||
);
|
||||
final result = node.inSelection(selection);
|
||||
expect(result, false);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:appflowy_editor/src/extensions/object_extensions.dart';
|
||||
|
||||
void main() {
|
||||
group('FlowyObjectExtensions::', () {
|
||||
test('unwrapOrNull', () {
|
||||
final result = const TextSpan().unwrapOrNull<HitTestTarget>();
|
||||
assert(result is TextSpan);
|
||||
});
|
||||
|
||||
test('unwrapOrNull - return null', () {
|
||||
final result = const TextSpan().unwrapOrNull<ServerSocket>();
|
||||
expect(result, null);
|
||||
});
|
||||
});
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
|
@ -0,0 +1,7 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
group('TextNodeExtension::', () {
|
||||
test('description', () {});
|
||||
});
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_style_extension.dart';
|
||||
|
||||
void main() {
|
||||
group('TextStyleExtensions::', () {
|
||||
const style = TextStyle(
|
||||
color: Colors.blue,
|
||||
backgroundColor: Colors.white,
|
||||
fontSize: 14,
|
||||
height: 100,
|
||||
wordSpacing: 2,
|
||||
fontWeight: FontWeight.w700,
|
||||
);
|
||||
|
||||
const otherStyle = TextStyle(
|
||||
color: Colors.red,
|
||||
backgroundColor: Colors.black,
|
||||
fontSize: 12,
|
||||
height: 10,
|
||||
wordSpacing: 1,
|
||||
);
|
||||
test('combine', () {
|
||||
final result = style.combine(otherStyle);
|
||||
expect(result.color, Colors.red);
|
||||
expect(result.backgroundColor, Colors.black);
|
||||
expect(result.fontSize, 12);
|
||||
expect(result.height, 10);
|
||||
expect(result.wordSpacing, 1);
|
||||
});
|
||||
|
||||
test('combine - return this', () {
|
||||
final result = style.combine(null);
|
||||
expect(result, style);
|
||||
});
|
||||
|
||||
test('combine - return null with inherit', () {
|
||||
final styleCopy = otherStyle.copyWith(inherit: false);
|
||||
final result = style.combine(styleCopy);
|
||||
expect(result, styleCopy);
|
||||
});
|
||||
});
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
import 'package:appflowy_editor/src/extensions/url_launcher_extension.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
test('safeLaunchUrl without scheme', () async {
|
||||
const href = null;
|
||||
final result = await safeLaunchUrl(href);
|
||||
expect(result, false);
|
||||
});
|
||||
}
|
@ -145,6 +145,9 @@ extension on LogicalKeyboardKey {
|
||||
if (this == LogicalKeyboardKey.underscore) {
|
||||
return PhysicalKeyboardKey.minus;
|
||||
}
|
||||
if (this == LogicalKeyboardKey.tilde) {
|
||||
return PhysicalKeyboardKey.backquote;
|
||||
}
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
|
@ -1,5 +1,4 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_item_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
@ -13,8 +12,8 @@ void main() async {
|
||||
|
||||
group('selection_menu_widget.dart', () {
|
||||
for (var i = 0; i < defaultSelectionMenuItems.length; i += 1) {
|
||||
testWidgets('Selects number.$i item in selection menu with enter', (
|
||||
tester) async {
|
||||
testWidgets('Selects number.$i item in selection menu with enter',
|
||||
(tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
for (var j = 0; j < i; j++) {
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
|
||||
@ -30,8 +29,8 @@ void main() async {
|
||||
}
|
||||
});
|
||||
|
||||
testWidgets('Selects number.$i item in selection menu with click', (
|
||||
tester) async {
|
||||
testWidgets('Selects number.$i item in selection menu with click',
|
||||
(tester) async {
|
||||
final editor = await _prepare(tester);
|
||||
|
||||
await tester.tap(find.byType(SelectionMenuItemWidget).at(i));
|
||||
@ -59,7 +58,7 @@ void main() async {
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||
expect(
|
||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||
findsNWidgets(4),
|
||||
findsNWidgets(5),
|
||||
);
|
||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||
expect(
|
||||
@ -148,7 +147,8 @@ Future<void> _testDefaultSelectionMenuItems(
|
||||
int index, EditorWidgetTester editor) async {
|
||||
expect(editor.documentLength, 4);
|
||||
expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0));
|
||||
expect((editor.nodeAtPath([1]) as TextNode).toRawString(), 'Welcome to Appflowy 😁');
|
||||
expect((editor.nodeAtPath([1]) as TextNode).toRawString(),
|
||||
'Welcome to Appflowy 😁');
|
||||
final node = editor.nodeAtPath([2]);
|
||||
final item = defaultSelectionMenuItems[index];
|
||||
final itemName = item.name();
|
||||
|
@ -2,7 +2,6 @@ import 'dart:collection';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/image/image_node_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/rich_text/flowy_rich_text.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:network_image_mock/network_image_mock.dart';
|
||||
|
@ -8,7 +8,6 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
|
@ -150,5 +150,111 @@ void main() async {
|
||||
expect(textNode.toRawString(), text);
|
||||
});
|
||||
});
|
||||
|
||||
group('convert double tilde to strikethrough', () {
|
||||
Future<void> insertTilde(
|
||||
EditorWidgetTester editor, {
|
||||
int repeat = 1,
|
||||
}) async {
|
||||
for (var i = 0; i < repeat; i++) {
|
||||
await editor.pressLogicKey(
|
||||
LogicalKeyboardKey.tilde,
|
||||
isShiftPressed: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
testWidgets('~~AppFlowy~~ to strikethrough AppFlowy', (tester) async {
|
||||
const text = '~~AppFlowy~';
|
||||
final editor = tester.editor..insertTextNode('');
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await editor.insertText(textNode, text[i], i);
|
||||
}
|
||||
await insertTilde(editor);
|
||||
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: textNode.toRawString().length,
|
||||
),
|
||||
);
|
||||
expect(allStrikethrough, true);
|
||||
expect(textNode.toRawString(), 'AppFlowy');
|
||||
});
|
||||
|
||||
testWidgets('App~~Flowy~~ to strikethrough AppFlowy', (tester) async {
|
||||
const text = 'App~~Flowy~';
|
||||
final editor = tester.editor..insertTextNode('');
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await editor.insertText(textNode, text[i], i);
|
||||
}
|
||||
await insertTilde(editor);
|
||||
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 3,
|
||||
endOffset: textNode.toRawString().length,
|
||||
),
|
||||
);
|
||||
expect(allStrikethrough, true);
|
||||
expect(textNode.toRawString(), 'AppFlowy');
|
||||
});
|
||||
|
||||
testWidgets('~~~AppFlowy~~ to bold ~AppFlowy', (tester) async {
|
||||
const text = '~~~AppFlowy~';
|
||||
final editor = tester.editor..insertTextNode('');
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await editor.insertText(textNode, text[i], i);
|
||||
}
|
||||
await insertTilde(editor);
|
||||
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 1,
|
||||
endOffset: textNode.toRawString().length,
|
||||
),
|
||||
);
|
||||
expect(allStrikethrough, true);
|
||||
expect(textNode.toRawString(), '~AppFlowy');
|
||||
});
|
||||
|
||||
testWidgets('~~~~ nothing changes', (tester) async {
|
||||
const text = '~~~';
|
||||
final editor = tester.editor..insertTextNode('');
|
||||
await editor.startTesting();
|
||||
await editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0),
|
||||
);
|
||||
final textNode = editor.nodeAtPath([0]) as TextNode;
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
await editor.insertText(textNode, text[i], i);
|
||||
}
|
||||
await insertTilde(editor);
|
||||
final allStrikethrough = textNode.allSatisfyStrikethroughInSelection(
|
||||
Selection.single(
|
||||
path: [0],
|
||||
startOffset: 0,
|
||||
endOffset: textNode.toRawString().length,
|
||||
),
|
||||
);
|
||||
expect(allStrikethrough, false);
|
||||
expect(textNode.toRawString(), text);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_item_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_service.dart';
|
||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
|
@ -3,7 +3,6 @@ import 'package:appflowy_editor/src/service/internal_key_event_handlers/whitespa
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
|
@ -4,7 +4,6 @@ import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import '../infra/test_editor.dart';
|
||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||
|
||||
void main() async {
|
||||
setUpAll(() {
|
||||
|
@ -2,6 +2,7 @@ import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flowy_infra/time/duration.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class RoundedInputField extends StatefulWidget {
|
||||
final String? hintText;
|
||||
@ -24,6 +25,7 @@ class RoundedInputField extends StatefulWidget {
|
||||
final FocusNode? focusNode;
|
||||
final TextEditingController? controller;
|
||||
final bool autoFocus;
|
||||
final int? maxLength;
|
||||
|
||||
const RoundedInputField({
|
||||
Key? key,
|
||||
@ -47,6 +49,7 @@ class RoundedInputField extends StatefulWidget {
|
||||
this.focusNode,
|
||||
this.controller,
|
||||
this.autoFocus = false,
|
||||
this.maxLength,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -89,6 +92,9 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
|
||||
initialValue: widget.initialValue,
|
||||
focusNode: widget.focusNode,
|
||||
autofocus: widget.autoFocus,
|
||||
maxLength: widget.maxLength,
|
||||
maxLengthEnforcement:
|
||||
MaxLengthEnforcement.truncateAfterCompositionEnds,
|
||||
onChanged: (value) {
|
||||
inputText = value;
|
||||
if (widget.onChanged != null) {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user