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:
@ -1,4 +1,4 @@
|
|||||||
name: FlowyEditor test
|
name: AppFlowyEditor test
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
@ -37,6 +37,8 @@ jobs:
|
|||||||
working-directory: frontend/app_flowy/packages/appflowy_editor
|
working-directory: frontend/app_flowy/packages/appflowy_editor
|
||||||
run: |
|
run: |
|
||||||
flutter pub get
|
flutter pub get
|
||||||
|
flutter format --set-exit-if-changed .
|
||||||
|
flutter analyze .
|
||||||
flutter test --coverage
|
flutter test --coverage
|
||||||
|
|
||||||
- uses: codecov/codecov-action@v3
|
- uses: codecov/codecov-action@v3
|
@ -94,7 +94,20 @@
|
|||||||
},
|
},
|
||||||
"tooltip": {
|
"tooltip": {
|
||||||
"lightMode": "Passer en mode clair",
|
"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": {
|
"contactsPage": {
|
||||||
"title": "Contacts",
|
"title": "Contacts",
|
||||||
@ -123,7 +136,7 @@
|
|||||||
"failedMsg": "Assurez-vous d'avoir terminé le processus de connexion dans votre navigateur."
|
"failedMsg": "Assurez-vous d'avoir terminé le processus de connexion dans votre navigateur."
|
||||||
},
|
},
|
||||||
"google": {
|
"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.",
|
"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:",
|
"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:",
|
"instruction3": "Accédez au lien suivant dans votre navigateur web et saisissez le code ci-dessus:",
|
||||||
@ -135,6 +148,7 @@
|
|||||||
"menu": {
|
"menu": {
|
||||||
"appearance": "Apparence",
|
"appearance": "Apparence",
|
||||||
"language": "Langue",
|
"language": "Langue",
|
||||||
|
"user": "Utilisateur",
|
||||||
"open": "Ouvrir les paramètres"
|
"open": "Ouvrir les paramètres"
|
||||||
},
|
},
|
||||||
"appearance": {
|
"appearance": {
|
||||||
@ -142,15 +156,12 @@
|
|||||||
"darkLabel": "Mode sombre"
|
"darkLabel": "Mode sombre"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sideBar": {
|
|
||||||
"openSidebar": "Open sidebar",
|
|
||||||
"closeSidebar": "Close sidebar"
|
|
||||||
},
|
|
||||||
"grid": {
|
"grid": {
|
||||||
"settings": {
|
"settings": {
|
||||||
"filter": "Filtrer",
|
"filter": "Filtrer",
|
||||||
"sortBy": "Filtrer par",
|
"sortBy": "Filtrer par",
|
||||||
"Properties": "Propriétés"
|
"Properties": "Propriétés",
|
||||||
|
"group": "Groupe"
|
||||||
},
|
},
|
||||||
"field": {
|
"field": {
|
||||||
"hide": "Cacher",
|
"hide": "Cacher",
|
||||||
@ -179,13 +190,17 @@
|
|||||||
"addSelectOption": "Ajouter une option",
|
"addSelectOption": "Ajouter une option",
|
||||||
"optionTitle": "Options",
|
"optionTitle": "Options",
|
||||||
"addOption": "Ajouter une option",
|
"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": {
|
"row": {
|
||||||
"duplicate": "Dupliquer",
|
"duplicate": "Dupliquer",
|
||||||
"delete": "Supprimer",
|
"delete": "Supprimer",
|
||||||
"textPlaceholder": "Vide",
|
"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": {
|
"selectOption": {
|
||||||
"create": "Créer",
|
"create": "Créer",
|
||||||
@ -211,5 +226,10 @@
|
|||||||
"timeHintTextInTwelveHour": "01:00 PM",
|
"timeHintTextInTwelveHour": "01:00 PM",
|
||||||
"timeHintTextInTwentyFourHour": "13:00"
|
"timeHintTextInTwentyFourHour": "13:00"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"board": {
|
||||||
|
"column": {
|
||||||
|
"create_new_card": "Nouveau"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,5 +45,7 @@
|
|||||||
<array>
|
<array>
|
||||||
<string>en</string>
|
<string>en</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
@ -89,18 +89,30 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
didCreateRow: (String groupId, RowPB row, int? index) {
|
didCreateRow: (group, row, int? index) {
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
editingRow: Some(BoardEditingRow(
|
editingRow: Some(BoardEditingRow(
|
||||||
columnId: groupId,
|
group: group,
|
||||||
row: row,
|
row: row,
|
||||||
index: index,
|
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) {
|
state.editingRow.fold(() => null, (editingRow) {
|
||||||
assert(editingRow.row.id == rowId);
|
assert(editingRow.row.id == rowId);
|
||||||
|
_groupItemStartEditing(editingRow.group, editingRow.row, false);
|
||||||
emit(state.copyWith(editingRow: none()));
|
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) {
|
void _moveRow(RowPB? fromRow, String columnId, RowPB? toRow) {
|
||||||
if (fromRow != null) {
|
if (fromRow != null) {
|
||||||
_rowService
|
_rowService
|
||||||
@ -136,11 +166,11 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _moveGroup(String fromColumnId, String toColumnId) {
|
void _moveGroup(String fromGroupId, String toGroupId) {
|
||||||
_rowService
|
_rowService
|
||||||
.moveGroup(
|
.moveGroup(
|
||||||
fromGroupId: fromColumnId,
|
fromGroupId: fromGroupId,
|
||||||
toGroupId: toColumnId,
|
toGroupId: toGroupId,
|
||||||
)
|
)
|
||||||
.then((result) {
|
.then((result) {
|
||||||
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
|
result.fold((l) => null, (r) => add(BoardEvent.didReceiveError(r)));
|
||||||
@ -156,7 +186,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
return super.close();
|
return super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
void initializeGroups(List<GroupPB> groups) {
|
void initializeGroups(List<GroupPB> groupsData) {
|
||||||
for (var controller in groupControllers.values) {
|
for (var controller in groupControllers.values) {
|
||||||
controller.dispose();
|
controller.dispose();
|
||||||
}
|
}
|
||||||
@ -164,27 +194,27 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
|||||||
boardController.clear();
|
boardController.clear();
|
||||||
|
|
||||||
//
|
//
|
||||||
List<AppFlowyGroupData> columns = groups
|
List<AppFlowyGroupData> groups = groupsData
|
||||||
.where((group) => fieldController.getField(group.fieldId) != null)
|
.where((group) => fieldController.getField(group.fieldId) != null)
|
||||||
.map((group) {
|
.map((group) {
|
||||||
return AppFlowyGroupData(
|
return AppFlowyGroupData(
|
||||||
id: group.groupId,
|
id: group.groupId,
|
||||||
name: group.desc,
|
name: group.desc,
|
||||||
items: _buildRows(group),
|
items: _buildGroupItems(group),
|
||||||
customData: BoardCustomData(
|
customData: GroupData(
|
||||||
group: group,
|
group: group,
|
||||||
fieldContext: fieldController.getField(group.fieldId)!,
|
fieldContext: fieldController.getField(group.fieldId)!,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
boardController.addGroups(columns);
|
boardController.addGroups(groups);
|
||||||
|
|
||||||
for (final group in groups) {
|
for (final group in groupsData) {
|
||||||
final delegate = GroupControllerDelegateImpl(
|
final delegate = GroupControllerDelegateImpl(
|
||||||
controller: boardController,
|
controller: boardController,
|
||||||
fieldController: fieldController,
|
fieldController: fieldController,
|
||||||
onNewColumnItem: (groupId, row, index) {
|
onNewColumnItem: (groupId, row, index) {
|
||||||
add(BoardEvent.didCreateRow(groupId, row, index));
|
add(BoardEvent.didCreateRow(group, row, index));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
final controller = GroupController(
|
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 items = group.rows.map((row) {
|
||||||
final fieldContext = fieldController.getField(group.fieldId);
|
final fieldContext = fieldController.getField(group.fieldId);
|
||||||
return BoardColumnItem(row: row, fieldContext: fieldContext!);
|
return GroupItem(
|
||||||
|
row: row,
|
||||||
|
fieldContext: fieldContext!,
|
||||||
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
return <AppFlowyGroupItem>[...items];
|
return <AppFlowyGroupItem>[...items];
|
||||||
@ -270,11 +303,15 @@ class BoardEvent with _$BoardEvent {
|
|||||||
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
const factory BoardEvent.createBottomRow(String groupId) = _CreateBottomRow;
|
||||||
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
const factory BoardEvent.createHeaderRow(String groupId) = _CreateHeaderRow;
|
||||||
const factory BoardEvent.didCreateRow(
|
const factory BoardEvent.didCreateRow(
|
||||||
String groupId,
|
GroupPB group,
|
||||||
RowPB row,
|
RowPB row,
|
||||||
int? index,
|
int? index,
|
||||||
) = _DidCreateRow;
|
) = _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.didReceiveError(FlowyError error) = _DidReceiveError;
|
||||||
const factory BoardEvent.didReceiveGridUpdate(
|
const factory BoardEvent.didReceiveGridUpdate(
|
||||||
GridPB grid,
|
GridPB grid,
|
||||||
@ -334,14 +371,17 @@ class GridFieldEquatable extends Equatable {
|
|||||||
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
UnmodifiableListView<FieldPB> get value => UnmodifiableListView(_fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoardColumnItem extends AppFlowyGroupItem {
|
class GroupItem extends AppFlowyGroupItem {
|
||||||
final RowPB row;
|
final RowPB row;
|
||||||
final GridFieldContext fieldContext;
|
final GridFieldContext fieldContext;
|
||||||
|
|
||||||
BoardColumnItem({
|
GroupItem({
|
||||||
required this.row,
|
required this.row,
|
||||||
required this.fieldContext,
|
required this.fieldContext,
|
||||||
});
|
bool draggable = true,
|
||||||
|
}) {
|
||||||
|
super.draggable = draggable;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get id => row.id;
|
String get id => row.id;
|
||||||
@ -367,10 +407,16 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
final item = GroupItem(
|
||||||
|
row: row,
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
);
|
||||||
controller.insertGroupItem(group.groupId, index, item);
|
controller.insertGroupItem(group.groupId, index, item);
|
||||||
} else {
|
} else {
|
||||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
final item = GroupItem(
|
||||||
|
row: row,
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
);
|
||||||
controller.addGroupItem(group.groupId, item);
|
controller.addGroupItem(group.groupId, item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -389,7 +435,10 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
|||||||
}
|
}
|
||||||
controller.updateGroupItem(
|
controller.updateGroupItem(
|
||||||
group.groupId,
|
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");
|
Log.warn("FieldContext should not be null");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final item = BoardColumnItem(row: row, fieldContext: fieldContext);
|
final item = GroupItem(
|
||||||
|
row: row,
|
||||||
|
fieldContext: fieldContext,
|
||||||
|
draggable: false,
|
||||||
|
);
|
||||||
|
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
controller.insertGroupItem(group.groupId, index, item);
|
controller.insertGroupItem(group.groupId, index, item);
|
||||||
@ -412,21 +465,21 @@ class GroupControllerDelegateImpl extends GroupControllerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BoardEditingRow {
|
class BoardEditingRow {
|
||||||
String columnId;
|
GroupPB group;
|
||||||
RowPB row;
|
RowPB row;
|
||||||
int? index;
|
int? index;
|
||||||
|
|
||||||
BoardEditingRow({
|
BoardEditingRow({
|
||||||
required this.columnId,
|
required this.group,
|
||||||
required this.row,
|
required this.row,
|
||||||
required this.index,
|
required this.index,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoardCustomData {
|
class GroupData {
|
||||||
final GroupPB group;
|
final GroupPB group;
|
||||||
final GridFieldContext fieldContext;
|
final GridFieldContext fieldContext;
|
||||||
BoardCustomData({
|
GroupData({
|
||||||
required this.group,
|
required this.group,
|
||||||
required this.fieldContext,
|
required this.fieldContext,
|
||||||
});
|
});
|
||||||
|
@ -87,13 +87,13 @@ class BoardDataController {
|
|||||||
onUpdatedGroup.call(changeset.updateGroups);
|
onUpdatedGroup.call(changeset.updateGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changeset.insertedGroups.isNotEmpty) {
|
|
||||||
onInsertedGroup.call(changeset.insertedGroups);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (changeset.deletedGroups.isNotEmpty) {
|
if (changeset.deletedGroups.isNotEmpty) {
|
||||||
onDeletedGroup.call(changeset.deletedGroups);
|
onDeletedGroup.call(changeset.deletedGroups);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (changeset.insertedGroups.isNotEmpty) {
|
||||||
|
onInsertedGroup.call(changeset.insertedGroups);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
(e) => _onError?.call(e),
|
(e) => _onError?.call(e),
|
||||||
);
|
);
|
||||||
|
@ -83,7 +83,7 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocListener<BoardBloc, BoardState>(
|
return BlocListener<BoardBloc, BoardState>(
|
||||||
listener: (context, state) => _handleEditState(state, context),
|
listener: (context, state) => _handleEditStateChanged(state, context),
|
||||||
child: BlocBuilder<BoardBloc, BoardState>(
|
child: BlocBuilder<BoardBloc, BoardState>(
|
||||||
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
buildWhen: (previous, current) => previous.groupIds != current.groupIds,
|
||||||
builder: (context, state) {
|
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(
|
state.editingRow.fold(
|
||||||
() => null,
|
() => null,
|
||||||
(editingRow) {
|
(editingRow) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (editingRow.index != null) {
|
if (editingRow.index != null) {
|
||||||
context
|
|
||||||
.read<BoardBloc>()
|
|
||||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
|
||||||
} else {
|
} else {
|
||||||
scrollManager.scrollToBottom(editingRow.columnId, (boardContext) {
|
scrollManager.scrollToBottom(editingRow.group.groupId);
|
||||||
context
|
|
||||||
.read<BoardBloc>()
|
|
||||||
.add(BoardEvent.endEditRow(editingRow.row.id));
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -156,14 +149,14 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
|
|
||||||
Widget _buildHeader(
|
Widget _buildHeader(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AppFlowyGroupData columnData,
|
AppFlowyGroupData groupData,
|
||||||
) {
|
) {
|
||||||
final boardCustomData = columnData.customData as BoardCustomData;
|
final boardCustomData = groupData.customData as GroupData;
|
||||||
return AppFlowyGroupHeader(
|
return AppFlowyGroupHeader(
|
||||||
title: Flexible(
|
title: Flexible(
|
||||||
fit: FlexFit.tight,
|
fit: FlexFit.tight,
|
||||||
child: FlowyText.medium(
|
child: FlowyText.medium(
|
||||||
columnData.headerData.groupName,
|
groupData.headerData.groupName,
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
overflow: TextOverflow.clip,
|
overflow: TextOverflow.clip,
|
||||||
color: context.read<AppTheme>().textColor,
|
color: context.read<AppTheme>().textColor,
|
||||||
@ -180,7 +173,7 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
),
|
),
|
||||||
onAddButtonClick: () {
|
onAddButtonClick: () {
|
||||||
context.read<BoardBloc>().add(
|
context.read<BoardBloc>().add(
|
||||||
BoardEvent.createHeaderRow(columnData.id),
|
BoardEvent.createHeaderRow(groupData.id),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
height: 50,
|
height: 50,
|
||||||
@ -218,15 +211,16 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
|
|
||||||
Widget _buildCard(
|
Widget _buildCard(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
AppFlowyGroupData group,
|
AppFlowyGroupData afGroupData,
|
||||||
AppFlowyGroupItem columnItem,
|
AppFlowyGroupItem afGroupItem,
|
||||||
) {
|
) {
|
||||||
final boardColumnItem = columnItem as BoardColumnItem;
|
final groupItem = afGroupItem as GroupItem;
|
||||||
final rowPB = boardColumnItem.row;
|
final groupData = afGroupData.customData as GroupData;
|
||||||
|
final rowPB = groupItem.row;
|
||||||
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
final rowCache = context.read<BoardBloc>().getRowCache(rowPB.blockId);
|
||||||
|
|
||||||
/// Return placeholder widget if the rowCache is null.
|
/// 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 fieldController = context.read<BoardBloc>().fieldController;
|
||||||
final gridId = context.read<BoardBloc>().gridId;
|
final gridId = context.read<BoardBloc>().gridId;
|
||||||
@ -241,19 +235,19 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
context.read<BoardBloc>().state.editingRow.fold(
|
context.read<BoardBloc>().state.editingRow.fold(
|
||||||
() => null,
|
() => null,
|
||||||
(editingRow) {
|
(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(
|
return AppFlowyGroupCard(
|
||||||
key: ValueKey(groupItemId),
|
key: ValueKey(groupItemId),
|
||||||
margin: config.cardPadding,
|
margin: config.cardPadding,
|
||||||
decoration: _makeBoxDecoration(context),
|
decoration: _makeBoxDecoration(context),
|
||||||
child: BoardCard(
|
child: BoardCard(
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
groupId: group.id,
|
groupId: groupData.group.groupId,
|
||||||
fieldId: boardColumnItem.fieldContext.id,
|
fieldId: groupItem.fieldContext.id,
|
||||||
isEditing: isEditing,
|
isEditing: isEditing,
|
||||||
cellBuilder: cellBuilder,
|
cellBuilder: cellBuilder,
|
||||||
dataController: cardController,
|
dataController: cardController,
|
||||||
@ -264,6 +258,19 @@ class _BoardContentState extends State<BoardContent> {
|
|||||||
rowCache,
|
rowCache,
|
||||||
context,
|
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;
|
Widget? widget;
|
||||||
switch (customData.fieldType) {
|
switch (customData.fieldType) {
|
||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
|
@ -76,6 +76,10 @@ class EditableRowNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class EditableCell {
|
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;
|
EditableCellNotifier? get editableNotifier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,9 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
|||||||
focusNode.requestFocus();
|
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(() {
|
focusNode.addListener(() {
|
||||||
if (!focusNode.hasFocus) {
|
if (!focusNode.hasFocus) {
|
||||||
focusWhenInit = false;
|
focusWhenInit = false;
|
||||||
@ -131,7 +134,11 @@ class _BoardTextCellState extends State<BoardTextCell> {
|
|||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
vertical: BoardSizes.cardCellVPadding,
|
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 CardDataController dataController;
|
||||||
final BoardCellBuilder cellBuilder;
|
final BoardCellBuilder cellBuilder;
|
||||||
final void Function(BuildContext) openCard;
|
final void Function(BuildContext) openCard;
|
||||||
|
final VoidCallback onStartEditing;
|
||||||
|
final VoidCallback onEndEditing;
|
||||||
|
|
||||||
const BoardCard({
|
const BoardCard({
|
||||||
required this.gridId,
|
required this.gridId,
|
||||||
@ -30,6 +32,8 @@ class BoardCard extends StatefulWidget {
|
|||||||
required this.dataController,
|
required this.dataController,
|
||||||
required this.cellBuilder,
|
required this.cellBuilder,
|
||||||
required this.openCard,
|
required this.openCard,
|
||||||
|
required this.onStartEditing,
|
||||||
|
required this.onEndEditing,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -56,6 +60,12 @@ class _BoardCardState extends State<BoardCard> {
|
|||||||
rowNotifier.isEditing.addListener(() {
|
rowNotifier.isEditing.addListener(() {
|
||||||
if (!mounted) return;
|
if (!mounted) return;
|
||||||
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
||||||
|
|
||||||
|
if (rowNotifier.isEditing.value) {
|
||||||
|
widget.onStartEditing();
|
||||||
|
} else {
|
||||||
|
widget.onEndEditing();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
popoverController = PopoverController();
|
popoverController = PopoverController();
|
||||||
|
@ -15,6 +15,7 @@ import 'package:clipboard/clipboard.dart';
|
|||||||
import 'package:dartz/dartz.dart' as dartz;
|
import 'package:dartz/dartz.dart' as dartz;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/size.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/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
@ -112,7 +113,6 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
double buttonWidth = 60;
|
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => getIt<DocShareBloc>(param1: view),
|
create: (context) => getIt<DocShareBloc>(param1: view),
|
||||||
child: BlocListener<DocShareBloc, DocShareState>(
|
child: BlocListener<DocShareBloc, DocShareState>(
|
||||||
@ -130,6 +130,7 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
child: BlocBuilder<DocShareBloc, DocShareState>(
|
child: BlocBuilder<DocShareBloc, DocShareState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
value: Provider.of<AppearanceSetting>(context, listen: true),
|
value: Provider.of<AppearanceSetting>(context, listen: true),
|
||||||
child: Selector<AppearanceSetting, Locale>(
|
child: Selector<AppearanceSetting, Locale>(
|
||||||
@ -137,16 +138,15 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
builder: (ctx, _, child) => ConstrainedBox(
|
builder: (ctx, _, child) => ConstrainedBox(
|
||||||
constraints: const BoxConstraints.expand(
|
constraints: const BoxConstraints.expand(
|
||||||
height: 30,
|
height: 30,
|
||||||
// minWidth: buttonWidth,
|
|
||||||
width: 100,
|
width: 100,
|
||||||
),
|
),
|
||||||
child: RoundedTextButton(
|
child: RoundedTextButton(
|
||||||
title: LocaleKeys.shareAction_buttonText.tr(),
|
title: LocaleKeys.shareAction_buttonText.tr(),
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
borderRadius: Corners.s6Border,
|
borderRadius: Corners.s6Border,
|
||||||
color: Colors.lightBlue,
|
color: theme.main1,
|
||||||
onPressed: () => _showActionList(
|
onPressed: () =>
|
||||||
context, Offset(-(buttonWidth / 2), 10)),
|
_showActionList(context, const Offset(0, 10)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -193,7 +193,7 @@ class DocumentShareButton extends StatelessWidget {
|
|||||||
});
|
});
|
||||||
actionList.show(
|
actionList.show(
|
||||||
context,
|
context,
|
||||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
anchorDirection: AnchorDirection.bottomWithRightAligned,
|
||||||
anchorOffset: offset,
|
anchorOffset: offset,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
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/cell/date_cal_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.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:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:dartz/dartz.dart' show Either;
|
import 'package:dartz/dartz.dart' show Either;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -157,6 +159,7 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
|||||||
focusedDay: state.focusedDay,
|
focusedDay: state.focusedDay,
|
||||||
rowHeight: 40,
|
rowHeight: 40,
|
||||||
calendarFormat: state.format,
|
calendarFormat: state.format,
|
||||||
|
daysOfWeekHeight: 40,
|
||||||
headerStyle: HeaderStyle(
|
headerStyle: HeaderStyle(
|
||||||
formatButtonVisible: false,
|
formatButtonVisible: false,
|
||||||
titleCentered: true,
|
titleCentered: true,
|
||||||
@ -166,15 +169,46 @@ class _CellCalendarWidgetState extends State<_CellCalendarWidget> {
|
|||||||
rightChevronPadding: EdgeInsets.zero,
|
rightChevronPadding: EdgeInsets.zero,
|
||||||
rightChevronMargin: EdgeInsets.zero,
|
rightChevronMargin: EdgeInsets.zero,
|
||||||
rightChevronIcon: svgWidget("home/arrow_right"),
|
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(
|
calendarStyle: CalendarStyle(
|
||||||
|
cellMargin: const EdgeInsets.all(3),
|
||||||
|
defaultDecoration: BoxDecoration(
|
||||||
|
color: theme.surface,
|
||||||
|
shape: BoxShape.rectangle,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
|
),
|
||||||
selectedDecoration: BoxDecoration(
|
selectedDecoration: BoxDecoration(
|
||||||
color: theme.main1,
|
color: theme.main1,
|
||||||
shape: BoxShape.circle,
|
shape: BoxShape.rectangle,
|
||||||
|
borderRadius: const BorderRadius.all(Radius.circular(6)),
|
||||||
),
|
),
|
||||||
todayDecoration: BoxDecoration(
|
todayDecoration: BoxDecoration(
|
||||||
color: theme.shader4,
|
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(
|
selectedTextStyle: TextStyle(
|
||||||
color: theme.surface,
|
color: theme.surface,
|
||||||
@ -230,11 +264,13 @@ class _IncludeTimeButton extends StatelessWidget {
|
|||||||
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(),
|
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(),
|
||||||
fontSize: 14),
|
fontSize: 14),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Switch(
|
Toggle(
|
||||||
value: includeTime,
|
value: includeTime,
|
||||||
onChanged: (newValue) => context
|
onChanged: (value) => context
|
||||||
.read<DateCalBloc>()
|
.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),
|
offset: const Offset(20, 0),
|
||||||
constraints: BoxConstraints.loose(const Size(140, 100)),
|
constraints: BoxConstraints.loose(const Size(140, 100)),
|
||||||
child: FlowyButton(
|
child: FlowyButton(
|
||||||
text: FlowyText.medium(title, fontSize: 12),
|
text: FlowyText.medium(title, fontSize: 14),
|
||||||
hoverColor: theme.hover,
|
hoverColor: theme.hover,
|
||||||
margin: kMargin,
|
margin: kMargin,
|
||||||
rightIcon: svgWidget("grid/more", color: theme.iconColor),
|
rightIcon: svgWidget("grid/more", color: theme.iconColor),
|
||||||
|
@ -130,14 +130,10 @@ class SelectOptionTagCell extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
SelectOptionTag.fromOption(
|
||||||
fit: FlexFit.loose,
|
context: context,
|
||||||
flex: 2,
|
option: option,
|
||||||
child: SelectOptionTag.fromOption(
|
onSelected: () => onSelected(option),
|
||||||
context: context,
|
|
||||||
option: option,
|
|
||||||
onSelected: () => onSelected(option),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
...children,
|
...children,
|
||||||
|
@ -142,11 +142,12 @@ class _TextField extends StatelessWidget {
|
|||||||
value: (option) => option);
|
value: (option) => option);
|
||||||
|
|
||||||
return SizedBox(
|
return SizedBox(
|
||||||
height: 42,
|
height: 62,
|
||||||
child: SelectOptionTextField(
|
child: SelectOptionTextField(
|
||||||
options: state.options,
|
options: state.options,
|
||||||
selectedOptionMap: optionMap,
|
selectedOptionMap: optionMap,
|
||||||
distanceToText: _editorPanelWidth * 0.7,
|
distanceToText: _editorPanelWidth * 0.7,
|
||||||
|
maxLength: 30,
|
||||||
tagController: _tagController,
|
tagController: _tagController,
|
||||||
onClick: () => popoverMutex.close(),
|
onClick: () => popoverMutex.close(),
|
||||||
newText: (text) {
|
newText: (text) {
|
||||||
@ -248,32 +249,25 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
|
|||||||
mutex: widget.popoverMutex,
|
mutex: widget.popoverMutex,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: GridSize.typeOptionItemHeight,
|
height: GridSize.typeOptionItemHeight,
|
||||||
child: Row(
|
child: SelectOptionTagCell(
|
||||||
|
option: widget.option,
|
||||||
|
onSelected: (option) {
|
||||||
|
context
|
||||||
|
.read<SelectOptionCellEditorBloc>()
|
||||||
|
.add(SelectOptionEditorEvent.selectOption(option.id));
|
||||||
|
},
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
if (widget.isSelected)
|
||||||
fit: FlexFit.loose,
|
Padding(
|
||||||
child: SelectOptionTagCell(
|
padding: const EdgeInsets.only(right: 6),
|
||||||
option: widget.option,
|
child: svgWidget("grid/checkmark"),
|
||||||
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"),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
FlowyIconButton(
|
FlowyIconButton(
|
||||||
width: 30,
|
width: 30,
|
||||||
onPressed: () => _popoverController.show(),
|
onPressed: () => _popoverController.show(),
|
||||||
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
iconPadding: const EdgeInsets.fromLTRB(4, 4, 4, 4),
|
||||||
icon: svgWidget("editor/details", color: theme.iconColor),
|
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:flutter/material.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:textfield_tags/textfield_tags.dart';
|
import 'package:textfield_tags/textfield_tags.dart';
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ class SelectOptionTextField extends StatefulWidget {
|
|||||||
final Function(String) onSubmitted;
|
final Function(String) onSubmitted;
|
||||||
final Function(String) newText;
|
final Function(String) newText;
|
||||||
final VoidCallback? onClick;
|
final VoidCallback? onClick;
|
||||||
|
final int? maxLength;
|
||||||
|
|
||||||
const SelectOptionTextField({
|
const SelectOptionTextField({
|
||||||
required this.options,
|
required this.options,
|
||||||
@ -29,6 +31,7 @@ class SelectOptionTextField extends StatefulWidget {
|
|||||||
required this.onSubmitted,
|
required this.onSubmitted,
|
||||||
required this.newText,
|
required this.newText,
|
||||||
this.onClick,
|
this.onClick,
|
||||||
|
this.maxLength,
|
||||||
TextEditingController? textController,
|
TextEditingController? textController,
|
||||||
FocusNode? focusNode,
|
FocusNode? focusNode,
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -93,6 +96,9 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
|
maxLength: widget.maxLength,
|
||||||
|
maxLengthEnforcement:
|
||||||
|
MaxLengthEnforcement.truncateAfterCompositionEnds,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
enabledBorder: OutlineInputBorder(
|
enabledBorder: OutlineInputBorder(
|
||||||
|
@ -9,6 +9,7 @@ class InputTextField extends StatefulWidget {
|
|||||||
final void Function() onCanceled;
|
final void Function() onCanceled;
|
||||||
final bool autoClearWhenDone;
|
final bool autoClearWhenDone;
|
||||||
final String text;
|
final String text;
|
||||||
|
final int? maxLength;
|
||||||
|
|
||||||
const InputTextField({
|
const InputTextField({
|
||||||
required this.text,
|
required this.text,
|
||||||
@ -16,6 +17,7 @@ class InputTextField extends StatefulWidget {
|
|||||||
required this.onCanceled,
|
required this.onCanceled,
|
||||||
this.onChanged,
|
this.onChanged,
|
||||||
this.autoClearWhenDone = false,
|
this.autoClearWhenDone = false,
|
||||||
|
this.maxLength,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@ -41,11 +43,14 @@ class _InputTextFieldState extends State<InputTextField> {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
|
|
||||||
|
final height = widget.maxLength == null ? 36.0 : 56.0;
|
||||||
|
|
||||||
return RoundedInputField(
|
return RoundedInputField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
autoFocus: true,
|
autoFocus: true,
|
||||||
height: 36,
|
height: height,
|
||||||
|
maxLength: widget.maxLength,
|
||||||
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
|
||||||
normalBorderColor: theme.shader4,
|
normalBorderColor: theme.shader4,
|
||||||
focusBorderColor: theme.main1,
|
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/date_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.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:easy_localization/easy_localization.dart' hide DateFormat;
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
@ -161,6 +163,7 @@ class _IncludeTimeButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
return BlocSelector<DateTypeOptionBloc, DateTypeOptionState, bool>(
|
return BlocSelector<DateTypeOptionBloc, DateTypeOptionState, bool>(
|
||||||
selector: (state) => state.typeOption.includeTime,
|
selector: (state) => state.typeOption.includeTime,
|
||||||
builder: (context, includeTime) {
|
builder: (context, includeTime) {
|
||||||
@ -173,13 +176,15 @@ class _IncludeTimeButton extends StatelessWidget {
|
|||||||
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(),
|
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr(),
|
||||||
fontSize: 12),
|
fontSize: 12),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
Switch(
|
Toggle(
|
||||||
value: includeTime,
|
value: includeTime,
|
||||||
onChanged: (newValue) {
|
onChanged: (value) {
|
||||||
context
|
context
|
||||||
.read<DateTypeOptionBloc>()
|
.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);
|
final text = state.newOptionName.foldRight("", (a, previous) => a);
|
||||||
return InputTextField(
|
return InputTextField(
|
||||||
autoClearWhenDone: true,
|
autoClearWhenDone: true,
|
||||||
|
maxLength: 30,
|
||||||
text: text,
|
text: text,
|
||||||
onCanceled: () {
|
onCanceled: () {
|
||||||
context
|
context
|
||||||
|
@ -106,6 +106,7 @@ class _OptionNameTextField extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return InputTextField(
|
return InputTextField(
|
||||||
text: name,
|
text: name,
|
||||||
|
maxLength: 30,
|
||||||
onCanceled: () {},
|
onCanceled: () {},
|
||||||
onDone: (optionName) {
|
onDone: (optionName) {
|
||||||
if (name != optionName) {
|
if (name != optionName) {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:app_flowy/user/application/user_listener.dart';
|
import 'package:app_flowy/user/application/user_listener.dart';
|
||||||
import 'package:app_flowy/workspace/application/edit_panel/edit_context.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/log.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error-code/code.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.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) {
|
unauthorized: (_Unauthorized value) {
|
||||||
emit(state.copyWith(unauthorized: true));
|
emit(state.copyWith(unauthorized: true));
|
||||||
},
|
},
|
||||||
collapseMenu: (e) {
|
collapseMenu: (_CollapseMenu e) {
|
||||||
emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed));
|
emit(state.copyWith(isMenuCollapsed: !state.isMenuCollapsed));
|
||||||
},
|
},
|
||||||
editPanelResized: (e) {
|
editPanelResizeStart: (_EditPanelResizeStart e) {
|
||||||
final newOffset =
|
emit(state.copyWith(
|
||||||
(state.resizeOffset + e.offset).clamp(-50, 200).toDouble();
|
resizeType: MenuResizeType.drag,
|
||||||
emit(state.copyWith(resizeOffset: newOffset));
|
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
|
@freezed
|
||||||
class HomeEvent with _$HomeEvent {
|
class HomeEvent with _$HomeEvent {
|
||||||
const factory HomeEvent.initial() = _Initial;
|
const factory HomeEvent.initial() = _Initial;
|
||||||
@ -91,6 +119,8 @@ class HomeEvent with _$HomeEvent {
|
|||||||
const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
|
const factory HomeEvent.unauthorized(String msg) = _Unauthorized;
|
||||||
const factory HomeEvent.collapseMenu() = _CollapseMenu;
|
const factory HomeEvent.collapseMenu() = _CollapseMenu;
|
||||||
const factory HomeEvent.editPanelResized(double offset) = _EditPanelResized;
|
const factory HomeEvent.editPanelResized(double offset) = _EditPanelResized;
|
||||||
|
const factory HomeEvent.editPanelResizeStart() = _EditPanelResizeStart;
|
||||||
|
const factory HomeEvent.editPanelResizeEnd() = _EditPanelResizeEnd;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -103,6 +133,8 @@ class HomeState with _$HomeState {
|
|||||||
required bool unauthorized,
|
required bool unauthorized,
|
||||||
required bool isMenuCollapsed,
|
required bool isMenuCollapsed,
|
||||||
required double resizeOffset,
|
required double resizeOffset,
|
||||||
|
required double resizeStart,
|
||||||
|
required MenuResizeType resizeType,
|
||||||
}) = _HomeState;
|
}) = _HomeState;
|
||||||
|
|
||||||
factory HomeState.initial(CurrentWorkspaceSettingPB workspaceSetting) =>
|
factory HomeState.initial(CurrentWorkspaceSettingPB workspaceSetting) =>
|
||||||
@ -114,5 +146,7 @@ class HomeState with _$HomeState {
|
|||||||
unauthorized: false,
|
unauthorized: false,
|
||||||
isMenuCollapsed: false,
|
isMenuCollapsed: false,
|
||||||
resizeOffset: 0,
|
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:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra/time/duration.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
// ignore: import_of_legacy_library_into_null_safe
|
// ignore: import_of_legacy_library_into_null_safe
|
||||||
import 'package:sized_context/sized_context.dart';
|
import 'package:sized_context/sized_context.dart';
|
||||||
@ -44,7 +43,7 @@ class HomeLayout {
|
|||||||
homePageLOffset = (showMenu && !menuIsDrawer) ? menuWidth : 0.0;
|
homePageLOffset = (showMenu && !menuIsDrawer) ? menuWidth : 0.0;
|
||||||
|
|
||||||
menuSpacing = !showMenu && Platform.isMacOS ? 80.0 : 0.0;
|
menuSpacing = !showMenu && Platform.isMacOS ? 80.0 : 0.0;
|
||||||
animDuration = .35.seconds;
|
animDuration = homeBlocState.resizeType.duration();
|
||||||
|
|
||||||
editPanelWidth = HomeSizes.editPanelWidth;
|
editPanelWidth = HomeSizes.editPanelWidth;
|
||||||
homePageROffset = showEditPanel ? editPanelWidth : 0;
|
homePageROffset = showEditPanel ? editPanelWidth : 0;
|
||||||
|
@ -176,11 +176,18 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
cursor: SystemMouseCursors.resizeLeftRight,
|
cursor: SystemMouseCursors.resizeLeftRight,
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
dragStartBehavior: DragStartBehavior.down,
|
dragStartBehavior: DragStartBehavior.down,
|
||||||
onPanUpdate: ((details) {
|
onHorizontalDragStart: (details) => context
|
||||||
context
|
.read<HomeBloc>()
|
||||||
.read<HomeBloc>()
|
.add(const HomeEvent.editPanelResizeStart()),
|
||||||
.add(HomeEvent.editPanelResized(details.delta.dx));
|
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,
|
behavior: HitTestBehavior.translucent,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 10,
|
width: 10,
|
||||||
@ -208,7 +215,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
top: 0,
|
top: 0,
|
||||||
animate: true)
|
animate: true)
|
||||||
.animate(layout.animDuration, Curves.easeOut),
|
.animate(layout.animDuration, Curves.easeOut),
|
||||||
homeMenuResizer.positioned(left: layout.homePageLOffset - 5),
|
|
||||||
bubble
|
bubble
|
||||||
.positioned(
|
.positioned(
|
||||||
right: 20,
|
right: 20,
|
||||||
@ -236,6 +242,9 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
bottom: 0,
|
bottom: 0,
|
||||||
animate: true)
|
animate: true)
|
||||||
.animate(layout.animDuration, Curves.easeOut),
|
.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,
|
app.name,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
color: theme.textColor,
|
color: theme.textColor,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -82,7 +82,7 @@ class ViewSectionItem extends StatelessWidget {
|
|||||||
child: FlowyText.regular(
|
child: FlowyText.regular(
|
||||||
state.view.name,
|
state.view.name,
|
||||||
fontSize: 12,
|
fontSize: 12,
|
||||||
overflow: TextOverflow.clip,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
@ -28,8 +28,9 @@ class MenuUser extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
_renderAvatar(context),
|
_renderAvatar(context),
|
||||||
const HSpace(10),
|
const HSpace(10),
|
||||||
_renderUserName(context),
|
Expanded(
|
||||||
const Spacer(),
|
child: _renderUserName(context),
|
||||||
|
),
|
||||||
_renderSettingsButton(context),
|
_renderSettingsButton(context),
|
||||||
//ToDo: when the user is allowed to create another workspace,
|
//ToDo: when the user is allowed to create another workspace,
|
||||||
//we get the below block back
|
//we get the below block back
|
||||||
@ -63,7 +64,7 @@ class MenuUser extends StatelessWidget {
|
|||||||
if (name.isEmpty) {
|
if (name.isEmpty) {
|
||||||
name = context.read<MenuUserBloc>().state.userProfile.email;
|
name = context.read<MenuUserBloc>().state.userProfile.email;
|
||||||
}
|
}
|
||||||
return FlowyText(name, fontSize: 12);
|
return FlowyText(name, fontSize: 12, overflow: TextOverflow.ellipsis);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _renderSettingsButton(BuildContext context) {
|
Widget _renderSettingsButton(BuildContext context) {
|
||||||
|
@ -13,7 +13,7 @@ class SettingsAppearanceView extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.read<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
@ -97,10 +97,8 @@ class ActionCell<T extends ActionItem> extends StatelessWidget {
|
|||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: itemHeight,
|
height: itemHeight,
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
children: [
|
||||||
if (icon != null) icon,
|
if (icon != null) ...[icon, HSpace(ActionListSizes.itemHPadding)],
|
||||||
HSpace(ActionListSizes.itemHPadding),
|
|
||||||
FlowyText.medium(action.name, fontSize: 12),
|
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
|
# 0.0.8
|
||||||
* Enable drag and drop group
|
* Enable drag and drop group
|
||||||
|
|
||||||
# 0.0.7
|
# 0.0.7
|
||||||
* Rename some classes
|
* Rename some classes
|
||||||
* Add documentation
|
* Add documentation
|
||||||
|
|
||||||
# 0.0.6
|
# 0.0.6
|
||||||
* Support scroll to bottom
|
* Support scroll to bottom
|
||||||
* Fix some bugs
|
* Fix some bugs
|
||||||
|
@ -78,7 +78,7 @@ class _MultiBoardListExampleState extends State<MultiBoardListExample> {
|
|||||||
height: 50,
|
height: 50,
|
||||||
margin: config.groupItemPadding,
|
margin: config.groupItemPadding,
|
||||||
onAddButtonClick: () {
|
onAddButtonClick: () {
|
||||||
boardController.scrollToBottom(columnData.id, (p0) {});
|
boardController.scrollToBottom(columnData.id);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -32,4 +32,8 @@ class Log {
|
|||||||
'AppFlowyBoard: ❗️[Trace] - ${DateTime.now().second}=> $message');
|
'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';
|
import '../rendering/board_overlay.dart';
|
||||||
|
|
||||||
class AppFlowyBoardScrollController {
|
class AppFlowyBoardScrollController {
|
||||||
AppFlowyBoardState? _groupState;
|
AppFlowyBoardState? _boardState;
|
||||||
|
|
||||||
void scrollToBottom(String groupId, void Function(BuildContext)? completed) {
|
void scrollToBottom(String groupId,
|
||||||
_groupState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
{void Function(BuildContext)? completed}) {
|
||||||
|
_boardState?.reorderFlexActionMap[groupId]?.scrollToBottom(completed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,9 +40,6 @@ class AppFlowyBoardConfig {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class AppFlowyBoard extends StatelessWidget {
|
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.
|
/// The widget that will be rendered as the background of the board.
|
||||||
final Widget? background;
|
final Widget? background;
|
||||||
|
|
||||||
@ -94,11 +92,7 @@ class AppFlowyBoard extends StatelessWidget {
|
|||||||
///
|
///
|
||||||
final AppFlowyBoardScrollController? boardScrollController;
|
final AppFlowyBoardScrollController? boardScrollController;
|
||||||
|
|
||||||
final AppFlowyBoardState _groupState = AppFlowyBoardState();
|
const AppFlowyBoard({
|
||||||
|
|
||||||
late final BoardPhantomController _phantomController;
|
|
||||||
|
|
||||||
AppFlowyBoard({
|
|
||||||
required this.controller,
|
required this.controller,
|
||||||
required this.cardBuilder,
|
required this.cardBuilder,
|
||||||
this.background,
|
this.background,
|
||||||
@ -109,12 +103,7 @@ class AppFlowyBoard extends StatelessWidget {
|
|||||||
this.groupConstraints = const BoxConstraints(maxWidth: 200),
|
this.groupConstraints = const BoxConstraints(maxWidth: 200),
|
||||||
this.config = const AppFlowyBoardConfig(),
|
this.config = const AppFlowyBoardConfig(),
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key) {
|
}) : super(key: key);
|
||||||
_phantomController = BoardPhantomController(
|
|
||||||
delegate: controller,
|
|
||||||
groupsState: _groupState,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -122,8 +111,14 @@ class AppFlowyBoard extends StatelessWidget {
|
|||||||
value: controller,
|
value: controller,
|
||||||
child: Consumer<AppFlowyBoardController>(
|
child: Consumer<AppFlowyBoardController>(
|
||||||
builder: (context, notifier, child) {
|
builder: (context, notifier, child) {
|
||||||
|
final boardState = AppFlowyBoardState();
|
||||||
|
BoardPhantomController phantomController = BoardPhantomController(
|
||||||
|
delegate: controller,
|
||||||
|
groupsState: boardState,
|
||||||
|
);
|
||||||
|
|
||||||
if (boardScrollController != null) {
|
if (boardScrollController != null) {
|
||||||
boardScrollController!._groupState = _groupState;
|
boardScrollController!._boardState = boardState;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _AppFlowyBoardContent(
|
return _AppFlowyBoardContent(
|
||||||
@ -131,14 +126,14 @@ class AppFlowyBoard extends StatelessWidget {
|
|||||||
dataController: controller,
|
dataController: controller,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
scrollManager: boardScrollController,
|
scrollManager: boardScrollController,
|
||||||
groupState: _groupState,
|
boardState: boardState,
|
||||||
background: background,
|
background: background,
|
||||||
delegate: _phantomController,
|
delegate: phantomController,
|
||||||
groupConstraints: groupConstraints,
|
groupConstraints: groupConstraints,
|
||||||
cardBuilder: cardBuilder,
|
cardBuilder: cardBuilder,
|
||||||
footerBuilder: footerBuilder,
|
footerBuilder: footerBuilder,
|
||||||
headerBuilder: headerBuilder,
|
headerBuilder: headerBuilder,
|
||||||
phantomController: _phantomController,
|
phantomController: phantomController,
|
||||||
onReorder: controller.moveGroup,
|
onReorder: controller.moveGroup,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -156,7 +151,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
|||||||
final ReorderFlexConfig reorderFlexConfig;
|
final ReorderFlexConfig reorderFlexConfig;
|
||||||
final BoxConstraints groupConstraints;
|
final BoxConstraints groupConstraints;
|
||||||
final AppFlowyBoardScrollController? scrollManager;
|
final AppFlowyBoardScrollController? scrollManager;
|
||||||
final AppFlowyBoardState groupState;
|
final AppFlowyBoardState boardState;
|
||||||
final AppFlowyBoardCardBuilder cardBuilder;
|
final AppFlowyBoardCardBuilder cardBuilder;
|
||||||
final AppFlowyBoardHeaderBuilder? headerBuilder;
|
final AppFlowyBoardHeaderBuilder? headerBuilder;
|
||||||
final AppFlowyBoardFooterBuilder? footerBuilder;
|
final AppFlowyBoardFooterBuilder? footerBuilder;
|
||||||
@ -169,7 +164,7 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
|||||||
required this.delegate,
|
required this.delegate,
|
||||||
required this.dataController,
|
required this.dataController,
|
||||||
required this.scrollManager,
|
required this.scrollManager,
|
||||||
required this.groupState,
|
required this.boardState,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.background,
|
this.background,
|
||||||
required this.groupConstraints,
|
required this.groupConstraints,
|
||||||
@ -178,7 +173,10 @@ class _AppFlowyBoardContent extends StatefulWidget {
|
|||||||
this.headerBuilder,
|
this.headerBuilder,
|
||||||
required this.phantomController,
|
required this.phantomController,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : reorderFlexConfig = const ReorderFlexConfig(),
|
}) : reorderFlexConfig = const ReorderFlexConfig(
|
||||||
|
direction: Axis.horizontal,
|
||||||
|
dragDirection: Axis.horizontal,
|
||||||
|
),
|
||||||
super(key: key);
|
super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -198,7 +196,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
reorderFlexId: widget.dataController.identifier,
|
reorderFlexId: widget.dataController.identifier,
|
||||||
acceptedReorderFlexId: widget.dataController.groupIds,
|
acceptedReorderFlexId: widget.dataController.groupIds,
|
||||||
delegate: widget.delegate,
|
delegate: widget.delegate,
|
||||||
columnsState: widget.groupState,
|
columnsState: widget.boardState,
|
||||||
);
|
);
|
||||||
|
|
||||||
final reorderFlex = ReorderFlex(
|
final reorderFlex = ReorderFlex(
|
||||||
@ -206,9 +204,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
scrollController: widget.scrollController,
|
scrollController: widget.scrollController,
|
||||||
onReorder: widget.onReorder,
|
onReorder: widget.onReorder,
|
||||||
dataSource: widget.dataController,
|
dataSource: widget.dataController,
|
||||||
direction: Axis.horizontal,
|
|
||||||
interceptor: interceptor,
|
interceptor: interceptor,
|
||||||
reorderable: true,
|
|
||||||
children: _buildColumns(),
|
children: _buildColumns(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -254,7 +250,7 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final reorderFlexAction = ReorderFlexActionImpl();
|
final reorderFlexAction = ReorderFlexActionImpl();
|
||||||
widget.groupState.reorderFlexActionMap[columnData.id] =
|
widget.boardState.reorderFlexActionMap[columnData.id] =
|
||||||
reorderFlexAction;
|
reorderFlexAction;
|
||||||
|
|
||||||
return ChangeNotifierProvider.value(
|
return ChangeNotifierProvider.value(
|
||||||
@ -275,8 +271,8 @@ class _AppFlowyBoardContentState extends State<_AppFlowyBoardContent> {
|
|||||||
onReorder: widget.dataController.moveGroupItem,
|
onReorder: widget.dataController.moveGroupItem,
|
||||||
cornerRadius: widget.config.cornerRadius,
|
cornerRadius: widget.config.cornerRadius,
|
||||||
backgroundColor: widget.config.groupBackgroundColor,
|
backgroundColor: widget.config.groupBackgroundColor,
|
||||||
dragStateStorage: widget.groupState,
|
dragStateStorage: widget.boardState,
|
||||||
dragTargetKeys: widget.groupState,
|
dragTargetKeys: widget.boardState,
|
||||||
reorderFlexAction: reorderFlexAction,
|
reorderFlexAction: reorderFlexAction,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -138,7 +138,11 @@ class AppFlowyBoardController extends ChangeNotifier
|
|||||||
/// groups or get ready to reinitialize the [AppFlowyBoard].
|
/// groups or get ready to reinitialize the [AppFlowyBoard].
|
||||||
void clear() {
|
void clear() {
|
||||||
_groupDatas.clear();
|
_groupDatas.clear();
|
||||||
|
for (final group in _groupControllers.values) {
|
||||||
|
group.dispose();
|
||||||
|
}
|
||||||
_groupControllers.clear();
|
_groupControllers.clear();
|
||||||
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,6 +206,14 @@ class AppFlowyBoardController extends ChangeNotifier
|
|||||||
getGroupController(groupId)?.replaceOrInsertItem(item);
|
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
|
/// Moves the item at [fromGroupIndex] in group with id [fromGroupId] to
|
||||||
/// group with id [toGroupId] at [toGroupIndex]
|
/// group with id [toGroupId] at [toGroupIndex]
|
||||||
@override
|
@override
|
||||||
@ -215,6 +227,8 @@ class AppFlowyBoardController extends ChangeNotifier
|
|||||||
final fromGroupController = getGroupController(fromGroupId)!;
|
final fromGroupController = getGroupController(fromGroupId)!;
|
||||||
final toGroupController = getGroupController(toGroupId)!;
|
final toGroupController = getGroupController(toGroupId)!;
|
||||||
final fromGroupItem = fromGroupController.removeAt(fromGroupIndex);
|
final fromGroupItem = fromGroupController.removeAt(fromGroupIndex);
|
||||||
|
if (fromGroupItem == null) return;
|
||||||
|
|
||||||
if (toGroupController.items.length > toGroupIndex) {
|
if (toGroupController.items.length > toGroupIndex) {
|
||||||
assert(toGroupController.items[toGroupIndex] is PhantomGroupItem);
|
assert(toGroupController.items[toGroupIndex] is PhantomGroupItem);
|
||||||
|
|
||||||
@ -275,7 +289,9 @@ class AppFlowyBoardController extends ChangeNotifier
|
|||||||
Log.trace(
|
Log.trace(
|
||||||
'[$BoardPhantomController] update $groupId:$index to $groupId:$newIndex');
|
'[$BoardPhantomController] update $groupId:$index to $groupId:$newIndex');
|
||||||
final item = groupController.removeAt(index, notify: false);
|
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);
|
widget.onDragStarted?.call(index);
|
||||||
},
|
},
|
||||||
onReorder: ((fromIndex, toIndex) {
|
onReorder: ((fromIndex, toIndex) {
|
||||||
if (widget.phantomController.isFromGroup(widget.groupId)) {
|
if (widget.phantomController.shouldReorder(widget.groupId)) {
|
||||||
widget.onReorder(widget.groupId, fromIndex, toIndex);
|
widget.onReorder(widget.groupId, fromIndex, toIndex);
|
||||||
widget.phantomController.transformIndex(fromIndex, toIndex);
|
widget.phantomController.updateIndex(fromIndex, toIndex);
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
onDragEnded: () {
|
onDragEnded: () {
|
||||||
|
@ -5,6 +5,8 @@ import 'package:appflowy_board/src/widgets/reorder_flex/reorder_flex.dart';
|
|||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
typedef IsDraggable = bool;
|
||||||
|
|
||||||
/// A item represents the generic data model of each group card.
|
/// A item represents the generic data model of each group card.
|
||||||
///
|
///
|
||||||
/// Each item displayed in the group required to implement this class.
|
/// 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
|
/// * [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.
|
/// listener. Set to false if you do not want to notify the listeners.
|
||||||
///
|
///
|
||||||
AppFlowyGroupItem removeAt(int index, {bool notify = true}) {
|
AppFlowyGroupItem? removeAt(int index, {bool notify = true}) {
|
||||||
assert(index >= 0);
|
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');
|
Log.debug('[$AppFlowyGroupController] $groupData remove item at $index');
|
||||||
final item = groupData._items.removeAt(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
|
/// Move the item from [fromIndex] to [toIndex]. It will do nothing if the
|
||||||
/// [fromIndex] equal to the [toIndex].
|
/// [fromIndex] equal to the [toIndex].
|
||||||
bool move(int fromIndex, int toIndex) {
|
bool move(int fromIndex, int toIndex) {
|
||||||
assert(fromIndex >= 0);
|
|
||||||
assert(toIndex >= 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) {
|
if (fromIndex == toIndex) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.debug(
|
Log.debug(
|
||||||
'[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex');
|
'[$AppFlowyGroupController] $groupData move item from $fromIndex to $toIndex');
|
||||||
final item = groupData._items.removeAt(fromIndex);
|
final item = groupData._items.removeAt(fromIndex);
|
||||||
@ -124,7 +140,7 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
Log.debug('[$AppFlowyGroupController] $groupData add $newItem');
|
Log.debug('[$AppFlowyGroupController] $groupData add $newItem');
|
||||||
} else {
|
} else {
|
||||||
if (index >= groupData._items.length) {
|
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}');
|
'[$AppFlowyGroupController] unexpected items length, index should less than the count of the items. Index: $index, items count: ${items.length}');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -155,6 +171,15 @@ class AppFlowyGroupController extends ChangeNotifier with EquatableMixin {
|
|||||||
-1;
|
-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void enableDragging(bool isEnable) {
|
||||||
|
groupData.draggable = isEnable;
|
||||||
|
|
||||||
|
for (var item in groupData._items) {
|
||||||
|
item.draggable = isEnable;
|
||||||
|
}
|
||||||
|
_notify();
|
||||||
|
}
|
||||||
|
|
||||||
void _notify() {
|
void _notify() {
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
@ -16,13 +16,13 @@ class FlexDragTargetData extends DragTargetData {
|
|||||||
@override
|
@override
|
||||||
final int draggingIndex;
|
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;
|
final String dragTargetId;
|
||||||
|
|
||||||
@ -40,8 +40,8 @@ class FlexDragTargetData extends DragTargetData {
|
|||||||
required this.reorderFlexId,
|
required this.reorderFlexId,
|
||||||
required this.reorderFlexItem,
|
required this.reorderFlexItem,
|
||||||
required this.dragTargetIndexKey,
|
required this.dragTargetIndexKey,
|
||||||
required DraggingState state,
|
required DraggingState draggingState,
|
||||||
}) : _state = state;
|
}) : _draggingState = draggingState;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:appflowy_board/src/utils/log.dart';
|
import 'package:appflowy_board/src/utils/log.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
@ -78,10 +79,12 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
|||||||
|
|
||||||
final bool useMoveAnimation;
|
final bool useMoveAnimation;
|
||||||
|
|
||||||
final bool draggable;
|
final IsDraggable draggable;
|
||||||
|
|
||||||
final double draggingOpacity;
|
final double draggingOpacity;
|
||||||
|
|
||||||
|
final Axis? dragDirection;
|
||||||
|
|
||||||
const ReorderDragTarget({
|
const ReorderDragTarget({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.child,
|
required this.child,
|
||||||
@ -99,6 +102,7 @@ class ReorderDragTarget<T extends DragTargetData> extends StatefulWidget {
|
|||||||
this.onLeave,
|
this.onLeave,
|
||||||
this.draggableTargetBuilder,
|
this.draggableTargetBuilder,
|
||||||
this.draggingOpacity = 0.3,
|
this.draggingOpacity = 0.3,
|
||||||
|
this.dragDirection,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -115,8 +119,10 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
|||||||
Widget dragTarget = DragTarget<T>(
|
Widget dragTarget = DragTarget<T>(
|
||||||
builder: _buildDraggableWidget,
|
builder: _buildDraggableWidget,
|
||||||
onWillAccept: (dragTargetData) {
|
onWillAccept: (dragTargetData) {
|
||||||
assert(dragTargetData != null);
|
if (dragTargetData == null) {
|
||||||
if (dragTargetData == null) return false;
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
return widget.onWillAccept(dragTargetData);
|
return widget.onWillAccept(dragTargetData);
|
||||||
},
|
},
|
||||||
onAccept: widget.onAccept,
|
onAccept: widget.onAccept,
|
||||||
@ -140,9 +146,6 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
|||||||
List<T?> acceptedCandidates,
|
List<T?> acceptedCandidates,
|
||||||
List<dynamic> rejectedCandidates,
|
List<dynamic> rejectedCandidates,
|
||||||
) {
|
) {
|
||||||
if (!widget.draggable) {
|
|
||||||
return widget.child;
|
|
||||||
}
|
|
||||||
Widget feedbackBuilder = Builder(builder: (BuildContext context) {
|
Widget feedbackBuilder = Builder(builder: (BuildContext context) {
|
||||||
BoxConstraints contentSizeConstraints =
|
BoxConstraints contentSizeConstraints =
|
||||||
BoxConstraints.loose(_draggingFeedbackSize!);
|
BoxConstraints.loose(_draggingFeedbackSize!);
|
||||||
@ -163,7 +166,8 @@ class _ReorderDragTargetState<T extends DragTargetData>
|
|||||||
widget.deleteAnimationController,
|
widget.deleteAnimationController,
|
||||||
) ??
|
) ??
|
||||||
Draggable<DragTargetData>(
|
Draggable<DragTargetData>(
|
||||||
maxSimultaneousDrags: 1,
|
axis: widget.dragDirection,
|
||||||
|
maxSimultaneousDrags: widget.draggable ? 1 : 0,
|
||||||
data: widget.dragTargetData,
|
data: widget.dragTargetData,
|
||||||
ignoringFeedbackSemantics: false,
|
ignoringFeedbackSemantics: false,
|
||||||
feedback: feedbackBuilder,
|
feedback: feedbackBuilder,
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:appflowy_board/appflowy_board.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import '../../utils/log.dart';
|
import '../../utils/log.dart';
|
||||||
@ -29,6 +30,8 @@ abstract class ReoderFlexDataSource {
|
|||||||
abstract class ReoderFlexItem {
|
abstract class ReoderFlexItem {
|
||||||
/// [id] is used to identify the item. It must be unique.
|
/// [id] is used to identify the item. It must be unique.
|
||||||
String get id;
|
String get id;
|
||||||
|
|
||||||
|
IsDraggable draggable = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cache each dragTarget's key.
|
/// Cache each dragTarget's key.
|
||||||
@ -73,8 +76,15 @@ class ReorderFlexConfig {
|
|||||||
|
|
||||||
final bool useMovePlaceholder;
|
final bool useMovePlaceholder;
|
||||||
|
|
||||||
|
/// [direction] How to place the children, default is Axis.vertical
|
||||||
|
final Axis direction;
|
||||||
|
|
||||||
|
final Axis? dragDirection;
|
||||||
|
|
||||||
const ReorderFlexConfig({
|
const ReorderFlexConfig({
|
||||||
this.useMoveAnimation = true,
|
this.useMoveAnimation = true,
|
||||||
|
this.direction = Axis.vertical,
|
||||||
|
this.dragDirection,
|
||||||
}) : useMovePlaceholder = !useMoveAnimation;
|
}) : useMovePlaceholder = !useMoveAnimation;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,8 +92,6 @@ class ReorderFlex extends StatefulWidget {
|
|||||||
final ReorderFlexConfig config;
|
final ReorderFlexConfig config;
|
||||||
final List<Widget> children;
|
final List<Widget> children;
|
||||||
|
|
||||||
/// [direction] How to place the children, default is Axis.vertical
|
|
||||||
final Axis direction;
|
|
||||||
final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start;
|
final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start;
|
||||||
|
|
||||||
final ScrollController? scrollController;
|
final ScrollController? scrollController;
|
||||||
@ -108,8 +116,6 @@ class ReorderFlex extends StatefulWidget {
|
|||||||
|
|
||||||
final ReorderFlexAction? reorderFlexAction;
|
final ReorderFlexAction? reorderFlexAction;
|
||||||
|
|
||||||
final bool reorderable;
|
|
||||||
|
|
||||||
ReorderFlex({
|
ReorderFlex({
|
||||||
Key? key,
|
Key? key,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
@ -117,14 +123,12 @@ class ReorderFlex extends StatefulWidget {
|
|||||||
required this.children,
|
required this.children,
|
||||||
required this.config,
|
required this.config,
|
||||||
required this.onReorder,
|
required this.onReorder,
|
||||||
this.reorderable = true,
|
|
||||||
this.dragStateStorage,
|
this.dragStateStorage,
|
||||||
this.dragTargetKeys,
|
this.dragTargetKeys,
|
||||||
this.onDragStarted,
|
this.onDragStarted,
|
||||||
this.onDragEnded,
|
this.onDragEnded,
|
||||||
this.interceptor,
|
this.interceptor,
|
||||||
this.reorderFlexAction,
|
this.reorderFlexAction,
|
||||||
this.direction = Axis.vertical,
|
|
||||||
}) : assert(children.every((Widget w) => w.key != null),
|
}) : assert(children.every((Widget w) => w.key != null),
|
||||||
'All child must have a key.'),
|
'All child must have a key.'),
|
||||||
super(key: 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.
|
/// Whether or not we are currently scrolling this view to show a widget.
|
||||||
bool _scrolling = false;
|
bool _scrolling = false;
|
||||||
|
|
||||||
/// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc.
|
/// [draggingState] records the dragging state including dragStartIndex, and phantomIndex, etc.
|
||||||
late DraggingState dragState;
|
late DraggingState draggingState;
|
||||||
|
|
||||||
/// [_animation] controls the dragging animations
|
/// [_animation] controls the dragging animations
|
||||||
late DragTargetAnimation _animation;
|
late DragTargetAnimation _animation;
|
||||||
@ -158,9 +162,9 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
void initState() {
|
void initState() {
|
||||||
_notifier = ReorderFlexNotifier();
|
_notifier = ReorderFlexNotifier();
|
||||||
final flexId = widget.reorderFlexId;
|
final flexId = widget.reorderFlexId;
|
||||||
dragState = widget.dragStateStorage?.readState(flexId) ??
|
draggingState = widget.dragStateStorage?.readState(flexId) ??
|
||||||
DraggingState(widget.reorderFlexId);
|
DraggingState(widget.reorderFlexId);
|
||||||
Log.trace('[DragTarget] init dragState: $dragState');
|
Log.trace('[DragTarget] init dragState: $draggingState');
|
||||||
|
|
||||||
widget.dragStateStorage?.removeState(flexId);
|
widget.dragStateStorage?.removeState(flexId);
|
||||||
|
|
||||||
@ -168,7 +172,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
reorderAnimationDuration: widget.config.reorderAnimationDuration,
|
reorderAnimationDuration: widget.config.reorderAnimationDuration,
|
||||||
entranceAnimateStatusChanged: (status) {
|
entranceAnimateStatusChanged: (status) {
|
||||||
if (status == AnimationStatus.completed) {
|
if (status == AnimationStatus.completed) {
|
||||||
if (dragState.nextIndex == -1) return;
|
if (draggingState.nextIndex == -1) return;
|
||||||
setState(() => _requestAnimationToNextIndex());
|
setState(() => _requestAnimationToNextIndex());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -225,7 +229,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
indexKey,
|
indexKey,
|
||||||
);
|
);
|
||||||
|
|
||||||
children.add(_wrap(child, i, indexKey));
|
children.add(_wrap(child, i, indexKey, item.draggable));
|
||||||
|
|
||||||
// if (widget.config.useMovePlaceholder) {
|
// if (widget.config.useMovePlaceholder) {
|
||||||
// children.add(DragTargeMovePlaceholder(
|
// children.add(DragTargeMovePlaceholder(
|
||||||
@ -256,64 +260,70 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
/// when the animation finish.
|
/// when the animation finish.
|
||||||
|
|
||||||
if (_animation.entranceController.isCompleted) {
|
if (_animation.entranceController.isCompleted) {
|
||||||
dragState.removePhantom();
|
draggingState.removePhantom();
|
||||||
|
|
||||||
if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) {
|
if (!isAcceptingNewTarget && draggingState.didDragTargetMoveToNext()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dragState.moveDragTargetToNext();
|
draggingState.moveDragTargetToNext();
|
||||||
_animation.animateToNext();
|
_animation.animateToNext();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// [child]: the child will be wrapped with dartTarget
|
/// [child]: the child will be wrapped with dartTarget
|
||||||
/// [childIndex]: the index of the child in a list
|
/// [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) {
|
return Builder(builder: (context) {
|
||||||
final ReorderDragTarget dragTarget = _buildDragTarget(
|
final ReorderDragTarget dragTarget = _buildDragTarget(
|
||||||
context,
|
context,
|
||||||
child,
|
child,
|
||||||
childIndex,
|
childIndex,
|
||||||
indexKey,
|
indexKey,
|
||||||
|
draggable,
|
||||||
);
|
);
|
||||||
int shiftedIndex = childIndex;
|
int shiftedIndex = childIndex;
|
||||||
|
|
||||||
if (dragState.isOverlapWithPhantom()) {
|
if (draggingState.isOverlapWithPhantom()) {
|
||||||
shiftedIndex = dragState.calculateShiftedIndex(childIndex);
|
shiftedIndex = draggingState.calculateShiftedIndex(childIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.trace(
|
Log.trace(
|
||||||
'Rebuild: Group:[${dragState.reorderFlexId}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
'Rebuild: Group:[${draggingState.reorderFlexId}] ${draggingState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex');
|
||||||
final currentIndex = dragState.currentIndex;
|
final currentIndex = draggingState.currentIndex;
|
||||||
final dragPhantomIndex = dragState.phantomIndex;
|
final dragPhantomIndex = draggingState.phantomIndex;
|
||||||
|
|
||||||
if (shiftedIndex == currentIndex || childIndex == dragPhantomIndex) {
|
if (shiftedIndex == currentIndex || childIndex == dragPhantomIndex) {
|
||||||
Widget dragSpace;
|
Widget dragSpace;
|
||||||
if (dragState.draggingWidget != null) {
|
if (draggingState.draggingWidget != null) {
|
||||||
if (dragState.draggingWidget is PhantomWidget) {
|
if (draggingState.draggingWidget is PhantomWidget) {
|
||||||
dragSpace = dragState.draggingWidget!;
|
dragSpace = draggingState.draggingWidget!;
|
||||||
} else {
|
} else {
|
||||||
dragSpace = PhantomWidget(
|
dragSpace = PhantomWidget(
|
||||||
opacity: widget.config.draggingWidgetOpacity,
|
opacity: widget.config.draggingWidgetOpacity,
|
||||||
child: dragState.draggingWidget,
|
child: draggingState.draggingWidget,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} 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
|
/// Returns the dragTarget it is not start dragging. The size of the
|
||||||
/// dragTarget is the same as the the passed in child.
|
/// dragTarget is the same as the the passed in child.
|
||||||
///
|
///
|
||||||
if (dragState.isNotDragging()) {
|
if (draggingState.isNotDragging()) {
|
||||||
return _buildDraggingContainer(children: [dragTarget]);
|
return _buildDraggingContainer(children: [dragTarget]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine the size of the drop area to show under the dragging widget.
|
/// Determine the size of the drop area to show under the dragging widget.
|
||||||
Size? feedbackSize = Size.zero;
|
Size? feedbackSize = Size.zero;
|
||||||
if (widget.config.useMoveAnimation) {
|
if (widget.config.useMoveAnimation) {
|
||||||
feedbackSize = dragState.feedbackSize;
|
feedbackSize = draggingState.feedbackSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize);
|
Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize);
|
||||||
@ -321,7 +331,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
|
|
||||||
/// When start dragging, the dragTarget, [ReorderDragTarget], will
|
/// When start dragging, the dragTarget, [ReorderDragTarget], will
|
||||||
/// return a [IgnorePointerWidget] which size is zero.
|
/// return a [IgnorePointerWidget] which size is zero.
|
||||||
if (dragState.isPhantomAboveDragTarget()) {
|
if (draggingState.isPhantomAboveDragTarget()) {
|
||||||
_notifier.updateDragTargetIndex(currentIndex);
|
_notifier.updateDragTargetIndex(currentIndex);
|
||||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||||
return _buildDraggingContainer(children: [
|
return _buildDraggingContainer(children: [
|
||||||
@ -343,7 +353,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
}
|
}
|
||||||
|
|
||||||
///
|
///
|
||||||
if (dragState.isPhantomBelowDragTarget()) {
|
if (draggingState.isPhantomBelowDragTarget()) {
|
||||||
_notifier.updateDragTargetIndex(currentIndex);
|
_notifier.updateDragTargetIndex(currentIndex);
|
||||||
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) {
|
||||||
return _buildDraggingContainer(children: [
|
return _buildDraggingContainer(children: [
|
||||||
@ -364,10 +374,10 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(!dragState.isOverlapWithPhantom());
|
assert(!draggingState.isOverlapWithPhantom());
|
||||||
|
|
||||||
List<Widget> children = [];
|
List<Widget> children = [];
|
||||||
if (dragState.isDragTargetMovingDown()) {
|
if (draggingState.isDragTargetMovingDown()) {
|
||||||
children.addAll([dragTarget, appearSpace]);
|
children.addAll([dragTarget, appearSpace]);
|
||||||
} else {
|
} else {
|
||||||
children.addAll([appearSpace, dragTarget]);
|
children.addAll([appearSpace, dragTarget]);
|
||||||
@ -395,15 +405,17 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
Widget child,
|
Widget child,
|
||||||
int dragTargetIndex,
|
int dragTargetIndex,
|
||||||
GlobalObjectKey indexKey,
|
GlobalObjectKey indexKey,
|
||||||
|
IsDraggable draggable,
|
||||||
) {
|
) {
|
||||||
final reorderFlexItem = widget.dataSource.items[dragTargetIndex];
|
final reorderFlexItem = widget.dataSource.items[dragTargetIndex];
|
||||||
return ReorderDragTarget<FlexDragTargetData>(
|
return ReorderDragTarget<FlexDragTargetData>(
|
||||||
indexGlobalKey: indexKey,
|
indexGlobalKey: indexKey,
|
||||||
|
draggable: draggable,
|
||||||
dragTargetData: FlexDragTargetData(
|
dragTargetData: FlexDragTargetData(
|
||||||
draggingIndex: dragTargetIndex,
|
draggingIndex: dragTargetIndex,
|
||||||
reorderFlexId: widget.reorderFlexId,
|
reorderFlexId: widget.reorderFlexId,
|
||||||
reorderFlexItem: reorderFlexItem,
|
reorderFlexItem: reorderFlexItem,
|
||||||
state: dragState,
|
draggingState: draggingState,
|
||||||
dragTargetId: reorderFlexItem.id,
|
dragTargetId: reorderFlexItem.id,
|
||||||
dragTargetIndexKey: indexKey,
|
dragTargetIndexKey: indexKey,
|
||||||
),
|
),
|
||||||
@ -432,11 +444,11 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
setState(() {
|
setState(() {
|
||||||
if (dragTargetData.reorderFlexId == widget.reorderFlexId) {
|
if (dragTargetData.reorderFlexId == widget.reorderFlexId) {
|
||||||
_onReordered(
|
_onReordered(
|
||||||
dragState.dragStartIndex,
|
draggingState.dragStartIndex,
|
||||||
dragState.currentIndex,
|
draggingState.currentIndex,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
dragState.endDragging();
|
draggingState.endDragging();
|
||||||
widget.onDragEnded?.call();
|
widget.onDragEnded?.call();
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
@ -482,8 +494,8 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
deleteAnimationController: _animation.deleteController,
|
deleteAnimationController: _animation.deleteController,
|
||||||
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder,
|
||||||
useMoveAnimation: widget.config.useMoveAnimation,
|
useMoveAnimation: widget.config.useMoveAnimation,
|
||||||
draggable: widget.reorderable,
|
|
||||||
draggingOpacity: widget.config.draggingWidgetOpacity,
|
draggingOpacity: widget.config.draggingWidgetOpacity,
|
||||||
|
dragDirection: widget.config.dragDirection,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -506,7 +518,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
child,
|
child,
|
||||||
_animation.entranceController,
|
_animation.entranceController,
|
||||||
feedbackSize,
|
feedbackSize,
|
||||||
widget.direction,
|
widget.config.direction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -515,7 +527,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
child,
|
child,
|
||||||
_animation.phantomController,
|
_animation.phantomController,
|
||||||
feedbackSize,
|
feedbackSize,
|
||||||
widget.direction,
|
widget.config.direction,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,7 +537,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
Size? feedbackSize,
|
Size? feedbackSize,
|
||||||
) {
|
) {
|
||||||
setState(() {
|
setState(() {
|
||||||
dragState.startDragging(draggingWidget, dragIndex, feedbackSize);
|
draggingState.startDragging(draggingWidget, dragIndex, feedbackSize);
|
||||||
_animation.startDragging();
|
_animation.startDragging();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -535,34 +547,34 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
dragState.setStartDraggingIndex(dragTargetIndex);
|
draggingState.setStartDraggingIndex(dragTargetIndex);
|
||||||
widget.dragStateStorage?.insertState(
|
widget.dragStateStorage?.insertState(
|
||||||
widget.reorderFlexId,
|
widget.reorderFlexId,
|
||||||
dragState,
|
draggingState,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool handleOnWillAccept(BuildContext context, int dragTargetIndex) {
|
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
|
/// The [willAccept] will be true if the dargTarget is the widget that gets
|
||||||
/// dragged and it is dragged on top of the other dragTargets.
|
/// dragged and it is dragged on top of the other dragTargets.
|
||||||
///
|
///
|
||||||
|
|
||||||
bool willAccept =
|
bool willAccept = draggingState.dragStartIndex == dragIndex &&
|
||||||
dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex;
|
dragIndex != dragTargetIndex;
|
||||||
setState(() {
|
setState(() {
|
||||||
if (willAccept) {
|
if (willAccept) {
|
||||||
int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex);
|
int shiftedIndex = draggingState.calculateShiftedIndex(dragTargetIndex);
|
||||||
dragState.updateNextIndex(shiftedIndex);
|
draggingState.updateNextIndex(shiftedIndex);
|
||||||
} else {
|
} else {
|
||||||
dragState.updateNextIndex(dragTargetIndex);
|
draggingState.updateNextIndex(dragTargetIndex);
|
||||||
}
|
}
|
||||||
_requestAnimationToNextIndex(isAcceptingNewTarget: true);
|
_requestAnimationToNextIndex(isAcceptingNewTarget: true);
|
||||||
});
|
});
|
||||||
|
|
||||||
Log.trace(
|
Log.trace(
|
||||||
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $dragState}');
|
'[$ReorderDragTarget] ${widget.reorderFlexId} dragging state: $draggingState}');
|
||||||
|
|
||||||
_scrollTo(context);
|
_scrollTo(context);
|
||||||
|
|
||||||
@ -587,7 +599,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
return child;
|
return child;
|
||||||
} else {
|
} else {
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
scrollDirection: widget.direction,
|
scrollDirection: widget.config.direction,
|
||||||
controller: _scrollController,
|
controller: _scrollController,
|
||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
@ -595,7 +607,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _wrapContainer(List<Widget> children) {
|
Widget _wrapContainer(List<Widget> children) {
|
||||||
switch (widget.direction) {
|
switch (widget.config.direction) {
|
||||||
case Axis.horizontal:
|
case Axis.horizontal:
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -613,7 +625,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildDraggingContainer({required List<Widget> children}) {
|
Widget _buildDraggingContainer({required List<Widget> children}) {
|
||||||
switch (widget.direction) {
|
switch (widget.config.direction) {
|
||||||
case Axis.horizontal:
|
case Axis.horizontal:
|
||||||
return Row(
|
return Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -660,6 +672,7 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
.ensureVisible(
|
.ensureVisible(
|
||||||
dragTargetRenderObject,
|
dragTargetRenderObject,
|
||||||
alignment: 0.5,
|
alignment: 0.5,
|
||||||
|
alignmentPolicy: ScrollPositionAlignmentPolicy.keepVisibleAtStart,
|
||||||
duration: const Duration(milliseconds: 120),
|
duration: const Duration(milliseconds: 120),
|
||||||
)
|
)
|
||||||
.then((value) {
|
.then((value) {
|
||||||
@ -683,9 +696,9 @@ class ReorderFlexState extends State<ReorderFlex>
|
|||||||
// If and only if the current scroll offset falls in-between the offsets
|
// 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
|
// necessary to reveal the selected context at the top or bottom of the
|
||||||
// screen, then it is already on-screen.
|
// screen, then it is already on-screen.
|
||||||
final double margin = widget.direction == Axis.horizontal
|
final double margin = widget.config.direction == Axis.horizontal
|
||||||
? dragState.dropAreaSize.width
|
? draggingState.dropAreaSize.width
|
||||||
: dragState.dropAreaSize.height / 2.0;
|
: draggingState.dropAreaSize.height / 2.0;
|
||||||
if (_scrollController.hasClients) {
|
if (_scrollController.hasClients) {
|
||||||
final double scrollOffset = _scrollController.offset;
|
final double scrollOffset = _scrollController.offset;
|
||||||
final double topOffset = max(
|
final double topOffset = max(
|
||||||
|
@ -46,15 +46,23 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
|||||||
required this.groupsState,
|
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) {
|
if (phantomRecord != null) {
|
||||||
return phantomRecord!.fromGroupId == groupId;
|
return phantomRecord!.toGroupId == groupId &&
|
||||||
|
phantomRecord!.fromGroupId == groupId;
|
||||||
} else {
|
} else {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void transformIndex(int fromIndex, int toIndex) {
|
void updateIndex(int fromIndex, int toIndex) {
|
||||||
if (phantomRecord == null) {
|
if (phantomRecord == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -69,7 +77,6 @@ class BoardPhantomController extends OverlapDragTargetDelegate
|
|||||||
/// Remove the phantom in the group when the group is end dragging.
|
/// Remove the phantom in the group when the group is end dragging.
|
||||||
void groupEndDragging(String groupId) {
|
void groupEndDragging(String groupId) {
|
||||||
phantomState.setGroupIsDragging(groupId, false);
|
phantomState.setGroupIsDragging(groupId, false);
|
||||||
|
|
||||||
if (phantomRecord == null) return;
|
if (phantomRecord == null) return;
|
||||||
|
|
||||||
final fromGroupId = phantomRecord!.fromGroupId;
|
final fromGroupId = phantomRecord!.fromGroupId;
|
||||||
@ -246,10 +253,6 @@ class PhantomRecord {
|
|||||||
});
|
});
|
||||||
|
|
||||||
void updateFromGroupIndex(int index) {
|
void updateFromGroupIndex(int index) {
|
||||||
if (fromGroupIndex == index) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fromGroupIndex = index;
|
fromGroupIndex = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
name: appflowy_board
|
name: appflowy_board
|
||||||
description: AppFlowyBoard is a board-style widget that consists of multi-groups. It supports drag and drop between different groups.
|
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
|
homepage: https://github.com/AppFlowy-IO/AppFlowy
|
||||||
repository: https://github.com/AppFlowy-IO/AppFlowy/tree/main/frontend/app_flowy/packages/appflowy_board
|
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
|
## 0.0.5
|
||||||
* Support customize the hotkeys for a shortcut on different platforms.
|
* Support customize the hotkeys for a shortcut on different platforms.
|
||||||
* Support customize a theme.
|
* Support customize a theme.
|
||||||
|
@ -2,6 +2,8 @@ import 'dart:convert';
|
|||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:example/plugin/code_block_node_widget.dart';
|
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/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -119,14 +121,19 @@ class _MyHomePageState extends State<MyHomePage> {
|
|||||||
editable: true,
|
editable: true,
|
||||||
customBuilders: {
|
customBuilders: {
|
||||||
'text/code_block': CodeBlockNodeWidgetBuilder(),
|
'text/code_block': CodeBlockNodeWidgetBuilder(),
|
||||||
|
'tex': TeXBlockNodeWidgetBuidler(),
|
||||||
|
'horizontal_rule': HorizontalRuleWidgetBuilder(),
|
||||||
},
|
},
|
||||||
shortcutEvents: [
|
shortcutEvents: [
|
||||||
enterInCodeBlock,
|
enterInCodeBlock,
|
||||||
ignoreKeysInCodeBlock,
|
ignoreKeysInCodeBlock,
|
||||||
underscoreToItalic,
|
underscoreToItalic,
|
||||||
|
insertHorizontalRule,
|
||||||
],
|
],
|
||||||
selectionMenuItems: [
|
selectionMenuItems: [
|
||||||
codeBlockItem,
|
codeBlockMenuItem,
|
||||||
|
teXBlockMenuItem,
|
||||||
|
horizontalRuleMenuItem,
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -44,9 +44,13 @@ ShortcutEventHandler _ignorekHandler = (editorState, event) {
|
|||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
};
|
};
|
||||||
|
|
||||||
SelectionMenuItem codeBlockItem = SelectionMenuItem(
|
SelectionMenuItem codeBlockMenuItem = SelectionMenuItem(
|
||||||
name: 'Code Block',
|
name: () => 'Code Block',
|
||||||
icon: const Icon(Icons.abc),
|
icon: const Icon(
|
||||||
|
Icons.abc,
|
||||||
|
color: Colors.black,
|
||||||
|
size: 18.0,
|
||||||
|
),
|
||||||
keywords: ['code block'],
|
keywords: ['code block'],
|
||||||
handler: (editorState, _, __) {
|
handler: (editorState, _, __) {
|
||||||
final selection =
|
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
|
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
FlutterMacOS: ae6af50a8ea7d6103d888583d46bd8328a7e9811
|
FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424
|
||||||
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
|
path_provider_macos: 3c0c3b4b0d4a76d2bf989a913c2de869c5641a19
|
||||||
rich_clipboard_macos: 43364b66b9dc69d203eb8dd6d758e2d12e02723c
|
rich_clipboard_macos: 43364b66b9dc69d203eb8dd6d758e2d12e02723c
|
||||||
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
|
url_launcher_macos: 597e05b8e514239626bcf4a850fcf9ef5c856ec3
|
||||||
|
@ -44,6 +44,7 @@ dependencies:
|
|||||||
file_picker: ^5.0.1
|
file_picker: ^5.0.1
|
||||||
universal_html: ^2.0.8
|
universal_html: ^2.0.8
|
||||||
highlight: ^0.7.0
|
highlight: ^0.7.0
|
||||||
|
flutter_math_fork: ^0.6.3+1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
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",
|
"@@locale": "fr-CA",
|
||||||
"bold": "",
|
"bold": "gras",
|
||||||
"@bold": {},
|
"@bold": {},
|
||||||
"bulletedList": "",
|
"bulletedList": "liste à puces",
|
||||||
"@bulletedList": {},
|
"@bulletedList": {},
|
||||||
"checkbox": "",
|
"checkbox": "case à cocher",
|
||||||
"@checkbox": {},
|
"@checkbox": {},
|
||||||
"embedCode": "",
|
"embedCode": "incorporer Code",
|
||||||
"@embedCode": {},
|
"@embedCode": {},
|
||||||
"heading1": "",
|
"heading1": "en-tête1",
|
||||||
"@heading1": {},
|
"@heading1": {},
|
||||||
"heading2": "",
|
"heading2": "en-tête2",
|
||||||
"@heading2": {},
|
"@heading2": {},
|
||||||
"heading3": "",
|
"heading3": "en-tête3",
|
||||||
"@heading3": {},
|
"@heading3": {},
|
||||||
"highlight": "",
|
"highlight": "mettre en évidence",
|
||||||
"@highlight": {},
|
"@highlight": {},
|
||||||
"image": "",
|
"image": "l’image",
|
||||||
"@image": {},
|
"@image": {},
|
||||||
"italic": "",
|
"italic": "italique",
|
||||||
"@italic": {},
|
"@italic": {},
|
||||||
"link": "",
|
"link": "lien",
|
||||||
"@link": {},
|
"@link": {},
|
||||||
"numberedList": "",
|
"numberedList": "liste numérotée",
|
||||||
"@numberedList": {},
|
"@numberedList": {},
|
||||||
"quote": "",
|
"quote": "citation",
|
||||||
"@quote": {},
|
"@quote": {},
|
||||||
"strikethrough": "",
|
"strikethrough": "barré",
|
||||||
"@strikethrough": {},
|
"@strikethrough": {},
|
||||||
"text": "",
|
"text": "texte",
|
||||||
"@text": {},
|
"@text": {},
|
||||||
"underline": "",
|
"underline": "souligner",
|
||||||
"@underline": {}
|
"@underline": {}
|
||||||
}
|
}
|
@ -1,35 +1,35 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "fr-FR",
|
"@@locale": "fr-FR",
|
||||||
"bold": "",
|
"bold": "Gras",
|
||||||
"@bold": {},
|
"@bold": {},
|
||||||
"bulletedList": "",
|
"bulletedList": "List à puces",
|
||||||
"@bulletedList": {},
|
"@bulletedList": {},
|
||||||
"checkbox": "",
|
"checkbox": "Case à cocher",
|
||||||
"@checkbox": {},
|
"@checkbox": {},
|
||||||
"embedCode": "",
|
"embedCode": "Incorporer code",
|
||||||
"@embedCode": {},
|
"@embedCode": {},
|
||||||
"heading1": "",
|
"heading1": "Titre 1",
|
||||||
"@heading1": {},
|
"@heading1": {},
|
||||||
"heading2": "",
|
"heading2": "Titre 2",
|
||||||
"@heading2": {},
|
"@heading2": {},
|
||||||
"heading3": "",
|
"heading3": "Titre 3",
|
||||||
"@heading3": {},
|
"@heading3": {},
|
||||||
"highlight": "",
|
"highlight": "Surligné",
|
||||||
"@highlight": {},
|
"@highlight": {},
|
||||||
"image": "",
|
"image": "Image",
|
||||||
"@image": {},
|
"@image": {},
|
||||||
"italic": "",
|
"italic": "Italique",
|
||||||
"@italic": {},
|
"@italic": {},
|
||||||
"link": "",
|
"link": "Lien",
|
||||||
"@link": {},
|
"@link": {},
|
||||||
"numberedList": "",
|
"numberedList": "Liste numérotée",
|
||||||
"@numberedList": {},
|
"@numberedList": {},
|
||||||
"quote": "",
|
"quote": "Citation",
|
||||||
"@quote": {},
|
"@quote": {},
|
||||||
"strikethrough": "",
|
"strikethrough": "Barré",
|
||||||
"@strikethrough": {},
|
"@strikethrough": {},
|
||||||
"text": "",
|
"text": "Texte",
|
||||||
"@text": {},
|
"@text": {},
|
||||||
"underline": "",
|
"underline": "Souligné",
|
||||||
"@underline": {}
|
"@underline": {}
|
||||||
}
|
}
|
@ -1,35 +1,35 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "hu-HU",
|
"@@locale": "hu-HU",
|
||||||
"bold": "",
|
"bold": "bátor",
|
||||||
"@bold": {},
|
"@bold": {},
|
||||||
"bulletedList": "",
|
"bulletedList": "pontozott lista",
|
||||||
"@bulletedList": {},
|
"@bulletedList": {},
|
||||||
"checkbox": "",
|
"checkbox": "jelölőnégyzetet",
|
||||||
"@checkbox": {},
|
"@checkbox": {},
|
||||||
"embedCode": "",
|
"embedCode": "Beágyazás",
|
||||||
"@embedCode": {},
|
"@embedCode": {},
|
||||||
"heading1": "",
|
"heading1": "címsor1",
|
||||||
"@heading1": {},
|
"@heading1": {},
|
||||||
"heading2": "",
|
"heading2": "címsor2",
|
||||||
"@heading2": {},
|
"@heading2": {},
|
||||||
"heading3": "",
|
"heading3": "címsor3",
|
||||||
"@heading3": {},
|
"@heading3": {},
|
||||||
"highlight": "",
|
"highlight": "Kiemel",
|
||||||
"@highlight": {},
|
"@highlight": {},
|
||||||
"image": "",
|
"image": "kép",
|
||||||
"@image": {},
|
"@image": {},
|
||||||
"italic": "",
|
"italic": "dőlt",
|
||||||
"@italic": {},
|
"@italic": {},
|
||||||
"link": "",
|
"link": "link",
|
||||||
"@link": {},
|
"@link": {},
|
||||||
"numberedList": "",
|
"numberedList": "számozottLista",
|
||||||
"@numberedList": {},
|
"@numberedList": {},
|
||||||
"quote": "",
|
"quote": "idézet",
|
||||||
"@quote": {},
|
"@quote": {},
|
||||||
"strikethrough": "",
|
"strikethrough": "áthúzott",
|
||||||
"@strikethrough": {},
|
"@strikethrough": {},
|
||||||
"text": "",
|
"text": "szöveg",
|
||||||
"@text": {},
|
"@text": {},
|
||||||
"underline": "",
|
"underline": "aláhúzás",
|
||||||
"@underline": {}
|
"@underline": {}
|
||||||
}
|
}
|
@ -1,35 +1,35 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "id-ID",
|
"@@locale": "id-ID",
|
||||||
"bold": "",
|
"bold": "berani",
|
||||||
"@bold": {},
|
"@bold": {},
|
||||||
"bulletedList": "",
|
"bulletedList": "daftar berpoin",
|
||||||
"@bulletedList": {},
|
"@bulletedList": {},
|
||||||
"checkbox": "",
|
"checkbox": "kotak centang",
|
||||||
"@checkbox": {},
|
"@checkbox": {},
|
||||||
"embedCode": "",
|
"embedCode": "menyematkan Kode",
|
||||||
"@embedCode": {},
|
"@embedCode": {},
|
||||||
"heading1": "",
|
"heading1": "pos1",
|
||||||
"@heading1": {},
|
"@heading1": {},
|
||||||
"heading2": "",
|
"heading2": "pos2",
|
||||||
"@heading2": {},
|
"@heading2": {},
|
||||||
"heading3": "",
|
"heading3": "pos3",
|
||||||
"@heading3": {},
|
"@heading3": {},
|
||||||
"highlight": "",
|
"highlight": "menyorot",
|
||||||
"@highlight": {},
|
"@highlight": {},
|
||||||
"image": "",
|
"image": "gambar",
|
||||||
"@image": {},
|
"@image": {},
|
||||||
"italic": "",
|
"italic": "miring",
|
||||||
"@italic": {},
|
"@italic": {},
|
||||||
"link": "",
|
"link": "tautan",
|
||||||
"@link": {},
|
"@link": {},
|
||||||
"numberedList": "",
|
"numberedList": "daftar bernomor",
|
||||||
"@numberedList": {},
|
"@numberedList": {},
|
||||||
"quote": "",
|
"quote": "mengutip",
|
||||||
"@quote": {},
|
"@quote": {},
|
||||||
"strikethrough": "",
|
"strikethrough": "coret",
|
||||||
"@strikethrough": {},
|
"@strikethrough": {},
|
||||||
"text": "",
|
"text": "teks",
|
||||||
"@text": {},
|
"@text": {},
|
||||||
"underline": "",
|
"underline": "menggarisbawahi",
|
||||||
"@underline": {}
|
"@underline": {}
|
||||||
}
|
}
|
@ -1,35 +1,35 @@
|
|||||||
{
|
{
|
||||||
"@@locale": "it-IT",
|
"@@locale": "it-IT",
|
||||||
"bold": "",
|
"bold": "Grassetto",
|
||||||
"@bold": {},
|
"@bold": {},
|
||||||
"bulletedList": "",
|
"bulletedList": "Elenco puntato",
|
||||||
"@bulletedList": {},
|
"@bulletedList": {},
|
||||||
"checkbox": "",
|
"checkbox": "Casella di spunta",
|
||||||
"@checkbox": {},
|
"@checkbox": {},
|
||||||
"embedCode": "",
|
"embedCode": "Incorpora codice",
|
||||||
"@embedCode": {},
|
"@embedCode": {},
|
||||||
"heading1": "",
|
"heading1": "H1",
|
||||||
"@heading1": {},
|
"@heading1": {},
|
||||||
"heading2": "",
|
"heading2": "H2",
|
||||||
"@heading2": {},
|
"@heading2": {},
|
||||||
"heading3": "",
|
"heading3": "H3",
|
||||||
"@heading3": {},
|
"@heading3": {},
|
||||||
"highlight": "",
|
"highlight": "Evidenzia",
|
||||||
"@highlight": {},
|
"@highlight": {},
|
||||||
"image": "",
|
"image": "Immagine",
|
||||||
"@image": {},
|
"@image": {},
|
||||||
"italic": "",
|
"italic": "Corsivo",
|
||||||
"@italic": {},
|
"@italic": {},
|
||||||
"link": "",
|
"link": "Collegamento",
|
||||||
"@link": {},
|
"@link": {},
|
||||||
"numberedList": "",
|
"numberedList": "Elenco numerato",
|
||||||
"@numberedList": {},
|
"@numberedList": {},
|
||||||
"quote": "",
|
"quote": "Cita",
|
||||||
"@quote": {},
|
"@quote": {},
|
||||||
"strikethrough": "",
|
"strikethrough": "Barrato",
|
||||||
"@strikethrough": {},
|
"@strikethrough": {},
|
||||||
"text": "",
|
"text": "Testo",
|
||||||
"@text": {},
|
"@text": {},
|
||||||
"underline": "",
|
"underline": "Sottolineato",
|
||||||
"@underline": {}
|
"@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",
|
"@@locale": "pt-PT",
|
||||||
"bold": "",
|
"bold": "negrito",
|
||||||
"@bold": {},
|
"@bold": {},
|
||||||
"bulletedList": "",
|
"bulletedList": "lista com marcadores",
|
||||||
"@bulletedList": {},
|
"@bulletedList": {},
|
||||||
"checkbox": "",
|
"checkbox": "caixa de seleção",
|
||||||
"@checkbox": {},
|
"@checkbox": {},
|
||||||
"embedCode": "",
|
"embedCode": "Código embutido",
|
||||||
"@embedCode": {},
|
"@embedCode": {},
|
||||||
"heading1": "",
|
"heading1": "Cabeçallho 1",
|
||||||
"@heading1": {},
|
"@heading1": {},
|
||||||
"heading2": "",
|
"heading2": "Cabeçallho 2",
|
||||||
"@heading2": {},
|
"@heading2": {},
|
||||||
"heading3": "",
|
"heading3": "Cabeçallho 3",
|
||||||
"@heading3": {},
|
"@heading3": {},
|
||||||
"highlight": "",
|
"highlight": "realçar",
|
||||||
"@highlight": {},
|
"@highlight": {},
|
||||||
"image": "",
|
"image": "imagem",
|
||||||
"@image": {},
|
"@image": {},
|
||||||
"italic": "",
|
"italic": "itálico",
|
||||||
"@italic": {},
|
"@italic": {},
|
||||||
"link": "",
|
"link": "link",
|
||||||
"@link": {},
|
"@link": {},
|
||||||
"numberedList": "",
|
"numberedList": "lista numerada",
|
||||||
"@numberedList": {},
|
"@numberedList": {},
|
||||||
"quote": "",
|
"quote": "citar",
|
||||||
"@quote": {},
|
"@quote": {},
|
||||||
"strikethrough": "",
|
"strikethrough": "tachado",
|
||||||
"@strikethrough": {},
|
"@strikethrough": {},
|
||||||
"text": "",
|
"text": "texto",
|
||||||
"@text": {},
|
"@text": {},
|
||||||
"underline": "",
|
"underline": "sublinhado",
|
||||||
"@underline": {}
|
"@underline": {}
|
||||||
}
|
}
|
@ -17,9 +17,9 @@ extension NodeAttributesExtensions on Attributes {
|
|||||||
return containsKey(BuiltInAttributeKey.quote);
|
return containsKey(BuiltInAttributeKey.quote);
|
||||||
}
|
}
|
||||||
|
|
||||||
int? get number {
|
num? get number {
|
||||||
if (containsKey(BuiltInAttributeKey.number) &&
|
if (containsKey(BuiltInAttributeKey.number) &&
|
||||||
this[BuiltInAttributeKey.number] is int) {
|
this[BuiltInAttributeKey.number] is num) {
|
||||||
return this[BuiltInAttributeKey.number];
|
return this[BuiltInAttributeKey.number];
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -27,7 +27,7 @@ extension NodeAttributesExtensions on Attributes {
|
|||||||
|
|
||||||
bool get code {
|
bool get code {
|
||||||
if (containsKey(BuiltInAttributeKey.code) &&
|
if (containsKey(BuiltInAttributeKey.code) &&
|
||||||
this[BuiltInAttributeKey.code] == true) {
|
this[BuiltInAttributeKey.code] is bool) {
|
||||||
return this[BuiltInAttributeKey.code];
|
return this[BuiltInAttributeKey.code];
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -63,11 +63,14 @@ extension DeltaAttributesExtensions on Attributes {
|
|||||||
this[BuiltInAttributeKey.strikethrough] == true);
|
this[BuiltInAttributeKey.strikethrough] == true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const whiteInt = 0XFFFFFFFF;
|
||||||
|
|
||||||
Color? get color {
|
Color? get color {
|
||||||
if (containsKey(BuiltInAttributeKey.color) &&
|
if (containsKey(BuiltInAttributeKey.color) &&
|
||||||
this[BuiltInAttributeKey.color] is String) {
|
this[BuiltInAttributeKey.color] is String) {
|
||||||
return Color(
|
return Color(
|
||||||
int.parse(this[BuiltInAttributeKey.color]),
|
// If the parse fails returns white by default
|
||||||
|
int.tryParse(this[BuiltInAttributeKey.color]) ?? whiteInt,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -77,8 +80,7 @@ extension DeltaAttributesExtensions on Attributes {
|
|||||||
if (containsKey(BuiltInAttributeKey.backgroundColor) &&
|
if (containsKey(BuiltInAttributeKey.backgroundColor) &&
|
||||||
this[BuiltInAttributeKey.backgroundColor] is String) {
|
this[BuiltInAttributeKey.backgroundColor] is String) {
|
||||||
return Color(
|
return Color(
|
||||||
int.parse(this[BuiltInAttributeKey.backgroundColor]),
|
int.tryParse(this[BuiltInAttributeKey.backgroundColor]) ?? whiteInt);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return null;
|
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';
|
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
||||||
|
|
||||||
extension TextNodeExtension on TextNode {
|
extension TextNodeExtension on TextNode {
|
||||||
dynamic getAttributeInSelection(Selection selection, String styleKey) {
|
T? getAttributeInSelection<T>(Selection selection, String styleKey) {
|
||||||
final ops = delta.whereType<TextInsert>();
|
final ops = delta.whereType<TextInsert>();
|
||||||
final startOffset =
|
final startOffset =
|
||||||
selection.isBackward ? selection.start.offset : selection.end.offset;
|
selection.isBackward ? selection.start.offset : selection.end.offset;
|
||||||
@ -19,8 +19,9 @@ extension TextNodeExtension on TextNode {
|
|||||||
}
|
}
|
||||||
final length = op.length;
|
final length = op.length;
|
||||||
if (start < endOffset && start + length > startOffset) {
|
if (start < endOffset && start + length > startOffset) {
|
||||||
if (op.attributes?.containsKey(styleKey) == true) {
|
final attributes = op.attributes;
|
||||||
return op.attributes![styleKey];
|
if (attributes != null && attributes[styleKey] is T?) {
|
||||||
|
return attributes[styleKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
start += length;
|
start += length;
|
||||||
|
@ -16,6 +16,7 @@ import 'package:intl/message_lookup_by_library.dart';
|
|||||||
import 'package:intl/src/intl_helpers.dart';
|
import 'package:intl/src/intl_helpers.dart';
|
||||||
|
|
||||||
import 'messages_ca.dart' as messages_ca;
|
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_de-DE.dart' as messages_de_de;
|
||||||
import 'messages_en.dart' as messages_en;
|
import 'messages_en.dart' as messages_en;
|
||||||
import 'messages_es-VE.dart' as messages_es_ve;
|
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_id-ID.dart' as messages_id_id;
|
||||||
import 'messages_it-IT.dart' as messages_it_it;
|
import 'messages_it-IT.dart' as messages_it_it;
|
||||||
import 'messages_ja-JP.dart' as messages_ja_jp;
|
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_pl-PL.dart' as messages_pl_pl;
|
||||||
import 'messages_pt-BR.dart' as messages_pt_br;
|
import 'messages_pt-BR.dart' as messages_pt_br;
|
||||||
import 'messages_pt-PT.dart' as messages_pt_pt;
|
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();
|
typedef Future<dynamic> LibraryLoader();
|
||||||
Map<String, LibraryLoader> _deferredLibraries = {
|
Map<String, LibraryLoader> _deferredLibraries = {
|
||||||
'ca': () => new Future.value(null),
|
'ca': () => new Future.value(null),
|
||||||
|
'cs_CZ': () => new Future.value(null),
|
||||||
'de_DE': () => new Future.value(null),
|
'de_DE': () => new Future.value(null),
|
||||||
'en': () => new Future.value(null),
|
'en': () => new Future.value(null),
|
||||||
'es_VE': () => new Future.value(null),
|
'es_VE': () => new Future.value(null),
|
||||||
@ -45,6 +49,8 @@ Map<String, LibraryLoader> _deferredLibraries = {
|
|||||||
'id_ID': () => new Future.value(null),
|
'id_ID': () => new Future.value(null),
|
||||||
'it_IT': () => new Future.value(null),
|
'it_IT': () => new Future.value(null),
|
||||||
'ja_JP': () => 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),
|
'pl_PL': () => new Future.value(null),
|
||||||
'pt_BR': () => new Future.value(null),
|
'pt_BR': () => new Future.value(null),
|
||||||
'pt_PT': () => new Future.value(null),
|
'pt_PT': () => new Future.value(null),
|
||||||
@ -58,6 +64,8 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
|||||||
switch (localeName) {
|
switch (localeName) {
|
||||||
case 'ca':
|
case 'ca':
|
||||||
return messages_ca.messages;
|
return messages_ca.messages;
|
||||||
|
case 'cs_CZ':
|
||||||
|
return messages_cs_cz.messages;
|
||||||
case 'de_DE':
|
case 'de_DE':
|
||||||
return messages_de_de.messages;
|
return messages_de_de.messages;
|
||||||
case 'en':
|
case 'en':
|
||||||
@ -76,6 +84,10 @@ MessageLookupByLibrary? _findExact(String localeName) {
|
|||||||
return messages_it_it.messages;
|
return messages_it_it.messages;
|
||||||
case 'ja_JP':
|
case 'ja_JP':
|
||||||
return messages_ja_jp.messages;
|
return messages_ja_jp.messages;
|
||||||
|
case 'ml_IN':
|
||||||
|
return messages_ml_in.messages;
|
||||||
|
case 'nl_NL':
|
||||||
|
return messages_nl_nl.messages;
|
||||||
case 'pl_PL':
|
case 'pl_PL':
|
||||||
return messages_pl_pl.messages;
|
return messages_pl_pl.messages;
|
||||||
case 'pt_BR':
|
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);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
"bold": MessageLookupByLibrary.simpleMessage("gras"),
|
||||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
"bulletedList": MessageLookupByLibrary.simpleMessage("liste à puces"),
|
||||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
"checkbox": MessageLookupByLibrary.simpleMessage("case à cocher"),
|
||||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
"embedCode": MessageLookupByLibrary.simpleMessage("incorporer Code"),
|
||||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
"heading1": MessageLookupByLibrary.simpleMessage("en-tête1"),
|
||||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
"heading2": MessageLookupByLibrary.simpleMessage("en-tête2"),
|
||||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
"heading3": MessageLookupByLibrary.simpleMessage("en-tête3"),
|
||||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
"highlight": MessageLookupByLibrary.simpleMessage("mettre en évidence"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
"image": MessageLookupByLibrary.simpleMessage("l’image"),
|
||||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
"italic": MessageLookupByLibrary.simpleMessage("italique"),
|
||||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
"link": MessageLookupByLibrary.simpleMessage("lien"),
|
||||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
"numberedList": MessageLookupByLibrary.simpleMessage("liste numérotée"),
|
||||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
"quote": MessageLookupByLibrary.simpleMessage("citation"),
|
||||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
"strikethrough": MessageLookupByLibrary.simpleMessage("barré"),
|
||||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
"text": MessageLookupByLibrary.simpleMessage("texte"),
|
||||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
"underline": MessageLookupByLibrary.simpleMessage("souligner")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
"bold": MessageLookupByLibrary.simpleMessage("Gras"),
|
||||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
"bulletedList": MessageLookupByLibrary.simpleMessage("List à puces"),
|
||||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
"checkbox": MessageLookupByLibrary.simpleMessage("Case à cocher"),
|
||||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
"embedCode": MessageLookupByLibrary.simpleMessage("Incorporer code"),
|
||||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
"heading1": MessageLookupByLibrary.simpleMessage("Titre 1"),
|
||||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
"heading2": MessageLookupByLibrary.simpleMessage("Titre 2"),
|
||||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
"heading3": MessageLookupByLibrary.simpleMessage("Titre 3"),
|
||||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
"highlight": MessageLookupByLibrary.simpleMessage("Surligné"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
"image": MessageLookupByLibrary.simpleMessage("Image"),
|
||||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
"italic": MessageLookupByLibrary.simpleMessage("Italique"),
|
||||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
"link": MessageLookupByLibrary.simpleMessage("Lien"),
|
||||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
"numberedList": MessageLookupByLibrary.simpleMessage("Liste numérotée"),
|
||||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
"quote": MessageLookupByLibrary.simpleMessage("Citation"),
|
||||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
"strikethrough": MessageLookupByLibrary.simpleMessage("Barré"),
|
||||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
"text": MessageLookupByLibrary.simpleMessage("Texte"),
|
||||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
"underline": MessageLookupByLibrary.simpleMessage("Souligné")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
"bold": MessageLookupByLibrary.simpleMessage("bátor"),
|
||||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
"bulletedList": MessageLookupByLibrary.simpleMessage("pontozott lista"),
|
||||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
"checkbox": MessageLookupByLibrary.simpleMessage("jelölőnégyzetet"),
|
||||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
"embedCode": MessageLookupByLibrary.simpleMessage("Beágyazás"),
|
||||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
"heading1": MessageLookupByLibrary.simpleMessage("címsor1"),
|
||||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
"heading2": MessageLookupByLibrary.simpleMessage("címsor2"),
|
||||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
"heading3": MessageLookupByLibrary.simpleMessage("címsor3"),
|
||||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
"highlight": MessageLookupByLibrary.simpleMessage("Kiemel"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
"image": MessageLookupByLibrary.simpleMessage("kép"),
|
||||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
"italic": MessageLookupByLibrary.simpleMessage("dőlt"),
|
||||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
"link": MessageLookupByLibrary.simpleMessage("link"),
|
||||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
"numberedList": MessageLookupByLibrary.simpleMessage("számozottLista"),
|
||||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
"quote": MessageLookupByLibrary.simpleMessage("idézet"),
|
||||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
"strikethrough": MessageLookupByLibrary.simpleMessage("áthúzott"),
|
||||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
"text": MessageLookupByLibrary.simpleMessage("szöveg"),
|
||||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
"underline": MessageLookupByLibrary.simpleMessage("aláhúzás")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
"bold": MessageLookupByLibrary.simpleMessage("berani"),
|
||||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
"bulletedList": MessageLookupByLibrary.simpleMessage("daftar berpoin"),
|
||||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
"checkbox": MessageLookupByLibrary.simpleMessage("kotak centang"),
|
||||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
"embedCode": MessageLookupByLibrary.simpleMessage("menyematkan Kode"),
|
||||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
"heading1": MessageLookupByLibrary.simpleMessage("pos1"),
|
||||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
"heading2": MessageLookupByLibrary.simpleMessage("pos2"),
|
||||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
"heading3": MessageLookupByLibrary.simpleMessage("pos3"),
|
||||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
"highlight": MessageLookupByLibrary.simpleMessage("menyorot"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
"image": MessageLookupByLibrary.simpleMessage("gambar"),
|
||||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
"italic": MessageLookupByLibrary.simpleMessage("miring"),
|
||||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
"link": MessageLookupByLibrary.simpleMessage("tautan"),
|
||||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
"numberedList": MessageLookupByLibrary.simpleMessage("daftar bernomor"),
|
||||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
"quote": MessageLookupByLibrary.simpleMessage("mengutip"),
|
||||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
"strikethrough": MessageLookupByLibrary.simpleMessage("coret"),
|
||||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
"text": MessageLookupByLibrary.simpleMessage("teks"),
|
||||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
"underline": MessageLookupByLibrary.simpleMessage("menggarisbawahi")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,21 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
"bold": MessageLookupByLibrary.simpleMessage("Grassetto"),
|
||||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
"bulletedList": MessageLookupByLibrary.simpleMessage("Elenco puntato"),
|
||||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
"checkbox": MessageLookupByLibrary.simpleMessage("Casella di spunta"),
|
||||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
"embedCode": MessageLookupByLibrary.simpleMessage("Incorpora codice"),
|
||||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
|
||||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
|
||||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
|
||||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
"highlight": MessageLookupByLibrary.simpleMessage("Evidenzia"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
"image": MessageLookupByLibrary.simpleMessage("Immagine"),
|
||||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
"italic": MessageLookupByLibrary.simpleMessage("Corsivo"),
|
||||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
"link": MessageLookupByLibrary.simpleMessage("Collegamento"),
|
||||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
"numberedList": MessageLookupByLibrary.simpleMessage("Elenco numerato"),
|
||||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
"quote": MessageLookupByLibrary.simpleMessage("Cita"),
|
||||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
"strikethrough": MessageLookupByLibrary.simpleMessage("Barrato"),
|
||||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
"text": MessageLookupByLibrary.simpleMessage("Testo"),
|
||||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
"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);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
"bold": MessageLookupByLibrary.simpleMessage("Negrito"),
|
||||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
"bulletedList":
|
||||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
MessageLookupByLibrary.simpleMessage("Lista de marcadores"),
|
||||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
"checkbox": MessageLookupByLibrary.simpleMessage("Caixa de seleção"),
|
||||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
"embedCode": MessageLookupByLibrary.simpleMessage("Código incorporado"),
|
||||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
"heading1": MessageLookupByLibrary.simpleMessage("H1"),
|
||||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
"heading2": MessageLookupByLibrary.simpleMessage("H2"),
|
||||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
"heading3": MessageLookupByLibrary.simpleMessage("H3"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
"highlight": MessageLookupByLibrary.simpleMessage("Destacar"),
|
||||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
"image": MessageLookupByLibrary.simpleMessage("Imagem"),
|
||||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
"italic": MessageLookupByLibrary.simpleMessage("Itálico"),
|
||||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
"link": MessageLookupByLibrary.simpleMessage("Link"),
|
||||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
"numberedList": MessageLookupByLibrary.simpleMessage("Lista numerada"),
|
||||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
"quote": MessageLookupByLibrary.simpleMessage("Citar"),
|
||||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
"strikethrough": MessageLookupByLibrary.simpleMessage("Rasurar"),
|
||||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
"text": MessageLookupByLibrary.simpleMessage("Texto"),
|
||||||
|
"underline": MessageLookupByLibrary.simpleMessage("Sublinhar")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -22,21 +22,22 @@ class MessageLookup extends MessageLookupByLibrary {
|
|||||||
|
|
||||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||||
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
|
||||||
"bold": MessageLookupByLibrary.simpleMessage(""),
|
"bold": MessageLookupByLibrary.simpleMessage("negrito"),
|
||||||
"bulletedList": MessageLookupByLibrary.simpleMessage(""),
|
"bulletedList":
|
||||||
"checkbox": MessageLookupByLibrary.simpleMessage(""),
|
MessageLookupByLibrary.simpleMessage("lista com marcadores"),
|
||||||
"embedCode": MessageLookupByLibrary.simpleMessage(""),
|
"checkbox": MessageLookupByLibrary.simpleMessage("caixa de seleção"),
|
||||||
"heading1": MessageLookupByLibrary.simpleMessage(""),
|
"embedCode": MessageLookupByLibrary.simpleMessage("Código embutido"),
|
||||||
"heading2": MessageLookupByLibrary.simpleMessage(""),
|
"heading1": MessageLookupByLibrary.simpleMessage("Cabeçallho 1"),
|
||||||
"heading3": MessageLookupByLibrary.simpleMessage(""),
|
"heading2": MessageLookupByLibrary.simpleMessage("Cabeçallho 2"),
|
||||||
"highlight": MessageLookupByLibrary.simpleMessage(""),
|
"heading3": MessageLookupByLibrary.simpleMessage("Cabeçallho 3"),
|
||||||
"image": MessageLookupByLibrary.simpleMessage(""),
|
"highlight": MessageLookupByLibrary.simpleMessage("realçar"),
|
||||||
"italic": MessageLookupByLibrary.simpleMessage(""),
|
"image": MessageLookupByLibrary.simpleMessage("imagem"),
|
||||||
"link": MessageLookupByLibrary.simpleMessage(""),
|
"italic": MessageLookupByLibrary.simpleMessage("itálico"),
|
||||||
"numberedList": MessageLookupByLibrary.simpleMessage(""),
|
"link": MessageLookupByLibrary.simpleMessage("link"),
|
||||||
"quote": MessageLookupByLibrary.simpleMessage(""),
|
"numberedList": MessageLookupByLibrary.simpleMessage("lista numerada"),
|
||||||
"strikethrough": MessageLookupByLibrary.simpleMessage(""),
|
"quote": MessageLookupByLibrary.simpleMessage("citar"),
|
||||||
"text": MessageLookupByLibrary.simpleMessage(""),
|
"strikethrough": MessageLookupByLibrary.simpleMessage("tachado"),
|
||||||
"underline": MessageLookupByLibrary.simpleMessage("")
|
"text": MessageLookupByLibrary.simpleMessage("texto"),
|
||||||
|
"underline": MessageLookupByLibrary.simpleMessage("sublinhado")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -220,6 +220,7 @@ class AppLocalizationDelegate
|
|||||||
return const <Locale>[
|
return const <Locale>[
|
||||||
Locale.fromSubtags(languageCode: 'en'),
|
Locale.fromSubtags(languageCode: 'en'),
|
||||||
Locale.fromSubtags(languageCode: 'ca'),
|
Locale.fromSubtags(languageCode: 'ca'),
|
||||||
|
Locale.fromSubtags(languageCode: 'cs', countryCode: 'CZ'),
|
||||||
Locale.fromSubtags(languageCode: 'de', countryCode: 'DE'),
|
Locale.fromSubtags(languageCode: 'de', countryCode: 'DE'),
|
||||||
Locale.fromSubtags(languageCode: 'es', countryCode: 'VE'),
|
Locale.fromSubtags(languageCode: 'es', countryCode: 'VE'),
|
||||||
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CA'),
|
Locale.fromSubtags(languageCode: 'fr', countryCode: 'CA'),
|
||||||
@ -228,6 +229,8 @@ class AppLocalizationDelegate
|
|||||||
Locale.fromSubtags(languageCode: 'id', countryCode: 'ID'),
|
Locale.fromSubtags(languageCode: 'id', countryCode: 'ID'),
|
||||||
Locale.fromSubtags(languageCode: 'it', countryCode: 'IT'),
|
Locale.fromSubtags(languageCode: 'it', countryCode: 'IT'),
|
||||||
Locale.fromSubtags(languageCode: 'ja', countryCode: 'JP'),
|
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: 'pl', countryCode: 'PL'),
|
||||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'BR'),
|
Locale.fromSubtags(languageCode: 'pt', countryCode: 'BR'),
|
||||||
Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'),
|
Locale.fromSubtags(languageCode: 'pt', countryCode: 'PT'),
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/render/rich_text/default_selectable.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
abstract class BuiltInTextWidget extends StatefulWidget {
|
abstract class BuiltInTextWidget extends StatefulWidget {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:appflowy_editor/src/render/selection/selectable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
class CursorWidget extends StatefulWidget {
|
class CursorWidget extends StatefulWidget {
|
||||||
@ -9,9 +10,13 @@ class CursorWidget extends StatefulWidget {
|
|||||||
required this.rect,
|
required this.rect,
|
||||||
required this.color,
|
required this.color,
|
||||||
this.blinkingInterval = 0.5,
|
this.blinkingInterval = 0.5,
|
||||||
|
this.shouldBlink = true,
|
||||||
|
this.cursorStyle = CursorStyle.verticalLine,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final double blinkingInterval; // milliseconds
|
final double blinkingInterval; // milliseconds
|
||||||
|
final bool shouldBlink;
|
||||||
|
final CursorStyle cursorStyle;
|
||||||
final Color color;
|
final Color color;
|
||||||
final Rect rect;
|
final Rect rect;
|
||||||
final LayerLink layerLink;
|
final LayerLink layerLink;
|
||||||
@ -67,11 +72,28 @@ class CursorWidgetState extends State<CursorWidget> {
|
|||||||
// Ignore the gestures in cursor
|
// Ignore the gestures in cursor
|
||||||
// to solve the problem that cursor area cannot be selected.
|
// to solve the problem that cursor area cannot be selected.
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: Container(
|
child: _buildCursor(context),
|
||||||
color: showCursor ? widget.color : Colors.transparent,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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:appflowy_editor/src/document/selection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
enum CursorStyle {
|
||||||
|
verticalLine,
|
||||||
|
borderLine,
|
||||||
|
}
|
||||||
|
|
||||||
/// [SelectableMixin] is used for the editor to calculate the position
|
/// [SelectableMixin] is used for the editor to calculate the position
|
||||||
/// and size of the selection.
|
/// and size of the selection.
|
||||||
///
|
///
|
||||||
@ -53,4 +58,8 @@ mixin SelectableMixin<T extends StatefulWidget> on State<T> {
|
|||||||
Selection? getWorldBoundaryInOffset(Offset start) {
|
Selection? getWorldBoundaryInOffset(Offset start) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool get shouldCursorBlink => true;
|
||||||
|
|
||||||
|
CursorStyle get cursorStyle => CursorStyle.verticalLine;
|
||||||
}
|
}
|
||||||
|
@ -169,6 +169,14 @@ final List<SelectionMenuItem> _defaultSelectionMenuItems = [
|
|||||||
insertBulletedListAfterSelection(editorState);
|
insertBulletedListAfterSelection(editorState);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
SelectionMenuItem(
|
||||||
|
name: () => AppFlowyEditorLocalizations.current.numberedList,
|
||||||
|
icon: _selectionMenuIcon('number'),
|
||||||
|
keywords: ['numbered list', 'list', 'ordered list'],
|
||||||
|
handler: (editorState, _, __) {
|
||||||
|
insertNumberedListAfterSelection(editorState);
|
||||||
|
},
|
||||||
|
),
|
||||||
SelectionMenuItem(
|
SelectionMenuItem(
|
||||||
name: () => AppFlowyEditorLocalizations.current.checkbox,
|
name: () => AppFlowyEditorLocalizations.current.checkbox,
|
||||||
icon: _selectionMenuIcon('checkbox'),
|
icon: _selectionMenuIcon('checkbox'),
|
||||||
|
@ -7,10 +7,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
typedef SelectionMenuItemHandler = void Function(
|
typedef SelectionMenuItemHandler = void Function(
|
||||||
EditorState editorState,
|
EditorState editorState,
|
||||||
SelectionMenuService menuService,
|
SelectionMenuService menuService,
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// Selection Menu Item
|
/// Selection Menu Item
|
||||||
class SelectionMenuItem {
|
class SelectionMenuItem {
|
||||||
@ -23,7 +23,7 @@ class SelectionMenuItem {
|
|||||||
this.handler = (editorState, menuService, context) {
|
this.handler = (editorState, menuService, context) {
|
||||||
_deleteToSlash(editorState);
|
_deleteToSlash(editorState);
|
||||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||||
handler(editorState, menuService, context);
|
handler(editorState, menuService, context);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -333,8 +333,10 @@ void showLinkMenu(
|
|||||||
final textNode = node.first as TextNode;
|
final textNode = node.first as TextNode;
|
||||||
String? linkText;
|
String? linkText;
|
||||||
if (textNode.allSatisfyLinkInSelection(selection)) {
|
if (textNode.allSatisfyLinkInSelection(selection)) {
|
||||||
linkText =
|
linkText = textNode.getAttributeInSelection<String>(
|
||||||
textNode.getAttributeInSelection(selection, BuiltInAttributeKey.href);
|
selection,
|
||||||
|
BuiltInAttributeKey.href,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
_linkMenuOverlay = OverlayEntry(builder: (context) {
|
_linkMenuOverlay = OverlayEntry(builder: (context) {
|
||||||
return Positioned(
|
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(
|
bool insertTextNodeAfterSelection(
|
||||||
EditorState editorState, Attributes attributes) {
|
EditorState editorState, Attributes attributes) {
|
||||||
final selection = editorState.service.selectionService.currentSelection.value;
|
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/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
|
||||||
|
|
||||||
// Handle delete text.
|
// Handle delete text.
|
||||||
ShortcutEventHandler deleteTextHandler = (editorState, event) {
|
ShortcutEventHandler deleteTextHandler = (editorState, event) {
|
||||||
@ -84,6 +83,11 @@ KeyEventResult _handleBackspace(EditorState editorState, RawKeyEvent event) {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (textNodes.isEmpty) {
|
if (textNodes.isEmpty) {
|
||||||
|
if (nonTextNodes.isNotEmpty) {
|
||||||
|
transactionBuilder.afterSelection =
|
||||||
|
Selection.collapsed(selection.start);
|
||||||
|
}
|
||||||
|
transactionBuilder.commit();
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
final startPosition = selection.start;
|
final startPosition = selection.start;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/infra/html_converter.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/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:appflowy_editor/src/service/internal_key_event_handlers/number_list_helper.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:rich_clipboard/rich_clipboard.dart';
|
import 'package:rich_clipboard/rich_clipboard.dart';
|
||||||
|
@ -3,7 +3,6 @@ import 'dart:collection';
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
|
||||||
import './number_list_helper.dart';
|
import './number_list_helper.dart';
|
||||||
|
|
||||||
/// Handle some cases where enter is pressed and shift is not pressed.
|
/// 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/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/extensions/text_node_extensions.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';
|
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);
|
.substring(selection.start.offset, selection.end.offset);
|
||||||
|
|
||||||
// toggle code style when selected some text
|
// toggle code style when selected some text
|
||||||
if (selectionText.length > 0) {
|
if (selectionText.isNotEmpty) {
|
||||||
formatEmbedCode(editorState);
|
formatEmbedCode(editorState);
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
}
|
}
|
||||||
@ -124,3 +123,121 @@ ShortcutEventHandler backquoteToCodeHandler = (editorState, event) {
|
|||||||
|
|
||||||
return KeyEventResult.handled;
|
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,
|
rect: cursorRect,
|
||||||
color: widget.cursorColor,
|
color: widget.cursorColor,
|
||||||
layerLink: node.layerLink,
|
layerLink: node.layerLink,
|
||||||
|
shouldBlink: selectable.shouldCursorBlink,
|
||||||
|
cursorStyle: selectable.cursorStyle,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -263,10 +263,21 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
|||||||
command: 'shift+underscore',
|
command: 'shift+underscore',
|
||||||
handler: doubleUnderscoresToBold,
|
handler: doubleUnderscoresToBold,
|
||||||
),
|
),
|
||||||
|
ShortcutEvent(
|
||||||
key: 'Backquote to code',
|
key: 'Backquote to code',
|
||||||
command: 'backquote',
|
command: 'backquote',
|
||||||
handler: backquoteToCodeHandler,
|
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
|
// https://github.com/flutter/flutter/issues/104944
|
||||||
// Workaround: Using space editing on the web platform often results in errors,
|
// 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
|
// so adding a shortcut event to handle the space input instead of using the
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
name: appflowy_editor
|
name: appflowy_editor
|
||||||
description: A highly customizable rich-text editor for Flutter
|
description: A highly customizable rich-text editor for Flutter
|
||||||
version: 0.0.5
|
version: 0.0.6
|
||||||
homepage: https://github.com/AppFlowy-IO/AppFlowy
|
homepage: https://github.com/AppFlowy-IO/AppFlowy
|
||||||
|
|
||||||
platforms:
|
platforms:
|
||||||
linux:
|
linux:
|
||||||
macos:
|
macos:
|
||||||
windows:
|
windows:
|
||||||
|
web:
|
||||||
|
|
||||||
environment:
|
environment:
|
||||||
sdk: ">=2.17.0 <3.0.0"
|
sdk: ">=2.17.0 <3.0.0"
|
||||||
@ -32,6 +33,7 @@ dev_dependencies:
|
|||||||
sdk: flutter
|
sdk: flutter
|
||||||
flutter_lints: ^2.0.1
|
flutter_lints: ^2.0.1
|
||||||
network_image_mock: ^2.1.1
|
network_image_mock: ^2.1.1
|
||||||
|
mockito: ^5.3.2
|
||||||
|
|
||||||
# For information on the generic Dart part of this file, see the
|
# For information on the generic Dart part of this file, see the
|
||||||
# following page: https://dart.dev/tools/pub/pubspec
|
# 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:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:appflowy_editor/src/extensions/path_extensions.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
setUpAll(() {
|
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) {
|
if (this == LogicalKeyboardKey.underscore) {
|
||||||
return PhysicalKeyboardKey.minus;
|
return PhysicalKeyboardKey.minus;
|
||||||
}
|
}
|
||||||
|
if (this == LogicalKeyboardKey.tilde) {
|
||||||
|
return PhysicalKeyboardKey.backquote;
|
||||||
|
}
|
||||||
throw UnimplementedError();
|
throw UnimplementedError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,4 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
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:appflowy_editor/src/extensions/text_node_extensions.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../../infra/test_editor.dart';
|
import '../../infra/test_editor.dart';
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
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/extensions/text_node_extensions.dart';
|
||||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart';
|
import 'package:appflowy_editor/src/render/toolbar/toolbar_item_widget.dart';
|
||||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_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/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_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_service.dart';
|
||||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../../infra/test_editor.dart';
|
import '../../infra/test_editor.dart';
|
||||||
@ -13,8 +12,8 @@ void main() async {
|
|||||||
|
|
||||||
group('selection_menu_widget.dart', () {
|
group('selection_menu_widget.dart', () {
|
||||||
for (var i = 0; i < defaultSelectionMenuItems.length; i += 1) {
|
for (var i = 0; i < defaultSelectionMenuItems.length; i += 1) {
|
||||||
testWidgets('Selects number.$i item in selection menu with enter', (
|
testWidgets('Selects number.$i item in selection menu with enter',
|
||||||
tester) async {
|
(tester) async {
|
||||||
final editor = await _prepare(tester);
|
final editor = await _prepare(tester);
|
||||||
for (var j = 0; j < i; j++) {
|
for (var j = 0; j < i; j++) {
|
||||||
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
|
await editor.pressLogicKey(LogicalKeyboardKey.arrowDown);
|
||||||
@ -30,8 +29,8 @@ void main() async {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('Selects number.$i item in selection menu with click', (
|
testWidgets('Selects number.$i item in selection menu with click',
|
||||||
tester) async {
|
(tester) async {
|
||||||
final editor = await _prepare(tester);
|
final editor = await _prepare(tester);
|
||||||
|
|
||||||
await tester.tap(find.byType(SelectionMenuItemWidget).at(i));
|
await tester.tap(find.byType(SelectionMenuItemWidget).at(i));
|
||||||
@ -59,7 +58,7 @@ void main() async {
|
|||||||
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
await editor.pressLogicKey(LogicalKeyboardKey.backspace);
|
||||||
expect(
|
expect(
|
||||||
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
find.byType(SelectionMenuItemWidget, skipOffstage: false),
|
||||||
findsNWidgets(4),
|
findsNWidgets(5),
|
||||||
);
|
);
|
||||||
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
await editor.pressLogicKey(LogicalKeyboardKey.keyE);
|
||||||
expect(
|
expect(
|
||||||
@ -148,7 +147,8 @@ Future<void> _testDefaultSelectionMenuItems(
|
|||||||
int index, EditorWidgetTester editor) async {
|
int index, EditorWidgetTester editor) async {
|
||||||
expect(editor.documentLength, 4);
|
expect(editor.documentLength, 4);
|
||||||
expect(editor.documentSelection, Selection.single(path: [2], startOffset: 0));
|
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 node = editor.nodeAtPath([2]);
|
||||||
final item = defaultSelectionMenuItems[index];
|
final item = defaultSelectionMenuItems[index];
|
||||||
final itemName = item.name();
|
final itemName = item.name();
|
||||||
|
@ -2,7 +2,6 @@ import 'dart:collection';
|
|||||||
|
|
||||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||||
import 'package:appflowy_editor/src/render/image/image_node_widget.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/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:network_image_mock/network_image_mock.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/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../../infra/test_editor.dart';
|
import '../../infra/test_editor.dart';
|
||||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
|
@ -150,5 +150,111 @@ void main() async {
|
|||||||
expect(textNode.toRawString(), text);
|
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/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_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_service.dart';
|
||||||
import 'package:appflowy_editor/src/render/selection_menu/selection_menu_widget.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../../infra/test_editor.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/services.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../../infra/test_editor.dart';
|
import '../../infra/test_editor.dart';
|
||||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
setUpAll(() {
|
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:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import '../infra/test_editor.dart';
|
import '../infra/test_editor.dart';
|
||||||
import 'package:appflowy_editor/src/document/built_in_attribute_keys.dart';
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
setUpAll(() {
|
setUpAll(() {
|
||||||
|
@ -2,6 +2,7 @@ import 'package:flowy_infra/size.dart';
|
|||||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flowy_infra/time/duration.dart';
|
import 'package:flowy_infra/time/duration.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
class RoundedInputField extends StatefulWidget {
|
class RoundedInputField extends StatefulWidget {
|
||||||
final String? hintText;
|
final String? hintText;
|
||||||
@ -24,6 +25,7 @@ class RoundedInputField extends StatefulWidget {
|
|||||||
final FocusNode? focusNode;
|
final FocusNode? focusNode;
|
||||||
final TextEditingController? controller;
|
final TextEditingController? controller;
|
||||||
final bool autoFocus;
|
final bool autoFocus;
|
||||||
|
final int? maxLength;
|
||||||
|
|
||||||
const RoundedInputField({
|
const RoundedInputField({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -47,6 +49,7 @@ class RoundedInputField extends StatefulWidget {
|
|||||||
this.focusNode,
|
this.focusNode,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.autoFocus = false,
|
this.autoFocus = false,
|
||||||
|
this.maxLength,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -89,6 +92,9 @@ class _RoundedInputFieldState extends State<RoundedInputField> {
|
|||||||
initialValue: widget.initialValue,
|
initialValue: widget.initialValue,
|
||||||
focusNode: widget.focusNode,
|
focusNode: widget.focusNode,
|
||||||
autofocus: widget.autoFocus,
|
autofocus: widget.autoFocus,
|
||||||
|
maxLength: widget.maxLength,
|
||||||
|
maxLengthEnforcement:
|
||||||
|
MaxLengthEnforcement.truncateAfterCompositionEnds,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
inputText = value;
|
inputText = value;
|
||||||
if (widget.onChanged != null) {
|
if (widget.onChanged != null) {
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user