Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
764b4db166 | ||
|
|
7701533ed2 | ||
|
|
29c89013f0 | ||
|
|
1fad5aa804 | ||
|
|
675e23e971 | ||
|
|
c9ee82ec75 | ||
|
|
0c9935ccf2 | ||
|
|
d28846bbaa | ||
|
|
58ecf62240 | ||
|
|
a5eaf15548 | ||
|
|
4589d86d81 | ||
|
|
9330df4ce1 | ||
|
|
7cdd47757b | ||
|
|
9d18e9de40 | ||
|
|
e2dd179001 | ||
|
|
35a47bfe5d | ||
|
|
9dc209bda7 | ||
|
|
104851d330 | ||
|
|
a1db5bff5c | ||
|
|
453346e69c | ||
|
|
923285bfcf | ||
|
|
0de8b92db6 | ||
|
|
125bcbc9db | ||
|
|
ef9596f4ab | ||
|
|
3c04b72932 | ||
|
|
a3bea472bf | ||
|
|
5ac7b8e165 | ||
|
|
95a938657b | ||
|
|
fbb1753350 | ||
|
|
c6401da0b5 | ||
|
|
f724185f1c | ||
|
|
63a12a1d2b | ||
|
|
890f7573f3 | ||
|
|
6f159e741b | ||
|
|
9a72f31d60 | ||
|
|
ab7acbd5de | ||
|
|
b7dfc85208 | ||
|
|
16a01e11ed | ||
|
|
70914e6228 |
No files matched your search
@@ -20,14 +20,6 @@ jobs:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
# - name: Create .env file
|
||||
# working-directory: frontend/appflowy_flutter
|
||||
# run: |
|
||||
# touch .env
|
||||
# echo SUPABASE_URL=${{ secrets.HOST_URL }} >> .env
|
||||
# echo SUPABASE_ANON_KEY=${{ secrets.HOST_ANON_KEY }} >> .env
|
||||
# echo SUPABASE_JWT_SECRET=${{ secrets.HOST_JWT_SECRET }} >> .env
|
||||
|
||||
- name: Build release notes
|
||||
run: |
|
||||
touch ${{ env.RELEASE_NOTES_PATH }}
|
||||
@@ -92,7 +84,7 @@ jobs:
|
||||
working-directory: frontend
|
||||
run: |
|
||||
flutter config --enable-windows-desktop
|
||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-windows-x86 appflowy
|
||||
dart ./scripts/flutter_release_build/build_flowy.dart . ${{ github.ref_name }}
|
||||
|
||||
- name: Archive Asset
|
||||
uses: vimtor/action-zip@v1
|
||||
@@ -132,7 +124,7 @@ jobs:
|
||||
asset_name: ${{ env.WINDOWS_INSTALLER_NAME }}.exe
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
build-for-macOS:
|
||||
build-for-macOS-x86_64:
|
||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
needs: create-release
|
||||
@@ -220,6 +212,96 @@ jobs:
|
||||
asset_name: ${{ env.MACOS_DMG_NAME }}.dmg
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
build-for-macOS-universal:
|
||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
needs: create-release
|
||||
env:
|
||||
MACOS_APP_RELEASE_PATH: frontend/appflowy_flutter/product/${{ github.ref_name }}/macos/Release
|
||||
MACOS_AARCH64_ZIP_NAME: AppFlowy_${{ github.ref_name }}_macos-universal.zip
|
||||
MACOS_DMG_NAME: AppFlowy_${{ github.ref_name }}_macos-universal
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
job:
|
||||
- {
|
||||
targets: "aarch64-apple-darwin,x86_64-apple-darwin",
|
||||
os: macos-11,
|
||||
extra-build-args: "",
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install flutter
|
||||
uses: subosito/flutter-action@v2
|
||||
with:
|
||||
channel: "stable"
|
||||
flutter-version: ${{ env.FLUTTER_VERSION }}
|
||||
cache: true
|
||||
|
||||
- name: Install Rust toolchain
|
||||
uses: dtolnay/rust-toolchain@stable
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
targets: ${{ matrix.job.targets }}
|
||||
components: rustfmt
|
||||
|
||||
- uses: Swatinem/rust-cache@v2
|
||||
with:
|
||||
prefix-key: appflowy-lib-cache
|
||||
key: ${{ matrix.job.os }}-${{ matrix.job.target }}
|
||||
|
||||
- name: Install prerequisites
|
||||
working-directory: frontend
|
||||
run: |
|
||||
cargo install --force cargo-make
|
||||
cargo install --force duckscript_cli
|
||||
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
run: |
|
||||
flutter config --enable-macos-desktop
|
||||
sh scripts/flutter_release_build/build_universal_package_for_macos.sh
|
||||
|
||||
- name: Create macOS dmg
|
||||
run: |
|
||||
brew install create-dmg
|
||||
create-dmg \
|
||||
--volname ${{ env.MACOS_DMG_NAME }} \
|
||||
--hide-extension "AppFlowy.app" \
|
||||
--background frontend/scripts/dmg_assets/AppFlowyInstallerBackground.jpg \
|
||||
--window-size 600 450 \
|
||||
--icon-size 94 \
|
||||
--icon "AppFlowy.app" 141 249 \
|
||||
--app-drop-link 458 249 \
|
||||
"${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg" \
|
||||
"${{ env.MACOS_APP_RELEASE_PATH }}/AppFlowy.app"
|
||||
|
||||
- name: Archive Asset
|
||||
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
||||
run: zip --symlinks -qr ${{ env.MACOS_AARCH64_ZIP_NAME }} AppFlowy.app
|
||||
|
||||
- name: Upload Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_AARCH64_ZIP_NAME }}
|
||||
asset_name: ${{ env.MACOS_AARCH64_ZIP_NAME }}
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
- name: Upload DMG Asset
|
||||
uses: actions/upload-release-asset@v1
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
upload_url: ${{ needs.create-release.outputs.upload_url }}
|
||||
asset_path: ${{ env.MACOS_APP_RELEASE_PATH }}/${{ env.MACOS_DMG_NAME }}.dmg
|
||||
asset_name: ${{ env.MACOS_DMG_NAME }}.dmg
|
||||
asset_content_type: application/octet-stream
|
||||
|
||||
build-for-linux:
|
||||
name: ${{ matrix.job.target }} (${{ matrix.job.os }}) [${{ matrix.job.extra-build-args }}]
|
||||
runs-on: ${{ matrix.job.os }}
|
||||
@@ -368,7 +450,13 @@ jobs:
|
||||
|
||||
notify-discord:
|
||||
runs-on: ubuntu-latest
|
||||
needs: [build-for-linux, build-for-windows, build-for-macOS]
|
||||
needs:
|
||||
[
|
||||
build-for-linux,
|
||||
build-for-windows,
|
||||
build-for-macOS-x86_64,
|
||||
build-for-macOS-universal,
|
||||
]
|
||||
steps:
|
||||
- name: Notify Discord
|
||||
run: |
|
||||
|
||||
@@ -1,5 +1,18 @@
|
||||
# Release Notes
|
||||
|
||||
## Version 0.2.9 - 08/08/2023
|
||||
|
||||
### New Features
|
||||
|
||||
- Improve tab and shortcut, click with alt/option to open a page in new tab.
|
||||
- Improve database tab bar UI.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Add button and more action button of the favorite section doesn't work.
|
||||
- Fix euro currency number format.
|
||||
- Some UI issues
|
||||
|
||||
## Version 0.2.8 - 08/03/2023
|
||||
|
||||
### New Features
|
||||
|
||||
+13
-1
@@ -24,7 +24,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
CURRENT_APP_VERSION = "0.2.8"
|
||||
CURRENT_APP_VERSION = "0.2.9"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
@@ -60,6 +60,7 @@ BUILD_FLAG = "debug"
|
||||
FLUTTER_OUTPUT_DIR = "Debug"
|
||||
PRODUCT_EXT = "app"
|
||||
BUILD_ARCHS = "arm64"
|
||||
BUILD_ACTIVE_ARCHS_ONLY = true
|
||||
CRATE_TYPE = "staticlib"
|
||||
|
||||
[env.development-mac-x86_64]
|
||||
@@ -70,6 +71,7 @@ BUILD_FLAG = "debug"
|
||||
FLUTTER_OUTPUT_DIR = "Debug"
|
||||
PRODUCT_EXT = "app"
|
||||
BUILD_ARCHS = "x86_64"
|
||||
BUILD_ACTIVE_ARCHS_ONLY = true
|
||||
CRATE_TYPE = "staticlib"
|
||||
|
||||
[env.production-mac-arm64]
|
||||
@@ -80,6 +82,7 @@ FLUTTER_OUTPUT_DIR = "Release"
|
||||
PRODUCT_EXT = "app"
|
||||
APP_ENVIRONMENT = "production"
|
||||
BUILD_ARCHS = "arm64"
|
||||
BUILD_ACTIVE_ARCHS_ONLY = false
|
||||
CRATE_TYPE = "staticlib"
|
||||
|
||||
[env.production-mac-x86_64]
|
||||
@@ -90,8 +93,17 @@ FLUTTER_OUTPUT_DIR = "Release"
|
||||
PRODUCT_EXT = "app"
|
||||
APP_ENVIRONMENT = "production"
|
||||
BUILD_ARCHS = "x86_64"
|
||||
BUILD_ACTIVE_ARCHS_ONLY = false
|
||||
CRATE_TYPE = "staticlib"
|
||||
|
||||
[env.production-mac-universal]
|
||||
BUILD_FLAG = "release"
|
||||
TARGET_OS = "macos"
|
||||
FLUTTER_OUTPUT_DIR = "Release"
|
||||
PRODUCT_EXT = "app"
|
||||
BUILD_ACTIVE_ARCHS_ONLY = false
|
||||
APP_ENVIRONMENT = "production"
|
||||
|
||||
[env.development-windows-x86]
|
||||
TARGET_OS = "windows"
|
||||
RUST_COMPILE_TARGET = "x86_64-pc-windows-msvc"
|
||||
|
||||
@@ -50,7 +50,7 @@ void main() {
|
||||
await tester.tapFirstDayOfWeekStartFromMonday();
|
||||
|
||||
// Open the other page and open the new calendar page again
|
||||
await tester.openPage(gettingStated);
|
||||
await tester.openPage(gettingStarted);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 300));
|
||||
await tester.openPage(name, layout: ViewLayoutPB.Calendar);
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('copy and paste in document', () {
|
||||
testWidgets('paste multiple lines at the first line', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithName();
|
||||
|
||||
// mock the clipboard
|
||||
const lines = 3;
|
||||
AppFlowyClipboard.mockSetData(
|
||||
AppFlowyClipboardData(
|
||||
text: List.generate(lines, (index) => 'line $index').join('\n'),
|
||||
),
|
||||
);
|
||||
|
||||
// paste the text
|
||||
await tester.simulateKeyEvent(
|
||||
LogicalKeyboardKey.keyV,
|
||||
isControlPressed: Platform.isLinux || Platform.isWindows,
|
||||
isMetaPressed: Platform.isMacOS,
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final editorState = tester.editor.getCurrentEditorState();
|
||||
expect(editorState.document.root.children.length, 4);
|
||||
for (var i = 0; i < lines; i++) {
|
||||
expect(
|
||||
editorState.getNodeAtPath([i])!.delta!.toPlainText(),
|
||||
'line $i',
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
+6
-6
@@ -34,20 +34,20 @@ void main() {
|
||||
|
||||
// delete the readme page
|
||||
await tester.hoverOnPageName(
|
||||
gettingStated,
|
||||
gettingStarted,
|
||||
onHover: () async => await tester.tapDeletePageButton(),
|
||||
);
|
||||
|
||||
// the banner should show up and the readme page should be gone
|
||||
tester.expectToSeeDocumentBanner();
|
||||
tester.expectNotToSeePageName(gettingStated);
|
||||
tester.expectNotToSeePageName(gettingStarted);
|
||||
|
||||
// restore the readme page
|
||||
await tester.tapRestoreButton();
|
||||
|
||||
// the banner should be gone and the readme page should be back
|
||||
tester.expectNotToSeeDocumentBanner();
|
||||
tester.expectToSeePageName(gettingStated);
|
||||
tester.expectToSeePageName(gettingStarted);
|
||||
});
|
||||
|
||||
testWidgets('delete the readme page and delete it permanently',
|
||||
@@ -58,20 +58,20 @@ void main() {
|
||||
|
||||
// delete the readme page
|
||||
await tester.hoverOnPageName(
|
||||
gettingStated,
|
||||
gettingStarted,
|
||||
onHover: () async => await tester.tapDeletePageButton(),
|
||||
);
|
||||
|
||||
// the banner should show up and the readme page should be gone
|
||||
tester.expectToSeeDocumentBanner();
|
||||
tester.expectNotToSeePageName(gettingStated);
|
||||
tester.expectNotToSeePageName(gettingStarted);
|
||||
|
||||
// delete the page permanently
|
||||
await tester.tapDeletePermanentlyButton();
|
||||
|
||||
// the banner should be gone and the readme page should be gone
|
||||
tester.expectNotToSeeDocumentBanner();
|
||||
tester.expectNotToSeePageName(gettingStated);
|
||||
tester.expectNotToSeePageName(gettingStarted);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
||||
import 'document_with_toggle_list_test.dart' as document_with_toggle_list_test;
|
||||
import 'edit_document_test.dart' as document_edit_test;
|
||||
import 'document_with_outline_block_test.dart' as document_with_outline_block;
|
||||
import 'document_copy_and_paste_test.dart' as document_copy_and_paste_test;
|
||||
|
||||
void startTesting() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
@@ -23,4 +24,5 @@ void startTesting() {
|
||||
document_with_cover_image_test.main();
|
||||
document_with_outline_block.main();
|
||||
document_with_toggle_list_test.main();
|
||||
document_copy_and_paste_test.main();
|
||||
}
|
||||
@@ -59,7 +59,7 @@ void main() {
|
||||
);
|
||||
|
||||
// switch to other page and switch back
|
||||
await tester.openPage(gettingStated);
|
||||
await tester.openPage(gettingStarted);
|
||||
await tester.openPage(pageName);
|
||||
|
||||
// the numbered list should be kept
|
||||
@@ -91,7 +91,7 @@ void main() {
|
||||
}
|
||||
|
||||
// switch to other page and switch back
|
||||
await tester.openPage(gettingStated);
|
||||
await tester.openPage(gettingStarted);
|
||||
await tester.openPage(pageName);
|
||||
|
||||
// this screenshots are different on different platform, so comment it out temporarily.
|
||||
|
||||
@@ -17,7 +17,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// expect to see a getting started page
|
||||
tester.expectToSeePageName(gettingStated);
|
||||
tester.expectToSeePageName(gettingStarted);
|
||||
|
||||
await tester.tapAddViewButton();
|
||||
await tester.tapImportButton();
|
||||
|
||||
@@ -16,6 +16,7 @@ import 'share_markdown_test.dart' as share_markdown_test;
|
||||
import 'switch_folder_test.dart' as switch_folder_test;
|
||||
import 'sidebar/sidebar_test_runner.dart' as sidebar_test_runner;
|
||||
import 'board/board_test_runner.dart' as board_test_runner;
|
||||
import 'tabs_test.dart' as tabs_test;
|
||||
|
||||
/// The main task runner for all integration tests in AppFlowy.
|
||||
///
|
||||
@@ -51,6 +52,9 @@ void main() {
|
||||
database_view_test.main();
|
||||
database_calendar_test.main();
|
||||
|
||||
// Tabs
|
||||
tabs_test.main();
|
||||
|
||||
// board_test.main();
|
||||
// empty_document_test.main();
|
||||
// smart_menu_test.main();
|
||||
|
||||
@@ -16,7 +16,7 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// expect to see a readme page
|
||||
tester.expectToSeePageName(gettingStated);
|
||||
tester.expectToSeePageName(gettingStarted);
|
||||
|
||||
// mock the file picker
|
||||
final path = await mockSaveFilePath(
|
||||
@@ -43,11 +43,11 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
// expect to see a getting started page
|
||||
tester.expectToSeePageName(gettingStated);
|
||||
tester.expectToSeePageName(gettingStarted);
|
||||
|
||||
// rename the document
|
||||
await tester.hoverOnPageName(
|
||||
gettingStated,
|
||||
gettingStarted,
|
||||
onHover: () async {
|
||||
await tester.renamePage('example');
|
||||
},
|
||||
@@ -94,6 +94,13 @@ const expectedMarkdown = r'''
|
||||
1. Keyboard shortcuts [guide](https://appflowy.gitbook.io/docs/essential-documentation/shortcuts)
|
||||
1. Markdown [reference](https://appflowy.gitbook.io/docs/essential-documentation/markdown)
|
||||
1. Type `/code` to insert a code block
|
||||
```rust
|
||||
// This is the main function.
|
||||
fn main() {
|
||||
// Print text to the console.
|
||||
println!("Hello World!");
|
||||
}
|
||||
```
|
||||
|
||||
## Have a question❓
|
||||
> Click `?` at the bottom right for help and support.
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:appflowy/workspace/application/sidebar/folder/folder_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/sidebar/folder/favorite_folder.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/view/view_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
@@ -28,7 +30,7 @@ void main() {
|
||||
2,
|
||||
].map((e) => 'document_$e').toList();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
final parentName = i == 0 ? gettingStated : names[i - 1];
|
||||
final parentName = i == 0 ? gettingStarted : names[i - 1];
|
||||
await tester.createNewPageWithName(
|
||||
name: names[i],
|
||||
parentName: parentName,
|
||||
@@ -42,9 +44,9 @@ void main() {
|
||||
);
|
||||
}
|
||||
|
||||
await tester.favoriteViewByName(gettingStated);
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
expect(
|
||||
tester.findFavoritePageName(gettingStated),
|
||||
tester.findFavoritePageName(gettingStarted),
|
||||
findsOneWidget,
|
||||
);
|
||||
|
||||
@@ -54,9 +56,9 @@ void main() {
|
||||
findsNWidgets(2),
|
||||
);
|
||||
|
||||
await tester.unfavoriteViewByName(gettingStated);
|
||||
await tester.unfavoriteViewByName(gettingStarted);
|
||||
expect(
|
||||
tester.findFavoritePageName(gettingStated),
|
||||
tester.findFavoritePageName(gettingStarted),
|
||||
findsNothing,
|
||||
);
|
||||
expect(
|
||||
@@ -82,9 +84,9 @@ void main() {
|
||||
await tester.tapGoButton();
|
||||
|
||||
const name = 'test';
|
||||
await tester.favoriteViewByName(gettingStated);
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
await tester.hoverOnPageName(
|
||||
gettingStated,
|
||||
gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
onHover: () async {
|
||||
await tester.renamePage(name);
|
||||
@@ -110,7 +112,7 @@ void main() {
|
||||
|
||||
final names = [1, 2].map((e) => 'document_$e').toList();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
final parentName = i == 0 ? gettingStated : names[i - 1];
|
||||
final parentName = i == 0 ? gettingStarted : names[i - 1];
|
||||
await tester.createNewPageWithName(
|
||||
name: names[i],
|
||||
parentName: parentName,
|
||||
@@ -118,7 +120,7 @@ void main() {
|
||||
);
|
||||
tester.expectToSeePageName(names[i], parentName: parentName);
|
||||
}
|
||||
await tester.favoriteViewByName(gettingStated);
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
await tester.favoriteViewByName(names[0]);
|
||||
await tester.favoriteViewByName(names[1]);
|
||||
|
||||
@@ -152,7 +154,7 @@ void main() {
|
||||
);
|
||||
|
||||
await tester.hoverOnPageName(
|
||||
gettingStated,
|
||||
gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
onHover: () async {
|
||||
await tester.tapDeletePageButton();
|
||||
@@ -171,5 +173,50 @@ void main() {
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'view selection is synced between favorites and personal folder',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithName();
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
expect(
|
||||
find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is FlowyHover &&
|
||||
widget.isSelected != null &&
|
||||
widget.isSelected!(),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets(
|
||||
'context menu opens up for favorites',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
await tester.createNewPageWithName();
|
||||
await tester.favoriteViewByName(gettingStarted);
|
||||
await tester.hoverOnPageName(
|
||||
gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
useLast: false,
|
||||
onHover: () async {
|
||||
await tester.tapPageOptionButton();
|
||||
await tester.pumpAndSettle();
|
||||
expect(
|
||||
find.byType(PopoverContainer),
|
||||
findsOneWidget,
|
||||
);
|
||||
},
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
@@ -70,7 +70,7 @@ void main() {
|
||||
break;
|
||||
}
|
||||
|
||||
await tester.openPage(gettingStated);
|
||||
await tester.openPage(gettingStarted);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -80,7 +80,7 @@ void main() {
|
||||
|
||||
final names = [1, 2, 3, 4].map((e) => 'document_$e').toList();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
final parentName = i == 0 ? gettingStated : names[i - 1];
|
||||
final parentName = i == 0 ? gettingStarted : names[i - 1];
|
||||
await tester.createNewPageWithName(
|
||||
name: names[i],
|
||||
parentName: parentName,
|
||||
@@ -92,7 +92,7 @@ void main() {
|
||||
// move the document_3 to the getting started page
|
||||
await tester.movePageToOtherPage(
|
||||
name: names[3],
|
||||
parentName: gettingStated,
|
||||
parentName: gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
parentLayout: ViewLayoutPB.Document,
|
||||
);
|
||||
@@ -101,7 +101,7 @@ void main() {
|
||||
.view
|
||||
.parentViewId;
|
||||
final toId = tester
|
||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStarted))
|
||||
.view
|
||||
.id;
|
||||
expect(fromId, toId);
|
||||
@@ -109,13 +109,13 @@ void main() {
|
||||
// move the document_2 before document_1
|
||||
await tester.movePageToOtherPage(
|
||||
name: names[2],
|
||||
parentName: gettingStated,
|
||||
parentName: gettingStarted,
|
||||
layout: ViewLayoutPB.Document,
|
||||
parentLayout: ViewLayoutPB.Document,
|
||||
position: DraggableHoverPosition.bottom,
|
||||
);
|
||||
final childViews = tester
|
||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStarted))
|
||||
.view
|
||||
.childViews;
|
||||
expect(
|
||||
@@ -170,7 +170,7 @@ void main() {
|
||||
|
||||
// it should not be moved
|
||||
final childViews = tester
|
||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStated))
|
||||
.widget<SingleInnerViewItem>(tester.findPageName(gettingStarted))
|
||||
.view
|
||||
.childViews;
|
||||
expect(
|
||||
|
||||
@@ -1,23 +1,26 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/presentation/home/tabs/flowy_tab.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/tabs/tabs_manager.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.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 'util/base.dart';
|
||||
import 'util/common_operations.dart';
|
||||
import 'util/expectation.dart';
|
||||
import 'util/keyboard.dart';
|
||||
|
||||
const _readmeName = 'Read me';
|
||||
const _documentName = 'Document';
|
||||
const _calendarName = 'Calendar';
|
||||
const _documentName = 'First Doc';
|
||||
const _documentTwoName = 'Second Doc';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('Tabs', () {
|
||||
testWidgets('Open AppFlowy and open/navigate multiple tabs',
|
||||
(tester) async {
|
||||
testWidgets('Open AppFlowy and open/navigate/close tabs', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -29,31 +32,21 @@ void main() {
|
||||
findsNothing,
|
||||
);
|
||||
|
||||
await tester.createNewPageWithName(
|
||||
name: _calendarName,
|
||||
layout: ViewLayoutPB.Calendar,
|
||||
);
|
||||
await tester.createNewPageWithName(
|
||||
name: _documentName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
// Navigate current view to "Read me" document again
|
||||
await tester.tapButtonWithName(_readmeName);
|
||||
await tester.createNewPageWithName(
|
||||
name: _documentTwoName,
|
||||
layout: ViewLayoutPB.Document,
|
||||
);
|
||||
|
||||
/// Open second menu item in a new tab
|
||||
await tester.openAppInNewTab(_calendarName);
|
||||
await tester.openAppInNewTab(gettingStarted, ViewLayoutPB.Document);
|
||||
|
||||
/// Open third menu item in a new tab
|
||||
await tester.openAppInNewTab(_documentName);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabsManager),
|
||||
matching: find.byType(TabBar),
|
||||
),
|
||||
findsOneWidget,
|
||||
);
|
||||
await tester.openAppInNewTab(_documentName, ViewLayoutPB.Document);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
@@ -63,13 +56,32 @@ void main() {
|
||||
findsNWidgets(3),
|
||||
);
|
||||
|
||||
/// Navigate to the first tab
|
||||
/// Navigate to the second tab
|
||||
await tester.tap(
|
||||
find.descendant(
|
||||
of: find.byType(FlowyTab),
|
||||
matching: find.text(_readmeName),
|
||||
matching: find.text(gettingStarted),
|
||||
),
|
||||
);
|
||||
|
||||
/// Close tab by shortcut
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
[
|
||||
Platform.isMacOS
|
||||
? LogicalKeyboardKey.meta
|
||||
: LogicalKeyboardKey.control,
|
||||
LogicalKeyboardKey.keyW,
|
||||
],
|
||||
tester: tester,
|
||||
);
|
||||
|
||||
expect(
|
||||
find.descendant(
|
||||
of: find.byType(TabBar),
|
||||
matching: find.byType(FlowyTab),
|
||||
),
|
||||
findsNWidgets(2),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -8,7 +8,6 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
|
||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||
import 'package:appflowy/user/presentation/skip_log_in_screen.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/app/section/item.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/widgets/settings_language_view.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -29,7 +28,7 @@ extension CommonOperations on WidgetTester {
|
||||
|
||||
/// Tap the + button on the home page.
|
||||
Future<void> tapAddViewButton({
|
||||
String name = gettingStated,
|
||||
String name = gettingStarted,
|
||||
}) async {
|
||||
await hoverOnPageName(
|
||||
name,
|
||||
@@ -211,6 +210,12 @@ extension CommonOperations on WidgetTester {
|
||||
await tapButtonWithName(ViewMoreActionType.unFavorite.name);
|
||||
}
|
||||
|
||||
/// Tap the Open in a new tab button
|
||||
Future<void> tapOpenInTabButton() async {
|
||||
await tapPageOptionButton();
|
||||
await tapButtonWithName(ViewMoreActionType.openInNewTab.name);
|
||||
}
|
||||
|
||||
/// Rename the page.
|
||||
Future<void> renamePage(String name) async {
|
||||
await tapRenamePageButton();
|
||||
@@ -272,7 +277,7 @@ extension CommonOperations on WidgetTester {
|
||||
bool openAfterCreated = true,
|
||||
}) async {
|
||||
// create a new page
|
||||
await tapAddViewButton(name: parentName ?? gettingStated);
|
||||
await tapAddViewButton(name: parentName ?? gettingStarted);
|
||||
await tapButtonWithName(layout.menuName);
|
||||
await pumpAndSettle();
|
||||
|
||||
@@ -336,11 +341,14 @@ extension CommonOperations on WidgetTester {
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
Future<void> openAppInNewTab(String name) async {
|
||||
await hoverOnPageName(name);
|
||||
await tap(find.byType(ViewDisclosureButton));
|
||||
await pumpAndSettle();
|
||||
await tap(find.text(LocaleKeys.disclosureAction_openNewTab.tr()));
|
||||
Future<void> openAppInNewTab(String name, ViewLayoutPB layout) async {
|
||||
await hoverOnPageName(
|
||||
name,
|
||||
onHover: () async {
|
||||
await tapOpenInTabButton();
|
||||
await pumpAndSettle();
|
||||
},
|
||||
);
|
||||
await pumpAndSettle();
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/so
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/filter_button.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_layout.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/sort_button.dart';
|
||||
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_header.dart';
|
||||
import 'package:appflowy/plugins/database_view/tar_bar/tar_bar_add_button.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/database_layout_ext.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/accessory/cell_accessory.dart';
|
||||
@@ -76,7 +76,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapGoButton();
|
||||
|
||||
// expect to see a readme page
|
||||
expectToSeePageName(gettingStated);
|
||||
expectToSeePageName(gettingStarted);
|
||||
|
||||
await tapAddViewButton();
|
||||
await tapImportButton();
|
||||
|
||||
@@ -87,7 +87,7 @@ class EditorOperations {
|
||||
|
||||
Future<void> switchSolidColorBackground() async {
|
||||
final findPurpleButton = find.byWidgetPredicate(
|
||||
(widget) => widget is ColorItem && widget.option.colorHex == "ffe8e0ff",
|
||||
(widget) => widget is ColorItem && widget.option.name == 'Purple',
|
||||
);
|
||||
await tester.tapButton(findPurpleButton);
|
||||
}
|
||||
|
||||
@@ -11,13 +11,13 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
// const String readme = 'Read me';
|
||||
const String gettingStated = '⭐️ Getting started';
|
||||
const String gettingStarted = '⭐️ Getting started';
|
||||
|
||||
extension Expectation on WidgetTester {
|
||||
/// Expect to see the home page and with a default read me page.
|
||||
void expectToSeeHomePage() {
|
||||
expect(find.byType(HomeStack), findsOneWidget);
|
||||
expect(find.textContaining(gettingStated), findsWidgets);
|
||||
expect(find.textContaining(gettingStarted), findsWidgets);
|
||||
}
|
||||
|
||||
/// Expect to see the page name on the home page.
|
||||
|
||||
@@ -8,4 +8,12 @@ extension RawKeyboardExtension on RawKeyboard {
|
||||
LogicalKeyboardKey.altRight,
|
||||
].contains(key),
|
||||
);
|
||||
|
||||
bool get isControlPressed => keysPressed.any(
|
||||
(key) => [
|
||||
LogicalKeyboardKey.control,
|
||||
LogicalKeyboardKey.controlLeft,
|
||||
LogicalKeyboardKey.controlRight,
|
||||
].contains(key),
|
||||
);
|
||||
}
|
||||
+2
-2
@@ -1,4 +1,5 @@
|
||||
import 'dart:async';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_listener.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_meta_listener.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
@@ -8,7 +9,6 @@ import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../field/field_controller.dart';
|
||||
import '../field/field_service.dart';
|
||||
import '../field/type_option/type_option_context.dart';
|
||||
import 'cell_listener.dart';
|
||||
@@ -104,7 +104,7 @@ class CellController<T, D> extends Equatable {
|
||||
);
|
||||
|
||||
// Only the primary can listen on the row meta changes.
|
||||
if (_cellContext.fieldInfo.isPrimary) {
|
||||
if (_cellContext.fieldInfo.field.isPrimary) {
|
||||
_rowMetaListener?.start(
|
||||
callback: (newRowMeta) {
|
||||
_cellContext = _cellContext.copyWith(rowMeta: newRowMeta);
|
||||
|
||||
+5
-5
@@ -15,7 +15,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:convert' show utf8;
|
||||
|
||||
import '../field/field_controller.dart';
|
||||
import '../field/field_info.dart';
|
||||
import '../row/row_service.dart';
|
||||
part 'cell_service.freezed.dart';
|
||||
part 'cell_data_loader.dart';
|
||||
@@ -62,14 +62,14 @@ class DatabaseCellContext with _$DatabaseCellContext {
|
||||
|
||||
String get rowId => rowMeta.id;
|
||||
|
||||
String get fieldId => fieldInfo.id;
|
||||
String get fieldId => fieldInfo.field.id;
|
||||
|
||||
FieldType get fieldType => fieldInfo.fieldType;
|
||||
FieldType get fieldType => fieldInfo.field.fieldType;
|
||||
|
||||
ValueKey key() {
|
||||
return ValueKey("${rowMeta.id}$fieldId${fieldInfo.fieldType}");
|
||||
return ValueKey("${rowMeta.id}$fieldId${fieldInfo.field.fieldType}");
|
||||
}
|
||||
|
||||
/// Only the primary field can have an emoji.
|
||||
String? get emoji => fieldInfo.isPrimary ? rowMeta.icon : null;
|
||||
String? get emoji => fieldInfo.field.isPrimary ? rowMeta.icon : null;
|
||||
}
|
||||
+10
@@ -13,8 +13,10 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'dart:async';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'database_view_service.dart';
|
||||
import 'defines.dart';
|
||||
import 'field/field_info.dart';
|
||||
import 'layout/layout_service.dart';
|
||||
import 'layout/layout_setting_listener.dart';
|
||||
import 'row/row_cache.dart';
|
||||
@@ -92,6 +94,8 @@ class DatabaseController {
|
||||
final DatabaseGroupListener _groupListener;
|
||||
final DatabaseLayoutSettingListener _layoutListener;
|
||||
|
||||
final ValueNotifier<bool> _isLoading = ValueNotifier(true);
|
||||
|
||||
DatabaseController({required ViewPB view})
|
||||
: viewId = view.id,
|
||||
_databaseViewBackendSvc = DatabaseViewBackendService(viewId: view.id),
|
||||
@@ -109,6 +113,12 @@ class DatabaseController {
|
||||
_listenOnLayoutChanged();
|
||||
}
|
||||
|
||||
void setIsLoading(bool isLoading) {
|
||||
_isLoading.value = isLoading;
|
||||
}
|
||||
|
||||
ValueNotifier<bool> get isLoading => _isLoading;
|
||||
|
||||
void addListener({
|
||||
DatabaseCallbacks? onDatabaseChanged,
|
||||
DatabaseLayoutSettingCallbacks? onLayoutChanged,
|
||||
|
||||
@@ -3,12 +3,16 @@ import 'dart:collection';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/database_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'field/field_controller.dart';
|
||||
import 'field/field_info.dart';
|
||||
import 'row/row_cache.dart';
|
||||
import 'row/row_service.dart';
|
||||
|
||||
part 'defines.freezed.dart';
|
||||
|
||||
typedef OnFieldsChanged = void Function(UnmodifiableListView<FieldInfo>);
|
||||
typedef OnFiltersChanged = void Function(List<FilterInfo>);
|
||||
typedef OnSortsChanged = void Function(List<SortInfo>);
|
||||
@@ -27,3 +31,11 @@ typedef OnNumOfRowsChanged = void Function(
|
||||
);
|
||||
|
||||
typedef OnError = void Function(FlowyError);
|
||||
|
||||
@freezed
|
||||
class LoadingState with _$LoadingState {
|
||||
const factory LoadingState.loading() = _Loading;
|
||||
const factory LoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail,
|
||||
) = _Finish;
|
||||
}
|
||||
+15
-78
@@ -19,6 +19,7 @@ import '../setting/setting_listener.dart';
|
||||
import '../setting/setting_service.dart';
|
||||
import '../sort/sort_listener.dart';
|
||||
import '../sort/sort_service.dart';
|
||||
import 'field_info.dart';
|
||||
import 'field_listener.dart';
|
||||
|
||||
class _GridFieldNotifier extends ChangeNotifier {
|
||||
@@ -438,12 +439,18 @@ class FieldController {
|
||||
}
|
||||
|
||||
void _updateFieldInfos() {
|
||||
final List<FieldInfo> newFieldInfos = [];
|
||||
for (final field in _fieldNotifier.fieldInfos) {
|
||||
field._isGroupField = _groupConfigurationByFieldId[field.id] != null;
|
||||
field._hasFilter = _filterPBByFieldId[field.id] != null;
|
||||
field._hasSort = _sortPBByFieldId[field.id] != null;
|
||||
newFieldInfos.add(
|
||||
field.copyWith(
|
||||
isGroupField: _groupConfigurationByFieldId[field.id] != null,
|
||||
hasFilter: _filterPBByFieldId[field.id] != null,
|
||||
hasSort: _sortPBByFieldId[field.id] != null,
|
||||
),
|
||||
);
|
||||
}
|
||||
_fieldNotifier.notify();
|
||||
|
||||
_fieldNotifier.fieldInfos = newFieldInfos;
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
@@ -488,7 +495,7 @@ class FieldController {
|
||||
}
|
||||
|
||||
_fieldNotifier.fieldInfos =
|
||||
newFields.map((field) => FieldInfo(field: field)).toList();
|
||||
newFields.map((field) => FieldInfo.initial(field)).toList();
|
||||
_loadFilters();
|
||||
_loadSorts();
|
||||
_updateFieldInfos();
|
||||
@@ -652,7 +659,7 @@ class FieldController {
|
||||
}
|
||||
final List<FieldInfo> newFieldInfos = fieldInfos;
|
||||
for (final indexField in insertedFields) {
|
||||
final fieldInfo = FieldInfo(field: indexField.field_1);
|
||||
final fieldInfo = FieldInfo.initial(indexField.field_1);
|
||||
if (newFieldInfos.length > indexField.index) {
|
||||
newFieldInfos.insert(indexField.index, fieldInfo);
|
||||
} else {
|
||||
@@ -674,7 +681,7 @@ class FieldController {
|
||||
newFields.indexWhere((field) => field.id == updatedFieldPB.id);
|
||||
if (index != -1) {
|
||||
newFields.removeAt(index);
|
||||
final fieldInfo = FieldInfo(field: updatedFieldPB);
|
||||
final fieldInfo = FieldInfo.initial(updatedFieldPB);
|
||||
newFields.insert(index, fieldInfo);
|
||||
updatedFields.add(fieldInfo);
|
||||
}
|
||||
@@ -693,7 +700,7 @@ class RowCacheDependenciesImpl extends RowFieldsDelegate with RowLifeCycle {
|
||||
RowCacheDependenciesImpl(FieldController cache) : _fieldController = cache;
|
||||
|
||||
@override
|
||||
UnmodifiableListView<FieldInfo> get fields =>
|
||||
UnmodifiableListView<FieldInfo> get fieldInfos =>
|
||||
UnmodifiableListView(_fieldController.fieldInfos);
|
||||
|
||||
@override
|
||||
@@ -729,73 +736,3 @@ FieldInfo? _findFieldInfo({
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
class FieldInfo {
|
||||
final FieldPB _field;
|
||||
bool _isGroupField = false;
|
||||
|
||||
bool _hasFilter = false;
|
||||
|
||||
bool _hasSort = false;
|
||||
|
||||
String get id => _field.id;
|
||||
|
||||
FieldType get fieldType => _field.fieldType;
|
||||
|
||||
bool get visibility => _field.visibility;
|
||||
|
||||
double get width => _field.width.toDouble();
|
||||
|
||||
bool get isPrimary => _field.isPrimary;
|
||||
|
||||
String get name => _field.name;
|
||||
|
||||
FieldPB get field => _field;
|
||||
|
||||
bool get isGroupField => _isGroupField;
|
||||
|
||||
bool get hasFilter => _hasFilter;
|
||||
|
||||
bool get canBeGroup {
|
||||
switch (_field.fieldType) {
|
||||
case FieldType.URL:
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.SingleSelect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get canCreateFilter {
|
||||
if (hasFilter) return false;
|
||||
|
||||
switch (_field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.RichText:
|
||||
case FieldType.SingleSelect:
|
||||
case FieldType.Checklist:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get canCreateSort {
|
||||
if (_hasSort) return false;
|
||||
|
||||
switch (_field.fieldType) {
|
||||
case FieldType.RichText:
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.Number:
|
||||
case FieldType.DateTime:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
FieldInfo({required FieldPB field}) : _field = field;
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'field_info.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class FieldInfo with _$FieldInfo {
|
||||
const FieldInfo._();
|
||||
|
||||
const factory FieldInfo({
|
||||
required FieldPB field,
|
||||
required bool isGroupField,
|
||||
required bool hasFilter,
|
||||
required bool hasSort,
|
||||
}) = _FieldInfo;
|
||||
|
||||
String get id => field.id;
|
||||
|
||||
FieldType get fieldType => field.fieldType;
|
||||
|
||||
String get name => field.name;
|
||||
|
||||
factory FieldInfo.initial(FieldPB field) => FieldInfo(
|
||||
field: field,
|
||||
hasFilter: false,
|
||||
hasSort: false,
|
||||
isGroupField: false,
|
||||
);
|
||||
|
||||
bool get canBeGroup {
|
||||
switch (field.fieldType) {
|
||||
case FieldType.URL:
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.SingleSelect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get canCreateFilter {
|
||||
if (hasFilter) return false;
|
||||
|
||||
switch (field.fieldType) {
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.MultiSelect:
|
||||
case FieldType.RichText:
|
||||
case FieldType.SingleSelect:
|
||||
case FieldType.Checklist:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool get canCreateSort {
|
||||
if (hasSort) return false;
|
||||
|
||||
switch (field.fieldType) {
|
||||
case FieldType.RichText:
|
||||
case FieldType.Checkbox:
|
||||
case FieldType.Number:
|
||||
case FieldType.DateTime:
|
||||
case FieldType.SingleSelect:
|
||||
case FieldType.MultiSelect:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
-1
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:flowy_infra/notifier.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
|
||||
+1
-14
@@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_listener.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
import 'package:appflowy/workspace/application/view/prelude.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/row_entities.pb.dart';
|
||||
@@ -115,18 +114,6 @@ class RowBannerBloc extends Bloc<RowBannerEvent, RowBannerState> {
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
|
||||
// Set the icon and cover of the view
|
||||
ViewBackendService.updateView(
|
||||
viewId: viewId,
|
||||
iconURL: iconURL,
|
||||
coverURL: coverURL,
|
||||
).then((result) {
|
||||
result.fold(
|
||||
(l) => null,
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +123,7 @@ class RowBannerEvent with _$RowBannerEvent {
|
||||
const factory RowBannerEvent.didReceiveRowMeta(RowMetaPB rowMeta) =
|
||||
_DidReceiveRowMeta;
|
||||
const factory RowBannerEvent.didReceiveFieldUpdate(FieldPB field) =
|
||||
_DidReceiveFieldUdate;
|
||||
_DidReceiveFieldUpdate;
|
||||
const factory RowBannerEvent.setIcon(String iconURL) = _SetIcon;
|
||||
const factory RowBannerEvent.setCover(String coverURL) = _SetCover;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:collection';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
@@ -6,7 +7,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
import '../cell/cell_service.dart';
|
||||
import '../field/field_controller.dart';
|
||||
import 'row_list.dart';
|
||||
import 'row_service.dart';
|
||||
part 'row_cache.freezed.dart';
|
||||
@@ -15,7 +15,7 @@ typedef RowUpdateCallback = void Function();
|
||||
|
||||
/// A delegate that provides the fields of the row.
|
||||
abstract class RowFieldsDelegate {
|
||||
UnmodifiableListView<FieldInfo> get fields;
|
||||
UnmodifiableListView<FieldInfo> get fieldInfos;
|
||||
void onFieldsChanged(void Function(List<FieldInfo>) callback);
|
||||
}
|
||||
|
||||
@@ -244,12 +244,12 @@ class RowCache {
|
||||
CellContextByFieldId _makeCells(RowMetaPB rowMeta) {
|
||||
// ignore: prefer_collection_literals
|
||||
final cellContextMap = CellContextByFieldId();
|
||||
for (final field in _fieldDelegate.fields) {
|
||||
if (field.visibility) {
|
||||
cellContextMap[field.id] = DatabaseCellContext(
|
||||
for (final fieldInfo in _fieldDelegate.fieldInfos) {
|
||||
if (fieldInfo.field.visibility) {
|
||||
cellContextMap[fieldInfo.id] = DatabaseCellContext(
|
||||
rowMeta: rowMeta,
|
||||
viewId: viewId,
|
||||
fieldInfo: field,
|
||||
fieldInfo: fieldInfo,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -259,7 +259,7 @@ class RowCache {
|
||||
RowInfo buildGridRow(RowMetaPB rowMetaPB) {
|
||||
return RowInfo(
|
||||
viewId: viewId,
|
||||
fields: _fieldDelegate.fields,
|
||||
fields: _fieldDelegate.fieldInfos,
|
||||
rowId: rowMetaPB.id,
|
||||
rowMeta: rowMetaPB,
|
||||
);
|
||||
|
||||
+8
-7
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -26,8 +27,8 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
|
||||
initial: () {
|
||||
_startListening();
|
||||
},
|
||||
didReceiveFieldUpdate: (fieldContexts) {
|
||||
emit(state.copyWith(fieldContexts: fieldContexts));
|
||||
didReceiveFieldUpdate: (fieldInfos) {
|
||||
emit(state.copyWith(fieldInfos: fieldInfos));
|
||||
},
|
||||
setGroupByField: (String fieldId, FieldType fieldType) async {
|
||||
final result = await _groupBackendSvc.groupByField(
|
||||
@@ -50,8 +51,8 @@ class DatabaseGroupBloc extends Bloc<DatabaseGroupEvent, DatabaseGroupState> {
|
||||
}
|
||||
|
||||
void _startListening() {
|
||||
_onFieldsFn = (fieldContexts) =>
|
||||
add(DatabaseGroupEvent.didReceiveFieldUpdate(fieldContexts));
|
||||
_onFieldsFn = (fieldInfos) =>
|
||||
add(DatabaseGroupEvent.didReceiveFieldUpdate(fieldInfos));
|
||||
_fieldController.addListener(
|
||||
onReceiveFields: _onFieldsFn,
|
||||
listenWhen: () => !isClosed,
|
||||
@@ -75,15 +76,15 @@ class DatabaseGroupEvent with _$DatabaseGroupEvent {
|
||||
class DatabaseGroupState with _$DatabaseGroupState {
|
||||
const factory DatabaseGroupState({
|
||||
required String viewId,
|
||||
required List<FieldInfo> fieldContexts,
|
||||
required List<FieldInfo> fieldInfos,
|
||||
}) = _DatabaseGroupState;
|
||||
|
||||
factory DatabaseGroupState.initial(
|
||||
String viewId,
|
||||
List<FieldInfo> fieldContexts,
|
||||
List<FieldInfo> fieldInfos,
|
||||
) =>
|
||||
DatabaseGroupState(
|
||||
viewId: viewId,
|
||||
fieldContexts: fieldContexts,
|
||||
fieldInfos: fieldInfos,
|
||||
);
|
||||
}
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
+11
-14
@@ -1,6 +1,8 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/defines.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
import 'package:appflowy_board/appflowy_board.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@@ -254,11 +256,14 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
Future<void> _openGrid(Emitter<BoardState> emit) async {
|
||||
final result = await databaseController.open();
|
||||
result.fold(
|
||||
(grid) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
),
|
||||
(grid) {
|
||||
databaseController.setIsLoading(false);
|
||||
emit(
|
||||
state.copyWith(loadingState: LoadingState.finish(left(unit))),
|
||||
);
|
||||
},
|
||||
(err) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
||||
state.copyWith(loadingState: LoadingState.finish(right(err))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -323,7 +328,7 @@ class BoardState with _$BoardState {
|
||||
required Option<DatabasePB> grid,
|
||||
required List<String> groupIds,
|
||||
required Option<BoardEditingRow> editingRow,
|
||||
required GridLoadingState loadingState,
|
||||
required LoadingState loadingState,
|
||||
required Option<FlowyError> noneOrError,
|
||||
}) = _BoardState;
|
||||
|
||||
@@ -333,18 +338,10 @@ class BoardState with _$BoardState {
|
||||
groupIds: [],
|
||||
editingRow: none(),
|
||||
noneOrError: none(),
|
||||
loadingState: const _Loading(),
|
||||
loadingState: const LoadingState.loading(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridLoadingState with _$GridLoadingState {
|
||||
const factory GridLoadingState.loading() = _Loading;
|
||||
const factory GridLoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail,
|
||||
) = _Finish;
|
||||
}
|
||||
|
||||
class GridFieldEquatable extends Equatable {
|
||||
final UnmodifiableListView<FieldPB> _fields;
|
||||
const GridFieldEquatable(
|
||||
|
||||
+2
-2
@@ -12,10 +12,10 @@ class BoardSettingBar extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
height: 20,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const Spacer(),
|
||||
SettingButton(databaseController: databaseController),
|
||||
],
|
||||
),
|
||||
|
||||
+11
-14
@@ -1,5 +1,7 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/defines.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@@ -133,11 +135,14 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
Future<void> _openDatabase(Emitter<CalendarState> emit) async {
|
||||
final result = await databaseController.open();
|
||||
result.fold(
|
||||
(database) => emit(
|
||||
state.copyWith(loadingState: DatabaseLoadingState.finish(left(unit))),
|
||||
),
|
||||
(database) {
|
||||
databaseController.setIsLoading(false);
|
||||
emit(
|
||||
state.copyWith(loadingState: LoadingState.finish(left(unit))),
|
||||
);
|
||||
},
|
||||
(err) => emit(
|
||||
state.copyWith(loadingState: DatabaseLoadingState.finish(right(err))),
|
||||
state.copyWith(loadingState: LoadingState.finish(right(err))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -425,7 +430,7 @@ class CalendarState with _$CalendarState {
|
||||
CalendarEventData<CalendarDayEvent>? updateEvent,
|
||||
required List<String> deleteEventIds,
|
||||
required Option<CalendarLayoutSettingPB> settings,
|
||||
required DatabaseLoadingState loadingState,
|
||||
required LoadingState loadingState,
|
||||
required Option<FlowyError> noneOrError,
|
||||
}) = _CalendarState;
|
||||
|
||||
@@ -436,18 +441,10 @@ class CalendarState with _$CalendarState {
|
||||
deleteEventIds: [],
|
||||
settings: none(),
|
||||
noneOrError: none(),
|
||||
loadingState: const _Loading(),
|
||||
loadingState: const LoadingState.loading(),
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class DatabaseLoadingState with _$DatabaseLoadingState {
|
||||
const factory DatabaseLoadingState.loading() = _Loading;
|
||||
const factory DatabaseLoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail,
|
||||
) = _Finish;
|
||||
}
|
||||
|
||||
class CalendarEditingRow {
|
||||
RowPB row;
|
||||
int? index;
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
|
||||
+16
-11
@@ -254,18 +254,23 @@ class _EventList extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Flexible(
|
||||
child: ListView.separated(
|
||||
itemBuilder: (BuildContext context, int index) => EventCard(
|
||||
event: events[index],
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
constraints: constraints,
|
||||
child: ScrollConfiguration(
|
||||
behavior: ScrollConfiguration.of(context).copyWith(
|
||||
scrollbars: true,
|
||||
),
|
||||
child: ListView.separated(
|
||||
itemBuilder: (BuildContext context, int index) => EventCard(
|
||||
event: events[index],
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
constraints: constraints,
|
||||
),
|
||||
itemCount: events.length,
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 8.0),
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
shrinkWrap: true,
|
||||
),
|
||||
itemCount: events.length,
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 8.0),
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
shrinkWrap: true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
+30
-17
@@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/calendar/application/calendar_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/tar_bar/tab_bar_view.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
@@ -159,10 +158,21 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
],
|
||||
child: BlocBuilder<CalendarBloc, CalendarState>(
|
||||
builder: (context, state) {
|
||||
return _buildCalendar(
|
||||
context,
|
||||
_eventController,
|
||||
state.settings.foldLeft(0, (previous, a) => a.firstDayOfWeek),
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: widget.databaseController.isLoading,
|
||||
builder: (_, value, ___) {
|
||||
if (value) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator.adaptive(),
|
||||
);
|
||||
}
|
||||
return _buildCalendar(
|
||||
context,
|
||||
_eventController,
|
||||
state.settings
|
||||
.foldLeft(0, (previous, a) => a.firstDayOfWeek),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
@@ -177,20 +187,23 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
int firstDayOfWeek,
|
||||
) {
|
||||
return Padding(
|
||||
padding: GridSize.contentInsets,
|
||||
padding: CalendarSize.contentInsets,
|
||||
child: LayoutBuilder(
|
||||
// must specify MonthView width for useAvailableVerticalSpace to work properly
|
||||
builder: (context, constraints) => MonthView(
|
||||
key: _calendarState,
|
||||
controller: _eventController,
|
||||
width: constraints.maxWidth,
|
||||
cellAspectRatio: 0.6,
|
||||
startDay: _weekdayFromInt(firstDayOfWeek),
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
headerBuilder: _headerNavigatorBuilder,
|
||||
weekDayBuilder: _headerWeekDayBuilder,
|
||||
cellBuilder: _calendarDayBuilder,
|
||||
useAvailableVerticalSpace: widget.shrinkWrap,
|
||||
builder: (context, constraints) => ScrollConfiguration(
|
||||
behavior: ScrollConfiguration.of(context).copyWith(scrollbars: false),
|
||||
child: MonthView(
|
||||
key: _calendarState,
|
||||
controller: _eventController,
|
||||
width: constraints.maxWidth,
|
||||
cellAspectRatio: 0.6,
|
||||
startDay: _weekdayFromInt(firstDayOfWeek),
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
headerBuilder: _headerNavigatorBuilder,
|
||||
weekDayBuilder: _headerWeekDayBuilder,
|
||||
cellBuilder: _calendarDayBuilder,
|
||||
useAvailableVerticalSpace: widget.shrinkWrap,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+10
@@ -1,8 +1,18 @@
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class CalendarSize {
|
||||
static double scale = 1;
|
||||
|
||||
static double get headerContainerPadding => 12 * scale;
|
||||
|
||||
static EdgeInsets get contentInsets => EdgeInsets.fromLTRB(
|
||||
GridSize.leadingHeaderPadding,
|
||||
CalendarSize.headerContainerPadding,
|
||||
GridSize.leadingHeaderPadding,
|
||||
CalendarSize.headerContainerPadding,
|
||||
);
|
||||
|
||||
static double get scrollBarSize => 8 * scale;
|
||||
static double get navigatorButtonWidth => 20 * scale;
|
||||
static double get navigatorButtonHeight => 25 * scale;
|
||||
|
||||
+9
-2
@@ -8,6 +8,7 @@ import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.da
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -23,11 +24,12 @@ class CalendarSettingBar extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 40,
|
||||
height: 20,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
UnscheduleEventsButton(databaseController: databaseController),
|
||||
const HSpace(2),
|
||||
SettingButton(
|
||||
databaseController: databaseController,
|
||||
),
|
||||
@@ -82,9 +84,14 @@ class _UnscheduleEventsButtonState extends State<UnscheduleEventsButton> {
|
||||
builder: (context, state) {
|
||||
return FlowyTextButton(
|
||||
"${LocaleKeys.calendar_settings_noDateTitle.tr()} (${state.unscheduleEvents.length})",
|
||||
fontSize: FontSizes.s11,
|
||||
fontColor: AFThemeExtension.of(context).textColor,
|
||||
fontWeight: FontWeight.w400,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
padding: GridSize.toolbarSettingButtonInsets,
|
||||
radius: Corners.s4Border,
|
||||
onPressed: () => _popoverController.show(),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/filter/filter_service.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
+16
-22
@@ -1,16 +1,16 @@
|
||||
import 'dart:async';
|
||||
import 'package:appflowy/plugins/database_view/application/defines.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/filter/filter_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import '../../application/field/field_controller.dart';
|
||||
import '../../application/database_controller.dart';
|
||||
import 'dart:collection';
|
||||
|
||||
@@ -134,12 +134,13 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
final result = await databaseController.open();
|
||||
result.fold(
|
||||
(grid) {
|
||||
databaseController.setIsLoading(false);
|
||||
emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(left(unit))),
|
||||
state.copyWith(loadingState: LoadingState.finish(left(unit))),
|
||||
);
|
||||
},
|
||||
(err) => emit(
|
||||
state.copyWith(loadingState: GridLoadingState.finish(right(err))),
|
||||
state.copyWith(loadingState: LoadingState.finish(right(err))),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -177,7 +178,7 @@ class GridState with _$GridState {
|
||||
required GridFieldEquatable fields,
|
||||
required List<RowInfo> rowInfos,
|
||||
required int rowCount,
|
||||
required GridLoadingState loadingState,
|
||||
required LoadingState loadingState,
|
||||
required bool reorderable,
|
||||
required ChangedReason reason,
|
||||
required List<SortInfo> sorts,
|
||||
@@ -191,40 +192,33 @@ class GridState with _$GridState {
|
||||
grid: none(),
|
||||
viewId: viewId,
|
||||
reorderable: true,
|
||||
loadingState: const _Loading(),
|
||||
loadingState: const LoadingState.loading(),
|
||||
reason: const InitialListState(),
|
||||
filters: [],
|
||||
sorts: [],
|
||||
);
|
||||
}
|
||||
|
||||
@freezed
|
||||
class GridLoadingState with _$GridLoadingState {
|
||||
const factory GridLoadingState.loading() = _Loading;
|
||||
const factory GridLoadingState.finish(
|
||||
Either<Unit, FlowyError> successOrFail,
|
||||
) = _Finish;
|
||||
}
|
||||
|
||||
class GridFieldEquatable extends Equatable {
|
||||
final List<FieldInfo> _fields;
|
||||
final List<FieldInfo> _fieldInfos;
|
||||
const GridFieldEquatable(
|
||||
List<FieldInfo> fields,
|
||||
) : _fields = fields;
|
||||
List<FieldInfo> fieldInfos,
|
||||
) : _fieldInfos = fieldInfos;
|
||||
|
||||
@override
|
||||
List<Object?> get props {
|
||||
if (_fields.isEmpty) {
|
||||
if (_fieldInfos.isEmpty) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [
|
||||
_fields.length,
|
||||
_fields
|
||||
.map((field) => field.width)
|
||||
_fieldInfos.length,
|
||||
_fieldInfos
|
||||
.map((fieldInfo) => fieldInfo.field.width)
|
||||
.reduce((value, element) => value + element),
|
||||
];
|
||||
}
|
||||
|
||||
UnmodifiableListView<FieldInfo> get value => UnmodifiableListView(_fields);
|
||||
UnmodifiableListView<FieldInfo> get value =>
|
||||
UnmodifiableListView(_fieldInfos);
|
||||
}
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -26,7 +27,7 @@ class GridHeaderBloc extends Bloc<GridHeaderEvent, GridHeaderState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
fields: value.fields
|
||||
.where((element) => element.visibility)
|
||||
.where((element) => element.field.visibility)
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
|
||||
+7
-7
@@ -1,4 +1,5 @@
|
||||
import 'dart:collection';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_listener.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
@@ -7,7 +8,6 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../application/cell/cell_service.dart';
|
||||
import '../../../application/field/field_controller.dart';
|
||||
import '../../../application/row/row_cache.dart';
|
||||
import '../../../application/row/row_controller.dart';
|
||||
import '../../../application/row/row_service.dart';
|
||||
@@ -119,16 +119,16 @@ class RowState with _$RowState {
|
||||
}
|
||||
|
||||
class GridCellEquatable extends Equatable {
|
||||
final FieldInfo _fieldContext;
|
||||
final FieldInfo _fieldInfo;
|
||||
|
||||
const GridCellEquatable(FieldInfo field) : _fieldContext = field;
|
||||
const GridCellEquatable(FieldInfo field) : _fieldInfo = field;
|
||||
|
||||
@override
|
||||
List<Object?> get props => [
|
||||
_fieldContext.id,
|
||||
_fieldContext.fieldType,
|
||||
_fieldContext.visibility,
|
||||
_fieldContext.width,
|
||||
_fieldInfo.id,
|
||||
_fieldInfo.fieldType,
|
||||
_fieldInfo.field.visibility,
|
||||
_fieldInfo.field.width,
|
||||
];
|
||||
}
|
||||
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pbserver.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbenum.dart';
|
||||
|
||||
+1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/sort/sort_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/sort/sort_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pbenum.dart';
|
||||
|
||||
+1
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import '../../../application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
|
||||
List<FieldInfo> getCreatableSorts(List<FieldInfo> fieldInfos) {
|
||||
final List<FieldInfo> creatableFields = List.from(fieldInfos);
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'sizes.dart';
|
||||
|
||||
class GridLayout {
|
||||
@@ -6,7 +6,7 @@ class GridLayout {
|
||||
if (fields.isEmpty) return 0;
|
||||
|
||||
final fieldsWidth = fields
|
||||
.map((field) => field.width.toDouble())
|
||||
.map((fieldInfo) => fieldInfo.field.width.toDouble())
|
||||
.reduce((value, element) => value + element);
|
||||
|
||||
return fieldsWidth +
|
||||
|
||||
+3
@@ -30,6 +30,9 @@ class GridSize {
|
||||
|
||||
static EdgeInsets get typeOptionContentInsets => const EdgeInsets.all(4);
|
||||
|
||||
static EdgeInsets get toolbarSettingButtonInsets =>
|
||||
const EdgeInsets.symmetric(horizontal: 8, vertical: 2);
|
||||
|
||||
static EdgeInsets get footerContentInsets => EdgeInsets.fromLTRB(
|
||||
GridSize.leadingHeaderPadding,
|
||||
GridSize.headerContainerPadding,
|
||||
|
||||
+1
-1
@@ -109,7 +109,7 @@ class _CheckboxFilterEditorState extends State<CheckboxFilterEditor> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
state.filterInfo.fieldInfo.name,
|
||||
state.filterInfo.fieldInfo.field.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -93,7 +93,7 @@ class ChecklistState extends State<ChecklistFilterEditor> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
state.filterInfo.fieldInfo.name,
|
||||
state.filterInfo.fieldInfo.field.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ class ChoiceChipButton extends StatelessWidget {
|
||||
decoration: decoration,
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(
|
||||
filterInfo.fieldInfo.name,
|
||||
filterInfo.fieldInfo.field.name,
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
|
||||
+1
-1
@@ -144,7 +144,7 @@ class _SelectOptionFilterEditorState extends State<SelectOptionFilterEditor> {
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText(
|
||||
state.filterInfo.fieldInfo.name,
|
||||
state.filterInfo.fieldInfo.field.name,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
|
||||
+2
-1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
@@ -161,7 +162,7 @@ class GridFilterPropertyCell extends StatelessWidget {
|
||||
return FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
fieldInfo.name,
|
||||
fieldInfo.field.name,
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
onTap: () => onTap(fieldInfo),
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_filter.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checklist_filter.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_filter.pb.dart';
|
||||
|
||||
+1
-1
@@ -96,7 +96,7 @@ class _GridHeaderState extends State<_GridHeader> {
|
||||
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||
builder: (context, state) {
|
||||
final cells = state.fields
|
||||
.where((field) => field.visibility)
|
||||
.where((fieldInfo) => fieldInfo.field.visibility)
|
||||
.map(
|
||||
(field) => FieldContext(
|
||||
viewId: widget.viewId,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_data_controller.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/checkbox_entities.pb.dart';
|
||||
|
||||
+3
-3
@@ -276,13 +276,13 @@ class RowContent extends StatelessWidget {
|
||||
final GridCellWidget child = builder.build(cellId);
|
||||
|
||||
return CellContainer(
|
||||
width: cellId.fieldInfo.width.toDouble(),
|
||||
isPrimary: cellId.fieldInfo.isPrimary,
|
||||
width: cellId.fieldInfo.field.width.toDouble(),
|
||||
isPrimary: cellId.fieldInfo.field.isPrimary,
|
||||
cellContainerNotifier: CellContainerNotifier(child),
|
||||
accessoryBuilder: (buildContext) {
|
||||
final builder = child.accessoryBuilder;
|
||||
final List<GridCellAccessoryBuilder> accessories = [];
|
||||
if (cellId.fieldInfo.isPrimary) {
|
||||
if (cellId.fieldInfo.field.isPrimary) {
|
||||
accessories.add(
|
||||
GridCellAccessoryBuilder(
|
||||
builder: (key) => PrimaryCellAccessory(
|
||||
|
||||
+1
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_create_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/sort_entities.pb.dart';
|
||||
|
||||
class SortInfo {
|
||||
|
||||
+18
-17
@@ -2,6 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -30,23 +31,23 @@ class _FilterButtonState extends State<FilterButton> {
|
||||
|
||||
return _wrapPopover(
|
||||
context,
|
||||
SizedBox(
|
||||
height: 26,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.grid_settings_filter.tr(),
|
||||
fontColor: textColor,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
onPressed: () {
|
||||
final bloc = context.read<GridFilterMenuBloc>();
|
||||
if (bloc.state.filters.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
bloc.add(const GridFilterMenuEvent.toggleMenu());
|
||||
}
|
||||
},
|
||||
),
|
||||
FlowyTextButton(
|
||||
LocaleKeys.grid_settings_filter.tr(),
|
||||
fontColor: textColor,
|
||||
fontSize: FontSizes.s11,
|
||||
fontWeight: FontWeight.w400,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.toolbarSettingButtonInsets,
|
||||
radius: Corners.s4Border,
|
||||
onPressed: () {
|
||||
final bloc = context.read<GridFilterMenuBloc>();
|
||||
if (bloc.state.filters.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
bloc.add(const GridFilterMenuEvent.toggleMenu());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+22
-14
@@ -2,8 +2,8 @@ import 'package:appflowy/plugins/database_view/application/database_controller.d
|
||||
import 'package:appflowy/plugins/database_view/grid/application/filter/filter_menu_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/setting/setting_button.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
@@ -47,20 +47,28 @@ class GridSettingBar extends StatelessWidget {
|
||||
listener: (context, state) => toggleExtension.toggle(),
|
||||
),
|
||||
],
|
||||
child: SizedBox(
|
||||
height: 40,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
SizedBox(width: GridSize.leadingHeaderPadding),
|
||||
const Spacer(),
|
||||
const FilterButton(),
|
||||
const SortButton(),
|
||||
SettingButton(
|
||||
databaseController: controller,
|
||||
child: ValueListenableBuilder<bool>(
|
||||
valueListenable: controller.isLoading,
|
||||
builder: (context, value, child) {
|
||||
if (value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
const FilterButton(),
|
||||
const HSpace(2),
|
||||
const SortButton(),
|
||||
const HSpace(2),
|
||||
SettingButton(
|
||||
databaseController: controller,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+18
-17
@@ -2,6 +2,7 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/sort/sort_menu_bloc.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -30,23 +31,23 @@ class _SortButtonState extends State<SortButton> {
|
||||
|
||||
return wrapPopover(
|
||||
context,
|
||||
SizedBox(
|
||||
height: 26,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.grid_settings_sort.tr(),
|
||||
fontColor: textColor,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
onPressed: () {
|
||||
final bloc = context.read<SortMenuBloc>();
|
||||
if (bloc.state.sortInfos.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
bloc.add(const SortMenuEvent.toggleMenu());
|
||||
}
|
||||
},
|
||||
),
|
||||
FlowyTextButton(
|
||||
LocaleKeys.grid_settings_sort.tr(),
|
||||
fontColor: textColor,
|
||||
fontSize: FontSizes.s11,
|
||||
fontWeight: FontWeight.w400,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.toolbarSettingButtonInsets,
|
||||
radius: Corners.s4Border,
|
||||
onPressed: () {
|
||||
final bloc = context.read<SortMenuBloc>();
|
||||
if (bloc.state.sortInfos.isEmpty) {
|
||||
_popoverController.show();
|
||||
} else {
|
||||
bloc.add(const SortMenuEvent.toggleMenu());
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:appflowy/plugins/database_view/application/database_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/grid_accessory_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/grid_page.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -38,7 +37,7 @@ class DatabaseViewSettingExtension extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox();
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
},
|
||||
),
|
||||
@@ -58,29 +57,18 @@ class _DatabaseViewSettingContent extends StatelessWidget {
|
||||
return BlocBuilder<DatabaseViewSettingExtensionBloc,
|
||||
DatabaseViewSettingExtensionState>(
|
||||
builder: (context, state) {
|
||||
final children = <Widget>[
|
||||
Divider(
|
||||
height: 1.0,
|
||||
color: AFThemeExtension.of(context).toggleOffFill,
|
||||
),
|
||||
const VSpace(6),
|
||||
IntrinsicHeight(
|
||||
child: Row(
|
||||
children: [
|
||||
SortMenu(
|
||||
fieldController: fieldController,
|
||||
),
|
||||
const HSpace(6),
|
||||
FilterMenu(
|
||||
fieldController: fieldController,
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
];
|
||||
|
||||
return _wrapPadding(
|
||||
Column(children: children),
|
||||
Row(
|
||||
children: [
|
||||
SortMenu(
|
||||
fieldController: fieldController,
|
||||
),
|
||||
const HSpace(6),
|
||||
FilterMenu(
|
||||
fieldController: fieldController,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -90,7 +78,7 @@ class _DatabaseViewSettingContent extends StatelessWidget {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.leadingHeaderPadding,
|
||||
vertical: 6,
|
||||
vertical: 8,
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
|
||||
@@ -0,0 +1,286 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../application/tar_bar_bloc.dart';
|
||||
import 'tar_bar_add_button.dart';
|
||||
|
||||
class TabBarHeader extends StatefulWidget {
|
||||
const TabBarHeader({super.key});
|
||||
|
||||
@override
|
||||
State<TabBarHeader> createState() => _TabBarHeaderState();
|
||||
}
|
||||
|
||||
class _TabBarHeaderState extends State<TabBarHeader> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Stack(
|
||||
children: [
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Divider(
|
||||
color: Theme.of(context).dividerColor,
|
||||
height: 1,
|
||||
thickness: 1,
|
||||
),
|
||||
),
|
||||
Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
return const Flexible(
|
||||
child: DatabaseTabBar(),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
return SizedBox(
|
||||
width: 200,
|
||||
child: Column(
|
||||
children: [
|
||||
const VSpace(3),
|
||||
pageSettingBarFromState(state),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget pageSettingBarFromState(GridTabBarState state) {
|
||||
if (state.tabBars.length < state.selectedIndex) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final tarBar = state.tabBars[state.selectedIndex];
|
||||
final controller =
|
||||
state.tabBarControllerByViewId[tarBar.viewId]!.controller;
|
||||
return tarBar.builder.settingBar(
|
||||
context,
|
||||
controller,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseTabBar extends StatefulWidget {
|
||||
const DatabaseTabBar({super.key});
|
||||
|
||||
@override
|
||||
State<DatabaseTabBar> createState() => _DatabaseTabBarState();
|
||||
}
|
||||
|
||||
class _DatabaseTabBarState extends State<DatabaseTabBar> {
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
final children = state.tabBars.indexed.map((indexed) {
|
||||
final isSelected = state.selectedIndex == indexed.$1;
|
||||
final tabBar = indexed.$2;
|
||||
return DatabaseTabBarItem(
|
||||
key: ValueKey(tabBar.viewId),
|
||||
view: tabBar.view,
|
||||
isSelected: isSelected,
|
||||
onTap: (selectedView) {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.selectView(selectedView.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
child: ListView(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
shrinkWrap: true,
|
||||
children: children,
|
||||
),
|
||||
),
|
||||
AddDatabaseViewButton(
|
||||
onTap: (action) async {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.createView(action),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseTabBarItem extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final ViewPB view;
|
||||
final Function(ViewPB) onTap;
|
||||
const DatabaseTabBarItem({
|
||||
required this.view,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 160),
|
||||
child: Stack(
|
||||
children: [
|
||||
SizedBox(
|
||||
height: 26,
|
||||
child: TabBarItemButton(
|
||||
view: view,
|
||||
isSelected: isSelected,
|
||||
onTap: () => onTap(view),
|
||||
),
|
||||
),
|
||||
if (isSelected)
|
||||
Positioned(
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0,
|
||||
child: Divider(
|
||||
height: 2,
|
||||
thickness: 2,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TabBarItemButton extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
final bool isSelected;
|
||||
final VoidCallback onTap;
|
||||
const TabBarItemButton({
|
||||
required this.view,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
required this.isSelected,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopoverActionList<TabBarViewAction>(
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
actions: TabBarViewAction.values,
|
||||
buildChild: (controller) {
|
||||
Color? color;
|
||||
if (!isSelected) {
|
||||
color = Theme.of(context).hintColor;
|
||||
}
|
||||
if (Theme.of(context).brightness == Brightness.dark) {
|
||||
color = null;
|
||||
}
|
||||
return IntrinsicWidth(
|
||||
child: FlowyButton(
|
||||
radius: Corners.s6Border,
|
||||
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||
onTap: onTap,
|
||||
onSecondaryTap: () {
|
||||
controller.show();
|
||||
},
|
||||
leftIcon: FlowySvg(
|
||||
name: view.iconName,
|
||||
size: const Size(14, 14),
|
||||
color: color,
|
||||
),
|
||||
text: FlowyText(
|
||||
view.name,
|
||||
fontSize: FontSizes.s11,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
color: color,
|
||||
fontWeight: isSelected ? null : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
onSelected: (action, controller) {
|
||||
switch (action) {
|
||||
case TabBarViewAction.rename:
|
||||
NavigatorTextFieldDialog(
|
||||
title: LocaleKeys.menuAppHeader_renameDialog.tr(),
|
||||
value: view.name,
|
||||
confirm: (newValue) {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.renameView(view.id, newValue),
|
||||
);
|
||||
},
|
||||
).show(context);
|
||||
break;
|
||||
case TabBarViewAction.delete:
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.grid_deleteView.tr(),
|
||||
confirm: () {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.deleteView(view.id),
|
||||
);
|
||||
},
|
||||
).show(context);
|
||||
|
||||
break;
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum TabBarViewAction implements ActionCell {
|
||||
rename,
|
||||
delete;
|
||||
|
||||
@override
|
||||
String get name {
|
||||
switch (this) {
|
||||
case TabBarViewAction.rename:
|
||||
return LocaleKeys.disclosureAction_rename.tr();
|
||||
case TabBarViewAction.delete:
|
||||
return LocaleKeys.disclosureAction_delete.tr();
|
||||
}
|
||||
}
|
||||
|
||||
Widget icon(Color iconColor) {
|
||||
switch (this) {
|
||||
case TabBarViewAction.rename:
|
||||
return const FlowySvg(name: 'editor/edit');
|
||||
case TabBarViewAction.delete:
|
||||
return const FlowySvg(name: 'editor/delete');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? leftIcon(Color iconColor) => icon(iconColor);
|
||||
|
||||
@override
|
||||
Widget? rightIcon(Color iconColor) => null;
|
||||
}
|
||||
@@ -1,26 +1,16 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/tar_bar_bloc.dart';
|
||||
import 'package:appflowy/plugins/util.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/left_bar_item.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/tab_bar_item.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../application/database_controller.dart';
|
||||
import '../grid/presentation/layout/sizes.dart';
|
||||
import 'tar_bar_add_button.dart';
|
||||
import 'tab_bar_header.dart';
|
||||
|
||||
abstract class DatabaseTabBarItemBuilder {
|
||||
const DatabaseTabBarItemBuilder();
|
||||
@@ -93,30 +83,29 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
||||
],
|
||||
child: Column(
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
return const Flexible(
|
||||
child: Padding(
|
||||
padding: EdgeInsets.only(left: 50),
|
||||
child: DatabaseTabBar(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
return ValueListenableBuilder<bool>(
|
||||
valueListenable: state
|
||||
.tabBarControllerByViewId[state.parentView.id]!
|
||||
.controller
|
||||
.isLoading,
|
||||
builder: (_, value, ___) {
|
||||
if (value) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return SizedBox(
|
||||
width: 300,
|
||||
height: 30,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(right: 50),
|
||||
child: pageSettingBarFromState(state),
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: GridSize.leadingHeaderPadding,
|
||||
),
|
||||
child: const TabBarHeader(),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
@@ -154,19 +143,6 @@ class _DatabaseTabBarViewState extends State<DatabaseTabBarView> {
|
||||
}).toList();
|
||||
}
|
||||
|
||||
Widget pageSettingBarFromState(GridTabBarState state) {
|
||||
if (state.tabBars.length < state.selectedIndex) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
final tarBar = state.tabBars[state.selectedIndex];
|
||||
final controller =
|
||||
state.tabBarControllerByViewId[tarBar.viewId]!.controller;
|
||||
return tarBar.builder.settingBar(
|
||||
context,
|
||||
controller,
|
||||
);
|
||||
}
|
||||
|
||||
Widget pageSettingBarExtensionFromState(GridTabBarState state) {
|
||||
if (state.tabBars.length < state.selectedIndex) {
|
||||
return const SizedBox.shrink();
|
||||
@@ -237,188 +213,3 @@ class DatabasePluginWidgetBuilder extends PluginWidgetBuilder {
|
||||
@override
|
||||
List<NavigationItem> get navigationItems => [this];
|
||||
}
|
||||
|
||||
class DatabaseTabBar extends StatefulWidget {
|
||||
const DatabaseTabBar({super.key});
|
||||
|
||||
@override
|
||||
State<DatabaseTabBar> createState() => _DatabaseTabBarState();
|
||||
}
|
||||
|
||||
class _DatabaseTabBarState extends State<DatabaseTabBar> {
|
||||
final _scrollController = ScrollController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<GridTabBarBloc, GridTabBarState>(
|
||||
builder: (context, state) {
|
||||
final children = state.tabBars.indexed.map((indexed) {
|
||||
final isSelected = state.selectedIndex == indexed.$1;
|
||||
final tabBar = indexed.$2;
|
||||
return DatabaseTabBarItem(
|
||||
key: ValueKey(tabBar.viewId),
|
||||
view: tabBar.view,
|
||||
isSelected: isSelected,
|
||||
onTap: (selectedView) {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.selectView(selectedView.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
}).toList();
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
Flexible(
|
||||
child: SingleChildScrollView(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: IntrinsicWidth(
|
||||
child: Row(children: children),
|
||||
),
|
||||
),
|
||||
),
|
||||
AddDatabaseViewButton(
|
||||
onTap: (action) async {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.createView(action),
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DatabaseTabBarItem extends StatelessWidget {
|
||||
final bool isSelected;
|
||||
final ViewPB view;
|
||||
final Function(ViewPB) onTap;
|
||||
const DatabaseTabBarItem({
|
||||
required this.view,
|
||||
required this.isSelected,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(minWidth: 80, maxWidth: 160),
|
||||
child: IntrinsicWidth(
|
||||
child: Column(
|
||||
children: [
|
||||
TabBarItemButton(
|
||||
view: view,
|
||||
onTap: () => onTap(view),
|
||||
),
|
||||
if (isSelected)
|
||||
Divider(
|
||||
height: 1,
|
||||
thickness: 2,
|
||||
color: Theme.of(context).colorScheme.secondary,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TabBarItemButton extends StatelessWidget {
|
||||
final ViewPB view;
|
||||
final VoidCallback onTap;
|
||||
const TabBarItemButton({
|
||||
required this.view,
|
||||
required this.onTap,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopoverActionList<TabBarViewAction>(
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
actions: TabBarViewAction.values,
|
||||
buildChild: (controller) {
|
||||
return FlowyButton(
|
||||
radius: Corners.s5Border,
|
||||
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||
onTap: onTap,
|
||||
onSecondaryTap: () {
|
||||
controller.show();
|
||||
},
|
||||
text: FlowyText.medium(
|
||||
view.name,
|
||||
maxLines: 1,
|
||||
textAlign: TextAlign.center,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
margin: GridSize.cellContentInsets,
|
||||
leftIcon: svgWidget(
|
||||
view.iconName,
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
);
|
||||
},
|
||||
onSelected: (action, controller) {
|
||||
switch (action) {
|
||||
case TabBarViewAction.rename:
|
||||
NavigatorTextFieldDialog(
|
||||
title: LocaleKeys.menuAppHeader_renameDialog.tr(),
|
||||
value: view.name,
|
||||
confirm: (newValue) {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.renameView(view.id, newValue),
|
||||
);
|
||||
},
|
||||
).show(context);
|
||||
break;
|
||||
case TabBarViewAction.delete:
|
||||
NavigatorAlertDialog(
|
||||
title: LocaleKeys.grid_deleteView.tr(),
|
||||
confirm: () {
|
||||
context.read<GridTabBarBloc>().add(
|
||||
GridTabBarEvent.deleteView(view.id),
|
||||
);
|
||||
},
|
||||
).show(context);
|
||||
|
||||
break;
|
||||
}
|
||||
controller.close();
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
enum TabBarViewAction implements ActionCell {
|
||||
rename,
|
||||
delete;
|
||||
|
||||
@override
|
||||
String get name {
|
||||
switch (this) {
|
||||
case TabBarViewAction.rename:
|
||||
return LocaleKeys.disclosureAction_rename.tr();
|
||||
case TabBarViewAction.delete:
|
||||
return LocaleKeys.disclosureAction_delete.tr();
|
||||
}
|
||||
}
|
||||
|
||||
Widget icon(Color iconColor) {
|
||||
switch (this) {
|
||||
case TabBarViewAction.rename:
|
||||
return const FlowySvg(name: 'editor/edit');
|
||||
case TabBarViewAction.delete:
|
||||
return const FlowySvg(name: 'editor/delete');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget? leftIcon(Color iconColor) => icon(iconColor);
|
||||
|
||||
@override
|
||||
Widget? rightIcon(Color iconColor) => null;
|
||||
}
|
||||
+22
-7
@@ -4,6 +4,7 @@ import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/extension.dart';
|
||||
@@ -32,13 +33,27 @@ class _AddDatabaseViewButtonState extends State<AddDatabaseViewButton> {
|
||||
offset: const Offset(0, 8),
|
||||
margin: EdgeInsets.zero,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
child: FlowyIconButton(
|
||||
iconPadding: const EdgeInsets.all(4),
|
||||
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||
onPressed: () => popoverController.show(),
|
||||
icon: svgWidget(
|
||||
'home/add',
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
child: SizedBox(
|
||||
height: 26,
|
||||
child: Row(
|
||||
children: [
|
||||
VerticalDivider(
|
||||
width: 1.0,
|
||||
thickness: 1.0,
|
||||
indent: 4.0,
|
||||
endIndent: 4.0,
|
||||
color: Theme.of(context).dividerColor,
|
||||
),
|
||||
FlowyIconButton(
|
||||
width: 26,
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
hoverColor: AFThemeExtension.of(context).greyHover,
|
||||
onPressed: () => popoverController.show(),
|
||||
radius: Corners.s4Border,
|
||||
icon: const FlowySvg(name: 'home/add'),
|
||||
iconColorOnHover: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
popupBuilder: (BuildContext context) {
|
||||
|
||||
+1
-1
@@ -1,10 +1,10 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
|
||||
import '../../../application/cell/cell_controller_builder.dart';
|
||||
import '../../../application/field/field_controller.dart';
|
||||
part 'date_card_cell_bloc.freezed.dart';
|
||||
|
||||
class DateCardCellBloc extends Bloc<DateCardCellEvent, DateCardCellState> {
|
||||
|
||||
+3
-2
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
@@ -95,7 +96,7 @@ class _GridPropertyCellState extends State<_GridPropertyCell> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final checkmark = svgWidget(
|
||||
widget.fieldInfo.visibility ? 'home/show' : 'home/hide',
|
||||
widget.fieldInfo.field.visibility ? 'home/show' : 'home/hide',
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
);
|
||||
|
||||
@@ -130,7 +131,7 @@ class _GridPropertyCellState extends State<_GridPropertyCell> {
|
||||
context.read<DatabasePropertyBloc>().add(
|
||||
DatabasePropertyEvent.setFieldVisibility(
|
||||
widget.fieldInfo.id,
|
||||
!widget.fieldInfo.visibility,
|
||||
!widget.fieldInfo.field.visibility,
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+3
-1
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/setting/group_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/header/field_type_extension.dart';
|
||||
@@ -30,8 +31,9 @@ class DatabaseGroupList extends StatelessWidget {
|
||||
fieldController: fieldController,
|
||||
)..add(const DatabaseGroupEvent.initial()),
|
||||
child: BlocBuilder<DatabaseGroupBloc, DatabaseGroupState>(
|
||||
buildWhen: (previous, current) => true,
|
||||
builder: (context, state) {
|
||||
final cells = state.fieldContexts.map((fieldInfo) {
|
||||
final cells = state.fieldInfos.map((fieldInfo) {
|
||||
Widget cell = _GridGroupCell(
|
||||
fieldInfo: fieldInfo,
|
||||
onSelected: () => onDismissed(),
|
||||
|
||||
+1
-1
@@ -141,7 +141,7 @@ class GridDateCellText extends StatelessWidget {
|
||||
padding: GridSize.cellContentInsets,
|
||||
child: FlowyText.medium(
|
||||
dateStr,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
maxLines: null,
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_controller_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_info.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/date_entities.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
+1
-1
@@ -54,7 +54,7 @@ class _NumberCellState extends GridEditableTextCell<GridNumberCell> {
|
||||
focusNode: focusNode,
|
||||
onEditingComplete: () => focusNode.unfocus(),
|
||||
onSubmitted: (_) => focusNode.unfocus(),
|
||||
maxLines: 1,
|
||||
maxLines: null,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
textInputAction: TextInputAction.done,
|
||||
decoration: const InputDecoration(
|
||||
|
||||
+1
-1
@@ -152,7 +152,7 @@ class _GridURLCellState extends GridEditableTextCell<GridURLCell> {
|
||||
child: TextField(
|
||||
controller: _controller,
|
||||
focusNode: focusNode,
|
||||
maxLines: 1,
|
||||
maxLines: null,
|
||||
style: (widget.cellStyle?.textStyle ??
|
||||
Theme.of(context).textTheme.bodyMedium)
|
||||
?.copyWith(
|
||||
|
||||
@@ -44,7 +44,7 @@ class RowPropertyList extends StatelessWidget {
|
||||
// The rest of the fields are displayed in the order of the field
|
||||
// list
|
||||
...state.cells
|
||||
.where((element) => !element.fieldInfo.isPrimary)
|
||||
.where((element) => !element.fieldInfo.field.isPrimary)
|
||||
.map(
|
||||
(cell) => _PropertyCell(
|
||||
cellContext: cell,
|
||||
|
||||
+23
-22
@@ -6,6 +6,7 @@ import 'package:appflowy_backend/protobuf/flowy-database2/calendar_entities.pb.d
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/setting_entities.pbenum.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -32,29 +33,29 @@ class _SettingButtonState extends State<SettingButton> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 26,
|
||||
child: AppFlowyPopover(
|
||||
controller: _popoverController,
|
||||
constraints: BoxConstraints.loose(const Size(200, 400)),
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
offset: const Offset(0, 8),
|
||||
margin: EdgeInsets.zero,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.settings_title.tr(),
|
||||
fontColor: AFThemeExtension.of(context).textColor,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
onPressed: () => _popoverController.show(),
|
||||
),
|
||||
popupBuilder: (BuildContext context) {
|
||||
return _DatabaseSettingListPopover(
|
||||
databaseController: widget.databaseController,
|
||||
);
|
||||
},
|
||||
return AppFlowyPopover(
|
||||
controller: _popoverController,
|
||||
constraints: BoxConstraints.loose(const Size(200, 400)),
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
offset: const Offset(0, 8),
|
||||
margin: EdgeInsets.zero,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
child: FlowyTextButton(
|
||||
LocaleKeys.settings_title.tr(),
|
||||
fontColor: AFThemeExtension.of(context).textColor,
|
||||
fontSize: FontSizes.s11,
|
||||
fontWeight: FontWeight.w400,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.toolbarSettingButtonInsets,
|
||||
radius: Corners.s4Border,
|
||||
onPressed: () => _popoverController.show(),
|
||||
),
|
||||
popupBuilder: (BuildContext context) {
|
||||
return _DatabaseSettingListPopover(
|
||||
databaseController: widget.databaseController,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+4
-3
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/application/doc_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart'
|
||||
show
|
||||
@@ -31,13 +32,13 @@ class TransactionAdapter {
|
||||
final String documentId;
|
||||
|
||||
Future<void> apply(Transaction transaction, EditorState editorState) async {
|
||||
// Log.debug('transaction => ${transaction.toJson()}');
|
||||
Log.debug('transaction => ${transaction.toJson()}');
|
||||
final actions = transaction.operations
|
||||
.map((op) => op.toBlockAction(editorState))
|
||||
.whereNotNull()
|
||||
.expand((element) => element)
|
||||
.toList(growable: false); // avoid lazy evaluation
|
||||
// Log.debug('actions => $actions');
|
||||
Log.debug('actions => $actions');
|
||||
await documentService.applyAction(
|
||||
documentId: documentId,
|
||||
actions: actions,
|
||||
@@ -72,7 +73,7 @@ extension on InsertOperation {
|
||||
editorState.getNodeAtPath(path.previous)?.id ??
|
||||
'';
|
||||
assert(parentId.isNotEmpty);
|
||||
if (path.equals(path.previous)) {
|
||||
if (path.equals(path.previous) && !path.equals([0])) {
|
||||
prevId = '';
|
||||
} else {
|
||||
assert(prevId.isNotEmpty && prevId != node.id);
|
||||
|
||||
@@ -65,7 +65,8 @@ class _DocumentPageState extends State<DocumentPage> {
|
||||
child: BlocBuilder<DocumentBloc, DocumentState>(
|
||||
builder: (context, state) {
|
||||
return state.loadingState.when(
|
||||
loading: () => const SizedBox.shrink(),
|
||||
loading: () =>
|
||||
const Center(child: CircularProgressIndicator.adaptive()),
|
||||
finish: (result) => result.fold(
|
||||
(error) {
|
||||
Log.error(error);
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'package:appflowy/workspace/application/settings/shortcuts/settings_short
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -185,6 +186,8 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
// OptionAction.moveDown,
|
||||
];
|
||||
|
||||
final calloutBGColor = AFThemeExtension.of(context).calloutBGColor;
|
||||
|
||||
final configuration = BlockComponentConfiguration(
|
||||
padding: (_) => const EdgeInsets.symmetric(vertical: 5.0),
|
||||
);
|
||||
@@ -250,6 +253,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
),
|
||||
CalloutBlockKeys.type: CalloutBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
defaultColor: calloutBGColor,
|
||||
),
|
||||
DividerBlockKeys.type: DividerBlockComponentBuilder(
|
||||
configuration: configuration,
|
||||
|
||||
+7
-5
@@ -16,6 +16,8 @@ enum OptionAction {
|
||||
turnInto,
|
||||
moveUp,
|
||||
moveDown,
|
||||
|
||||
/// callout background color
|
||||
color,
|
||||
divider,
|
||||
align;
|
||||
@@ -241,11 +243,12 @@ class ColorOptionAction extends PopoverActionCell {
|
||||
final bgColor =
|
||||
node.attributes[blockComponentBackgroundColor] as String?;
|
||||
final selectedColor = bgColor?.toColor();
|
||||
|
||||
// get default background color from themeExtension
|
||||
final defaultColor = AFThemeExtension.of(context).calloutBGColor;
|
||||
final colors = [
|
||||
// clear background color.
|
||||
// reset to default background color
|
||||
FlowyColorOption(
|
||||
color: Colors.transparent,
|
||||
color: defaultColor,
|
||||
name: LocaleKeys.document_plugins_optionAction_defaultColor.tr(),
|
||||
),
|
||||
...FlowyTint.values.map(
|
||||
@@ -265,8 +268,7 @@ class ColorOptionAction extends PopoverActionCell {
|
||||
),
|
||||
onTap: (color, index) async {
|
||||
final transaction = editorState.transaction;
|
||||
final backgroundColor =
|
||||
color == Colors.transparent ? null : color.toHex();
|
||||
final backgroundColor = color.toHex();
|
||||
transaction.updateNode(node, {
|
||||
blockComponentBackgroundColor: backgroundColor,
|
||||
});
|
||||
|
||||
+21
-21
@@ -1,4 +1,5 @@
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -26,16 +27,16 @@ class CalloutBlockKeys {
|
||||
static const String icon = 'icon';
|
||||
}
|
||||
|
||||
// creating a new callout node
|
||||
// The one is inserted through selection menu
|
||||
Node calloutNode({
|
||||
Delta? delta,
|
||||
String emoji = '📌',
|
||||
String backgroundColor = '#F0F0F0',
|
||||
Color? defaultColor,
|
||||
}) {
|
||||
final attributes = {
|
||||
CalloutBlockKeys.delta: (delta ?? Delta()).toJson(),
|
||||
CalloutBlockKeys.icon: emoji,
|
||||
CalloutBlockKeys.backgroundColor: backgroundColor,
|
||||
CalloutBlockKeys.backgroundColor: defaultColor?.toHex() ?? '#f2f2f2',
|
||||
};
|
||||
return Node(
|
||||
type: CalloutBlockKeys.type,
|
||||
@@ -43,12 +44,13 @@ Node calloutNode({
|
||||
);
|
||||
}
|
||||
|
||||
// defining the callout block menu item for selection
|
||||
// defining the callout block menu item in selection menu
|
||||
SelectionMenuItem calloutItem = SelectionMenuItem.node(
|
||||
name: 'Callout',
|
||||
iconData: Icons.note,
|
||||
keywords: ['callout'],
|
||||
nodeBuilder: (editorState) => calloutNode(),
|
||||
keywords: [CalloutBlockKeys.type],
|
||||
nodeBuilder: (editorState, context) =>
|
||||
calloutNode(defaultColor: AFThemeExtension.of(context).calloutBGColor),
|
||||
replace: (_, node) => node.delta?.isEmpty ?? false,
|
||||
updateSelection: (_, path, __, ___) {
|
||||
return Selection.single(path: path, startOffset: 0);
|
||||
@@ -59,17 +61,21 @@ SelectionMenuItem calloutItem = SelectionMenuItem.node(
|
||||
class CalloutBlockComponentBuilder extends BlockComponentBuilder {
|
||||
CalloutBlockComponentBuilder({
|
||||
this.configuration = const BlockComponentConfiguration(),
|
||||
required this.defaultColor,
|
||||
});
|
||||
|
||||
@override
|
||||
final BlockComponentConfiguration configuration;
|
||||
|
||||
final Color defaultColor;
|
||||
|
||||
@override
|
||||
BlockComponentWidget build(BlockComponentContext blockComponentContext) {
|
||||
final node = blockComponentContext.node;
|
||||
return CalloutBlockComponentWidget(
|
||||
key: node.key,
|
||||
node: node,
|
||||
defaultColor: defaultColor,
|
||||
configuration: configuration,
|
||||
showActions: showActions(node),
|
||||
actionBuilder: (context, state) => actionBuilder(
|
||||
@@ -96,8 +102,11 @@ class CalloutBlockComponentWidget extends BlockComponentStatefulWidget {
|
||||
super.showActions,
|
||||
super.actionBuilder,
|
||||
super.configuration = const BlockComponentConfiguration(),
|
||||
required this.defaultColor,
|
||||
});
|
||||
|
||||
final Color defaultColor;
|
||||
|
||||
@override
|
||||
State<CalloutBlockComponentWidget> createState() =>
|
||||
_CalloutBlockComponentWidgetState();
|
||||
@@ -128,17 +137,8 @@ class _CalloutBlockComponentWidgetState
|
||||
// get the background color of the note block from the node's attributes
|
||||
Color get backgroundColor {
|
||||
final colorString =
|
||||
node.attributes[CalloutBlockKeys.backgroundColor] as String?;
|
||||
if (colorString == null) {
|
||||
return Colors.transparent;
|
||||
}
|
||||
|
||||
final brightness = Theme.of(context).brightness;
|
||||
final bool isDarkMode = brightness == Brightness.dark;
|
||||
|
||||
final Color bgColor = colorString.toColor();
|
||||
|
||||
return isDarkMode ? bgColor.withOpacity(0.3) : bgColor;
|
||||
node.attributes[CalloutBlockKeys.backgroundColor] as String;
|
||||
return colorString.toColor();
|
||||
}
|
||||
|
||||
// get the emoji of the note block from the node's attributes or default to '📌'
|
||||
@@ -162,9 +162,9 @@ class _CalloutBlockComponentWidgetState
|
||||
// the emoji picker button for the note
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 6.0,
|
||||
left: 2.0,
|
||||
right: 2.0,
|
||||
top: 8.0,
|
||||
left: 4.0,
|
||||
right: 4.0,
|
||||
),
|
||||
child: EmojiPickerButton(
|
||||
key: ValueKey(
|
||||
@@ -183,7 +183,7 @@ class _CalloutBlockComponentWidgetState
|
||||
child: buildCalloutBlockComponent(context),
|
||||
),
|
||||
),
|
||||
const VSpace(10),
|
||||
const HSpace(8.0),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ SelectionMenuItem codeBlockItem = SelectionMenuItem.node(
|
||||
name: 'Code Block',
|
||||
iconData: Icons.abc,
|
||||
keywords: ['code', 'codeblock'],
|
||||
nodeBuilder: (editorState) => codeBlockNode(),
|
||||
nodeBuilder: (editorState, _) => codeBlockNode(),
|
||||
replace: (_, node) => node.delta?.isEmpty ?? false,
|
||||
);
|
||||
|
||||
|
||||
+1
-1
@@ -37,7 +37,7 @@ SelectionMenuItem mathEquationItem = SelectionMenuItem.node(
|
||||
name: 'MathEquation',
|
||||
iconData: Icons.text_fields_rounded,
|
||||
keywords: ['tex, latex, katex', 'math equation', 'formula'],
|
||||
nodeBuilder: (editorState) => mathEquationNode(),
|
||||
nodeBuilder: (editorState, _) => mathEquationNode(),
|
||||
replace: (_, node) => node.delta?.isEmpty ?? false,
|
||||
updateSelection: (editorState, path, __, ___) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
|
||||
+1
-1
@@ -45,7 +45,7 @@ SelectionMenuItem autoGeneratorMenuItem = SelectionMenuItem.node(
|
||||
name: LocaleKeys.document_plugins_autoGeneratorMenuItemName.tr(),
|
||||
iconData: Icons.generating_tokens,
|
||||
keywords: ['ai', 'openai' 'writer', 'autogenerator'],
|
||||
nodeBuilder: (editorState) {
|
||||
nodeBuilder: (editorState, _) {
|
||||
final node = autoCompletionNode(start: editorState.selection!);
|
||||
return node;
|
||||
},
|
||||
|
||||
+5
-3
@@ -19,7 +19,7 @@ SelectionMenuItem outlineItem = SelectionMenuItem.node(
|
||||
name: LocaleKeys.document_selectionMenu_outline.tr(),
|
||||
iconData: Icons.list_alt,
|
||||
keywords: ['outline', 'table of contents'],
|
||||
nodeBuilder: (editorState) => outlineBlockNode(),
|
||||
nodeBuilder: (editorState, _) => outlineBlockNode(),
|
||||
replace: (_, node) => node.delta?.isEmpty ?? false,
|
||||
);
|
||||
|
||||
@@ -180,14 +180,16 @@ class OutlineItemWidget extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
void updateBlockSelection(BuildContext context) {
|
||||
void updateBlockSelection(BuildContext context) async {
|
||||
final editorState = context.read<EditorState>();
|
||||
editorState.selectionType = SelectionType.block;
|
||||
editorState.selection = Selection.collapse(
|
||||
node.path,
|
||||
node.delta?.length ?? 0,
|
||||
);
|
||||
editorState.selectionType = null;
|
||||
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
|
||||
editorState.selectionType = null;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ SelectionMenuItem toggleListBlockItem = SelectionMenuItem.node(
|
||||
name: LocaleKeys.document_plugins_toggleList.tr(),
|
||||
iconData: Icons.arrow_right,
|
||||
keywords: ['collapsed list', 'toggle list', 'list'],
|
||||
nodeBuilder: (editorState) => toggleListBlockNode(),
|
||||
nodeBuilder: (editorState, _) => toggleListBlockNode(),
|
||||
replace: (_, node) => node.delta?.isEmpty ?? false,
|
||||
);
|
||||
|
||||
|
||||
@@ -22,7 +22,11 @@ class InitAppWidgetTask extends LaunchTask {
|
||||
final widget = context.getIt<EntryPoint>().create(context.config);
|
||||
final appearanceSetting =
|
||||
await UserSettingsBackendService().getAppearanceSetting();
|
||||
|
||||
// If the passed-in context is not the same as the context of the
|
||||
// application widget, the application widget will be rebuilt.
|
||||
final app = ApplicationWidget(
|
||||
key: ValueKey(context),
|
||||
appearanceSetting: appearanceSetting,
|
||||
child: widget,
|
||||
);
|
||||
|
||||
@@ -1,9 +1,27 @@
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'package:url_protocol/url_protocol.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
import '../startup.dart';
|
||||
|
||||
bool isSupabaseInitialized = false;
|
||||
// ONLY supports in macOS and Windows now.
|
||||
//
|
||||
// If you need to update the schema, please update the following files:
|
||||
// - appflowy_flutter/macos/Runner/Info.plist (macOS)
|
||||
// - the callback url in Supabase dashboard
|
||||
const appflowyDeepLinkSchema = 'appflowy-flutter';
|
||||
const supabaseLoginCallback = '$appflowyDeepLinkSchema://login-callback';
|
||||
|
||||
const hiveBoxName = 'appflowy_supabase_authentication';
|
||||
|
||||
// Used to store the session of the supabase in case of the user switch the different folder.
|
||||
Supabase? supabase;
|
||||
|
||||
class InitSupabaseTask extends LaunchTask {
|
||||
@override
|
||||
@@ -12,16 +30,70 @@ class InitSupabaseTask extends LaunchTask {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSupabaseInitialized) {
|
||||
return;
|
||||
// register deep link for Windows
|
||||
if (Platform.isWindows) {
|
||||
registerProtocolHandler(appflowyDeepLinkSchema);
|
||||
}
|
||||
await Supabase.initialize(
|
||||
|
||||
supabase?.dispose();
|
||||
supabase = null;
|
||||
supabase = await Supabase.initialize(
|
||||
url: Env.supabaseUrl,
|
||||
anonKey: Env.supabaseAnonKey,
|
||||
debug: true,
|
||||
// authFlowType: AuthFlowType.pkce,
|
||||
debug: kDebugMode,
|
||||
localStorage: const SupabaseLocalStorage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// customize the supabase auth storage
|
||||
///
|
||||
/// We don't use the default one because it always save the session in the document directory.
|
||||
/// When we switch to the different folder, the session still exists.
|
||||
class SupabaseLocalStorage extends LocalStorage {
|
||||
const SupabaseLocalStorage()
|
||||
: super(
|
||||
initialize: _initialize,
|
||||
hasAccessToken: _hasAccessToken,
|
||||
accessToken: _accessToken,
|
||||
removePersistedSession: _removePersistedSession,
|
||||
persistSession: _persistSession,
|
||||
);
|
||||
|
||||
static Future<void> _initialize() async {
|
||||
HiveCipher? encryptionCipher;
|
||||
|
||||
// customize the path for Hive
|
||||
final path = await getIt<ApplicationDataStorage>().getPath();
|
||||
Hive.init(p.join(path, 'supabase_auth'));
|
||||
await Hive.openBox(
|
||||
hiveBoxName,
|
||||
encryptionCipher: encryptionCipher,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<bool> _hasAccessToken() {
|
||||
return Future.value(
|
||||
Hive.box(hiveBoxName).containsKey(
|
||||
supabasePersistSessionKey,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<String?> _accessToken() {
|
||||
return Future.value(
|
||||
Hive.box(hiveBoxName).get(supabasePersistSessionKey) as String?,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _removePersistedSession() {
|
||||
return Hive.box(hiveBoxName).delete(supabasePersistSessionKey);
|
||||
}
|
||||
|
||||
static Future<void> _persistSession(String persistSessionString) {
|
||||
return Hive.box(hiveBoxName).put(
|
||||
supabasePersistSessionKey,
|
||||
persistSessionString,
|
||||
);
|
||||
|
||||
isSupabaseInitialized = true;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy/user/application/auth/appflowy_auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
@@ -13,9 +14,6 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
import 'auth_error.dart';
|
||||
|
||||
// can't use underscore here.
|
||||
const loginCallback = 'io.appflowy.appflowy-flutter://login-callback';
|
||||
|
||||
class SupabaseAuthService implements AuthService {
|
||||
SupabaseAuthService();
|
||||
|
||||
@@ -123,7 +121,7 @@ class SupabaseAuthService implements AuthService {
|
||||
final response = await _auth.signInWithOAuth(
|
||||
provider,
|
||||
queryParams: queryParamsForProvider(provider),
|
||||
redirectTo: loginCallback,
|
||||
redirectTo: supabaseLoginCallback,
|
||||
);
|
||||
if (!response) {
|
||||
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
||||
@@ -171,7 +169,7 @@ class SupabaseAuthService implements AuthService {
|
||||
|
||||
await _auth.signInWithOtp(
|
||||
email: email,
|
||||
emailRedirectTo: kIsWeb ? null : loginCallback,
|
||||
emailRedirectTo: kIsWeb ? null : supabaseLoginCallback,
|
||||
);
|
||||
return completer.future;
|
||||
}
|
||||
@@ -217,6 +215,22 @@ extension on String {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a completer that listens to Supabase authentication state changes and
|
||||
/// completes when a user signs in.
|
||||
///
|
||||
/// This function sets up a listener on Supabase's authentication state. When a user
|
||||
/// signs in, it triggers the provided [onSuccess] callback with the user's `id` and
|
||||
/// `email`. Once the [onSuccess] callback is executed and a response is received,
|
||||
/// the completer completes with the response, and the listener is canceled.
|
||||
///
|
||||
/// Parameters:
|
||||
/// - [onSuccess]: A callback function that's executed when a user signs in. It
|
||||
/// should take in a user's `id` and `email` and return a `Future` containing either
|
||||
/// a `FlowyError` or a `UserProfilePB`.
|
||||
///
|
||||
/// Returns:
|
||||
/// A completer of type `Either<FlowyError, UserProfilePB>`. This completer completes
|
||||
/// with the response from the [onSuccess] callback when a user signs in.
|
||||
Completer<Either<FlowyError, UserProfilePB>> supabaseLoginCompleter({
|
||||
required Future<Either<FlowyError, UserProfilePB>> Function(
|
||||
String userId,
|
||||
@@ -229,16 +243,15 @@ Completer<Either<FlowyError, UserProfilePB>> supabaseLoginCompleter({
|
||||
|
||||
subscription = auth.onAuthStateChange.listen((event) async {
|
||||
final user = event.session?.user;
|
||||
if (event.event != AuthChangeEvent.signedIn || user == null) {
|
||||
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
||||
} else {
|
||||
if (event.event == AuthChangeEvent.signedIn && user != null) {
|
||||
final response = await onSuccess(
|
||||
user.id,
|
||||
user.email ?? user.newEmail ?? '',
|
||||
);
|
||||
// Only cancel the subscription if the Event is signedIn.
|
||||
subscription.cancel();
|
||||
completer.complete(response);
|
||||
}
|
||||
subscription.cancel();
|
||||
});
|
||||
return completer;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/auth.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'historical_user_bloc.freezed.dart';
|
||||
|
||||
class HistoricalUserBloc
|
||||
extends Bloc<HistoricalUserEvent, HistoricalUserState> {
|
||||
HistoricalUserBloc() : super(HistoricalUserState.initial()) {
|
||||
on<HistoricalUserEvent>((event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
await _loadHistoricalUsers();
|
||||
},
|
||||
didLoadHistoricalUsers: (List<HistoricalUserPB> historicalUsers) {
|
||||
emit(state.copyWith(historicalUsers: historicalUsers));
|
||||
},
|
||||
openHistoricalUser: (HistoricalUserPB historicalUser) async {
|
||||
await UserBackendService.openHistoricalUser(historicalUser);
|
||||
emit(state.copyWith(openedHistoricalUser: historicalUser));
|
||||
},
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _loadHistoricalUsers() async {
|
||||
final result = await UserBackendService.loadHistoricalUsers();
|
||||
result.fold(
|
||||
(historicalUsers) {
|
||||
historicalUsers
|
||||
.retainWhere((element) => element.authType == AuthTypePB.Local);
|
||||
add(HistoricalUserEvent.didLoadHistoricalUsers(historicalUsers));
|
||||
},
|
||||
(error) => Log.error(error),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class HistoricalUserEvent with _$HistoricalUserEvent {
|
||||
const factory HistoricalUserEvent.initial() = _Initial;
|
||||
const factory HistoricalUserEvent.didLoadHistoricalUsers(
|
||||
List<HistoricalUserPB> historicalUsers,
|
||||
) = _DidLoadHistoricalUsers;
|
||||
const factory HistoricalUserEvent.openHistoricalUser(
|
||||
HistoricalUserPB historicalUser,
|
||||
) = _OpenHistoricalUser;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class HistoricalUserState with _$HistoricalUserState {
|
||||
const factory HistoricalUserState({
|
||||
required List<HistoricalUserPB> historicalUsers,
|
||||
required HistoricalUserPB? openedHistoricalUser,
|
||||
}) = _HistoricalUserState;
|
||||
|
||||
factory HistoricalUserState.initial() => const HistoricalUserState(
|
||||
historicalUsers: [],
|
||||
openedHistoricalUser: null,
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ class UserBackendService {
|
||||
static Future<Either<FlowyError, UserProfilePB>>
|
||||
getCurrentUserProfile() async {
|
||||
final result = await UserEventGetUserProfile().send().then((value) {
|
||||
value.fold((l) => null, (r) => Log.error(r));
|
||||
value.fold((l) => null, (r) => Log.info(r));
|
||||
return value;
|
||||
});
|
||||
return result.swap();
|
||||
@@ -70,6 +70,24 @@ class UserBackendService {
|
||||
return UserEventInitUser().send();
|
||||
}
|
||||
|
||||
static Future<Either<List<HistoricalUserPB>, FlowyError>>
|
||||
loadHistoricalUsers() async {
|
||||
return UserEventGetHistoricalUsers().send().then(
|
||||
(result) {
|
||||
return result.fold(
|
||||
(historicalUsers) => left(historicalUsers.items),
|
||||
(error) => right(error),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Either<Unit, FlowyError>> openHistoricalUser(
|
||||
HistoricalUserPB user,
|
||||
) async {
|
||||
return UserEventOpenHistoricalUser(user).send();
|
||||
}
|
||||
|
||||
Future<Either<List<WorkspacePB>, FlowyError>> getWorkspaces() {
|
||||
final request = WorkspaceIdPB.create();
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/user/application/historical_user_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
class HistoricalUserList extends StatelessWidget {
|
||||
final VoidCallback didOpenUser;
|
||||
const HistoricalUserList({required this.didOpenUser, super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider(
|
||||
create: (context) => HistoricalUserBloc()
|
||||
..add(
|
||||
const HistoricalUserEvent.initial(),
|
||||
),
|
||||
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
|
||||
builder: (context, state) {
|
||||
if (state.historicalUsers.isEmpty) {
|
||||
return const SizedBox.shrink();
|
||||
} else {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Opacity(
|
||||
opacity: 0.6,
|
||||
child: FlowyText.regular(
|
||||
LocaleKeys.settings_menu_historicalUserListTooltip.tr(),
|
||||
fontSize: 13,
|
||||
maxLines: null,
|
||||
),
|
||||
),
|
||||
const VSpace(6),
|
||||
Expanded(
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final user = state.historicalUsers[index];
|
||||
return HistoricalUserItem(
|
||||
key: ValueKey(user.userId),
|
||||
user: user,
|
||||
isSelected: false,
|
||||
didOpenUser: didOpenUser,
|
||||
);
|
||||
},
|
||||
itemCount: state.historicalUsers.length,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class HistoricalUserItem extends StatelessWidget {
|
||||
final VoidCallback didOpenUser;
|
||||
final bool isSelected;
|
||||
final HistoricalUserPB user;
|
||||
const HistoricalUserItem({
|
||||
required this.user,
|
||||
required this.isSelected,
|
||||
required this.didOpenUser,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final icon = isSelected ? const FlowySvg(name: "grid/checkmark") : null;
|
||||
final isDisabled = isSelected || user.authType != AuthTypePB.Local;
|
||||
final outputFormat = DateFormat('MM/dd/yyyy hh:mm a');
|
||||
final date =
|
||||
DateTime.fromMillisecondsSinceEpoch(user.lastTime.toInt() * 1000);
|
||||
final lastTime = outputFormat.format(date);
|
||||
final desc = "${user.userName}\t ${user.authType}\t$lastTime";
|
||||
final child = SizedBox(
|
||||
height: 30,
|
||||
child: FlowyButton(
|
||||
disable: isDisabled,
|
||||
text: FlowyText.medium(
|
||||
desc,
|
||||
fontSize: 12,
|
||||
),
|
||||
rightIcon: icon,
|
||||
onTap: () {
|
||||
context
|
||||
.read<HistoricalUserBloc>()
|
||||
.add(HistoricalUserEvent.openHistoricalUser(user));
|
||||
didOpenUser();
|
||||
},
|
||||
),
|
||||
);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
@@ -13,6 +13,13 @@ import 'package:appflowy_backend/protobuf/flowy-user/protobuf.dart'
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
const routerNameRoot = '/';
|
||||
const routerNameSignUp = '/signUp';
|
||||
const routerNameSignIn = '/signIn';
|
||||
const routerNameSkipLogIn = '/skipLogIn';
|
||||
const routerNameWelcome = '/welcome';
|
||||
const routerNameHome = '/home';
|
||||
|
||||
class AuthRouter {
|
||||
void pushForgetPasswordScreen(BuildContext context) {}
|
||||
|
||||
@@ -24,6 +31,7 @@ class AuthRouter {
|
||||
Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => SignUpScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: routerNameSignUp),
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -41,6 +49,7 @@ class AuthRouter {
|
||||
workspaceSetting,
|
||||
key: ValueKey(profile.id),
|
||||
),
|
||||
const RouteSettings(name: routerNameHome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@@ -71,6 +80,7 @@ class SplashRoute {
|
||||
await Navigator.of(context).push(
|
||||
PageRoutes.fade(
|
||||
() => screen,
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@@ -97,6 +107,7 @@ class SplashRoute {
|
||||
workspaceSetting,
|
||||
key: ValueKey(userProfile.id),
|
||||
),
|
||||
const RouteSettings(name: routerNameWelcome),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@@ -107,6 +118,7 @@ class SplashRoute {
|
||||
context,
|
||||
PageRoutes.fade(
|
||||
() => SignInScreen(router: getIt<AuthRouter>()),
|
||||
const RouteSettings(name: routerNameSignIn),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
@@ -120,6 +132,7 @@ class SplashRoute {
|
||||
router: getIt<AuthRouter>(),
|
||||
authService: getIt<AuthService>(),
|
||||
),
|
||||
const RouteSettings(name: routerNameSkipLogIn),
|
||||
RouteDurations.slow.inMilliseconds * .001,
|
||||
),
|
||||
);
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/core/frameless_window.dart';
|
||||
import 'package:appflowy/startup/entry_point.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/historical_user_bloc.dart';
|
||||
import 'package:appflowy/user/application/sign_in_bloc.dart';
|
||||
import 'package:appflowy/user/presentation/router.dart';
|
||||
import 'package:appflowy/user/presentation/widgets/background.dart';
|
||||
@@ -71,6 +73,8 @@ class SignInForm extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isSubmitting = context.read<SignInBloc>().state.isSubmitting;
|
||||
const indicatorMinHeight = 4.0;
|
||||
return Align(
|
||||
alignment: Alignment.center,
|
||||
child: AuthFormContainer(
|
||||
@@ -104,13 +108,30 @@ class SignInForm extends StatelessWidget {
|
||||
const VSpace(10),
|
||||
const ThirdPartySignInButtons(),
|
||||
const VSpace(20),
|
||||
|
||||
// loading status
|
||||
if (context.read<SignInBloc>().state.isSubmitting) ...[
|
||||
const SizedBox(height: 8),
|
||||
const LinearProgressIndicator(value: null),
|
||||
const VSpace(20),
|
||||
],
|
||||
...isSubmitting
|
||||
? [
|
||||
const VSpace(indicatorMinHeight),
|
||||
const LinearProgressIndicator(
|
||||
value: null,
|
||||
minHeight: indicatorMinHeight,
|
||||
),
|
||||
]
|
||||
: [
|
||||
const VSpace(indicatorMinHeight * 2.0)
|
||||
], // add the same space when there's no loading status.
|
||||
// ConstrainedBox(
|
||||
// constraints: const BoxConstraints(maxHeight: 140),
|
||||
// child: HistoricalUserList(
|
||||
// didOpenUser: () async {
|
||||
// await FlowyRunner.run(
|
||||
// FlowyApp(),
|
||||
// integrationEnv(),
|
||||
// );
|
||||
// },
|
||||
// ),
|
||||
// ),
|
||||
const VSpace(20),
|
||||
],
|
||||
),
|
||||
);
|
||||
@@ -175,14 +196,49 @@ class SignInAsGuestButton extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return RoundedTextButton(
|
||||
title: LocaleKeys.signIn_loginAsGuestButtonText.tr(),
|
||||
height: 48,
|
||||
borderRadius: Corners.s6Border,
|
||||
onPressed: () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
|
||||
context.read<SignInBloc>().add(const SignInEvent.signedInAsGuest());
|
||||
},
|
||||
return BlocProvider(
|
||||
create: (context) => HistoricalUserBloc()
|
||||
..add(
|
||||
const HistoricalUserEvent.initial(),
|
||||
),
|
||||
child: BlocListener<HistoricalUserBloc, HistoricalUserState>(
|
||||
listenWhen: (previous, current) =>
|
||||
previous.openedHistoricalUser != current.openedHistoricalUser,
|
||||
listener: (context, state) async {
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
integrationEnv(),
|
||||
);
|
||||
},
|
||||
child: BlocBuilder<HistoricalUserBloc, HistoricalUserState>(
|
||||
builder: (context, state) {
|
||||
if (state.historicalUsers.isEmpty) {
|
||||
return RoundedTextButton(
|
||||
title: LocaleKeys.signIn_loginAsGuestButtonText.tr(),
|
||||
height: 48,
|
||||
borderRadius: Corners.s6Border,
|
||||
onPressed: () {
|
||||
getIt<KeyValueStorage>().set(KVKeys.loginType, 'local');
|
||||
context
|
||||
.read<SignInBloc>()
|
||||
.add(const SignInEvent.signedInAsGuest());
|
||||
},
|
||||
);
|
||||
} else {
|
||||
return RoundedTextButton(
|
||||
title: LocaleKeys.signIn_continueAnonymousUser.tr(),
|
||||
height: 48,
|
||||
borderRadius: Corners.s6Border,
|
||||
onPressed: () {
|
||||
final bloc = context.read<HistoricalUserBloc>();
|
||||
final user = bloc.state.historicalUsers.first;
|
||||
bloc.add(HistoricalUserEvent.openHistoricalUser(user));
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -320,14 +376,16 @@ class ThirdPartySignInButton extends StatelessWidget {
|
||||
}
|
||||
|
||||
class ThirdPartySignInButtons extends StatelessWidget {
|
||||
final MainAxisAlignment mainAxisAlignment;
|
||||
const ThirdPartySignInButtons({
|
||||
this.mainAxisAlignment = MainAxisAlignment.center,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisAlignment: mainAxisAlignment,
|
||||
children: [
|
||||
ThirdPartySignInButton(
|
||||
icon: 'login/google-mark',
|
||||
|
||||
@@ -346,6 +346,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
fontSize: FontSizes.s11,
|
||||
fontColor: theme.shader3,
|
||||
),
|
||||
calloutBGColor: theme.hoverBG3,
|
||||
caption: _getFontStyle(
|
||||
fontFamily: fontFamily,
|
||||
fontSize: FontSizes.s11,
|
||||
|
||||
@@ -35,9 +35,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
||||
_listener.start(appsChanged: _handleAppsOrFail);
|
||||
await _fetchApps(emit);
|
||||
},
|
||||
openPage: (e) async {
|
||||
emit(state.copyWith(plugin: e.plugin));
|
||||
},
|
||||
createApp: (_CreateApp event) async {
|
||||
final result = await _workspaceService.createApp(
|
||||
name: event.name,
|
||||
@@ -110,7 +107,6 @@ class MenuBloc extends Bloc<MenuEvent, MenuState> {
|
||||
@freezed
|
||||
class MenuEvent with _$MenuEvent {
|
||||
const factory MenuEvent.initial() = _Initial;
|
||||
const factory MenuEvent.openPage(Plugin plugin) = _OpenPage;
|
||||
const factory MenuEvent.createApp(String name, {String? desc, int? index}) =
|
||||
_CreateApp;
|
||||
const factory MenuEvent.moveApp(int fromIndex, int toIndex) = _MoveApp;
|
||||
|
||||
+14
-15
@@ -8,10 +8,9 @@ import 'package:protobuf/protobuf.dart';
|
||||
|
||||
part 'setting_supabase_bloc.freezed.dart';
|
||||
|
||||
class SettingSupabaseBloc
|
||||
extends Bloc<SettingSupabaseEvent, SettingSupabaseState> {
|
||||
SettingSupabaseBloc() : super(SettingSupabaseState.initial()) {
|
||||
on<SettingSupabaseEvent>((event, emit) async {
|
||||
class SyncSettingBloc extends Bloc<SyncSettingEvent, SyncSettingState> {
|
||||
SyncSettingBloc() : super(SyncSettingState.initial()) {
|
||||
on<SyncSettingEvent>((event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
await getSupabaseConfig();
|
||||
@@ -27,7 +26,7 @@ class SettingSupabaseBloc
|
||||
emit(state.copyWith(config: newConfig));
|
||||
}
|
||||
},
|
||||
didReceiveSupabseConfig: (SupabaseConfigPB config) {
|
||||
didReceiveSyncConfig: (SupabaseConfigPB config) {
|
||||
emit(state.copyWith(config: config));
|
||||
},
|
||||
);
|
||||
@@ -43,7 +42,7 @@ class SettingSupabaseBloc
|
||||
result.fold(
|
||||
(config) {
|
||||
if (!isClosed) {
|
||||
add(SettingSupabaseEvent.didReceiveSupabseConfig(config));
|
||||
add(SyncSettingEvent.didReceiveSyncConfig(config));
|
||||
}
|
||||
},
|
||||
(r) => Log.error(r),
|
||||
@@ -52,22 +51,22 @@ class SettingSupabaseBloc
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingSupabaseEvent with _$SettingSupabaseEvent {
|
||||
const factory SettingSupabaseEvent.initial() = _Initial;
|
||||
const factory SettingSupabaseEvent.didReceiveSupabseConfig(
|
||||
class SyncSettingEvent with _$SyncSettingEvent {
|
||||
const factory SyncSettingEvent.initial() = _Initial;
|
||||
const factory SyncSettingEvent.didReceiveSyncConfig(
|
||||
SupabaseConfigPB config,
|
||||
) = _DidReceiveSupabaseConfig;
|
||||
const factory SettingSupabaseEvent.enableSync(bool enable) = _EnableSync;
|
||||
) = _DidSyncSupabaseConfig;
|
||||
const factory SyncSettingEvent.enableSync(bool enable) = _EnableSync;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class SettingSupabaseState with _$SettingSupabaseState {
|
||||
const factory SettingSupabaseState({
|
||||
class SyncSettingState with _$SyncSettingState {
|
||||
const factory SyncSettingState({
|
||||
SupabaseConfigPB? config,
|
||||
required Either<Unit, String> successOrFailure,
|
||||
}) = _SettingSupabaseState;
|
||||
}) = _SyncSettingState;
|
||||
|
||||
factory SettingSupabaseState.initial() => SettingSupabaseState(
|
||||
factory SyncSettingState.initial() => SyncSettingState(
|
||||
successOrFailure: left(unit),
|
||||
);
|
||||
}
|
||||
+1
-1
@@ -13,7 +13,7 @@ enum SettingsPage {
|
||||
language,
|
||||
files,
|
||||
user,
|
||||
supabaseSetting,
|
||||
syncSetting,
|
||||
shortcuts,
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:appflowy/plugins/util.dart';
|
||||
import 'package:appflowy/startup/plugin/plugin.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_ext.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/home_stack.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
@@ -21,7 +22,9 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
|
||||
on<TabsEvent>((event, emit) async {
|
||||
event.when(
|
||||
selectTab: (int index) {
|
||||
if (index != state.currentIndex) {
|
||||
if (index != state.currentIndex &&
|
||||
index >= 0 &&
|
||||
index < state.pages) {
|
||||
emit(state.copyWith(newIndex: index));
|
||||
_setLatestOpenView();
|
||||
}
|
||||
@@ -31,6 +34,10 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
|
||||
emit(state.closeView(pluginId));
|
||||
_setLatestOpenView();
|
||||
},
|
||||
closeCurrentTab: () {
|
||||
emit(state.closeView(state.currentPageManager.plugin.id));
|
||||
_setLatestOpenView();
|
||||
},
|
||||
openTab: (Plugin plugin, ViewPB view) {
|
||||
emit(state.openView(plugin, view));
|
||||
_setLatestOpenView(view);
|
||||
@@ -54,4 +61,12 @@ class TabsBloc extends Bloc<TabsEvent, TabsState> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds a [TabsEvent.openTab] event for the provided [ViewPB]
|
||||
void openTab(ViewPB view) =>
|
||||
add(TabsEvent.openTab(plugin: view.plugin(), view: view));
|
||||
|
||||
/// Adds a [TabsEvent.openPlugin] event for the provided [ViewPB]
|
||||
void openPlugin(ViewPB view) =>
|
||||
add(TabsEvent.openPlugin(plugin: view.plugin(), view: view));
|
||||
}
|
||||
@@ -4,6 +4,7 @@ part of 'tabs_bloc.dart';
|
||||
class TabsEvent with _$TabsEvent {
|
||||
const factory TabsEvent.moveTab() = _MoveTab;
|
||||
const factory TabsEvent.closeTab(String pluginId) = _CloseTab;
|
||||
const factory TabsEvent.closeCurrentTab() = _CloseCurrentTab;
|
||||
const factory TabsEvent.selectTab(int index) = _SelectTab;
|
||||
const factory TabsEvent.openTab({
|
||||
required Plugin plugin,
|
||||
|
||||
Loaded 100 of 334 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user