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 feature/context_menu
This commit is contained in:
commit
9e40b7f992
2
.github/workflows/appflowy_editor_test.yml
vendored
2
.github/workflows/appflowy_editor_test.yml
vendored
@ -4,8 +4,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- "main"
|
- "main"
|
||||||
paths:
|
|
||||||
- "frontend/app_flowy/packages/appflowy_editor/**"
|
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
|
2
.github/workflows/dart_test.yml
vendored
2
.github/workflows/dart_test.yml
vendored
@ -67,7 +67,7 @@ jobs:
|
|||||||
- name: Build FlowySDK
|
- name: Build FlowySDK
|
||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: |
|
run: |
|
||||||
cargo make --profile development-linux-x86_64 flowy-sdk-dev
|
cargo make --profile test-linux test-lib-build
|
||||||
|
|
||||||
- name: Code Generation
|
- name: Code Generation
|
||||||
working-directory: frontend/app_flowy
|
working-directory: frontend/app_flowy
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -118,7 +118,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Archive macOS app
|
- name: Archive macOS app
|
||||||
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
||||||
run: zip -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
|
run: zip --symlinks -qr ${{ env.MACOS_X86_ZIP_NAME }} AppFlowy.app
|
||||||
|
|
||||||
- name: Upload Release Asset
|
- name: Upload Release Asset
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
|
@ -40,10 +40,15 @@ PRODUCT_NAME = "AppFlowy"
|
|||||||
# for cdylib:
|
# for cdylib:
|
||||||
# if (Platform.isMacOS) return DynamicLibrary.open('${prefix}/libdart_ffi.dylib');
|
# if (Platform.isMacOS) return DynamicLibrary.open('${prefix}/libdart_ffi.dylib');
|
||||||
CRATE_TYPE = "staticlib"
|
CRATE_TYPE = "staticlib"
|
||||||
SDK_EXT = "a"
|
LIB_EXT = "a"
|
||||||
APP_ENVIRONMENT = "local"
|
APP_ENVIRONMENT = "local"
|
||||||
FLUTTER_FLOWY_SDK_PATH = "app_flowy/packages/flowy_sdk"
|
FLUTTER_FLOWY_SDK_PATH = "app_flowy/packages/flowy_sdk"
|
||||||
PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
||||||
|
# Test default config
|
||||||
|
TEST_CRATE_TYPE = "cdylib"
|
||||||
|
TEST_LIB_EXT = "dylib"
|
||||||
|
TEST_BUILD_FLAG = "debug"
|
||||||
|
TEST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||||
|
|
||||||
[env.development-mac-arm64]
|
[env.development-mac-arm64]
|
||||||
RUST_LOG = "info"
|
RUST_LOG = "info"
|
||||||
@ -88,7 +93,7 @@ BUILD_FLAG = "debug"
|
|||||||
FLUTTER_OUTPUT_DIR = "Debug"
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
PRODUCT_EXT = "exe"
|
PRODUCT_EXT = "exe"
|
||||||
CRATE_TYPE = "cdylib"
|
CRATE_TYPE = "cdylib"
|
||||||
SDK_EXT = "dll"
|
LIB_EXT = "dll"
|
||||||
|
|
||||||
[env.production-windows-x86]
|
[env.production-windows-x86]
|
||||||
BUILD_FLAG = "release"
|
BUILD_FLAG = "release"
|
||||||
@ -97,7 +102,7 @@ RUST_COMPILE_TARGET = "x86_64-pc-windows-msvc"
|
|||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
PRODUCT_EXT = "exe"
|
PRODUCT_EXT = "exe"
|
||||||
CRATE_TYPE = "cdylib"
|
CRATE_TYPE = "cdylib"
|
||||||
SDK_EXT = "dll"
|
LIB_EXT = "dll"
|
||||||
APP_ENVIRONMENT = "production"
|
APP_ENVIRONMENT = "production"
|
||||||
|
|
||||||
[env.development-linux-x86_64]
|
[env.development-linux-x86_64]
|
||||||
@ -106,7 +111,7 @@ RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
|
|||||||
BUILD_FLAG = "debug"
|
BUILD_FLAG = "debug"
|
||||||
CRATE_TYPE = "cdylib"
|
CRATE_TYPE = "cdylib"
|
||||||
FLUTTER_OUTPUT_DIR = "Debug"
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
SDK_EXT = "so"
|
LIB_EXT = "so"
|
||||||
LINUX_ARCH = "x64"
|
LINUX_ARCH = "x64"
|
||||||
|
|
||||||
[env.production-linux-x86_64]
|
[env.production-linux-x86_64]
|
||||||
@ -115,7 +120,7 @@ TARGET_OS = "linux"
|
|||||||
RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
|
RUST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
|
||||||
CRATE_TYPE = "cdylib"
|
CRATE_TYPE = "cdylib"
|
||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
SDK_EXT = "so"
|
LIB_EXT = "so"
|
||||||
LINUX_ARCH = "x64"
|
LINUX_ARCH = "x64"
|
||||||
APP_ENVIRONMENT = "production"
|
APP_ENVIRONMENT = "production"
|
||||||
|
|
||||||
@ -125,7 +130,7 @@ RUST_COMPILE_TARGET = "aarch64-unknown-linux-gnu"
|
|||||||
BUILD_FLAG = "debug"
|
BUILD_FLAG = "debug"
|
||||||
CRATE_TYPE = "cdylib"
|
CRATE_TYPE = "cdylib"
|
||||||
FLUTTER_OUTPUT_DIR = "Debug"
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
SDK_EXT = "so"
|
LIB_EXT = "so"
|
||||||
LINUX_ARCH = "arm64"
|
LINUX_ARCH = "arm64"
|
||||||
|
|
||||||
[env.production-linux-aarch64]
|
[env.production-linux-aarch64]
|
||||||
@ -134,7 +139,7 @@ TARGET_OS = "linux"
|
|||||||
RUST_COMPILE_TARGET = "aarch64-unknown-linux-gnu"
|
RUST_COMPILE_TARGET = "aarch64-unknown-linux-gnu"
|
||||||
CRATE_TYPE = "cdylib"
|
CRATE_TYPE = "cdylib"
|
||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
SDK_EXT = "so"
|
LIB_EXT = "so"
|
||||||
LINUX_ARCH = "arm64"
|
LINUX_ARCH = "arm64"
|
||||||
APP_ENVIRONMENT = "production"
|
APP_ENVIRONMENT = "production"
|
||||||
|
|
||||||
@ -197,6 +202,46 @@ script = [
|
|||||||
]
|
]
|
||||||
script_runner = "@duckscript"
|
script_runner = "@duckscript"
|
||||||
|
|
||||||
|
[env.test-macos]
|
||||||
|
TEST_CRATE_TYPE = "cdylib"
|
||||||
|
TEST_LIB_EXT = "dylib"
|
||||||
|
# For the moment, the DynamicLibrary only supports open x86_64 architectures binary.
|
||||||
|
TEST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||||
|
|
||||||
|
[env.test-linux]
|
||||||
|
TEST_CRATE_TYPE = "cdylib"
|
||||||
|
TEST_LIB_EXT = "so"
|
||||||
|
TEST_COMPILE_TARGET = "x86_64-unknown-linux-gnu"
|
||||||
|
|
||||||
|
[env.test-windows]
|
||||||
|
TEST_CRATE_TYPE = "cdylib"
|
||||||
|
TEST_LIB_EXT = "dll"
|
||||||
|
TEST_COMPILE_TARGET = "x86_64-pc-windows-msvc"
|
||||||
|
|
||||||
|
[tasks.setup-test-crate-type]
|
||||||
|
private = true
|
||||||
|
script = [
|
||||||
|
"""
|
||||||
|
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
|
||||||
|
val = replace ${toml} "staticlib" ${TEST_CRATE_TYPE}
|
||||||
|
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
|
||||||
|
assert ${result}
|
||||||
|
""",
|
||||||
|
]
|
||||||
|
script_runner = "@duckscript"
|
||||||
|
|
||||||
|
[tasks.restore-test-crate-type]
|
||||||
|
private = true
|
||||||
|
script = [
|
||||||
|
"""
|
||||||
|
toml = readfile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml
|
||||||
|
val = replace ${toml} ${TEST_CRATE_TYPE} "staticlib"
|
||||||
|
result = writefile ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/${CARGO_MAKE_CRATE_NAME}/Cargo.toml ${val}
|
||||||
|
assert ${result}
|
||||||
|
""",
|
||||||
|
]
|
||||||
|
script_runner = "@duckscript"
|
||||||
|
|
||||||
[tasks.test-build]
|
[tasks.test-build]
|
||||||
condition = { env_set = ["FLUTTER_FLOWY_SDK_PATH"] }
|
condition = { env_set = ["FLUTTER_FLOWY_SDK_PATH"] }
|
||||||
script = ["""
|
script = ["""
|
||||||
@ -204,3 +249,5 @@ script = ["""
|
|||||||
cargo build -vv --features=dart
|
cargo build -vv --features=dart
|
||||||
"""]
|
"""]
|
||||||
script_runner = "@shell"
|
script_runner = "@shell"
|
||||||
|
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
"letsGoButtonText": "Vamos",
|
"letsGoButtonText": "Vamos",
|
||||||
"title": "Título",
|
"title": "Título",
|
||||||
"signUp": {
|
"signUp": {
|
||||||
"buttonText": "Registar",
|
"buttonText": "Registrar",
|
||||||
"title": "Registrar en @:appName",
|
"title": "Registrar en @:appName",
|
||||||
"getStartedText": "Empezar",
|
"getStartedText": "Empezar",
|
||||||
"emptyPasswordError": "La contraseña no puede estar en blanco",
|
"emptyPasswordError": "La contraseña no puede estar en blanco",
|
||||||
@ -16,7 +16,7 @@
|
|||||||
"alreadyHaveAnAccount": "¿Posee credenciales?",
|
"alreadyHaveAnAccount": "¿Posee credenciales?",
|
||||||
"emailHint": "Correo",
|
"emailHint": "Correo",
|
||||||
"passwordHint": "Contraseña",
|
"passwordHint": "Contraseña",
|
||||||
"repeatPasswordHint": "Repite la contraseña"
|
"repeatPasswordHint": "Repetir contraseña"
|
||||||
},
|
},
|
||||||
"signIn": {
|
"signIn": {
|
||||||
"loginTitle": "Ingresa a @:appName",
|
"loginTitle": "Ingresa a @:appName",
|
||||||
@ -58,7 +58,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deletePagePrompt": {
|
"deletePagePrompt": {
|
||||||
"text": "Esta paágina esta en la Papelera",
|
"text": "Esta página está en la Papelera",
|
||||||
"restore": "Recuperar página",
|
"restore": "Recuperar página",
|
||||||
"deletePermanent": "Eliminar permanentemente"
|
"deletePermanent": "Eliminar permanentemente"
|
||||||
},
|
},
|
||||||
@ -69,7 +69,7 @@
|
|||||||
"debug": {
|
"debug": {
|
||||||
"name": "Información de depuración",
|
"name": "Información de depuración",
|
||||||
"success": "¡Información copiada!",
|
"success": "¡Información copiada!",
|
||||||
"fail": "No fué posible copiar la información"
|
"fail": "No fue posible copiar la información"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"menuAppHeader": {
|
"menuAppHeader": {
|
||||||
@ -167,15 +167,15 @@
|
|||||||
"singleSelectFieldName": "Seleccionar",
|
"singleSelectFieldName": "Seleccionar",
|
||||||
"multiSelectFieldName": "Selección múltiple",
|
"multiSelectFieldName": "Selección múltiple",
|
||||||
"urlFieldName": "URL",
|
"urlFieldName": "URL",
|
||||||
"numberFormat": " Formato numérico",
|
"numberFormat": "Formato numérico",
|
||||||
"dateFormat": " Formato de fecha",
|
"dateFormat": "Formato de fecha",
|
||||||
"includeTime": " Incluir tiempo",
|
"includeTime": "Incluir tiempo",
|
||||||
"dateFormatFriendly": "Mes Día, Año",
|
"dateFormatFriendly": "Mes Día, Año",
|
||||||
"dateFormatISO": "Año-Mes-Día",
|
"dateFormatISO": "Año-Mes-Día",
|
||||||
"dateFormatLocal": "Mes/Día/Año",
|
"dateFormatLocal": "Mes/Día/Año",
|
||||||
"dateFormatUS": "Año/Mes/Día",
|
"dateFormatUS": "Año/Mes/Día",
|
||||||
"timeFormat": " Time format",
|
"timeFormat": "Formato de tiempo",
|
||||||
"invalidTimeFormat": "Formato de tiempo",
|
"invalidTimeFormat": "Formato de tiempo inválido",
|
||||||
"timeFormatTwelveHour": "12 horas",
|
"timeFormatTwelveHour": "12 horas",
|
||||||
"timeFormatTwentyFourHour": "24 horas",
|
"timeFormatTwentyFourHour": "24 horas",
|
||||||
"addSelectOption": "Añadir una opción",
|
"addSelectOption": "Añadir una opción",
|
||||||
@ -205,17 +205,22 @@
|
|||||||
"panelTitle": "Selecciona una opción o crea una",
|
"panelTitle": "Selecciona una opción o crea una",
|
||||||
"searchOption": "Buscar una opción"
|
"searchOption": "Buscar una opción"
|
||||||
},
|
},
|
||||||
"menuName": "Grid"
|
"menuName": "Cuadrícula"
|
||||||
},
|
},
|
||||||
"document": {
|
"document": {
|
||||||
"menuName": "Doc",
|
"menuName": "Documento",
|
||||||
"date": {
|
"date": {
|
||||||
"timeHintTextInTwelveHour": "01:00 PM",
|
"timeHintTextInTwelveHour": "01:00 PM",
|
||||||
"timeHintTextInTwentyFourHour": "13:00"
|
"timeHintTextInTwentyFourHour": "13:00"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sideBar": {
|
"sideBar": {
|
||||||
"openSidebar": "Open sidebar",
|
"openSidebar": "Abrir panel lateral",
|
||||||
"closeSidebar": "Close sidebar"
|
"closeSidebar": "Cerrar panel lateral"
|
||||||
|
},
|
||||||
|
"board": {
|
||||||
|
"column": {
|
||||||
|
"create_new_card": "Nuevo"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -27,7 +27,7 @@ class BoardPluginBuilder implements PluginBuilder {
|
|||||||
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ViewLayoutTypePB? get subDataType => ViewLayoutTypePB.Board;
|
ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Board;
|
||||||
}
|
}
|
||||||
|
|
||||||
class BoardPluginConfig implements PluginConfig {
|
class BoardPluginConfig implements PluginConfig {
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
// import 'package:flutter_test/flutter_test.dart';
|
||||||
|
// import 'package:integration_test/integration_test.dart';
|
||||||
|
// import 'package:app_flowy/main.dart' as app;
|
||||||
|
|
||||||
|
// void main() {
|
||||||
|
// IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
// group('end-to-end test', () {
|
||||||
|
// testWidgets('tap on the floating action button, verify counter',
|
||||||
|
// (tester) async {
|
||||||
|
// app.main();
|
||||||
|
|
||||||
|
// await tester.pumpAndSettle();
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
@ -80,7 +80,12 @@ class TypeOptionDataController {
|
|||||||
Future<void> switchToField(FieldType newFieldType) {
|
Future<void> switchToField(FieldType newFieldType) {
|
||||||
return loader.switchToField(field.id, newFieldType).then((result) {
|
return loader.switchToField(field.id, newFieldType).then((result) {
|
||||||
return result.fold(
|
return result.fold(
|
||||||
(_) {},
|
(_) {
|
||||||
|
// Should load the type-option data after switching to a new field.
|
||||||
|
// After loading the type-option data, the editor widget that uses
|
||||||
|
// the type-option data will be rebuild.
|
||||||
|
loadTypeOptionData();
|
||||||
|
},
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -12,10 +12,12 @@ import 'grid_data_controller.dart';
|
|||||||
import 'row/row_cache.dart';
|
import 'row/row_cache.dart';
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
|
|
||||||
|
import 'row/row_service.dart';
|
||||||
part 'grid_bloc.freezed.dart';
|
part 'grid_bloc.freezed.dart';
|
||||||
|
|
||||||
class GridBloc extends Bloc<GridEvent, GridState> {
|
class GridBloc extends Bloc<GridEvent, GridState> {
|
||||||
final GridDataController dataController;
|
final GridDataController dataController;
|
||||||
|
void Function()? _createRowOperation;
|
||||||
|
|
||||||
GridBloc({required ViewPB view})
|
GridBloc({required ViewPB view})
|
||||||
: dataController = GridDataController(view: view),
|
: dataController = GridDataController(view: view),
|
||||||
@ -28,7 +30,19 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
await _loadGrid(emit);
|
await _loadGrid(emit);
|
||||||
},
|
},
|
||||||
createRow: () {
|
createRow: () {
|
||||||
dataController.createRow();
|
state.loadingState.when(
|
||||||
|
loading: () {
|
||||||
|
_createRowOperation = () => dataController.createRow();
|
||||||
|
},
|
||||||
|
finish: (_) => dataController.createRow(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
deleteRow: (rowInfo) async {
|
||||||
|
final rowService = RowFFIService(
|
||||||
|
blockId: rowInfo.rowPB.blockId,
|
||||||
|
gridId: rowInfo.gridId,
|
||||||
|
);
|
||||||
|
await rowService.deleteRow(rowInfo.rowPB.id);
|
||||||
},
|
},
|
||||||
didReceiveGridUpdate: (grid) {
|
didReceiveGridUpdate: (grid) {
|
||||||
emit(state.copyWith(grid: Some(grid)));
|
emit(state.copyWith(grid: Some(grid)));
|
||||||
@ -84,9 +98,15 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
Future<void> _loadGrid(Emitter<GridState> emit) async {
|
Future<void> _loadGrid(Emitter<GridState> emit) async {
|
||||||
final result = await dataController.loadData();
|
final result = await dataController.loadData();
|
||||||
result.fold(
|
result.fold(
|
||||||
(grid) => emit(
|
(grid) {
|
||||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
if (_createRowOperation != null) {
|
||||||
),
|
_createRowOperation?.call();
|
||||||
|
_createRowOperation = null;
|
||||||
|
}
|
||||||
|
emit(
|
||||||
|
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||||
|
);
|
||||||
|
},
|
||||||
(err) => emit(
|
(err) => emit(
|
||||||
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
||||||
),
|
),
|
||||||
@ -98,6 +118,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
class GridEvent with _$GridEvent {
|
class GridEvent with _$GridEvent {
|
||||||
const factory GridEvent.initial() = InitialGrid;
|
const factory GridEvent.initial() = InitialGrid;
|
||||||
const factory GridEvent.createRow() = _CreateRow;
|
const factory GridEvent.createRow() = _CreateRow;
|
||||||
|
const factory GridEvent.deleteRow(RowInfo rowInfo) = _DeleteRow;
|
||||||
const factory GridEvent.didReceiveRowUpdate(
|
const factory GridEvent.didReceiveRowUpdate(
|
||||||
List<RowInfo> rows,
|
List<RowInfo> rows,
|
||||||
RowsChangedReason listState,
|
RowsChangedReason listState,
|
||||||
|
@ -64,6 +64,7 @@ class GridDataController {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Loads the rows from each block
|
||||||
Future<Either<Unit, FlowyError>> loadData() async {
|
Future<Either<Unit, FlowyError>> loadData() async {
|
||||||
final result = await _gridFFIService.loadGrid();
|
final result = await _gridFFIService.loadGrid();
|
||||||
return Future(
|
return Future(
|
||||||
|
@ -29,7 +29,7 @@ class GridPluginBuilder implements PluginBuilder {
|
|||||||
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
ViewDataTypePB get dataType => ViewDataTypePB.Database;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ViewLayoutTypePB? get subDataType => ViewLayoutTypePB.Grid;
|
ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridPluginConfig implements PluginConfig {
|
class GridPluginConfig implements PluginConfig {
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@ -56,7 +57,6 @@ class CellContainer extends StatelessWidget {
|
|||||||
child: Container(
|
child: Container(
|
||||||
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
||||||
decoration: _makeBoxDecoration(context, isFocus),
|
decoration: _makeBoxDecoration(context, isFocus),
|
||||||
padding: GridSize.cellContentInsets,
|
|
||||||
child: container,
|
child: container,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -92,8 +92,11 @@ class _GridCellEnterRegion extends StatelessWidget {
|
|||||||
builder: (context, onEnter, _) {
|
builder: (context, onEnter, _) {
|
||||||
List<Widget> children = [child];
|
List<Widget> children = [child];
|
||||||
if (onEnter) {
|
if (onEnter) {
|
||||||
children.add(CellAccessoryContainer(accessories: accessories)
|
children.add(
|
||||||
.positioned(right: 0));
|
CellAccessoryContainer(accessories: accessories).positioned(
|
||||||
|
right: GridSize.cellContentInsets.right,
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return MouseRegion(
|
return MouseRegion(
|
||||||
|
@ -4,6 +4,7 @@ import 'package:flowy_infra/image.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../layout/sizes.dart';
|
||||||
import 'cell_builder.dart';
|
import 'cell_builder.dart';
|
||||||
|
|
||||||
class GridCheckboxCell extends GridCellWidget {
|
class GridCheckboxCell extends GridCellWidget {
|
||||||
@ -40,13 +41,16 @@ class _CheckboxCellState extends GridCellState<GridCheckboxCell> {
|
|||||||
: svgWidget('editor/editor_uncheck');
|
: svgWidget('editor/editor_uncheck');
|
||||||
return Align(
|
return Align(
|
||||||
alignment: Alignment.centerLeft,
|
alignment: Alignment.centerLeft,
|
||||||
child: FlowyIconButton(
|
child: Padding(
|
||||||
onPressed: () => context
|
padding: GridSize.cellContentInsets,
|
||||||
.read<CheckboxCellBloc>()
|
child: FlowyIconButton(
|
||||||
.add(const CheckboxCellEvent.select()),
|
onPressed: () => context
|
||||||
iconPadding: EdgeInsets.zero,
|
.read<CheckboxCellBloc>()
|
||||||
icon: icon,
|
.add(const CheckboxCellEvent.select()),
|
||||||
width: 20,
|
iconPadding: EdgeInsets.zero,
|
||||||
|
icon: icon,
|
||||||
|
width: 20,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@ import 'package:app_flowy/startup/startup.dart';
|
|||||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
|
|
||||||
|
import '../../../layout/sizes.dart';
|
||||||
import '../cell_builder.dart';
|
import '../cell_builder.dart';
|
||||||
import 'date_editor.dart';
|
import 'date_editor.dart';
|
||||||
|
|
||||||
@ -75,7 +76,10 @@ class _DateCellState extends GridCellState<GridDateCell> {
|
|||||||
onTap: () => _popover.show(),
|
onTap: () => _popover.show(),
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
child: FlowyText.medium(state.dateStr, fontSize: 12),
|
child: Padding(
|
||||||
|
padding: GridSize.cellContentInsets,
|
||||||
|
child: FlowyText.medium(state.dateStr, fontSize: 12),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -4,6 +4,7 @@ import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../layout/sizes.dart';
|
||||||
import 'cell_builder.dart';
|
import 'cell_builder.dart';
|
||||||
|
|
||||||
class GridNumberCell extends GridCellWidget {
|
class GridNumberCell extends GridCellWidget {
|
||||||
@ -45,18 +46,21 @@ class _NumberCellState extends GridFocusNodeCellState<GridNumberCell> {
|
|||||||
_controller.text = contentFromState(state),
|
_controller.text = contentFromState(state),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: TextField(
|
child: Padding(
|
||||||
controller: _controller,
|
padding: GridSize.cellContentInsets,
|
||||||
focusNode: focusNode,
|
child: TextField(
|
||||||
onEditingComplete: () => focusNode.unfocus(),
|
controller: _controller,
|
||||||
onSubmitted: (_) => focusNode.unfocus(),
|
focusNode: focusNode,
|
||||||
maxLines: 1,
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
onSubmitted: (_) => focusNode.unfocus(),
|
||||||
textInputAction: TextInputAction.done,
|
maxLines: 1,
|
||||||
decoration: const InputDecoration(
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
contentPadding: EdgeInsets.zero,
|
textInputAction: TextInputAction.done,
|
||||||
border: InputBorder.none,
|
decoration: const InputDecoration(
|
||||||
isDense: true,
|
contentPadding: EdgeInsets.zero,
|
||||||
|
border: InputBorder.none,
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -11,6 +11,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
|
import '../../../layout/sizes.dart';
|
||||||
import '../cell_builder.dart';
|
import '../cell_builder.dart';
|
||||||
import 'extension.dart';
|
import 'extension.dart';
|
||||||
import 'select_option_editor.dart';
|
import 'select_option_editor.dart';
|
||||||
@ -170,7 +171,10 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
|
|||||||
alignment: AlignmentDirectional.center,
|
alignment: AlignmentDirectional.center,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
children: [
|
children: [
|
||||||
_wrapPopover(child),
|
Padding(
|
||||||
|
padding: GridSize.cellContentInsets,
|
||||||
|
child: _wrapPopover(child),
|
||||||
|
),
|
||||||
InkWell(onTap: () => _popover.show()),
|
InkWell(onTap: () => _popover.show()),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
@ -3,6 +3,7 @@ import 'dart:collection';
|
|||||||
import 'package:flowy_infra/size.dart';
|
import 'package:flowy_infra/size.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/select_type_option.pb.dart';
|
||||||
|
import 'package:flutter/gestures.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';
|
||||||
@ -170,10 +171,21 @@ class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
|
|||||||
.toList();
|
.toList();
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: SingleChildScrollView(
|
child: ScrollConfiguration(
|
||||||
controller: sc,
|
behavior: ScrollConfiguration.of(context).copyWith(
|
||||||
scrollDirection: Axis.horizontal,
|
dragDevices: {
|
||||||
child: Wrap(spacing: 4, children: children),
|
PointerDeviceKind.mouse,
|
||||||
|
PointerDeviceKind.touch,
|
||||||
|
PointerDeviceKind.trackpad,
|
||||||
|
PointerDeviceKind.stylus,
|
||||||
|
PointerDeviceKind.invertedStylus,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
child: SingleChildScrollView(
|
||||||
|
controller: sc,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
child: Wrap(spacing: 4, children: children),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||||
|
import '../../layout/sizes.dart';
|
||||||
import 'cell_builder.dart';
|
import 'cell_builder.dart';
|
||||||
|
|
||||||
class GridTextCellStyle extends GridCellStyle {
|
class GridTextCellStyle extends GridCellStyle {
|
||||||
@ -56,18 +57,21 @@ class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
|
|||||||
_controller.text = state.content;
|
_controller.text = state.content;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
child: TextField(
|
child: Padding(
|
||||||
controller: _controller,
|
padding: GridSize.cellContentInsets,
|
||||||
focusNode: focusNode,
|
child: TextField(
|
||||||
onChanged: (value) => focusChanged(),
|
controller: _controller,
|
||||||
onEditingComplete: () => focusNode.unfocus(),
|
focusNode: focusNode,
|
||||||
maxLines: null,
|
onChanged: (value) => focusChanged(),
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
decoration: InputDecoration(
|
maxLines: null,
|
||||||
contentPadding: EdgeInsets.zero,
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
border: InputBorder.none,
|
decoration: InputDecoration(
|
||||||
hintText: widget.cellStyle?.placeholder,
|
contentPadding: EdgeInsets.zero,
|
||||||
isDense: true,
|
border: InputBorder.none,
|
||||||
|
hintText: widget.cellStyle?.placeholder,
|
||||||
|
isDense: true,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -12,6 +12,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
import 'package:app_flowy/plugins/grid/application/prelude.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import '../../../layout/sizes.dart';
|
||||||
import '../cell_accessory.dart';
|
import '../cell_accessory.dart';
|
||||||
import '../cell_builder.dart';
|
import '../cell_builder.dart';
|
||||||
import 'cell_editor.dart';
|
import 'cell_editor.dart';
|
||||||
@ -115,14 +116,17 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
|
|||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocBuilder<URLCellBloc, URLCellState>(
|
child: BlocBuilder<URLCellBloc, URLCellState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final richText = RichText(
|
final richText = Padding(
|
||||||
textAlign: TextAlign.left,
|
padding: GridSize.cellContentInsets,
|
||||||
text: TextSpan(
|
child: RichText(
|
||||||
text: state.content,
|
textAlign: TextAlign.left,
|
||||||
style: TextStyle(
|
text: TextSpan(
|
||||||
color: theme.main2,
|
text: state.content,
|
||||||
fontSize: 14,
|
style: TextStyle(
|
||||||
decoration: TextDecoration.underline,
|
color: theme.main2,
|
||||||
|
fontSize: 14,
|
||||||
|
decoration: TextDecoration.underline,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
|
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
|
||||||
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
|
||||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||||
import 'package:dartz/dartz.dart' show none;
|
import 'package:dartz/dartz.dart' show none;
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
@ -58,19 +59,22 @@ class _FieldEditorState extends State<FieldEditor> {
|
|||||||
isGroupField: widget.isGroupField,
|
isGroupField: widget.isGroupField,
|
||||||
loader: widget.typeOptionLoader,
|
loader: widget.typeOptionLoader,
|
||||||
)..add(const FieldEditorEvent.initial()),
|
)..add(const FieldEditorEvent.initial()),
|
||||||
child: ListView(
|
child: Padding(
|
||||||
shrinkWrap: true,
|
padding: GridSize.typeOptionContentInsets,
|
||||||
children: [
|
child: ListView(
|
||||||
FlowyText.medium(
|
shrinkWrap: true,
|
||||||
LocaleKeys.grid_field_editProperty.tr(),
|
children: [
|
||||||
fontSize: 12,
|
FlowyText.medium(
|
||||||
),
|
LocaleKeys.grid_field_editProperty.tr(),
|
||||||
const VSpace(10),
|
fontSize: 12,
|
||||||
_FieldNameTextField(popoverMutex: popoverMutex),
|
),
|
||||||
const VSpace(10),
|
const VSpace(10),
|
||||||
..._addDeleteFieldButton(),
|
_FieldNameTextField(popoverMutex: popoverMutex),
|
||||||
_FieldTypeOptionCell(popoverMutex: popoverMutex),
|
const VSpace(10),
|
||||||
],
|
..._addDeleteFieldButton(),
|
||||||
|
_FieldTypeOptionCell(popoverMutex: popoverMutex),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -118,11 +118,7 @@ void _resolveFolderDeps(GetIt getIt) {
|
|||||||
|
|
||||||
// AppPB
|
// AppPB
|
||||||
getIt.registerFactoryParam<AppBloc, AppPB, void>(
|
getIt.registerFactoryParam<AppBloc, AppPB, void>(
|
||||||
(app, _) => AppBloc(
|
(app, _) => AppBloc(app: app),
|
||||||
app: app,
|
|
||||||
appService: AppService(),
|
|
||||||
appListener: AppListener(appId: app.id),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// trash
|
// trash
|
||||||
|
@ -51,7 +51,7 @@ abstract class PluginBuilder {
|
|||||||
|
|
||||||
ViewDataTypePB get dataType => ViewDataTypePB.Text;
|
ViewDataTypePB get dataType => ViewDataTypePB.Text;
|
||||||
|
|
||||||
ViewLayoutTypePB? get subDataType => ViewLayoutTypePB.Document;
|
ViewLayoutTypePB? get layoutType => ViewLayoutTypePB.Document;
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class PluginConfig {
|
abstract class PluginConfig {
|
||||||
|
@ -16,12 +16,12 @@ class InitRustSDKTask extends LaunchTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Directory> appFlowyDocumentDirectory() async {
|
Future<Directory> appFlowyDocumentDirectory() async {
|
||||||
Directory documentsDir = await getApplicationDocumentsDirectory();
|
|
||||||
|
|
||||||
switch (integrationEnv()) {
|
switch (integrationEnv()) {
|
||||||
case IntegrationMode.develop:
|
case IntegrationMode.develop:
|
||||||
|
Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||||
return Directory('${documentsDir.path}/flowy_dev').create();
|
return Directory('${documentsDir.path}/flowy_dev').create();
|
||||||
case IntegrationMode.release:
|
case IntegrationMode.release:
|
||||||
|
Directory documentsDir = await getApplicationDocumentsDirectory();
|
||||||
return Directory('${documentsDir.path}/flowy').create();
|
return Directory('${documentsDir.path}/flowy').create();
|
||||||
case IntegrationMode.test:
|
case IntegrationMode.test:
|
||||||
return Directory("${Directory.current.path}/.sandbox");
|
return Directory("${Directory.current.path}/.sandbox");
|
||||||
|
@ -22,19 +22,24 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
final AppService appService;
|
final AppService appService;
|
||||||
final AppListener appListener;
|
final AppListener appListener;
|
||||||
|
|
||||||
AppBloc(
|
AppBloc({required this.app})
|
||||||
{required this.app, required this.appService, required this.appListener})
|
: appService = AppService(),
|
||||||
: super(AppState.initial(app)) {
|
appListener = AppListener(appId: app.id),
|
||||||
|
super(AppState.initial(app)) {
|
||||||
on<AppEvent>((event, emit) async {
|
on<AppEvent>((event, emit) async {
|
||||||
await event.map(initial: (e) async {
|
await event.map(initial: (e) async {
|
||||||
_startListening();
|
_startListening();
|
||||||
await _loadViews(emit);
|
await _loadViews(emit);
|
||||||
}, createView: (CreateView value) async {
|
}, createView: (CreateView value) async {
|
||||||
await _createView(value, emit);
|
await _createView(value, emit);
|
||||||
|
}, loadViews: (_) async {
|
||||||
|
await _loadViews(emit);
|
||||||
}, didReceiveViewUpdated: (e) async {
|
}, didReceiveViewUpdated: (e) async {
|
||||||
await _didReceiveViewUpdated(e.views, emit);
|
await _didReceiveViewUpdated(e.views, emit);
|
||||||
}, delete: (e) async {
|
}, delete: (e) async {
|
||||||
await _deleteView(emit);
|
await _deleteApp(emit);
|
||||||
|
}, deleteView: (deletedView) async {
|
||||||
|
await _deleteView(emit, deletedView.viewId);
|
||||||
}, rename: (e) async {
|
}, rename: (e) async {
|
||||||
await _renameView(e, emit);
|
await _renameView(e, emit);
|
||||||
}, appDidUpdate: (e) async {
|
}, appDidUpdate: (e) async {
|
||||||
@ -71,7 +76,8 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _deleteView(Emitter<AppState> emit) async {
|
// Delete the current app
|
||||||
|
Future<void> _deleteApp(Emitter<AppState> emit) async {
|
||||||
final result = await appService.delete(appId: app.id);
|
final result = await appService.delete(appId: app.id);
|
||||||
result.fold(
|
result.fold(
|
||||||
(unit) => emit(state.copyWith(successOrFailure: left(unit))),
|
(unit) => emit(state.copyWith(successOrFailure: left(unit))),
|
||||||
@ -79,16 +85,24 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> _deleteView(Emitter<AppState> emit, String viewId) async {
|
||||||
|
final result = await appService.deleteView(viewId: viewId);
|
||||||
|
result.fold(
|
||||||
|
(unit) => emit(state.copyWith(successOrFailure: left(unit))),
|
||||||
|
(error) => emit(state.copyWith(successOrFailure: right(error))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
|
Future<void> _createView(CreateView value, Emitter<AppState> emit) async {
|
||||||
final viewOrFailed = await appService.createView(
|
final result = await appService.createView(
|
||||||
appId: app.id,
|
appId: app.id,
|
||||||
name: value.name,
|
name: value.name,
|
||||||
desc: value.desc,
|
desc: value.desc ?? "",
|
||||||
dataType: value.dataType,
|
dataType: value.pluginBuilder.dataType,
|
||||||
pluginType: value.pluginType,
|
pluginType: value.pluginBuilder.pluginType,
|
||||||
layout: value.layout,
|
layoutType: value.pluginBuilder.layoutType!,
|
||||||
);
|
);
|
||||||
viewOrFailed.fold(
|
result.fold(
|
||||||
(view) => emit(state.copyWith(
|
(view) => emit(state.copyWith(
|
||||||
latestCreatedView: view,
|
latestCreatedView: view,
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
@ -107,7 +121,9 @@ class AppBloc extends Bloc<AppEvent, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _didReceiveViewUpdated(
|
Future<void> _didReceiveViewUpdated(
|
||||||
List<ViewPB> views, Emitter<AppState> emit) async {
|
List<ViewPB> views,
|
||||||
|
Emitter<AppState> emit,
|
||||||
|
) async {
|
||||||
final latestCreatedView = state.latestCreatedView;
|
final latestCreatedView = state.latestCreatedView;
|
||||||
AppState newState = state.copyWith(views: views);
|
AppState newState = state.copyWith(views: views);
|
||||||
if (latestCreatedView != null) {
|
if (latestCreatedView != null) {
|
||||||
@ -138,12 +154,12 @@ class AppEvent with _$AppEvent {
|
|||||||
const factory AppEvent.initial() = Initial;
|
const factory AppEvent.initial() = Initial;
|
||||||
const factory AppEvent.createView(
|
const factory AppEvent.createView(
|
||||||
String name,
|
String name,
|
||||||
String desc,
|
PluginBuilder pluginBuilder, {
|
||||||
ViewDataTypePB dataType,
|
String? desc,
|
||||||
ViewLayoutTypePB layout,
|
}) = CreateView;
|
||||||
PluginType pluginType,
|
const factory AppEvent.loadViews() = LoadApp;
|
||||||
) = CreateView;
|
const factory AppEvent.delete() = DeleteApp;
|
||||||
const factory AppEvent.delete() = Delete;
|
const factory AppEvent.deleteView(String viewId) = DeleteView;
|
||||||
const factory AppEvent.rename(String newName) = Rename;
|
const factory AppEvent.rename(String newName) = Rename;
|
||||||
const factory AppEvent.didReceiveViewUpdated(List<ViewPB> views) =
|
const factory AppEvent.didReceiveViewUpdated(List<ViewPB> views) =
|
||||||
ReceiveViews;
|
ReceiveViews;
|
||||||
@ -161,7 +177,7 @@ class AppState with _$AppState {
|
|||||||
|
|
||||||
factory AppState.initial(AppPB app) => AppState(
|
factory AppState.initial(AppPB app) => AppState(
|
||||||
app: app,
|
app: app,
|
||||||
views: [],
|
views: app.belongings.items,
|
||||||
successOrFailure: left(unit),
|
successOrFailure: left(unit),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -18,17 +18,17 @@ class AppService {
|
|||||||
Future<Either<ViewPB, FlowyError>> createView({
|
Future<Either<ViewPB, FlowyError>> createView({
|
||||||
required String appId,
|
required String appId,
|
||||||
required String name,
|
required String name,
|
||||||
required String desc,
|
String? desc,
|
||||||
required ViewDataTypePB dataType,
|
required ViewDataTypePB dataType,
|
||||||
required PluginType pluginType,
|
required PluginType pluginType,
|
||||||
required ViewLayoutTypePB layout,
|
required ViewLayoutTypePB layoutType,
|
||||||
}) {
|
}) {
|
||||||
var payload = CreateViewPayloadPB.create()
|
var payload = CreateViewPayloadPB.create()
|
||||||
..belongToId = appId
|
..belongToId = appId
|
||||||
..name = name
|
..name = name
|
||||||
..desc = desc
|
..desc = desc ?? ""
|
||||||
..dataType = dataType
|
..dataType = dataType
|
||||||
..layout = layout;
|
..layout = layoutType;
|
||||||
|
|
||||||
return FolderEventCreateView(payload).send();
|
return FolderEventCreateView(payload).send();
|
||||||
}
|
}
|
||||||
@ -49,6 +49,11 @@ class AppService {
|
|||||||
return FolderEventDeleteApp(request).send();
|
return FolderEventDeleteApp(request).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<Either<Unit, FlowyError>> deleteView({required String viewId}) {
|
||||||
|
final request = RepeatedViewIdPB.create()..items.add(viewId);
|
||||||
|
return FolderEventDeleteView(request).send();
|
||||||
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> updateApp(
|
Future<Either<Unit, FlowyError>> updateApp(
|
||||||
{required String appId, String? name}) {
|
{required String appId, String? name}) {
|
||||||
UpdateAppPayloadPB payload = UpdateAppPayloadPB.create()..appId = appId;
|
UpdateAppPayloadPB payload = UpdateAppPayloadPB.create()..appId = appId;
|
||||||
|
@ -31,12 +31,14 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
},
|
},
|
||||||
viewDidUpdate: (e) {
|
viewDidUpdate: (e) {
|
||||||
e.result.fold(
|
e.result.fold(
|
||||||
(view) => emit(state.copyWith(view: view, successOrFailure: left(unit))),
|
(view) =>
|
||||||
|
emit(state.copyWith(view: view, successOrFailure: left(unit))),
|
||||||
(error) => emit(state.copyWith(successOrFailure: right(error))),
|
(error) => emit(state.copyWith(successOrFailure: right(error))),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
rename: (e) async {
|
rename: (e) async {
|
||||||
final result = await service.updateView(viewId: view.id, name: e.newName);
|
final result =
|
||||||
|
await service.updateView(viewId: view.id, name: e.newName);
|
||||||
emit(
|
emit(
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) => state.copyWith(successOrFailure: left(unit)),
|
(l) => state.copyWith(successOrFailure: left(unit)),
|
||||||
@ -46,7 +48,6 @@ class ViewBloc extends Bloc<ViewEvent, ViewState> {
|
|||||||
},
|
},
|
||||||
delete: (e) async {
|
delete: (e) async {
|
||||||
final result = await service.delete(viewId: view.id);
|
final result = await service.delete(viewId: view.id);
|
||||||
await service.updateView(viewId: view.id);
|
|
||||||
emit(
|
emit(
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) => state.copyWith(successOrFailure: left(unit)),
|
(l) => state.copyWith(successOrFailure: left(unit)),
|
||||||
@ -81,7 +82,8 @@ class ViewEvent with _$ViewEvent {
|
|||||||
const factory ViewEvent.rename(String newName) = Rename;
|
const factory ViewEvent.rename(String newName) = Rename;
|
||||||
const factory ViewEvent.delete() = Delete;
|
const factory ViewEvent.delete() = Delete;
|
||||||
const factory ViewEvent.duplicate() = Duplicate;
|
const factory ViewEvent.duplicate() = Duplicate;
|
||||||
const factory ViewEvent.viewDidUpdate(Either<ViewPB, FlowyError> result) = ViewDidUpdate;
|
const factory ViewEvent.viewDidUpdate(Either<ViewPB, FlowyError> result) =
|
||||||
|
ViewDidUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
@ -17,11 +17,11 @@ class WorkspaceService {
|
|||||||
required this.workspaceId,
|
required this.workspaceId,
|
||||||
});
|
});
|
||||||
Future<Either<AppPB, FlowyError>> createApp(
|
Future<Either<AppPB, FlowyError>> createApp(
|
||||||
{required String name, required String desc}) {
|
{required String name, String? desc}) {
|
||||||
final payload = CreateAppPayloadPB.create()
|
final payload = CreateAppPayloadPB.create()
|
||||||
..name = name
|
..name = name
|
||||||
..workspaceId = workspaceId
|
..workspaceId = workspaceId
|
||||||
..desc = desc;
|
..desc = desc ?? "";
|
||||||
return FolderEventCreateApp(payload).send();
|
return FolderEventCreateApp(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -104,13 +104,12 @@ class MenuAppHeader extends StatelessWidget {
|
|||||||
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
message: LocaleKeys.menuAppHeader_addPageTooltip.tr(),
|
||||||
child: AddButton(
|
child: AddButton(
|
||||||
onSelected: (pluginBuilder) {
|
onSelected: (pluginBuilder) {
|
||||||
context.read<AppBloc>().add(AppEvent.createView(
|
context.read<AppBloc>().add(
|
||||||
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
AppEvent.createView(
|
||||||
"",
|
LocaleKeys.menuAppHeader_defaultNewPageName.tr(),
|
||||||
pluginBuilder.dataType,
|
pluginBuilder,
|
||||||
pluginBuilder.subDataType!,
|
),
|
||||||
pluginBuilder.pluginType,
|
);
|
||||||
));
|
|
||||||
},
|
},
|
||||||
).padding(right: MenuAppSizes.headerPadding),
|
).padding(right: MenuAppSizes.headerPadding),
|
||||||
);
|
);
|
||||||
|
@ -50,7 +50,6 @@ class _MenuAppState extends State<MenuApp> {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
BlocListener<AppBloc, AppState>(
|
BlocListener<AppBloc, AppState>(
|
||||||
listenWhen: (p, c) => p.views != c.views,
|
|
||||||
listener: (context, state) => viewDataContext.views = state.views,
|
listener: (context, state) => viewDataContext.views = state.views,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -188,9 +188,7 @@ ShortcutEventHandler doubleTildeToStrikethrough = (editorState, event) {
|
|||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
|
||||||
/// To create a link, enclose the link text in brackets (e.g., [link text]).
|
ShortcutEventHandler markdownLinkOrImageHandler = (editorState, event) {
|
||||||
/// Then, immediately follow it with the URL in parentheses (e.g., (https://example.com)).
|
|
||||||
ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
|
|
||||||
final selectionService = editorState.service.selectionService;
|
final selectionService = editorState.service.selectionService;
|
||||||
final selection = selectionService.currentSelection.value;
|
final selection = selectionService.currentSelection.value;
|
||||||
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
|
||||||
@ -198,48 +196,72 @@ ShortcutEventHandler markdownLinkToLinkHandler = (editorState, event) {
|
|||||||
return KeyEventResult.ignored;
|
return KeyEventResult.ignored;
|
||||||
}
|
}
|
||||||
|
|
||||||
// find all of the indexs for important characters
|
// Find all of the indexes of the relevant characters
|
||||||
final textNode = textNodes.first;
|
final textNode = textNodes.first;
|
||||||
final text = textNode.toPlainText();
|
final text = textNode.toPlainText();
|
||||||
|
final firstExclamation = text.indexOf('!');
|
||||||
final firstOpeningBracket = text.indexOf('[');
|
final firstOpeningBracket = text.indexOf('[');
|
||||||
final firstClosingBracket = text.indexOf(']');
|
final firstClosingBracket = text.indexOf(']');
|
||||||
|
|
||||||
// use regex to validate the format of the link
|
// Use RegEx to determine whether it's an image or a link
|
||||||
// note: this enforces that the link has http or https
|
// Difference between image and link syntax is that image
|
||||||
final regexp = RegExp(r'\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d./?=#]+)$');
|
// has an exclamation point at the beginning.
|
||||||
final match = regexp.firstMatch(text);
|
// Note: The RegEx enforces that the URL has http or https
|
||||||
if (match == null) {
|
final imgRegEx =
|
||||||
|
RegExp(r'\!\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d-./?=#%&]+)$');
|
||||||
|
final lnkRegEx =
|
||||||
|
RegExp(r'\[([\w\s\d]+)\]\(((?:\/|https?:\/\/)[\w\d-./?=#%&]+)$');
|
||||||
|
|
||||||
|
if (imgRegEx.firstMatch(text) != null) {
|
||||||
|
// Extract the alt text and the URL of the image
|
||||||
|
final match = lnkRegEx.firstMatch(text);
|
||||||
|
final imgUrl = match?.group(2);
|
||||||
|
|
||||||
|
// Delete the text and replace it with the image pointed to by the URL
|
||||||
|
final transaction = editorState.transaction
|
||||||
|
..deleteText(textNode, firstExclamation, text.length)
|
||||||
|
..insertNode(
|
||||||
|
textNode.path,
|
||||||
|
Node.fromJson({
|
||||||
|
'type': 'image',
|
||||||
|
'attributes': {
|
||||||
|
'image_src': imgUrl,
|
||||||
|
'align': 'center',
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
editorState.apply(transaction);
|
||||||
|
} else if (lnkRegEx.firstMatch(text) != null) {
|
||||||
|
// Extract the text and the URL of the link
|
||||||
|
final match = lnkRegEx.firstMatch(text);
|
||||||
|
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.
|
||||||
|
final transaction = editorState.transaction
|
||||||
|
..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,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
editorState.apply(transaction);
|
||||||
|
} else {
|
||||||
return KeyEventResult.ignored;
|
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.
|
|
||||||
final transaction = editorState.transaction
|
|
||||||
..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,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
editorState.apply(transaction);
|
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -369,6 +391,5 @@ ShortcutEventHandler doubleUnderscoresToBold = (editorState, event) {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
editorState.apply(transaction);
|
editorState.apply(transaction);
|
||||||
|
|
||||||
return KeyEventResult.handled;
|
return KeyEventResult.handled;
|
||||||
};
|
};
|
||||||
|
@ -16,7 +16,6 @@ import 'package:appflowy_editor/src/service/internal_key_event_handlers/whitespa
|
|||||||
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
|
import 'package:appflowy_editor/src/service/shortcut_event/shortcut_event.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
//
|
|
||||||
List<ShortcutEvent> builtInShortcutEvents = [
|
List<ShortcutEvent> builtInShortcutEvents = [
|
||||||
ShortcutEvent(
|
ShortcutEvent(
|
||||||
key: 'Move cursor up',
|
key: 'Move cursor up',
|
||||||
@ -276,9 +275,9 @@ List<ShortcutEvent> builtInShortcutEvents = [
|
|||||||
handler: doubleTildeToStrikethrough,
|
handler: doubleTildeToStrikethrough,
|
||||||
),
|
),
|
||||||
ShortcutEvent(
|
ShortcutEvent(
|
||||||
key: 'Markdown link to link',
|
key: 'Markdown link or image',
|
||||||
command: 'shift+parenthesis right',
|
command: 'shift+parenthesis right',
|
||||||
handler: markdownLinkToLinkHandler,
|
handler: markdownLinkOrImageHandler,
|
||||||
),
|
),
|
||||||
// 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,
|
||||||
|
@ -7,10 +7,10 @@ import 'package:ffi/ffi.dart' as ffi;
|
|||||||
import 'package:flutter/foundation.dart' as Foundation;
|
import 'package:flutter/foundation.dart' as Foundation;
|
||||||
|
|
||||||
// ignore_for_file: unused_import, camel_case_types, non_constant_identifier_names
|
// ignore_for_file: unused_import, camel_case_types, non_constant_identifier_names
|
||||||
final DynamicLibrary _dl = _open();
|
final DynamicLibrary _dart_ffi_lib = _open();
|
||||||
|
|
||||||
/// Reference to the Dynamic Library, it should be only used for low-level access
|
/// Reference to the Dynamic Library, it should be only used for low-level access
|
||||||
final DynamicLibrary dl = _dl;
|
final DynamicLibrary dl = _dart_ffi_lib;
|
||||||
DynamicLibrary _open() {
|
DynamicLibrary _open() {
|
||||||
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||||
final prefix = "${Directory.current.path}/.sandbox";
|
final prefix = "${Directory.current.path}/.sandbox";
|
||||||
@ -18,7 +18,8 @@ DynamicLibrary _open() {
|
|||||||
return DynamicLibrary.open('${prefix}/libdart_ffi.so');
|
return DynamicLibrary.open('${prefix}/libdart_ffi.so');
|
||||||
if (Platform.isAndroid)
|
if (Platform.isAndroid)
|
||||||
return DynamicLibrary.open('${prefix}/libdart_ffi.so');
|
return DynamicLibrary.open('${prefix}/libdart_ffi.so');
|
||||||
if (Platform.isMacOS) return DynamicLibrary.open('${prefix}/libdart_ffi.a');
|
if (Platform.isMacOS)
|
||||||
|
return DynamicLibrary.open('${prefix}/libdart_ffi.dylib');
|
||||||
if (Platform.isIOS) return DynamicLibrary.open('${prefix}/libdart_ffi.a');
|
if (Platform.isIOS) return DynamicLibrary.open('${prefix}/libdart_ffi.a');
|
||||||
if (Platform.isWindows)
|
if (Platform.isWindows)
|
||||||
return DynamicLibrary.open('${prefix}/dart_ffi.dll');
|
return DynamicLibrary.open('${prefix}/dart_ffi.dll');
|
||||||
@ -42,8 +43,8 @@ void async_event(
|
|||||||
_invoke_async(port, input, len);
|
_invoke_async(port, input, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _invoke_async_Dart _invoke_async =
|
final _invoke_async_Dart _invoke_async = _dart_ffi_lib
|
||||||
_dl.lookupFunction<_invoke_async_C, _invoke_async_Dart>('async_event');
|
.lookupFunction<_invoke_async_C, _invoke_async_Dart>('async_event');
|
||||||
typedef _invoke_async_C = Void Function(
|
typedef _invoke_async_C = Void Function(
|
||||||
Int64 port,
|
Int64 port,
|
||||||
Pointer<Uint8> input,
|
Pointer<Uint8> input,
|
||||||
@ -63,8 +64,8 @@ Pointer<Uint8> sync_event(
|
|||||||
return _invoke_sync(input, len);
|
return _invoke_sync(input, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _invoke_sync_Dart _invoke_sync =
|
final _invoke_sync_Dart _invoke_sync = _dart_ffi_lib
|
||||||
_dl.lookupFunction<_invoke_sync_C, _invoke_sync_Dart>('sync_event');
|
.lookupFunction<_invoke_sync_C, _invoke_sync_Dart>('sync_event');
|
||||||
typedef _invoke_sync_C = Pointer<Uint8> Function(
|
typedef _invoke_sync_C = Pointer<Uint8> Function(
|
||||||
Pointer<Uint8> input,
|
Pointer<Uint8> input,
|
||||||
Uint64 len,
|
Uint64 len,
|
||||||
@ -82,7 +83,7 @@ int init_sdk(
|
|||||||
}
|
}
|
||||||
|
|
||||||
final _init_sdk_Dart _init_sdk =
|
final _init_sdk_Dart _init_sdk =
|
||||||
_dl.lookupFunction<_init_sdk_C, _init_sdk_Dart>('init_sdk');
|
_dart_ffi_lib.lookupFunction<_init_sdk_C, _init_sdk_Dart>('init_sdk');
|
||||||
typedef _init_sdk_C = Int64 Function(
|
typedef _init_sdk_C = Int64 Function(
|
||||||
Pointer<ffi.Utf8> path,
|
Pointer<ffi.Utf8> path,
|
||||||
);
|
);
|
||||||
@ -96,7 +97,7 @@ int set_stream_port(int port) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final _set_stream_port_Dart _set_stream_port =
|
final _set_stream_port_Dart _set_stream_port =
|
||||||
_dl.lookupFunction<_set_stream_port_C, _set_stream_port_Dart>(
|
_dart_ffi_lib.lookupFunction<_set_stream_port_C, _set_stream_port_Dart>(
|
||||||
'set_stream_port');
|
'set_stream_port');
|
||||||
|
|
||||||
typedef _set_stream_port_C = Int32 Function(
|
typedef _set_stream_port_C = Int32 Function(
|
||||||
@ -111,7 +112,7 @@ void link_me_please() {
|
|||||||
_link_me_please();
|
_link_me_please();
|
||||||
}
|
}
|
||||||
|
|
||||||
final _link_me_please_Dart _link_me_please = _dl
|
final _link_me_please_Dart _link_me_please = _dart_ffi_lib
|
||||||
.lookupFunction<_link_me_please_C, _link_me_please_Dart>('link_me_please');
|
.lookupFunction<_link_me_please_C, _link_me_please_Dart>('link_me_please');
|
||||||
typedef _link_me_please_C = Void Function();
|
typedef _link_me_please_C = Void Function();
|
||||||
typedef _link_me_please_Dart = void Function();
|
typedef _link_me_please_Dart = void Function();
|
||||||
@ -123,7 +124,7 @@ void store_dart_post_cobject(
|
|||||||
_store_dart_post_cobject(ptr);
|
_store_dart_post_cobject(ptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
final _store_dart_post_cobject_Dart _store_dart_post_cobject = _dl
|
final _store_dart_post_cobject_Dart _store_dart_post_cobject = _dart_ffi_lib
|
||||||
.lookupFunction<_store_dart_post_cobject_C, _store_dart_post_cobject_Dart>(
|
.lookupFunction<_store_dart_post_cobject_C, _store_dart_post_cobject_Dart>(
|
||||||
'store_dart_post_cobject');
|
'store_dart_post_cobject');
|
||||||
typedef _store_dart_post_cobject_C = Void Function(
|
typedef _store_dart_post_cobject_C = Void Function(
|
||||||
|
@ -49,7 +49,7 @@ packages:
|
|||||||
name: archive
|
name: archive
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.3.1"
|
version: "3.1.11"
|
||||||
args:
|
args:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -245,7 +245,7 @@ packages:
|
|||||||
name: coverage
|
name: coverage
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.2"
|
version: "1.2.0"
|
||||||
cross_file:
|
cross_file:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -259,7 +259,7 @@ packages:
|
|||||||
name: crypto
|
name: crypto
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.2"
|
version: "3.0.1"
|
||||||
csslib:
|
csslib:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -454,6 +454,11 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.6.1"
|
version: "0.6.1"
|
||||||
|
flutter_driver:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
flutter_inappwebview:
|
flutter_inappwebview:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -555,6 +560,11 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
fuchsia_remote_debug_protocol:
|
||||||
|
dependency: transitive
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
get_it:
|
get_it:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -660,6 +670,11 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.5.0"
|
version: "2.5.0"
|
||||||
|
integration_test:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description: flutter
|
||||||
|
source: sdk
|
||||||
|
version: "0.0.0"
|
||||||
intl:
|
intl:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1246,6 +1261,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.3.1+2"
|
version: "0.3.1+2"
|
||||||
|
sync_http:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sync_http
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
table_calendar:
|
table_calendar:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
@ -1322,7 +1344,7 @@ packages:
|
|||||||
name: typed_data
|
name: typed_data
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.1"
|
version: "1.3.0"
|
||||||
universal_platform:
|
universal_platform:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1441,7 +1463,7 @@ packages:
|
|||||||
name: vm_service
|
name: vm_service
|
||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.3.0"
|
version: "8.2.2"
|
||||||
watcher:
|
watcher:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -1456,6 +1478,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.2.0"
|
version: "2.2.0"
|
||||||
|
webdriver:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: webdriver
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "3.0.0"
|
||||||
webkit_inspection_protocol:
|
webkit_inspection_protocol:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -97,6 +97,8 @@ dev_dependencies:
|
|||||||
|
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
integration_test:
|
||||||
|
sdk: flutter
|
||||||
build_runner: ^2.2.0
|
build_runner: ^2.2.0
|
||||||
freezed: ^2.1.0+1
|
freezed: ^2.1.0+1
|
||||||
bloc_test: ^9.0.2
|
bloc_test: ^9.0.2
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/grid_bloc.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late AppFlowyGridTest gridTest;
|
||||||
|
setUpAll(() async {
|
||||||
|
gridTest = await AppFlowyGridTest.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('GridBloc', () {
|
||||||
|
blocTest<GridBloc, GridState>(
|
||||||
|
"Create row",
|
||||||
|
build: () =>
|
||||||
|
GridBloc(view: gridTest.gridView)..add(const GridEvent.initial()),
|
||||||
|
act: (bloc) => bloc.add(const GridEvent.createRow()),
|
||||||
|
wait: const Duration(milliseconds: 300),
|
||||||
|
verify: (bloc) {
|
||||||
|
assert(bloc.state.rowInfos.length == 4);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
group('GridBloc', () {
|
||||||
|
late GridBloc gridBloc;
|
||||||
|
setUpAll(() async {
|
||||||
|
gridBloc = GridBloc(view: gridTest.gridView)
|
||||||
|
..add(const GridEvent.initial());
|
||||||
|
await gridResponseFuture();
|
||||||
|
});
|
||||||
|
|
||||||
|
// The initial number of rows is three
|
||||||
|
test('', () async {
|
||||||
|
assert(gridBloc.state.rowInfos.length == 3);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('delete row', () async {
|
||||||
|
gridBloc.add(GridEvent.deleteRow(gridBloc.state.rowInfos.last));
|
||||||
|
await gridResponseFuture();
|
||||||
|
assert(gridBloc.state.rowInfos.length == 2);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
@ -0,0 +1,36 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import 'util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late AppFlowyGridSelectOptionCellTest cellTest;
|
||||||
|
setUpAll(() async {
|
||||||
|
cellTest = await AppFlowyGridSelectOptionCellTest.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
group('SingleSelectOptionBloc', () {
|
||||||
|
late GridSelectOptionCellController cellController;
|
||||||
|
setUp(() async {
|
||||||
|
cellController =
|
||||||
|
await cellTest.makeCellController(FieldType.SingleSelect);
|
||||||
|
});
|
||||||
|
|
||||||
|
blocTest<SelectOptionCellEditorBloc, SelectOptionEditorState>(
|
||||||
|
"create option",
|
||||||
|
build: () {
|
||||||
|
final bloc = SelectOptionCellEditorBloc(cellController: cellController);
|
||||||
|
bloc.add(const SelectOptionEditorEvent.initial());
|
||||||
|
return bloc;
|
||||||
|
},
|
||||||
|
act: (bloc) => bloc.add(const SelectOptionEditorEvent.newOption("A")),
|
||||||
|
wait: gridResponseDuration(),
|
||||||
|
verify: (bloc) {
|
||||||
|
assert(bloc.state.options.length == 1);
|
||||||
|
assert(bloc.state.options[0].name == "A");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
124
frontend/app_flowy/test/bloc_test/grid_test/util.dart
Normal file
124
frontend/app_flowy/test/bloc_test/grid_test/util.dart
Normal file
@ -0,0 +1,124 @@
|
|||||||
|
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/grid_data_controller.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_bloc.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/grid.dart';
|
||||||
|
import 'package:app_flowy/workspace/application/app/app_service.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
|
||||||
|
|
||||||
|
import '../../util.dart';
|
||||||
|
|
||||||
|
/// Create a empty Grid for test
|
||||||
|
class AppFlowyGridTest {
|
||||||
|
// ignore: unused_field
|
||||||
|
final AppFlowyUnitTest _inner;
|
||||||
|
late ViewPB gridView;
|
||||||
|
AppFlowyGridTest(AppFlowyUnitTest unitTest) : _inner = unitTest;
|
||||||
|
|
||||||
|
static Future<AppFlowyGridTest> ensureInitialized() async {
|
||||||
|
final inner = await AppFlowyUnitTest.ensureInitialized();
|
||||||
|
final test = AppFlowyGridTest(inner);
|
||||||
|
await test._createTestGrid();
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _createTestGrid() async {
|
||||||
|
final app = await _inner.createTestApp();
|
||||||
|
final builder = GridPluginBuilder();
|
||||||
|
final result = await AppService().createView(
|
||||||
|
appId: app.id,
|
||||||
|
name: "Test Grid",
|
||||||
|
dataType: builder.dataType,
|
||||||
|
pluginType: builder.pluginType,
|
||||||
|
layoutType: builder.layoutType!,
|
||||||
|
);
|
||||||
|
result.fold(
|
||||||
|
(view) => gridView = view,
|
||||||
|
(error) {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppFlowyGridSelectOptionCellTest {
|
||||||
|
final AppFlowyGridCellTest _cellTest;
|
||||||
|
|
||||||
|
AppFlowyGridSelectOptionCellTest(AppFlowyGridCellTest cellTest)
|
||||||
|
: _cellTest = cellTest;
|
||||||
|
|
||||||
|
static Future<AppFlowyGridSelectOptionCellTest> ensureInitialized() async {
|
||||||
|
final cellTest = await AppFlowyGridCellTest.ensureInitialized();
|
||||||
|
final test = AppFlowyGridSelectOptionCellTest(cellTest);
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// For the moment, just edit the first row of the grid.
|
||||||
|
Future<GridSelectOptionCellController> makeCellController(
|
||||||
|
FieldType fieldType) async {
|
||||||
|
assert(fieldType == FieldType.SingleSelect ||
|
||||||
|
fieldType == FieldType.MultiSelect);
|
||||||
|
|
||||||
|
final fieldContexts =
|
||||||
|
_cellTest._dataController.fieldController.fieldContexts;
|
||||||
|
final field =
|
||||||
|
fieldContexts.firstWhere((element) => element.fieldType == fieldType);
|
||||||
|
final builder = await _cellTest.cellControllerBuilder(0, field.id);
|
||||||
|
final cellController = builder.build() as GridSelectOptionCellController;
|
||||||
|
return cellController;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppFlowyGridCellTest {
|
||||||
|
// ignore: unused_field
|
||||||
|
final AppFlowyGridTest _gridTest;
|
||||||
|
final GridDataController _dataController;
|
||||||
|
AppFlowyGridCellTest(AppFlowyGridTest gridTest)
|
||||||
|
: _gridTest = gridTest,
|
||||||
|
_dataController = GridDataController(view: gridTest.gridView);
|
||||||
|
|
||||||
|
static Future<AppFlowyGridCellTest> ensureInitialized() async {
|
||||||
|
final gridTest = await AppFlowyGridTest.ensureInitialized();
|
||||||
|
final test = AppFlowyGridCellTest(gridTest);
|
||||||
|
await test._loadGridData();
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _loadGridData() async {
|
||||||
|
final result = await _dataController.loadData();
|
||||||
|
result.fold((l) => null, (r) => throw Exception(r));
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<GridCellControllerBuilder> cellControllerBuilder(
|
||||||
|
int rowIndex, String fieldId) async {
|
||||||
|
final RowInfo rowInfo = _dataController.rowInfos[rowIndex];
|
||||||
|
final blockCache = _dataController.blocks[rowInfo.rowPB.blockId];
|
||||||
|
final rowCache = blockCache?.rowCache;
|
||||||
|
|
||||||
|
final rowDataController = GridRowDataController(
|
||||||
|
rowInfo: rowInfo,
|
||||||
|
fieldController: _dataController.fieldController,
|
||||||
|
rowCache: rowCache!,
|
||||||
|
);
|
||||||
|
|
||||||
|
final rowBloc = RowBloc(
|
||||||
|
rowInfo: rowInfo,
|
||||||
|
dataController: rowDataController,
|
||||||
|
)..add(const RowEvent.initial());
|
||||||
|
await gridResponseFuture(milliseconds: 300);
|
||||||
|
|
||||||
|
return GridCellControllerBuilder(
|
||||||
|
cellId: rowBloc.state.gridCellMap[fieldId]!,
|
||||||
|
cellCache: rowCache.cellCache,
|
||||||
|
delegate: rowDataController,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> gridResponseFuture({int milliseconds = 200}) {
|
||||||
|
return Future.delayed(gridResponseDuration(milliseconds: milliseconds));
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration gridResponseDuration({int milliseconds = 200}) {
|
||||||
|
return Duration(milliseconds: milliseconds);
|
||||||
|
}
|
104
frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart
Normal file
104
frontend/app_flowy/test/bloc_test/menu_test/app_bloc_test.dart
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import 'package:app_flowy/plugins/board/board.dart';
|
||||||
|
import 'package:app_flowy/plugins/doc/document.dart';
|
||||||
|
import 'package:app_flowy/plugins/grid/grid.dart';
|
||||||
|
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-folder/view.pb.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:bloc_test/bloc_test.dart';
|
||||||
|
import '../../util.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
late AppFlowyUnitTest test;
|
||||||
|
setUpAll(() async {
|
||||||
|
test = await AppFlowyUnitTest.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
group(
|
||||||
|
'AppBloc',
|
||||||
|
() {
|
||||||
|
late AppPB app;
|
||||||
|
setUp(() async {
|
||||||
|
app = await test.createTestApp();
|
||||||
|
});
|
||||||
|
|
||||||
|
blocTest<AppBloc, AppState>(
|
||||||
|
"Create a document",
|
||||||
|
build: () => AppBloc(app: app)..add(const AppEvent.initial()),
|
||||||
|
act: (bloc) {
|
||||||
|
bloc.add(
|
||||||
|
AppEvent.createView("Test document", DocumentPluginBuilder()));
|
||||||
|
},
|
||||||
|
wait: blocResponseDuration(),
|
||||||
|
verify: (bloc) {
|
||||||
|
assert(bloc.state.views.length == 1);
|
||||||
|
assert(bloc.state.views.last.name == "Test document");
|
||||||
|
assert(bloc.state.views.last.layout == ViewLayoutTypePB.Document);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<AppBloc, AppState>(
|
||||||
|
"Create a grid",
|
||||||
|
build: () => AppBloc(app: app)..add(const AppEvent.initial()),
|
||||||
|
act: (bloc) {
|
||||||
|
bloc.add(AppEvent.createView("Test grid", GridPluginBuilder()));
|
||||||
|
},
|
||||||
|
wait: blocResponseDuration(),
|
||||||
|
verify: (bloc) {
|
||||||
|
assert(bloc.state.views.length == 1);
|
||||||
|
assert(bloc.state.views.last.name == "Test grid");
|
||||||
|
assert(bloc.state.views.last.layout == ViewLayoutTypePB.Grid);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
blocTest<AppBloc, AppState>(
|
||||||
|
"Create a Kanban board",
|
||||||
|
build: () => AppBloc(app: app)..add(const AppEvent.initial()),
|
||||||
|
act: (bloc) {
|
||||||
|
bloc.add(AppEvent.createView("Test board", BoardPluginBuilder()));
|
||||||
|
},
|
||||||
|
wait: const Duration(milliseconds: 100),
|
||||||
|
verify: (bloc) {
|
||||||
|
assert(bloc.state.views.length == 1);
|
||||||
|
assert(bloc.state.views.last.name == "Test board");
|
||||||
|
assert(bloc.state.views.last.layout == ViewLayoutTypePB.Board);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
group('AppBloc', () {
|
||||||
|
late ViewPB view;
|
||||||
|
late AppPB app;
|
||||||
|
setUpAll(() async {
|
||||||
|
app = await test.createTestApp();
|
||||||
|
});
|
||||||
|
|
||||||
|
blocTest<AppBloc, AppState>(
|
||||||
|
"create a document",
|
||||||
|
build: () => AppBloc(app: app)..add(const AppEvent.initial()),
|
||||||
|
act: (bloc) {
|
||||||
|
bloc.add(AppEvent.createView("Test document", DocumentPluginBuilder()));
|
||||||
|
},
|
||||||
|
wait: blocResponseDuration(),
|
||||||
|
verify: (bloc) {
|
||||||
|
assert(bloc.state.views.length == 1);
|
||||||
|
view = bloc.state.views.last;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
blocTest<AppBloc, AppState>(
|
||||||
|
"delete the document",
|
||||||
|
build: () => AppBloc(app: app)..add(const AppEvent.initial()),
|
||||||
|
act: (bloc) => bloc.add(AppEvent.deleteView(view.id)),
|
||||||
|
);
|
||||||
|
blocTest<AppBloc, AppState>(
|
||||||
|
"verify the document is exist",
|
||||||
|
build: () => AppBloc(app: app)..add(const AppEvent.initial()),
|
||||||
|
act: (bloc) => bloc.add(const AppEvent.loadViews()),
|
||||||
|
wait: blocResponseDuration(),
|
||||||
|
verify: (bloc) {
|
||||||
|
assert(bloc.state.views.isEmpty);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
109
frontend/app_flowy/test/util.dart
Normal file
109
frontend/app_flowy/test/util.dart
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
|
import 'package:app_flowy/user/application/auth_service.dart';
|
||||||
|
import 'package:app_flowy/user/application/user_service.dart';
|
||||||
|
import 'package:app_flowy/workspace/application/workspace/workspace_service.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:flowy_infra/uuid.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-folder/app.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-folder/workspace.pb.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:integration_test/integration_test.dart';
|
||||||
|
import 'package:app_flowy/main.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
|
||||||
|
class AppFlowyIntegrateTest {
|
||||||
|
static Future<AppFlowyIntegrateTest> ensureInitialized() async {
|
||||||
|
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
main();
|
||||||
|
return AppFlowyIntegrateTest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AppFlowyUnitTest {
|
||||||
|
late UserProfilePB userProfile;
|
||||||
|
late UserService userService;
|
||||||
|
late WorkspaceService workspaceService;
|
||||||
|
late List<WorkspacePB> workspaces;
|
||||||
|
|
||||||
|
static Future<AppFlowyUnitTest> ensureInitialized() async {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
SharedPreferences.setMockInitialValues({});
|
||||||
|
_pathProviderInitialized();
|
||||||
|
|
||||||
|
await EasyLocalization.ensureInitialized();
|
||||||
|
await FlowyRunner.run(FlowyTestApp());
|
||||||
|
|
||||||
|
final test = AppFlowyUnitTest();
|
||||||
|
await test._signIn();
|
||||||
|
await test._loadWorkspace();
|
||||||
|
|
||||||
|
await test._initialServices();
|
||||||
|
return test;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _signIn() async {
|
||||||
|
final authService = getIt<AuthService>();
|
||||||
|
const password = "AppFlowy123@";
|
||||||
|
final uid = uuid();
|
||||||
|
final userEmail = "$uid@appflowy.io";
|
||||||
|
final result = await authService.signUp(
|
||||||
|
name: "TestUser",
|
||||||
|
password: password,
|
||||||
|
email: userEmail,
|
||||||
|
);
|
||||||
|
return result.fold(
|
||||||
|
(user) {
|
||||||
|
userProfile = user;
|
||||||
|
userService = UserService(userId: userProfile.id);
|
||||||
|
},
|
||||||
|
(error) {},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
WorkspacePB get currentWorkspace => workspaces[0];
|
||||||
|
|
||||||
|
Future<void> _loadWorkspace() async {
|
||||||
|
final result = await userService.getWorkspaces();
|
||||||
|
result.fold(
|
||||||
|
(value) => workspaces = value,
|
||||||
|
(error) {
|
||||||
|
throw Exception(error);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _initialServices() async {
|
||||||
|
workspaceService = WorkspaceService(workspaceId: currentWorkspace.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<AppPB> createTestApp() async {
|
||||||
|
final result = await workspaceService.createApp(name: "Test App");
|
||||||
|
return result.fold(
|
||||||
|
(app) => app,
|
||||||
|
(error) => throw Exception(error),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _pathProviderInitialized() {
|
||||||
|
const MethodChannel channel =
|
||||||
|
MethodChannel('plugins.flutter.io/path_provider');
|
||||||
|
channel.setMockMethodCallHandler((MethodCall methodCall) async {
|
||||||
|
return ".";
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class FlowyTestApp implements EntryPoint {
|
||||||
|
@override
|
||||||
|
Widget create() {
|
||||||
|
return Container();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration blocResponseDuration({int millseconds = 100}) {
|
||||||
|
return Duration(milliseconds: millseconds);
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/user/application/auth_service.dart';
|
|
||||||
import 'package:flowy_infra/uuid.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
|
|
||||||
class FlowyTest {
|
|
||||||
static Future<FlowyTest> setup() async {
|
|
||||||
TestWidgetsFlutterBinding.ensureInitialized();
|
|
||||||
// await EasyLocalization.ensureInitialized();
|
|
||||||
|
|
||||||
await FlowyRunner.run(FlowyTestApp());
|
|
||||||
return FlowyTest();
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<UserProfilePB> signIn() async {
|
|
||||||
final authService = getIt<AuthService>();
|
|
||||||
const password = "AppFlowy123@";
|
|
||||||
final uid = uuid();
|
|
||||||
final userEmail = "$uid@appflowy.io";
|
|
||||||
final result = await authService.signUp(
|
|
||||||
name: "FlowyTestUser",
|
|
||||||
password: password,
|
|
||||||
email: userEmail,
|
|
||||||
);
|
|
||||||
return result.fold(
|
|
||||||
(user) => user,
|
|
||||||
(error) {
|
|
||||||
throw StateError("$error");
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FlowyTestApp implements EntryPoint {
|
|
||||||
@override
|
|
||||||
Widget create() {
|
|
||||||
return Container();
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,29 +0,0 @@
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/workspace/application/workspace/welcome_bloc.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-user/protobuf.dart';
|
|
||||||
import 'package:flutter_test/flutter_test.dart';
|
|
||||||
import 'package:bloc_test/bloc_test.dart';
|
|
||||||
|
|
||||||
import 'util/test_env.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
UserProfilePB? userInfo;
|
|
||||||
setUpAll(() async {
|
|
||||||
final flowyTest = await FlowyTest.setup();
|
|
||||||
userInfo = await flowyTest.signIn();
|
|
||||||
});
|
|
||||||
|
|
||||||
group('WelcomeBloc', () {
|
|
||||||
blocTest<WelcomeBloc, WelcomeState>(
|
|
||||||
"welcome screen init",
|
|
||||||
build: () => getIt<WelcomeBloc>(param1: userInfo),
|
|
||||||
act: (bloc) {
|
|
||||||
bloc.add(const WelcomeEvent.initial());
|
|
||||||
},
|
|
||||||
wait: const Duration(seconds: 3),
|
|
||||||
verify: (bloc) {
|
|
||||||
assert(bloc.state.isLoading == false);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
@ -93,10 +93,9 @@ impl GridManager {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all, fields(grid_id), err)]
|
#[tracing::instrument(level = "debug", skip_all, err)]
|
||||||
pub async fn open_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<Arc<GridRevisionEditor>> {
|
pub async fn open_grid<T: AsRef<str>>(&self, grid_id: T) -> FlowyResult<Arc<GridRevisionEditor>> {
|
||||||
let grid_id = grid_id.as_ref();
|
let grid_id = grid_id.as_ref();
|
||||||
tracing::Span::current().record("grid_id", &grid_id);
|
|
||||||
let _ = self.migration.run_v1_migration(grid_id).await;
|
let _ = self.migration.run_v1_migration(grid_id).await;
|
||||||
self.get_or_create_grid_editor(grid_id).await
|
self.get_or_create_grid_editor(grid_id).await
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
mod multi_select_type_option;
|
mod multi_select_type_option;
|
||||||
mod select_type_option;
|
mod select_type_option;
|
||||||
mod single_select_type_option;
|
mod single_select_type_option;
|
||||||
|
mod type_option_transform;
|
||||||
|
|
||||||
pub use multi_select_type_option::*;
|
pub use multi_select_type_option::*;
|
||||||
pub use select_type_option::*;
|
pub use select_type_option::*;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
use crate::impl_type_option;
|
use crate::impl_type_option;
|
||||||
use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
|
use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
|
||||||
|
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
|
||||||
use crate::services::field::type_options::util::get_cell_data;
|
use crate::services::field::type_options::util::get_cell_data;
|
||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
|
BoxTypeOptionBuilder, SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
|
||||||
@ -110,7 +111,7 @@ impl TypeOptionBuilder for MultiSelectTypeOptionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
|
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
|
||||||
self.0.transform_type_option(field_type, type_option_data);
|
SelectOptionTypeOptionTransformer::transform_type_option(&mut self.0, field_type, type_option_data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -2,7 +2,8 @@ use crate::entities::{CellChangesetPB, FieldType, GridCellIdPB, GridCellIdParams
|
|||||||
use crate::services::cell::{
|
use crate::services::cell::{
|
||||||
CellBytes, CellBytesParser, CellData, CellDataIsEmpty, CellDisplayable, FromCellChangeset, FromCellString,
|
CellBytes, CellBytesParser, CellData, CellDataIsEmpty, CellDisplayable, FromCellChangeset, FromCellString,
|
||||||
};
|
};
|
||||||
use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB, CHECK, UNCHECK};
|
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
|
||||||
|
use crate::services::field::{MultiSelectTypeOptionPB, SingleSelectTypeOptionPB};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_error::{internal_error, ErrorCode, FlowyResult};
|
use flowy_error::{internal_error, ErrorCode, FlowyResult};
|
||||||
@ -119,25 +120,6 @@ pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform_type_option(&mut self, field_type: &FieldType, _type_option_data: String) {
|
|
||||||
match field_type {
|
|
||||||
FieldType::Checkbox => {
|
|
||||||
//add Yes and No options if it does not exist.
|
|
||||||
if !self.options().iter().any(|option| option.name == CHECK) {
|
|
||||||
let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
|
|
||||||
self.mut_options().push(check_option);
|
|
||||||
}
|
|
||||||
|
|
||||||
if !self.options().iter().any(|option| option.name == UNCHECK) {
|
|
||||||
let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
|
|
||||||
self.mut_options().push(uncheck_option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
FieldType::MultiSelect => {}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn transform_cell_data(
|
fn transform_cell_data(
|
||||||
&self,
|
&self,
|
||||||
cell_data: CellData<SelectOptionIds>,
|
cell_data: CellData<SelectOptionIds>,
|
||||||
@ -150,6 +132,21 @@ pub trait SelectTypeOptionSharedAction: TypeOptionDataSerializer + Send + Sync {
|
|||||||
}
|
}
|
||||||
FieldType::Checkbox => {
|
FieldType::Checkbox => {
|
||||||
// transform the cell data to the option id
|
// transform the cell data to the option id
|
||||||
|
let mut transformed_ids = Vec::new();
|
||||||
|
let options = self.options();
|
||||||
|
cell_data.0.iter().for_each(|ids| {
|
||||||
|
ids.0.iter().for_each(|name| {
|
||||||
|
let id = options
|
||||||
|
.iter()
|
||||||
|
.find(|option| option.name == name.clone())
|
||||||
|
.unwrap()
|
||||||
|
.id
|
||||||
|
.clone();
|
||||||
|
transformed_ids.push(id);
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
return CellBytes::from(self.get_selected_options(CellData(Some(SelectOptionIds(transformed_ids)))));
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Ok(CellBytes::default());
|
return Ok(CellBytes::default());
|
||||||
@ -174,7 +171,12 @@ where
|
|||||||
decoded_field_type: &FieldType,
|
decoded_field_type: &FieldType,
|
||||||
field_rev: &FieldRevision,
|
field_rev: &FieldRevision,
|
||||||
) -> FlowyResult<CellBytes> {
|
) -> FlowyResult<CellBytes> {
|
||||||
self.transform_cell_data(cell_data, decoded_field_type, field_rev)
|
SelectOptionTypeOptionTransformer::transform_type_option_cell_data(
|
||||||
|
self,
|
||||||
|
cell_data,
|
||||||
|
decoded_field_type,
|
||||||
|
field_rev,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn displayed_cell_string(
|
fn displayed_cell_string(
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
use crate::entities::FieldType;
|
use crate::entities::FieldType;
|
||||||
use crate::impl_type_option;
|
use crate::impl_type_option;
|
||||||
use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
|
use crate::services::cell::{CellBytes, CellData, CellDataChangeset, CellDataOperation, CellDisplayable};
|
||||||
|
use crate::services::field::selection_type_option::type_option_transform::SelectOptionTypeOptionTransformer;
|
||||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||||
use crate::services::field::{
|
use crate::services::field::{
|
||||||
SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
|
SelectOptionCellChangeset, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction,
|
||||||
@ -96,7 +97,7 @@ impl TypeOptionBuilder for SingleSelectTypeOptionBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
|
fn transform(&mut self, field_type: &FieldType, type_option_data: String) {
|
||||||
self.0.transform_type_option(field_type, type_option_data);
|
SelectOptionTypeOptionTransformer::transform_type_option(&mut self.0, field_type, type_option_data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
use crate::entities::FieldType;
|
||||||
|
use crate::services::cell::{CellBytes, CellData};
|
||||||
|
use crate::services::field::{
|
||||||
|
SelectOptionColorPB, SelectOptionIds, SelectOptionPB, SelectTypeOptionSharedAction, CHECK, UNCHECK,
|
||||||
|
};
|
||||||
|
use flowy_error::FlowyResult;
|
||||||
|
use flowy_grid_data_model::revision::FieldRevision;
|
||||||
|
|
||||||
|
/// Handles how to transform the cell data when switching between different field types
|
||||||
|
pub struct SelectOptionTypeOptionTransformer();
|
||||||
|
impl SelectOptionTypeOptionTransformer {
|
||||||
|
pub fn transform_type_option<T>(shared: &mut T, field_type: &FieldType, _type_option_data: String)
|
||||||
|
where
|
||||||
|
T: SelectTypeOptionSharedAction,
|
||||||
|
{
|
||||||
|
match field_type {
|
||||||
|
FieldType::Checkbox => {
|
||||||
|
//add Yes and No options if it does not exist.
|
||||||
|
if !shared.options().iter().any(|option| option.name == CHECK) {
|
||||||
|
let check_option = SelectOptionPB::with_color(CHECK, SelectOptionColorPB::Green);
|
||||||
|
shared.mut_options().push(check_option);
|
||||||
|
}
|
||||||
|
|
||||||
|
if !shared.options().iter().any(|option| option.name == UNCHECK) {
|
||||||
|
let uncheck_option = SelectOptionPB::with_color(UNCHECK, SelectOptionColorPB::Yellow);
|
||||||
|
shared.mut_options().push(uncheck_option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FieldType::MultiSelect => {}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn transform_type_option_cell_data<T>(
|
||||||
|
shared: &T,
|
||||||
|
cell_data: CellData<SelectOptionIds>,
|
||||||
|
decoded_field_type: &FieldType,
|
||||||
|
_field_rev: &FieldRevision,
|
||||||
|
) -> FlowyResult<CellBytes>
|
||||||
|
where
|
||||||
|
T: SelectTypeOptionSharedAction,
|
||||||
|
{
|
||||||
|
match decoded_field_type {
|
||||||
|
FieldType::SingleSelect | FieldType::MultiSelect => {
|
||||||
|
//
|
||||||
|
CellBytes::from(shared.get_selected_options(cell_data))
|
||||||
|
}
|
||||||
|
FieldType::Checkbox => {
|
||||||
|
// transform the cell data to the option id
|
||||||
|
let mut transformed_ids = Vec::new();
|
||||||
|
let options = shared.options();
|
||||||
|
cell_data.try_into_inner()?.iter().for_each(|name| {
|
||||||
|
if let Some(option) = options.iter().find(|option| &option.name == name) {
|
||||||
|
transformed_ids.push(option.id.clone());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
let transformed_cell_data = CellData::from(SelectOptionIds::from(transformed_ids));
|
||||||
|
CellBytes::from(shared.get_selected_options(transformed_cell_data))
|
||||||
|
}
|
||||||
|
_ => Ok(CellBytes::default()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -31,11 +31,13 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// For the moment, the `RichTextTypeOptionPB` is empty. The `data` property is not
|
||||||
|
/// used yet.
|
||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
|
||||||
pub struct RichTextTypeOptionPB {
|
pub struct RichTextTypeOptionPB {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
data: String, //It's not used yet
|
data: String,
|
||||||
}
|
}
|
||||||
impl_type_option!(RichTextTypeOptionPB, FieldType::RichText);
|
impl_type_option!(RichTextTypeOptionPB, FieldType::RichText);
|
||||||
|
|
||||||
|
@ -100,7 +100,8 @@ impl GridRevisionEditor {
|
|||||||
///
|
///
|
||||||
/// * `grid_id`: the id of the grid
|
/// * `grid_id`: the id of the grid
|
||||||
/// * `field_id`: the id of the field
|
/// * `field_id`: the id of the field
|
||||||
/// * `type_option_data`: the updated type-option data.
|
/// * `type_option_data`: the updated type-option data. The `type-option` data might be empty
|
||||||
|
/// if there is no type-option config for that field. For example, the `RichTextTypeOptionPB`.
|
||||||
///
|
///
|
||||||
pub async fn update_field_type_option(
|
pub async fn update_field_type_option(
|
||||||
&self,
|
&self,
|
||||||
@ -108,7 +109,6 @@ impl GridRevisionEditor {
|
|||||||
field_id: &str,
|
field_id: &str,
|
||||||
type_option_data: Vec<u8>,
|
type_option_data: Vec<u8>,
|
||||||
) -> FlowyResult<()> {
|
) -> FlowyResult<()> {
|
||||||
debug_assert!(!type_option_data.is_empty());
|
|
||||||
if type_option_data.is_empty() {
|
if type_option_data.is_empty() {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -21,17 +21,18 @@ run_task = { name = ["setup-crate-type","sdk-build-android", "restore-crate-type
|
|||||||
[tasks.flowy-sdk-dev-macos]
|
[tasks.flowy-sdk-dev-macos]
|
||||||
category = "Build"
|
category = "Build"
|
||||||
dependencies = ["env_check"]
|
dependencies = ["env_check"]
|
||||||
run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type", "copy-to-sys-tmpdir"] }
|
run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type"] }
|
||||||
|
|
||||||
[tasks.flowy-sdk-dev-windows]
|
[tasks.flowy-sdk-dev-windows]
|
||||||
category = "Build"
|
category = "Build"
|
||||||
dependencies = ["env_check"]
|
dependencies = ["env_check"]
|
||||||
run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type", "copy-to-sys-tmpdir"] }
|
run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type"] }
|
||||||
|
|
||||||
[tasks.flowy-sdk-dev-linux]
|
[tasks.flowy-sdk-dev-linux]
|
||||||
category = "Build"
|
category = "Build"
|
||||||
dependencies = ["env_check"]
|
dependencies = ["env_check"]
|
||||||
run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type", "copy-to-sys-tmpdir"] }
|
run_task = { name = ["setup-crate-type","sdk-build", "post-desktop", "restore-crate-type"] }
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
[tasks.sdk-build]
|
[tasks.sdk-build]
|
||||||
@ -114,7 +115,7 @@ script = [
|
|||||||
"""
|
"""
|
||||||
echo "🚀 🚀 🚀 Flowy-SDK(macOS) build success"
|
echo "🚀 🚀 🚀 Flowy-SDK(macOS) build success"
|
||||||
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/packages/flowy_sdk/${TARGET_OS}
|
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/packages/flowy_sdk/${TARGET_OS}
|
||||||
lib = set lib${LIB_NAME}.${SDK_EXT}
|
lib = set lib${LIB_NAME}.${LIB_EXT}
|
||||||
|
|
||||||
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/${lib} \
|
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/${lib} \
|
||||||
${dart_ffi_dir}/${lib}
|
${dart_ffi_dir}/${lib}
|
||||||
@ -131,7 +132,7 @@ script = [
|
|||||||
"""
|
"""
|
||||||
echo "🚀 🚀 🚀 Flowy-SDK(windows) build success"
|
echo "🚀 🚀 🚀 Flowy-SDK(windows) build success"
|
||||||
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/windows/flutter/dart_ffi
|
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/windows/flutter/dart_ffi
|
||||||
lib = set ${LIB_NAME}.${SDK_EXT}
|
lib = set ${LIB_NAME}.${LIB_EXT}
|
||||||
|
|
||||||
# copy dll
|
# copy dll
|
||||||
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/${lib} \
|
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/${lib} \
|
||||||
@ -150,7 +151,7 @@ script = [
|
|||||||
"""
|
"""
|
||||||
echo "🚀 🚀 🚀 Flowy-SDK(linux) build success"
|
echo "🚀 🚀 🚀 Flowy-SDK(linux) build success"
|
||||||
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/linux/flutter/dart_ffi
|
dart_ffi_dir= set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/linux/flutter/dart_ffi
|
||||||
lib = set lib${LIB_NAME}.${SDK_EXT}
|
lib = set lib${LIB_NAME}.${LIB_EXT}
|
||||||
|
|
||||||
# copy dll
|
# copy dll
|
||||||
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/${lib} \
|
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/${lib} \
|
||||||
@ -163,25 +164,34 @@ script = [
|
|||||||
]
|
]
|
||||||
script_runner = "@duckscript"
|
script_runner = "@duckscript"
|
||||||
|
|
||||||
[tasks.copy-to-sys-tmpdir]
|
[tasks.test-lib-build]
|
||||||
|
category = "Build"
|
||||||
|
dependencies = ["env_check"]
|
||||||
|
run_task = { name = ["setup-test-crate-type","test-sdk-build", "copy-to-sandbox-folder", "restore-test-crate-type"] }
|
||||||
|
|
||||||
|
[tasks.test-sdk-build]
|
||||||
|
private = true
|
||||||
|
script = [
|
||||||
|
"""
|
||||||
|
cd rust-lib/
|
||||||
|
rustup show
|
||||||
|
echo cargo build --package=dart-ffi --target ${TEST_COMPILE_TARGET} --features "${FEATURES}"
|
||||||
|
cargo build --package=dart-ffi --target ${TEST_COMPILE_TARGET} --features "${FEATURES}"
|
||||||
|
cd ../
|
||||||
|
""",
|
||||||
|
]
|
||||||
|
script_runner = "@shell"
|
||||||
|
|
||||||
|
[tasks.copy-to-sandbox-folder]
|
||||||
private = true
|
private = true
|
||||||
script = [
|
script = [
|
||||||
"""
|
"""
|
||||||
# Copy the flowy_sdk lib to system temp directory for flutter unit test.
|
# Copy the flowy_sdk lib to system temp directory for flutter unit test.
|
||||||
lib = set lib${LIB_NAME}.${SDK_EXT}
|
lib = set lib${LIB_NAME}.${TEST_LIB_EXT}
|
||||||
dest = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/.sandbox/${lib}
|
dest = set ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/app_flowy/.sandbox/${lib}
|
||||||
rm ${dest}
|
rm ${dest}
|
||||||
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${RUST_COMPILE_TARGET}/${BUILD_FLAG}/${lib} \
|
cp ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/target/${TEST_COMPILE_TARGET}/${TEST_BUILD_FLAG}/${lib} \
|
||||||
${dest}
|
${dest}
|
||||||
""",
|
""",
|
||||||
]
|
]
|
||||||
script_runner = "@duckscript"
|
script_runner = "@duckscript"
|
||||||
|
|
||||||
[tasks.copy-to-sys-tmpdir.windows]
|
|
||||||
private = true
|
|
||||||
script = [
|
|
||||||
"""
|
|
||||||
# Doesn't work on windows
|
|
||||||
""",
|
|
||||||
]
|
|
||||||
script_runner = "@duckscript"
|
|
@ -43,7 +43,7 @@ run_task = { name = "remove_files_with_pattern" }
|
|||||||
#Dart Clean
|
#Dart Clean
|
||||||
[tasks.rm_dart_generated_files]
|
[tasks.rm_dart_generated_files]
|
||||||
env = { "dart_flowy_sdk_path" = "./app_flowy/packages/flowy_sdk/" }
|
env = { "dart_flowy_sdk_path" = "./app_flowy/packages/flowy_sdk/" }
|
||||||
run_task = { name = ["rm_dart_generated_protobuf_files"] }
|
run_task = { name = ["rm_dart_generated_protobuf_files", "rm_dart_generated_event_files"] }
|
||||||
|
|
||||||
[tasks.rm_dart_generated_protobuf_files]
|
[tasks.rm_dart_generated_protobuf_files]
|
||||||
private = true
|
private = true
|
||||||
@ -63,6 +63,24 @@ script = [
|
|||||||
script_runner = "@duckscript"
|
script_runner = "@duckscript"
|
||||||
|
|
||||||
|
|
||||||
|
[tasks.rm_dart_generated_event_files]
|
||||||
|
private = true
|
||||||
|
script = [
|
||||||
|
"""
|
||||||
|
dart_event_folder = glob_array ${dart_flowy_sdk_path}/lib/dispatch/dart_event
|
||||||
|
|
||||||
|
if not array_is_empty ${dart_event_folder}
|
||||||
|
echo Remove generated dart event files:
|
||||||
|
for path in ${dart_event_folder}
|
||||||
|
echo remove ${path}
|
||||||
|
rm -rf ${path}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
""",
|
||||||
|
]
|
||||||
|
script_runner = "@duckscript"
|
||||||
|
|
||||||
|
|
||||||
[tasks.remove_files_with_pattern]
|
[tasks.remove_files_with_pattern]
|
||||||
private = true
|
private = true
|
||||||
script = [
|
script = [
|
||||||
|
@ -112,7 +112,7 @@ pub struct ASTField<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> ASTField<'a> {
|
impl<'a> ASTField<'a> {
|
||||||
pub fn new(cx: &Ctxt, field: &'a syn::Field, index: usize) -> Self {
|
pub fn new(cx: &Ctxt, field: &'a syn::Field, index: usize) -> Result<Self, String> {
|
||||||
let mut bracket_inner_ty = None;
|
let mut bracket_inner_ty = None;
|
||||||
let mut bracket_ty = None;
|
let mut bracket_ty = None;
|
||||||
let mut bracket_category = Some(BracketCategory::Other);
|
let mut bracket_category = Some(BracketCategory::Other);
|
||||||
@ -144,15 +144,16 @@ impl<'a> ASTField<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(None) => {
|
Ok(None) => {
|
||||||
cx.error_spanned_by(&field.ty, "fail to get the ty inner type");
|
let msg = format!("Fail to get the ty inner type: {:?}", field);
|
||||||
|
return Err(msg);
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
eprintln!("ASTField parser failed: {:?} with error: {}", field, e);
|
eprintln!("ASTField parser failed: {:?} with error: {}", field, e);
|
||||||
panic!()
|
return Err(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ASTField {
|
Ok(ASTField {
|
||||||
member: match &field.ident {
|
member: match &field.ident {
|
||||||
Some(ident) => syn::Member::Named(ident.clone()),
|
Some(ident) => syn::Member::Named(ident.clone()),
|
||||||
None => syn::Member::Unnamed(index.into()),
|
None => syn::Member::Unnamed(index.into()),
|
||||||
@ -163,7 +164,7 @@ impl<'a> ASTField<'a> {
|
|||||||
bracket_ty,
|
bracket_ty,
|
||||||
bracket_inner_ty,
|
bracket_inner_ty,
|
||||||
bracket_category,
|
bracket_category,
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ty_as_str(&self) -> String {
|
pub fn ty_as_str(&self) -> String {
|
||||||
@ -235,6 +236,6 @@ fn fields_from_ast<'a>(cx: &Ctxt, fields: &'a Punctuated<syn::Field, Token![,]>)
|
|||||||
fields
|
fields
|
||||||
.iter()
|
.iter()
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(index, field)| ASTField::new(cx, field, index))
|
.flat_map(|(index, field)| ASTField::new(cx, field, index).ok())
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -74,8 +74,7 @@ pub fn parse_ty<'a>(ctxt: &Ctxt, ty: &'a syn::Type) -> Result<Option<TyInfo<'a>>
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
ctxt.error_spanned_by(ty, "Unsupported inner type, get inner type fail".to_string());
|
Err("Unsupported inner type, get inner type fail".to_string())
|
||||||
Ok(None)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_bracketed(bracketed: &AngleBracketedGenericArguments) -> Vec<&syn::Type> {
|
fn parse_bracketed(bracketed: &AngleBracketedGenericArguments) -> Vec<&syn::Type> {
|
||||||
|
Loading…
Reference in New Issue
Block a user