Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2c669f1b85 | ||
|
|
b9976b072e | ||
|
|
409e3bf1e3 | ||
|
|
ff9b3c56c5 | ||
|
|
4c17298432 | ||
|
|
0401fc4b22 | ||
|
|
4805d0c568 | ||
|
|
edc7933c66 | ||
|
|
89ff44b035 | ||
|
|
793884427c | ||
|
|
e0ad364fa3 | ||
|
|
5d76a7864d | ||
|
|
323cb3b60f | ||
|
|
9fb8f221cf | ||
|
|
8dfbfe3c42 | ||
|
|
453be8352a | ||
|
|
452d7eb6d0 | ||
|
|
f0d5f51703 |
No files matched your search
@@ -134,6 +134,6 @@ jobs:
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
os: ${{ matrix.os }}
|
||||
attempt_limit: 5
|
||||
attempt_limit: 20
|
||||
attempt_delay: 10000
|
||||
|
||||
@@ -29,25 +29,31 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
tests:
|
||||
build:
|
||||
if: github.event.pull_request.draft != true
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
|
||||
os: [ubuntu-latest, windows-latest]
|
||||
include:
|
||||
- os: ubuntu-latest
|
||||
flutter_profile: development-linux-x86_64
|
||||
target: x86_64-unknown-linux-gnu
|
||||
- os: windows-latest
|
||||
flutter_profile: development-windows-x86
|
||||
target: x86_64-pc-windows-msvc
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: "stable-2022-04-07"
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Install Rust toolchain
|
||||
id: rust_toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: ${{ env.RUST_TOOLCHAIN }}
|
||||
target: ${{ matrix.target }}
|
||||
override: true
|
||||
profile: minimal
|
||||
|
||||
@@ -64,6 +70,7 @@ jobs:
|
||||
prefix-key: ${{ matrix.os }}
|
||||
workspaces: |
|
||||
frontend/rust-lib
|
||||
cache-all-crates: true
|
||||
|
||||
- uses: davidB/rust-cargo-make@v1
|
||||
with:
|
||||
@@ -87,30 +94,24 @@ jobs:
|
||||
cargo make appflowy-flutter-deps-tools
|
||||
shell: bash
|
||||
|
||||
- name: Config Flutter
|
||||
- name: Enable Flutter Desktop
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
flutter config --enable-linux-desktop
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
flutter config --enable-macos-desktop
|
||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||
git config --system core.longpaths true
|
||||
flutter config --enable-windows-desktop
|
||||
fi
|
||||
shell: bash
|
||||
|
||||
- name: Build Test lib
|
||||
- name: Build AppFlowy
|
||||
working-directory: frontend
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
cargo make --profile development-linux-x86_64 appflowy-dev
|
||||
elif [ "$RUNNER_OS" == "macOS" ]; then
|
||||
cargo make --profile development-mac-x86_64 appflowy-dev
|
||||
elif [ "$RUNNER_OS" == "Windows" ]; then
|
||||
cargo make --profile development-windows-x86 appflowy-dev
|
||||
fi
|
||||
shell: bash
|
||||
cargo make --profile ${{ matrix.flutter_profile }} appflowy-dev
|
||||
|
||||
- name: Run AppFlowy tests
|
||||
- name: Run Flutter integration tests
|
||||
working-directory: frontend/appflowy_flutter
|
||||
run: |
|
||||
if [ "$RUNNER_OS" == "Linux" ]; then
|
||||
@@ -135,5 +136,5 @@ jobs:
|
||||
fail_ci_if_error: true
|
||||
verbose: true
|
||||
os: ${{ matrix.os }}
|
||||
attempt_limit: 5
|
||||
attempt_limit: 20
|
||||
attempt_delay: 10000
|
||||
+17
-3
@@ -1,10 +1,24 @@
|
||||
# Release Notes
|
||||
|
||||
## Version 0.2.4 - 06/23/2023
|
||||
## Version 0.2.6 - 07/11/2023
|
||||
|
||||
### Bug fixes:
|
||||
### New Features
|
||||
|
||||
- Dynamic load themes
|
||||
- Inline math equation
|
||||
|
||||
|
||||
## Version 0.2.5 - 07/02/2023
|
||||
|
||||
### New Features
|
||||
|
||||
- Insert local images
|
||||
- Mention a page
|
||||
- Outlines (Table of contents)
|
||||
- Added support for aligning the image by image menu
|
||||
|
||||
### Bug fixes
|
||||
|
||||
- Unable to copy and paste a word
|
||||
- Some UI issues
|
||||
|
||||
## Version 0.2.4 - 06/23/2023
|
||||
|
||||
Vendored
+31
-10
@@ -118,15 +118,11 @@
|
||||
{
|
||||
"label": "AF: Generate Freezed Files",
|
||||
"type": "shell",
|
||||
"command": "dart run build_runner build -d",
|
||||
"command": "sh ./scripts/code_generation/freezed/generate_freezed.sh",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/appflowy_flutter"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "AF: Generate Language Files",
|
||||
"type": "shell",
|
||||
"command": "sh ./scripts/generate_language_files.sh",
|
||||
"cwd": "${workspaceFolder}"
|
||||
},
|
||||
"group": "build",
|
||||
"windows": {
|
||||
"options": {
|
||||
"shell": {
|
||||
@@ -134,7 +130,24 @@
|
||||
"args": [
|
||||
"/d",
|
||||
"/c",
|
||||
".\\scripts\\generate_language_files.cmd"
|
||||
".\\scripts\\code_generation\\freezed\\generate_freezed.cmd"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
"label": "AF: Generate Language Files",
|
||||
"type": "shell",
|
||||
"command": "sh ./scripts/code_generation/language_files/generate_language_files.sh",
|
||||
"windows": {
|
||||
"options": {
|
||||
"shell": {
|
||||
"executable": "cmd.exe",
|
||||
"args": [
|
||||
"/d",
|
||||
"/c",
|
||||
".\\scripts\\code_generation\\language_files\\generate_language_files.cmd"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -221,5 +234,13 @@
|
||||
"cwd": "${workspaceFolder}/appflowy_tauri"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "AF: Generate Env",
|
||||
"type": "shell",
|
||||
"command": "dart run build_runner clean && dart run build_runner build --delete-conflicting-outputs ",
|
||||
"options": {
|
||||
"cwd": "${workspaceFolder}/appflowy_flutter"
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,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.5"
|
||||
CURRENT_APP_VERSION = "0.2.6"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
|
||||
@@ -70,6 +70,7 @@ windows/flutter/dart_ffi/
|
||||
**/.vscode/
|
||||
|
||||
*.env
|
||||
*.env.*
|
||||
|
||||
coverage/
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns:xlink="http://www.w3.org/1999/xlink" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 30 30" style="width: 16px; height: 16px; display: block; fill: inherit; flex-shrink: 0; backface-visibility: hidden;" width="30" height="30" ><path d="M6.04883 27.3232C7.24219 27.3232 7.86426 26.4854 8.20703 25.3936L13.9834 6.79492H27.5039C28.2275 6.79492 28.7227 6.35059 28.7227 5.65234C28.7227 4.97949 28.2275 4.53516 27.5039 4.53516H13.958C12.6631 4.53516 12.0791 5.01758 11.7363 6.13477L6.18848 24.3525H5.97266L3.58594 15.9355C3.38281 15.2373 3.00195 14.9072 2.40527 14.9072C1.73242 14.9072 1.2373 15.3896 1.2373 16.0117C1.2373 16.2656 1.30078 16.4941 1.35156 16.6846L4.04297 25.5332C4.36035 26.5615 4.93164 27.3232 6.04883 27.3232ZM16.3955 24.7334C16.8652 24.7334 17.1064 24.5684 17.4619 24.0732L20.4707 19.8203H20.5215L23.5049 24.0732C23.873 24.5684 24.1143 24.7334 24.5713 24.7334C25.2061 24.7334 25.6758 24.3018 25.6758 23.7051C25.6758 23.4258 25.6123 23.1973 25.4219 22.9561L21.9307 18.208L25.4473 13.4346C25.6377 13.168 25.7139 12.9395 25.7139 12.6855C25.7139 12.127 25.2568 11.6953 24.6475 11.6953C24.2031 11.6953 23.9365 11.8477 23.6064 12.3174L20.7246 16.5957H20.6611L17.6904 12.3047C17.373 11.8477 17.0938 11.6953 16.624 11.6953C16.0146 11.6953 15.5068 12.165 15.5068 12.7363C15.5068 13.0537 15.583 13.2568 15.8115 13.5488L19.1504 18.1445L15.6084 23.0322C15.418 23.2861 15.3672 23.4639 15.3672 23.7559C15.3672 24.3018 15.8115 24.7334 16.3955 24.7334Z" fill="#2F3030"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -419,4 +419,4 @@
|
||||
"layoutDateField": "تقويم التخطيط بواسطة"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -149,5 +149,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "petit"
|
||||
}
|
||||
@@ -186,13 +186,29 @@
|
||||
"open": "Open Settings"
|
||||
},
|
||||
"appearance": {
|
||||
"fontFamily": {
|
||||
"label": "Font Family",
|
||||
"search": "Search"
|
||||
},
|
||||
"themeMode": {
|
||||
"label": "Theme Mode",
|
||||
"light": "Light Mode",
|
||||
"dark": "Dark Mode",
|
||||
"system": "Adapt to System"
|
||||
},
|
||||
"theme": "Theme"
|
||||
"themeUpload": {
|
||||
"button": "Upload",
|
||||
"description": "Upload your own AppFlowy theme using the button below.",
|
||||
"failure": "The theme that was uploaded had an invalid format.",
|
||||
"loading": "Please wait while we validate and upload your theme...",
|
||||
"uploadSuccess": "Your theme was uploaded successfully",
|
||||
"deletionFailure": "Failed to delete the theme. Try to delete it manually.",
|
||||
"filePickerDialogTitle": "Choose a .flowy_plugin file",
|
||||
"urlUploadFailure": "Failed to open url: {}"
|
||||
},
|
||||
"theme": "Theme",
|
||||
"builtInsLabel": "Built-in Themes",
|
||||
"pluginsLabel": "Plugins"
|
||||
},
|
||||
"files": {
|
||||
"copy": "Copy",
|
||||
@@ -410,6 +426,7 @@
|
||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||
"discardResponse": "Do you want to discard the AI responses?",
|
||||
"createInlineMathEquation": "Create equation",
|
||||
"cover": {
|
||||
"changeCover": "Change Cover",
|
||||
"colors": "Colors",
|
||||
|
||||
@@ -227,5 +227,6 @@
|
||||
"column": {
|
||||
"create_new_card": "Nuevo"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreAction.small": "pequeño"
|
||||
}
|
||||
@@ -149,5 +149,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "petit"
|
||||
}
|
||||
@@ -235,5 +235,6 @@
|
||||
"column": {
|
||||
"create_new_card": "Nouveau"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreAction.small": "petit"
|
||||
}
|
||||
@@ -147,7 +147,9 @@
|
||||
}
|
||||
},
|
||||
"sideBar": {
|
||||
"openSidebar": "Oldalsáv megnyitása",
|
||||
"closeSidebar": "Oldalsáv bezárása"
|
||||
}
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
},
|
||||
"moreAction.small": "kicsi"
|
||||
}
|
||||
|
||||
@@ -222,5 +222,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "kecil"
|
||||
}
|
||||
@@ -155,5 +155,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "piccolo"
|
||||
}
|
||||
@@ -203,5 +203,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "小さい"
|
||||
}
|
||||
@@ -231,5 +231,6 @@
|
||||
"column": {
|
||||
"create_new_card": "추가"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreAction.small": "작은"
|
||||
}
|
||||
@@ -149,5 +149,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "mały"
|
||||
}
|
||||
@@ -145,5 +145,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "pequeno"
|
||||
}
|
||||
@@ -235,5 +235,6 @@
|
||||
"column": {
|
||||
"create_new_card": "Nytt"
|
||||
}
|
||||
}
|
||||
},
|
||||
"moreAction.small": "små"
|
||||
}
|
||||
@@ -149,5 +149,6 @@
|
||||
"sideBar": {
|
||||
"openSidebar": "Open sidebar",
|
||||
"closeSidebar": "Close sidebar"
|
||||
}
|
||||
},
|
||||
"moreAction.small": "küçük"
|
||||
}
|
||||
@@ -458,6 +458,7 @@
|
||||
"menuName": "看板",
|
||||
"referencedBoardPrefix": "视图"
|
||||
},
|
||||
"moreAction.small": "小的",
|
||||
"calendar": {
|
||||
"menuName": "日历",
|
||||
"defaultNewCalendarTitle": "未命名",
|
||||
@@ -485,3 +486,4 @@
|
||||
"github": "在 GitHub 查看"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,22 +9,7 @@ import 'util/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('database', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
group('calendar database view', () {
|
||||
testWidgets('update calendar layout', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
@@ -75,7 +60,7 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('create new calendar event using new button', (tester) async {
|
||||
testWidgets('creating and editing calendar events', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -98,28 +83,8 @@ void main() {
|
||||
// Dismiss the row details page
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Make sure that the event is inserted in the cell
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
// Create another event on the same day
|
||||
await tester.hoverOnTodayCalendarCell();
|
||||
await tester.tapAddCalendarEventButton();
|
||||
await tester.dismissRowDetailPage();
|
||||
tester.assertNumberOfEventsInCalendar(2);
|
||||
tester.assertNumberofEventsOnSpecificDay(2, DateTime.now());
|
||||
});
|
||||
|
||||
testWidgets('create new calendar event by double-clicking', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// Create the calendar view
|
||||
await tester.tapAddButton();
|
||||
await tester.tapCreateCalendarButton();
|
||||
|
||||
// Scroll until today's date cell is visible
|
||||
await tester.scrollToToday();
|
||||
|
||||
// Double click on today's calendar cell to create a new event
|
||||
await tester.doubleClickCalendarCell(DateTime.now());
|
||||
|
||||
@@ -130,74 +95,42 @@ void main() {
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Make sure that the event is inserted in the cell
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
|
||||
// Create another event on the same day
|
||||
await tester.doubleClickCalendarCell(DateTime.now());
|
||||
await tester.dismissRowDetailPage();
|
||||
tester.assertNumberOfEventsInCalendar(2);
|
||||
tester.assertNumberofEventsOnSpecificDay(2, DateTime.now());
|
||||
});
|
||||
|
||||
testWidgets('duplicate/delete a calendar event', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
// Click on the event
|
||||
await tester.openCalendarEvent(index: 0);
|
||||
tester.assertRowDetailPageOpened();
|
||||
|
||||
// Create the calendar view
|
||||
await tester.tapAddButton();
|
||||
await tester.tapCreateCalendarButton();
|
||||
// Change the title of the event
|
||||
await tester.editTitleInRowDetailPage('hello world');
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Create a new event in today's calendar cell
|
||||
await tester.scrollToToday();
|
||||
await tester.doubleClickCalendarCell(DateTime.now());
|
||||
// Make sure that the event is edited
|
||||
tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
|
||||
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
|
||||
|
||||
// Click on the event
|
||||
await tester.openCalendarEvent(index: 1);
|
||||
tester.assertRowDetailPageOpened();
|
||||
|
||||
// Duplicate the event
|
||||
await tester.tapRowDetailPageDuplicateRowButton();
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Check that there are 2 events
|
||||
tester.assertNumberOfEventsInCalendar(2);
|
||||
tester.assertNumberOfEventsInCalendar(2, title: 'hello world');
|
||||
tester.assertNumberOfEventsOnSpecificDay(3, DateTime.now());
|
||||
|
||||
// Delete an event
|
||||
await tester.openCalendarEvent(index: 0);
|
||||
await tester.openCalendarEvent(index: 1);
|
||||
await tester.tapRowDetailPageDeleteRowButton();
|
||||
|
||||
// Check that there is 1 event
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
});
|
||||
|
||||
testWidgets('edit the title of a calendar date event', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// Create the calendar view
|
||||
await tester.tapAddButton();
|
||||
await tester.tapCreateCalendarButton();
|
||||
|
||||
// Create a new event in today's calendar cell
|
||||
await tester.scrollToToday();
|
||||
await tester.doubleClickCalendarCell(DateTime.now());
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Click on the event
|
||||
await tester.openCalendarEvent(index: 0);
|
||||
|
||||
// Make sure that the row details page is opened
|
||||
tester.assertRowDetailPageOpened();
|
||||
|
||||
// Change the title of the event
|
||||
await tester.editTitleInRowDetailPage('hello world');
|
||||
|
||||
// Dismiss the row details page
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Make sure that the event is edited
|
||||
tester.assertNumberOfEventsInCalendar(1, title: 'hello world');
|
||||
tester.assertNumberOfEventsOnSpecificDay(2, DateTime.now());
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'edit the date of the date field being used to layout the calendar',
|
||||
(tester) async {
|
||||
testWidgets('rescheduling events', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -205,14 +138,31 @@ void main() {
|
||||
await tester.tapAddButton();
|
||||
await tester.tapCreateCalendarButton();
|
||||
|
||||
// Create a new event in today's calendar cell
|
||||
// Create a new event on the first of this month
|
||||
final today = DateTime.now();
|
||||
final firstOfThisMonth = DateTime(today.year, today.month, 1);
|
||||
await tester.doubleClickCalendarCell(firstOfThisMonth);
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Drag and drop the event onto the next week, same day
|
||||
await tester.dragDropRescheduleCalendarEvent(firstOfThisMonth);
|
||||
|
||||
// Make sure that the event has been rescheduled to the new date
|
||||
final sameDayNextWeek = firstOfThisMonth.add(const Duration(days: 7));
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
tester.assertNumberOfEventsOnSpecificDay(1, sameDayNextWeek);
|
||||
|
||||
// Delete the event
|
||||
await tester.openCalendarEvent(index: 0, date: sameDayNextWeek);
|
||||
await tester.tapRowDetailPageDeleteRowButton();
|
||||
|
||||
// Create a new event in today's calendar cell
|
||||
await tester.scrollToToday();
|
||||
await tester.doubleClickCalendarCell(today);
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Make sure that the event is today
|
||||
tester.assertNumberofEventsOnSpecificDay(1, today);
|
||||
tester.assertNumberOfEventsOnSpecificDay(1, today);
|
||||
|
||||
// Click on the event
|
||||
await tester.openCalendarEvent(index: 0);
|
||||
@@ -233,32 +183,7 @@ void main() {
|
||||
|
||||
// Make sure that the event is edited
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
tester.assertNumberofEventsOnSpecificDay(1, newDate);
|
||||
});
|
||||
|
||||
testWidgets('reschedule an event by drag-and-drop', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// Create the calendar view
|
||||
await tester.tapAddButton();
|
||||
await tester.tapCreateCalendarButton();
|
||||
|
||||
// Create a new event on the first of this month
|
||||
final today = DateTime.now();
|
||||
final firstOfThisMonth = DateTime(today.year, today.month, 1);
|
||||
await tester.doubleClickCalendarCell(firstOfThisMonth);
|
||||
await tester.dismissRowDetailPage();
|
||||
|
||||
// Drag and drop the event onto the next week, same day
|
||||
await tester.dragDropRescheduleCalendarEvent(firstOfThisMonth);
|
||||
|
||||
// Make sure that the event has been rescheduled to the new date
|
||||
tester.assertNumberOfEventsInCalendar(1);
|
||||
tester.assertNumberofEventsOnSpecificDay(
|
||||
1,
|
||||
firstOfThisMonth.add(const Duration(days: 7)),
|
||||
);
|
||||
tester.assertNumberOfEventsOnSpecificDay(1, newDate);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -11,21 +11,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid cell', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('edit text cell', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -10,21 +10,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid page', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('rename existing field', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -5,27 +5,11 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/database_test_op.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid', () {
|
||||
const location = 'import_files';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('add text filter', (tester) async {
|
||||
await tester.openV020database();
|
||||
|
||||
|
||||
@@ -14,21 +14,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('row details page opens', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
@@ -187,9 +172,10 @@ void main() {
|
||||
// Focus on the editor
|
||||
final textBlock = find.byType(TextBlockComponentWidget);
|
||||
await tester.tapAt(tester.getCenter(textBlock));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Input some text
|
||||
const inputText = 'Hello world';
|
||||
const inputText = 'Hello World';
|
||||
await tester.ime.insertText(inputText);
|
||||
expect(
|
||||
find.textContaining(inputText, findRichText: true),
|
||||
|
||||
@@ -8,21 +8,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('create row of the grid', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -9,21 +9,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('update layout', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -1,66 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pbenum.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/database_test_op.dart';
|
||||
import 'util/mock/mock_file_picker.dart';
|
||||
import 'util/util.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('database', () {
|
||||
const location = 'import_files';
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('import v0.2.0 database data', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// expect to see a readme page
|
||||
tester.expectToSeePageName(readme);
|
||||
|
||||
await tester.tapAddButton();
|
||||
await tester.tapImportButton();
|
||||
|
||||
final testFileNames = ['v020.afdb'];
|
||||
final fileLocation = await tester.currentFileLocation();
|
||||
for (final fileName in testFileNames) {
|
||||
final str = await rootBundle.loadString(
|
||||
p.join(
|
||||
'assets/test/workspaces/database',
|
||||
fileName,
|
||||
),
|
||||
);
|
||||
File(p.join(fileLocation, fileName)).writeAsStringSync(str);
|
||||
}
|
||||
// mock get files
|
||||
await mockPickFilePaths(testFileNames, name: location);
|
||||
await tester.tapDatabaseRawDataButton();
|
||||
await tester.openPage('v020');
|
||||
|
||||
// check the import content
|
||||
// await tester.assertCellContent(
|
||||
// rowIndex: 7,
|
||||
// fieldType: FieldType.RichText,
|
||||
// // fieldName: 'Name',
|
||||
// content: '',
|
||||
// );
|
||||
await tester.openV020database();
|
||||
// wait the database data is loaded
|
||||
await tester.pumpAndSettle(const Duration(microseconds: 500));
|
||||
|
||||
// check the text cell
|
||||
final textCells = <String>['A', 'B', 'C', 'D', 'E', '', '', '', '', ''];
|
||||
|
||||
@@ -3,27 +3,11 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/database_test_op.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('grid', () {
|
||||
const location = 'import_files';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('add text sort', (tester) async {
|
||||
await tester.openV020database();
|
||||
// create a filter
|
||||
|
||||
@@ -11,21 +11,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('database', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('create linked view', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
+1
-16
@@ -9,22 +9,7 @@ import '../util/util.dart';
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
group('create and delete the document', () {
|
||||
testWidgets('create a new document when launching app in first time',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
@@ -0,0 +1,22 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'document_create_and_delete_test.dart'
|
||||
as document_create_and_delete_test;
|
||||
import 'document_with_cover_image_test.dart' as document_with_cover_image_test;
|
||||
import 'document_with_database_test.dart' as document_with_database_test;
|
||||
import 'document_with_inline_math_equation_test.dart'
|
||||
as document_with_inline_math_equation_test;
|
||||
import 'document_with_inline_page_test.dart' as document_with_inline_page_test;
|
||||
import 'edit_document_test.dart' as document_edit_test;
|
||||
|
||||
void startTesting() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
// Document integration tests
|
||||
document_create_and_delete_test.main();
|
||||
document_edit_test.main();
|
||||
document_with_database_test.main();
|
||||
document_with_inline_page_test.main();
|
||||
document_with_inline_math_equation_test.main();
|
||||
document_with_cover_image_test.main();
|
||||
}
|
||||
-15
@@ -9,21 +9,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('cover image', () {
|
||||
const location = 'cover_image';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('document cover tests', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
@@ -14,17 +14,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('database view in document', () {
|
||||
const location = 'database_view';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('insert a referenced grid', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
+121
@@ -0,0 +1,121 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import '../util/ime.dart';
|
||||
import '../util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
TestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('inline math equation in document', () {
|
||||
testWidgets('insert an inline math equation', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithName(
|
||||
ViewLayoutPB.Document,
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// insert a inline page
|
||||
const formula = 'E = MC ^ 2';
|
||||
await tester.ime.insertText(formula);
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
final inlineMathEquationButton = find.byTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
|
||||
// expect to see the math equation block
|
||||
final inlineMathEquation = find.byType(InlineMathEquation);
|
||||
expect(inlineMathEquation, findsOneWidget);
|
||||
|
||||
// tap it and update the content
|
||||
await tester.tapButton(inlineMathEquation);
|
||||
final textFormField = find.descendant(
|
||||
of: find.byType(MathInputTextField),
|
||||
matching: find.byType(TextFormField),
|
||||
);
|
||||
const newFormula = 'E = MC ^ 3';
|
||||
await tester.enterText(textFormField, newFormula);
|
||||
await tester.tapButton(
|
||||
find.descendant(
|
||||
of: find.byType(MathInputTextField),
|
||||
matching: find.byType(FlowyButton),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle();
|
||||
});
|
||||
|
||||
testWidgets('remove the inline math equation format', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// create a new document
|
||||
await tester.createNewPageWithName(
|
||||
ViewLayoutPB.Document,
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
|
||||
// tap the first line of the document
|
||||
await tester.editor.tapLineOfEditorAt(0);
|
||||
// insert a inline page
|
||||
const formula = 'E = MC ^ 2';
|
||||
await tester.ime.insertText(formula);
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0, endOffset: formula.length),
|
||||
);
|
||||
|
||||
// tap the inline math equation button
|
||||
var inlineMathEquationButton = find.byTooltip(
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
|
||||
// expect to see the math equation block
|
||||
var inlineMathEquation = find.byType(InlineMathEquation);
|
||||
expect(inlineMathEquation, findsOneWidget);
|
||||
|
||||
// highlight the math equation block
|
||||
await tester.editor.updateSelection(
|
||||
Selection.single(path: [0], startOffset: 0, endOffset: 1),
|
||||
);
|
||||
|
||||
// expect to the see the inline math equation button is highlighted
|
||||
inlineMathEquationButton = find.byWidgetPredicate(
|
||||
(widget) =>
|
||||
widget is IconItemWidget &&
|
||||
widget.tooltip ==
|
||||
LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
);
|
||||
expect(
|
||||
tester.widget<IconItemWidget>(inlineMathEquationButton).isHighlight,
|
||||
isTrue,
|
||||
);
|
||||
|
||||
// cancel the format
|
||||
await tester.tapButton(inlineMathEquationButton);
|
||||
|
||||
// expect to see the math equation block is removed
|
||||
inlineMathEquation = find.byType(InlineMathEquation);
|
||||
expect(inlineMathEquation, findsNothing);
|
||||
|
||||
tester.expectToSeeText(formula);
|
||||
});
|
||||
});
|
||||
}
|
||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.1 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
-11
@@ -11,17 +11,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('inline page view in document', () {
|
||||
const location = 'inline_page';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('insert a inline page - grid', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -13,17 +13,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('edit document', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('redo & undo', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
import 'dart:io';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/menu.dart';
|
||||
import 'package:appflowy/workspace/presentation/settings/settings_dialog.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'util/keyboard.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('hotkeys test', () {
|
||||
testWidgets('toggle theme mode', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
|
||||
await tester.tapGoButton();
|
||||
tester.expectToSeeHomePage();
|
||||
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.appearance);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tester.expectToSeeText(
|
||||
LocaleKeys.settings_appearance_themeMode_system.tr(),
|
||||
);
|
||||
|
||||
await tester.tapButton(
|
||||
find.bySemanticsLabel(
|
||||
LocaleKeys.settings_appearance_themeMode_system.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tapButton(
|
||||
find.bySemanticsLabel(
|
||||
LocaleKeys.settings_appearance_themeMode_dark.tr(),
|
||||
),
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle(const Duration(seconds: 1));
|
||||
|
||||
await tester.tap(find.byType(SettingsDialog));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
[
|
||||
Platform.isMacOS
|
||||
? LogicalKeyboardKey.meta
|
||||
: LogicalKeyboardKey.control,
|
||||
LogicalKeyboardKey.shift,
|
||||
LogicalKeyboardKey.keyL,
|
||||
],
|
||||
tester: tester,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
tester.expectToSeeText(
|
||||
LocaleKeys.settings_appearance_themeMode_light.tr(),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('show or hide home menu', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
|
||||
await tester.tapGoButton();
|
||||
tester.expectToSeeHomePage();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(HomeMenu), findsOneWidget);
|
||||
|
||||
await FlowyTestKeyboard.simulateKeyDownEvent(
|
||||
[
|
||||
Platform.isMacOS
|
||||
? LogicalKeyboardKey.meta
|
||||
: LogicalKeyboardKey.control,
|
||||
LogicalKeyboardKey.backslash,
|
||||
],
|
||||
tester: tester,
|
||||
);
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(HomeMenu), findsNothing);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -12,23 +12,8 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('import files', () {
|
||||
const location = 'import_files';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('import multiple markdown files', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
final context = await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// expect to see a readme page
|
||||
@@ -38,18 +23,21 @@ void main() {
|
||||
await tester.tapImportButton();
|
||||
|
||||
final testFileNames = ['test1.md', 'test2.md'];
|
||||
final fileLocation = await tester.currentFileLocation();
|
||||
final paths = <String>[];
|
||||
for (final fileName in testFileNames) {
|
||||
final str = await rootBundle.loadString(
|
||||
p.join(
|
||||
'assets/test/workspaces/markdowns',
|
||||
fileName,
|
||||
),
|
||||
'assets/test/workspaces/markdowns/$fileName',
|
||||
);
|
||||
File(p.join(fileLocation, fileName)).writeAsStringSync(str);
|
||||
final path = p.join(context.applicationDataDirectory, fileName);
|
||||
paths.add(path);
|
||||
File(path).writeAsStringSync(str);
|
||||
}
|
||||
// mock get files
|
||||
await mockPickFilePaths(testFileNames, name: location);
|
||||
await mockPickFilePaths(
|
||||
paths: testFileNames
|
||||
.map((e) => p.join(context.applicationDataDirectory, e))
|
||||
.toList(),
|
||||
);
|
||||
|
||||
await tester.tapTextAndMarkdownButton();
|
||||
|
||||
|
||||
@@ -8,16 +8,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('document', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUpAll(() async {
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'change the language successfully when launching the app for the first time',
|
||||
(tester) async {
|
||||
|
||||
@@ -17,7 +17,8 @@ void main() {
|
||||
setUpAll(() async => await service.setUpAll());
|
||||
setUp(() async => await service.setUp());
|
||||
|
||||
testWidgets('testing selection on open-ai smart menu replace', (tester) async {
|
||||
testWidgets('testing selection on open-ai smart menu replace',
|
||||
(tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
@@ -38,7 +39,8 @@ void main() {
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
|
||||
await tester
|
||||
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
@@ -49,7 +51,8 @@ void main() {
|
||||
),
|
||||
);
|
||||
});
|
||||
testWidgets('testing selection on open-ai smart menu insert', (tester) async {
|
||||
testWidgets('testing selection on open-ai smart menu insert',
|
||||
(tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
@@ -69,7 +72,8 @@ void main() {
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
|
||||
await tester
|
||||
.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
|
||||
@@ -12,17 +12,6 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('outline block test', () {
|
||||
const location = 'outline_test';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('insert an outline block', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
@@ -1,23 +1,19 @@
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'switch_folder_test.dart' as switch_folder_test;
|
||||
import 'document/document_test.dart' as document_test;
|
||||
import 'document/cover_image_test.dart' as cover_image_test;
|
||||
import 'share_markdown_test.dart' as share_markdown_test;
|
||||
import 'import_files_test.dart' as import_files_test;
|
||||
import 'document/document_with_database_test.dart'
|
||||
as document_with_database_test;
|
||||
import 'document/edit_document_test.dart' as edit_document_test;
|
||||
import 'database_calendar_test.dart' as database_calendar_test;
|
||||
import 'database_cell_test.dart' as database_cell_test;
|
||||
import 'database_field_test.dart' as database_field_test;
|
||||
import 'database_share_test.dart' as database_share_test;
|
||||
import 'database_filter_test.dart' as database_filter_test;
|
||||
import 'database_row_page_test.dart' as database_row_page_test;
|
||||
import 'database_row_test.dart' as database_row_test;
|
||||
import 'database_setting_test.dart' as database_setting_test;
|
||||
import 'database_filter_test.dart' as database_filter_test;
|
||||
import 'database_view_test.dart' as database_view_test;
|
||||
import 'database_calendar_test.dart' as database_calendar_test;
|
||||
import 'database_share_test.dart' as database_share_test;
|
||||
import 'database_sort_test.dart' as database_sort_test;
|
||||
import 'database_view_test.dart' as database_view_test;
|
||||
import 'document/document_test_runner.dart' as document_test_runner;
|
||||
import 'import_files_test.dart' as import_files_test;
|
||||
import 'share_markdown_test.dart' as share_markdown_test;
|
||||
import 'switch_folder_test.dart' as switch_folder_test;
|
||||
|
||||
/// The main task runner for all integration tests in AppFlowy.
|
||||
///
|
||||
@@ -33,10 +29,7 @@ void main() {
|
||||
import_files_test.main();
|
||||
|
||||
// Document integration tests
|
||||
cover_image_test.main();
|
||||
document_test.main();
|
||||
document_with_database_test.main();
|
||||
edit_document_test.main();
|
||||
document_test_runner.startTesting();
|
||||
|
||||
// Database integration tests
|
||||
database_cell_test.main();
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'dart:io';
|
||||
import 'package:appflowy/plugins/document/presentation/share/share_button.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'util/mock/mock_file_picker.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
@@ -11,30 +11,17 @@ void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('share markdown in document page', () {
|
||||
const location = 'markdown';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('click the share button in document page', (tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
final context = await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// expect to see a readme page
|
||||
tester.expectToSeePageName(readme);
|
||||
|
||||
// mock the file picker
|
||||
final path = await mockSaveFilePath(location, 'test.md');
|
||||
final path = await mockSaveFilePath(
|
||||
p.join(context.applicationDataDirectory, 'test.md'),
|
||||
);
|
||||
// click the share button and select markdown
|
||||
await tester.tapShareButton();
|
||||
await tester.tapMarkdownButton();
|
||||
@@ -52,7 +39,7 @@ void main() {
|
||||
testWidgets(
|
||||
'share the markdown after renaming the document name',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
final context = await tester.initializeAppFlowy();
|
||||
await tester.tapGoButton();
|
||||
|
||||
// expect to see a readme page
|
||||
@@ -65,8 +52,12 @@ void main() {
|
||||
final shareButton = find.byType(ShareActionList);
|
||||
final shareButtonState =
|
||||
tester.state(shareButton) as ShareActionListState;
|
||||
final path =
|
||||
await mockSaveFilePath(location, '${shareButtonState.name}.md');
|
||||
final path = await mockSaveFilePath(
|
||||
p.join(
|
||||
context.applicationDataDirectory,
|
||||
'${shareButtonState.name}.md',
|
||||
),
|
||||
);
|
||||
|
||||
// click the share button and select markdown
|
||||
await tester.tapShareButton();
|
||||
|
||||
@@ -1,48 +1,36 @@
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
|
||||
import 'package:path/path.dart' as p;
|
||||
import 'util/mock/mock_file_picker.dart';
|
||||
import 'util/util.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
group('customize the folder path', () {
|
||||
const location = 'appflowy';
|
||||
|
||||
setUp(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
await TestFolder.setTestLocation(location);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
await TestFolder.cleanTestLocation(location);
|
||||
});
|
||||
|
||||
tearDownAll(() async {
|
||||
await TestFolder.cleanTestLocation(null);
|
||||
});
|
||||
|
||||
testWidgets('switch to B from A, then switch to A again', (tester) async {
|
||||
final userA = uuid();
|
||||
final userB = uuid();
|
||||
const userA = 'UserA';
|
||||
const userB = 'UserB';
|
||||
|
||||
await TestFolder.cleanTestLocation(userA);
|
||||
await TestFolder.cleanTestLocation(userB);
|
||||
await TestFolder.setTestLocation(p.join(userA, appFlowyDataFolder));
|
||||
|
||||
await tester.initializeAppFlowy();
|
||||
final initialPath = p.join(userA, appFlowyDataFolder);
|
||||
final context = await tester.initializeAppFlowy(
|
||||
pathExtension: initialPath,
|
||||
);
|
||||
// remove the last extension
|
||||
final rootPath = context.applicationDataDirectory.replaceFirst(
|
||||
initialPath,
|
||||
'',
|
||||
);
|
||||
|
||||
await tester.tapGoButton();
|
||||
tester.expectToSeeHomePage();
|
||||
|
||||
// switch to user B
|
||||
{
|
||||
// set user name to userA
|
||||
// set user name for userA
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.user);
|
||||
await tester.enterUserName(userA);
|
||||
@@ -51,12 +39,14 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// mock the file_picker result
|
||||
await mockGetDirectoryPath(userB);
|
||||
await mockGetDirectoryPath(
|
||||
p.join(rootPath, userB),
|
||||
);
|
||||
await tester.tapCustomLocationButton();
|
||||
await tester.pumpAndSettle();
|
||||
tester.expectToSeeHomePage();
|
||||
|
||||
// set user name to userB
|
||||
// set user name for userB
|
||||
await tester.openSettings();
|
||||
await tester.openSettingsPage(SettingsPage.user);
|
||||
await tester.enterUserName(userB);
|
||||
@@ -68,7 +58,9 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// mock the file_picker result
|
||||
await mockGetDirectoryPath(userA);
|
||||
await mockGetDirectoryPath(
|
||||
p.join(rootPath, userA),
|
||||
);
|
||||
await tester.tapCustomLocationButton();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
@@ -83,16 +75,15 @@ void main() {
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// mock the file_picker result
|
||||
await mockGetDirectoryPath(userB);
|
||||
await mockGetDirectoryPath(
|
||||
p.join(rootPath, userB),
|
||||
);
|
||||
await tester.tapCustomLocationButton();
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
tester.expectToSeeHomePage();
|
||||
tester.expectToSeeUserName(userB);
|
||||
}
|
||||
|
||||
await TestFolder.cleanTestLocation(userA);
|
||||
await TestFolder.cleanTestLocation(userB);
|
||||
});
|
||||
|
||||
testWidgets('reset to default location', (tester) async {
|
||||
@@ -109,8 +100,8 @@ void main() {
|
||||
await tester.restoreLocation();
|
||||
|
||||
expect(
|
||||
await TestFolder.defaultDevelopmentLocation(),
|
||||
await TestFolder.currentLocation(),
|
||||
await appFlowyApplicationDataDirectory().then((value) => value.path),
|
||||
await getIt<ApplicationDataStorage>().getPath(),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,88 +1,88 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/entry_point.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class TestFolder {
|
||||
/// Location / Path
|
||||
class FlowyTestContext {
|
||||
FlowyTestContext({
|
||||
required this.applicationDataDirectory,
|
||||
});
|
||||
|
||||
/// Set a given AppFlowy data storage location under test environment.
|
||||
///
|
||||
/// To pass null means clear the location.
|
||||
///
|
||||
/// The file_picker is a system component and can't be tapped, so using logic instead of tapping.
|
||||
///
|
||||
static Future<void> setTestLocation(String? name) async {
|
||||
final location = await testLocation(name);
|
||||
SharedPreferences.setMockInitialValues({
|
||||
KVKeys.pathLocation: location.path,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
/// Clean the location.
|
||||
static Future<void> cleanTestLocation(String? name) async {
|
||||
final dir = await testLocation(name);
|
||||
await dir.delete(recursive: true);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Get current using location.
|
||||
static Future<String> currentLocation() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
return prefs.getString(KVKeys.pathLocation)!;
|
||||
}
|
||||
|
||||
/// Get default location under development environment.
|
||||
static Future<String> defaultDevelopmentLocation() async {
|
||||
final dir = await appFlowyApplicationDataDirectory();
|
||||
return dir.path;
|
||||
}
|
||||
|
||||
/// Get default location under test environment.
|
||||
static Future<Directory> testLocation(String? name) async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
var path = '${dir.path}/flowy_test';
|
||||
if (name != null) {
|
||||
path += '/$name';
|
||||
}
|
||||
return Directory(path).create(recursive: true);
|
||||
}
|
||||
final String applicationDataDirectory;
|
||||
}
|
||||
|
||||
extension AppFlowyTestBase on WidgetTester {
|
||||
Future<void> initializeAppFlowy() async {
|
||||
Future<FlowyTestContext> initializeAppFlowy({
|
||||
// use to append after the application data directory
|
||||
String? pathExtension,
|
||||
Size windowsSize = const Size(1600, 1200),
|
||||
}) async {
|
||||
binding.setSurfaceSize(windowsSize);
|
||||
|
||||
mockHotKeyManagerHandlers();
|
||||
final directory = await mockApplicationDataStorage(
|
||||
pathExtension: pathExtension,
|
||||
);
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await FlowyRunner.run(
|
||||
FlowyApp(),
|
||||
IntegrationMode.integrationTest,
|
||||
);
|
||||
|
||||
await wait(3000);
|
||||
await pumpAndSettle(const Duration(seconds: 2));
|
||||
return FlowyTestContext(
|
||||
applicationDataDirectory: directory,
|
||||
);
|
||||
}
|
||||
|
||||
void mockHotKeyManagerHandlers() {
|
||||
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
|
||||
.setMockMethodCallHandler(const MethodChannel('hotkey_manager'),
|
||||
(MethodCall methodCall) async {
|
||||
if (methodCall.method == 'unregisterAll') {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await FlowyRunner.run(FlowyApp(), IntegrationMode.integrationTest);
|
||||
Future<String> mockApplicationDataStorage({
|
||||
// use to append after the application data directory
|
||||
String? pathExtension,
|
||||
}) async {
|
||||
final dir = await getTemporaryDirectory();
|
||||
|
||||
await wait(3000);
|
||||
await pumpAndSettle(const Duration(seconds: 2));
|
||||
// Use a random uuid to avoid conflict.
|
||||
String path = p.join(dir.path, 'appflowy_integration_test', uuid());
|
||||
if (pathExtension != null && pathExtension.isNotEmpty) {
|
||||
path = '$path/$pathExtension';
|
||||
}
|
||||
final directory = Directory(path);
|
||||
if (!directory.existsSync()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
||||
MockApplicationDataStorage.initialPath = directory.path;
|
||||
|
||||
return directory.path;
|
||||
}
|
||||
|
||||
Future<void> tapButton(
|
||||
Finder finder, {
|
||||
int? pointer,
|
||||
int buttons = kPrimaryButton,
|
||||
bool warnIfMissed = true,
|
||||
bool warnIfMissed = false,
|
||||
int milliseconds = 500,
|
||||
}) async {
|
||||
await tap(
|
||||
@@ -127,6 +127,18 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
return;
|
||||
}
|
||||
|
||||
Future<void> doubleTapAt(
|
||||
Offset location, {
|
||||
int? pointer,
|
||||
int buttons = kPrimaryButton,
|
||||
int milliseconds = 500,
|
||||
}) async {
|
||||
await tapAt(location, pointer: pointer, buttons: buttons);
|
||||
await pump(kDoubleTapMinTime);
|
||||
await tapAt(location, pointer: pointer, buttons: buttons);
|
||||
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
||||
}
|
||||
|
||||
Future<void> doubleTapButton(
|
||||
Finder finder, {
|
||||
int? pointer,
|
||||
@@ -134,20 +146,22 @@ extension AppFlowyTestBase on WidgetTester {
|
||||
bool warnIfMissed = true,
|
||||
int milliseconds = 500,
|
||||
}) async {
|
||||
await tapButton(
|
||||
await tap(
|
||||
finder,
|
||||
pointer: pointer,
|
||||
buttons: buttons,
|
||||
warnIfMissed: warnIfMissed,
|
||||
milliseconds: kDoubleTapMinTime.inMilliseconds,
|
||||
);
|
||||
await tapButton(
|
||||
|
||||
await pump(kDoubleTapMinTime);
|
||||
|
||||
await tap(
|
||||
finder,
|
||||
pointer: pointer,
|
||||
buttons: buttons,
|
||||
pointer: pointer,
|
||||
warnIfMissed: warnIfMissed,
|
||||
milliseconds: milliseconds,
|
||||
);
|
||||
await pumpAndSettle(Duration(milliseconds: milliseconds));
|
||||
}
|
||||
|
||||
Future<void> wait(int milliseconds) async {
|
||||
|
||||
@@ -17,11 +17,6 @@ import 'package:flutter_test/flutter_test.dart';
|
||||
import 'util.dart';
|
||||
|
||||
extension CommonOperations on WidgetTester {
|
||||
/// Get current file location of AppFlowy.
|
||||
Future<String> currentFileLocation() async {
|
||||
return TestFolder.currentLocation();
|
||||
}
|
||||
|
||||
/// Tap the GetStart button on the launch page.
|
||||
Future<void> tapGoButton() async {
|
||||
final goButton = find.byType(GoButton);
|
||||
|
||||
@@ -73,7 +73,7 @@ import 'mock/mock_file_picker.dart';
|
||||
|
||||
extension AppFlowyDatabaseTest on WidgetTester {
|
||||
Future<void> openV020database() async {
|
||||
await initializeAppFlowy();
|
||||
final context = await initializeAppFlowy();
|
||||
await tapGoButton();
|
||||
|
||||
// expect to see a readme page
|
||||
@@ -83,18 +83,25 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
await tapImportButton();
|
||||
|
||||
final testFileNames = ['v020.afdb'];
|
||||
final fileLocation = await currentFileLocation();
|
||||
final paths = <String>[];
|
||||
for (final fileName in testFileNames) {
|
||||
final str = await rootBundle.loadString(
|
||||
p.join(
|
||||
'assets/test/workspaces/database',
|
||||
fileName,
|
||||
),
|
||||
// Don't use the p.join to build the path that used in loadString. It
|
||||
// is not working on windows.
|
||||
final str = await rootBundle
|
||||
.loadString("assets/test/workspaces/database/$fileName");
|
||||
|
||||
// Write the content to the file.
|
||||
final path = p.join(
|
||||
context.applicationDataDirectory,
|
||||
fileName,
|
||||
);
|
||||
File(p.join(fileLocation, fileName)).writeAsStringSync(str);
|
||||
paths.add(path);
|
||||
File(path).writeAsStringSync(str);
|
||||
}
|
||||
// mock get files
|
||||
await mockPickFilePaths(testFileNames, name: 'import_files');
|
||||
await mockPickFilePaths(
|
||||
paths: paths,
|
||||
);
|
||||
await tapDatabaseRawDataButton();
|
||||
await openPage('v020');
|
||||
}
|
||||
@@ -446,8 +453,15 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
}
|
||||
|
||||
Future<void> dismissRowDetailPage() async {
|
||||
await sendKeyEvent(LogicalKeyboardKey.escape);
|
||||
// use tap empty area instead of clicking ESC to dismiss the row detail page
|
||||
// sometimes, the ESC key is not working.
|
||||
await simulateKeyEvent(LogicalKeyboardKey.escape);
|
||||
await pumpAndSettle();
|
||||
final findRowDetailPage = find.byType(RowDetailPage);
|
||||
if (findRowDetailPage.evaluate().isNotEmpty) {
|
||||
await tapAt(const Offset(0, 0));
|
||||
await pumpAndSettle();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> editTitleInRowDetailPage(String title) async {
|
||||
@@ -1024,7 +1038,7 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
expect(findEvents, findsNWidgets(number));
|
||||
}
|
||||
|
||||
void assertNumberofEventsOnSpecificDay(
|
||||
void assertNumberOfEventsOnSpecificDay(
|
||||
int number,
|
||||
DateTime date, {
|
||||
String? title,
|
||||
@@ -1051,8 +1065,8 @@ extension AppFlowyDatabaseTest on WidgetTester {
|
||||
final todayCell = find.byWidgetPredicate(
|
||||
(widget) => widget is CalendarDayCard && isSameDay(date, widget.date),
|
||||
);
|
||||
|
||||
await doubleTapButton(todayCell);
|
||||
final location = getTopLeft(todayCell).translate(10, 10);
|
||||
await doubleTapAt(location);
|
||||
}
|
||||
|
||||
Future<void> openCalendarEvent({required index, DateTime? date}) async {
|
||||
|
||||
@@ -32,7 +32,7 @@ class EditorOperations {
|
||||
|
||||
/// Tap the line of editor at [index]
|
||||
Future<void> tapLineOfEditorAt(int index) async {
|
||||
final textBlocks = find.byType(FlowyRichText);
|
||||
final textBlocks = find.byType(AppFlowyRichText);
|
||||
index = index.clamp(0, textBlocks.evaluate().length - 1);
|
||||
await tester.tapAt(tester.getTopRight(textBlocks.at(index)));
|
||||
await tester.pumpAndSettle();
|
||||
@@ -165,4 +165,14 @@ class EditorOperations {
|
||||
);
|
||||
await tester.tapButton(atMenuItem);
|
||||
}
|
||||
|
||||
/// Update the editor's selection
|
||||
Future<void> updateSelection(Selection selection) async {
|
||||
final editorState = getCurrentEditorState();
|
||||
editorState.updateSelectionWithReason(
|
||||
selection,
|
||||
reason: SelectionUpdateReason.uiEvent,
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 200));
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,5 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||
import 'package:file_picker/file_picker.dart' as fp;
|
||||
import 'package:path/path.dart' as p;
|
||||
import '../util.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
|
||||
class MockFilePicker implements FilePickerService {
|
||||
MockFilePicker({
|
||||
@@ -25,7 +20,7 @@ class MockFilePicker implements FilePickerService {
|
||||
String? dialogTitle,
|
||||
String? fileName,
|
||||
String? initialDirectory,
|
||||
fp.FileType type = fp.FileType.any,
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
bool lockParentWindow = false,
|
||||
}) {
|
||||
@@ -36,18 +31,17 @@ class MockFilePicker implements FilePickerService {
|
||||
Future<FilePickerResult?> pickFiles({
|
||||
String? dialogTitle,
|
||||
String? initialDirectory,
|
||||
fp.FileType type = fp.FileType.any,
|
||||
FileType type = FileType.any,
|
||||
List<String>? allowedExtensions,
|
||||
Function(fp.FilePickerStatus p1)? onFileLoading,
|
||||
Function(FilePickerStatus p1)? onFileLoading,
|
||||
bool allowCompression = true,
|
||||
bool allowMultiple = false,
|
||||
bool withData = false,
|
||||
bool withReadStream = false,
|
||||
bool lockParentWindow = false,
|
||||
}) {
|
||||
final platformFiles = mockPaths
|
||||
.map((e) => fp.PlatformFile(path: e, name: '', size: 0))
|
||||
.toList();
|
||||
final platformFiles =
|
||||
mockPaths.map((e) => PlatformFile(path: e, name: '', size: 0)).toList();
|
||||
return Future.value(
|
||||
FilePickerResult(
|
||||
platformFiles,
|
||||
@@ -56,20 +50,21 @@ class MockFilePicker implements FilePickerService {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> mockGetDirectoryPath(String? name) async {
|
||||
final dir = await TestFolder.testLocation(name);
|
||||
Future<void> mockGetDirectoryPath(
|
||||
String path,
|
||||
) async {
|
||||
getIt.unregister<FilePickerService>();
|
||||
getIt.registerFactory<FilePickerService>(
|
||||
() => MockFilePicker(
|
||||
mockPath: dir.path,
|
||||
mockPath: path,
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
Future<String> mockSaveFilePath(String? name, String fileName) async {
|
||||
final dir = await TestFolder.testLocation(name);
|
||||
final path = p.join(dir.path, fileName);
|
||||
Future<String> mockSaveFilePath(
|
||||
String path,
|
||||
) async {
|
||||
getIt.unregister<FilePickerService>();
|
||||
getIt.registerFactory<FilePickerService>(
|
||||
() => MockFilePicker(
|
||||
@@ -79,18 +74,16 @@ Future<String> mockSaveFilePath(String? name, String fileName) async {
|
||||
return path;
|
||||
}
|
||||
|
||||
Future<List<String>> mockPickFilePaths(
|
||||
List<String> fileNames, {
|
||||
String? name,
|
||||
String? customPath,
|
||||
Future<List<String>> mockPickFilePaths({
|
||||
required List<String> paths,
|
||||
}) async {
|
||||
late final Directory dir;
|
||||
if (customPath != null) {
|
||||
dir = Directory(customPath);
|
||||
} else {
|
||||
dir = await TestFolder.testLocation(name);
|
||||
}
|
||||
final paths = fileNames.map((e) => p.join(dir.path, e)).toList();
|
||||
// late final Directory dir;
|
||||
// if (customPath != null) {
|
||||
// dir = Directory(customPath);
|
||||
// } else {
|
||||
// dir = await TestFolder.testLocation(applicationDataPath, name);
|
||||
// }
|
||||
// final paths = fileNames.map((e) => p.join(dir.path, e)).toList();
|
||||
getIt.unregister<FilePickerService>();
|
||||
getIt.registerFactory<FilePickerService>(
|
||||
() => MockFilePicker(
|
||||
|
||||
@@ -7,32 +7,24 @@ class Config {
|
||||
required String anonKey,
|
||||
required String key,
|
||||
required String secret,
|
||||
required String pgUrl,
|
||||
required String pgUser,
|
||||
required String pgPassword,
|
||||
required String pgPort,
|
||||
}) async {
|
||||
final postgresConfig = PostgresConfigurationPB.create()
|
||||
..url = pgUrl
|
||||
..userName = pgUser
|
||||
..password = pgPassword
|
||||
..port = int.parse(pgPort);
|
||||
|
||||
await ConfigEventSetSupabaseConfig(
|
||||
SupabaseConfigPB.create()
|
||||
..supabaseUrl = url
|
||||
..key = key
|
||||
..anonKey = anonKey
|
||||
..jwtSecret = secret,
|
||||
..jwtSecret = secret
|
||||
..postgresConfig = postgresConfig,
|
||||
).send();
|
||||
}
|
||||
|
||||
static Future<void> setSupabaseCollabPluginConfig({
|
||||
required String url,
|
||||
required String key,
|
||||
required String jwtSecret,
|
||||
required String collabTable,
|
||||
}) async {
|
||||
final payload = CollabPluginConfigPB.create();
|
||||
final collabTableConfig = CollabTableConfigPB.create()
|
||||
..tableName = collabTable;
|
||||
|
||||
payload.supabaseConfig = SupabaseDBConfigPB.create()
|
||||
..supabaseUrl = url
|
||||
..key = key
|
||||
..jwtSecret = jwtSecret
|
||||
..collabTableConfig = collabTableConfig;
|
||||
|
||||
await ConfigEventSetCollabPluginConfig(payload).send();
|
||||
}
|
||||
}
|
||||
@@ -18,4 +18,9 @@ class KVKeys {
|
||||
/// The value is a json string with the following format:
|
||||
/// {'height': 600.0, 'width': 800.0}
|
||||
static const String windowSize = 'windowSize';
|
||||
|
||||
static const String kDocumentAppearanceFontSize =
|
||||
'kDocumentAppearanceFontSize';
|
||||
static const String kDocumentAppearanceFontFamily =
|
||||
'kDocumentAppearanceFontFamily';
|
||||
}
|
||||
+39
-3
@@ -3,6 +3,17 @@ import 'package:envied/envied.dart';
|
||||
|
||||
part 'env.g.dart';
|
||||
|
||||
/// The environment variables are defined in `.env` file that is located in the
|
||||
/// appflowy_flutter.
|
||||
/// Run `dart run build_runner build --delete-conflicting-outputs`
|
||||
/// to generate the keys from the env file.
|
||||
///
|
||||
/// If you want to regenerate the keys, you need to run `dart run
|
||||
/// build_runner clean` before running `dart run build_runner build
|
||||
/// --delete-conflicting-outputs`.
|
||||
|
||||
/// Follow the guide on https://supabase.com/docs/guides/auth/social-login/auth-google to setup the auth provider.
|
||||
///
|
||||
@Envied(path: '.env')
|
||||
abstract class Env {
|
||||
@EnviedField(
|
||||
@@ -32,14 +43,39 @@ abstract class Env {
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_COLLAB_TABLE',
|
||||
varName: 'SUPABASE_DB',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String supabaseCollabTable = _Env.supabaseCollabTable;
|
||||
static final String supabaseDb = _Env.supabaseDb;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_DB_USER',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String supabaseDbUser = _Env.supabaseDbUser;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_DB_PASSWORD',
|
||||
defaultValue: '',
|
||||
)
|
||||
static final String supabaseDbPassword = _Env.supabaseDbPassword;
|
||||
|
||||
@EnviedField(
|
||||
obfuscate: true,
|
||||
varName: 'SUPABASE_DB_PORT',
|
||||
defaultValue: '5432',
|
||||
)
|
||||
static final String supabaseDbPort = _Env.supabaseDbPort;
|
||||
}
|
||||
|
||||
bool get isSupabaseEnable =>
|
||||
Env.supabaseUrl.isNotEmpty &&
|
||||
Env.supabaseAnonKey.isNotEmpty &&
|
||||
Env.supabaseKey.isNotEmpty &&
|
||||
Env.supabaseJwtSecret.isNotEmpty;
|
||||
Env.supabaseJwtSecret.isNotEmpty &&
|
||||
Env.supabaseDb.isNotEmpty &&
|
||||
Env.supabaseDbUser.isNotEmpty &&
|
||||
Env.supabaseDbPassword.isNotEmpty &&
|
||||
Env.supabaseDbPort.isNotEmpty;
|
||||
@@ -3,7 +3,6 @@ import 'package:appflowy/plugins/database_view/grid/application/row/row_document
|
||||
import 'package:appflowy/plugins/document/application/doc_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_page.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
@@ -86,7 +85,6 @@ class _RowEditorState extends State<RowEditor> {
|
||||
Widget build(BuildContext context) {
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider(create: (_) => DocumentAppearanceCubit()),
|
||||
BlocProvider.value(value: documentBloc),
|
||||
],
|
||||
child: BlocBuilder<DocumentBloc, DocumentState>(
|
||||
|
||||
@@ -39,8 +39,6 @@ class DocumentPluginBuilder extends PluginBuilder {
|
||||
|
||||
class DocumentPlugin extends Plugin<int> {
|
||||
late PluginType _pluginType;
|
||||
final DocumentAppearanceCubit _documentAppearanceCubit =
|
||||
DocumentAppearanceCubit();
|
||||
|
||||
@override
|
||||
final ViewPluginNotifier notifier;
|
||||
@@ -52,20 +50,12 @@ class DocumentPlugin extends Plugin<int> {
|
||||
Key? key,
|
||||
}) : notifier = ViewPluginNotifier(view: view) {
|
||||
_pluginType = pluginType;
|
||||
_documentAppearanceCubit.fetch();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_documentAppearanceCubit.close();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
PluginWidgetBuilder get widgetBuilder {
|
||||
return DocumentPluginWidgetBuilder(
|
||||
notifier: notifier,
|
||||
documentAppearanceCubit: _documentAppearanceCubit,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -81,11 +71,9 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
|
||||
final ViewPluginNotifier notifier;
|
||||
ViewPB get view => notifier.view;
|
||||
int? deletedViewIndex;
|
||||
DocumentAppearanceCubit documentAppearanceCubit;
|
||||
|
||||
DocumentPluginWidgetBuilder({
|
||||
required this.notifier,
|
||||
required this.documentAppearanceCubit,
|
||||
Key? key,
|
||||
});
|
||||
|
||||
@@ -102,17 +90,14 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
|
||||
});
|
||||
});
|
||||
|
||||
return BlocProvider.value(
|
||||
value: documentAppearanceCubit,
|
||||
child: BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
|
||||
builder: (_, state) {
|
||||
return DocumentPage(
|
||||
view: view,
|
||||
onDeleted: () => context?.onDeleted(view, deletedViewIndex),
|
||||
key: ValueKey(view.id),
|
||||
);
|
||||
},
|
||||
),
|
||||
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
|
||||
builder: (_, state) {
|
||||
return DocumentPage(
|
||||
view: view,
|
||||
onDeleted: () => context?.onDeleted(view, deletedViewIndex),
|
||||
key: ValueKey(view.id),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -128,10 +113,7 @@ class DocumentPluginWidgetBuilder extends PluginWidgetBuilder
|
||||
view: view,
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
BlocProvider.value(
|
||||
value: documentAppearanceCubit,
|
||||
child: const DocumentMoreButton(),
|
||||
),
|
||||
const DocumentMoreButton(),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,12 +10,12 @@ import 'package:appflowy/plugins/document/presentation/editor_style.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/export_page_widget.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/base64_string.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/protobuf.dart'
|
||||
hide DocumentEvent;
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/widget/error_page.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -49,9 +49,10 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
quoteItem,
|
||||
bulletedListItem,
|
||||
numberedListItem,
|
||||
inlineMathEquationItem,
|
||||
linkItem,
|
||||
textColorItem,
|
||||
highlightColorItem,
|
||||
buildTextColorItem(),
|
||||
buildHighlightColorItem(),
|
||||
];
|
||||
|
||||
late final List<SelectionMenuItem> slashMenuItems;
|
||||
@@ -125,7 +126,7 @@ class _AppFlowyEditorPageState extends State<AppFlowyEditorPage> {
|
||||
final (bool autoFocus, Selection? selection) =
|
||||
_computeAutoFocusParameters();
|
||||
|
||||
final editor = AppFlowyEditor.custom(
|
||||
final editor = AppFlowyEditor(
|
||||
editorState: widget.editorState,
|
||||
editable: true,
|
||||
shrinkWrap: widget.shrinkWrap,
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
|
||||
+1
-1
@@ -46,7 +46,7 @@ void showLinkToPageMenu(
|
||||
linkToPageMenuEntry.remove();
|
||||
} on FlowyError catch (e) {
|
||||
Dialogs.show(
|
||||
FlowyErrorPage.message(
|
||||
child: FlowyErrorPage.message(
|
||||
e.msg,
|
||||
howToFix: LocaleKeys.errorDialog_howToFixFallback.tr(),
|
||||
),
|
||||
|
||||
+2
-2
@@ -105,7 +105,7 @@ class CalloutBlockComponentWidget extends BlockComponentStatefulWidget {
|
||||
|
||||
class _CalloutBlockComponentWidgetState
|
||||
extends State<CalloutBlockComponentWidget>
|
||||
with SelectableMixin, DefaultSelectable, BlockComponentConfigurable {
|
||||
with SelectableMixin, DefaultSelectableMixin, BlockComponentConfigurable {
|
||||
// the key used to forward focus to the richtext child
|
||||
@override
|
||||
final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text');
|
||||
@@ -192,7 +192,7 @@ class _CalloutBlockComponentWidgetState
|
||||
Widget buildCalloutBlockComponent(BuildContext context) {
|
||||
return Padding(
|
||||
padding: padding,
|
||||
child: FlowyRichText(
|
||||
child: AppFlowyRichText(
|
||||
key: forwardKey,
|
||||
node: widget.node,
|
||||
editorState: editorState,
|
||||
|
||||
+2
-2
@@ -96,7 +96,7 @@ class CodeBlockComponentWidget extends BlockComponentStatefulWidget {
|
||||
}
|
||||
|
||||
class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
||||
with SelectableMixin, DefaultSelectable, BlockComponentConfigurable {
|
||||
with SelectableMixin, DefaultSelectableMixin, BlockComponentConfigurable {
|
||||
// the key used to forward focus to the richtext child
|
||||
@override
|
||||
final forwardKey = GlobalKey(debugLabel: 'flowy_rich_text');
|
||||
@@ -216,7 +216,7 @@ class _CodeBlockComponentWidgetState extends State<CodeBlockComponentWidget>
|
||||
final codeTextSpans = _convert(codeNodes);
|
||||
return Padding(
|
||||
padding: widget.padding,
|
||||
child: FlowyRichText(
|
||||
child: AppFlowyRichText(
|
||||
key: forwardKey,
|
||||
node: widget.node,
|
||||
editorState: editorState,
|
||||
|
||||
+3
-4
@@ -1,12 +1,11 @@
|
||||
import 'dart:io';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:file_picker/file_picker.dart' as fp;
|
||||
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:dartz/dartz.dart';
|
||||
@@ -121,7 +120,7 @@ class CoverImagePickerBloc
|
||||
final result = await getIt<FilePickerService>().pickFiles(
|
||||
dialogTitle: LocaleKeys.document_plugins_cover_addLocalImage.tr(),
|
||||
allowMultiple: false,
|
||||
type: fp.FileType.image,
|
||||
type: FileType.image,
|
||||
allowedExtensions: allowedExtensions,
|
||||
);
|
||||
if (result != null && result.files.isNotEmpty) {
|
||||
|
||||
+1
-15
@@ -2,7 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@@ -43,20 +43,6 @@ enum CoverType {
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentHeaderNodeWidgetBuilder implements NodeWidgetBuilder {
|
||||
@override
|
||||
Widget build(NodeWidgetContext<Node> context) {
|
||||
return DocumentHeaderNodeWidget(
|
||||
key: context.node.key,
|
||||
node: context.node,
|
||||
editorState: context.editorState,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
NodeValidator<Node> get nodeValidator => (_) => true;
|
||||
}
|
||||
|
||||
class DocumentHeaderNodeWidget extends StatefulWidget {
|
||||
const DocumentHeaderNodeWidget({
|
||||
required this.node,
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:flowy_infra/uuid.dart';
|
||||
|
||||
+173
@@ -0,0 +1,173 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text_input.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_math_fork/flutter_math.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class InlineMathEquationKeys {
|
||||
const InlineMathEquationKeys._();
|
||||
|
||||
static const formula = 'formula';
|
||||
}
|
||||
|
||||
class InlineMathEquation extends StatefulWidget {
|
||||
const InlineMathEquation({
|
||||
super.key,
|
||||
required this.formula,
|
||||
required this.node,
|
||||
required this.index,
|
||||
this.textStyle,
|
||||
});
|
||||
|
||||
final Node node;
|
||||
final int index;
|
||||
final String formula;
|
||||
final TextStyle? textStyle;
|
||||
|
||||
@override
|
||||
State<InlineMathEquation> createState() => _InlineMathEquationState();
|
||||
}
|
||||
|
||||
class _InlineMathEquationState extends State<InlineMathEquation> {
|
||||
final popoverController = PopoverController();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return _IgnoreParentPointer(
|
||||
child: AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithLeftAligned,
|
||||
popupBuilder: (_) {
|
||||
return MathInputTextField(
|
||||
initialText: widget.formula,
|
||||
onSubmit: (value) async {
|
||||
popoverController.close();
|
||||
if (value == widget.formula) {
|
||||
return;
|
||||
}
|
||||
final editorState = context.read<EditorState>();
|
||||
final transaction = editorState.transaction
|
||||
..formatText(widget.node, widget.index, 1, {
|
||||
InlineMathEquationKeys.formula: value,
|
||||
});
|
||||
await editorState.apply(transaction);
|
||||
},
|
||||
);
|
||||
},
|
||||
offset: const Offset(0, 10),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const HSpace(2),
|
||||
Math.tex(
|
||||
widget.formula,
|
||||
options: MathOptions(
|
||||
style: MathStyle.text,
|
||||
mathFontOptions: const FontOptions(
|
||||
fontShape: FontStyle.italic,
|
||||
),
|
||||
fontSize: 14.0,
|
||||
color: widget.textStyle?.color ??
|
||||
theme.colorScheme.onBackground,
|
||||
),
|
||||
),
|
||||
const HSpace(2),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MathInputTextField extends StatefulWidget {
|
||||
const MathInputTextField({
|
||||
super.key,
|
||||
required this.initialText,
|
||||
required this.onSubmit,
|
||||
});
|
||||
|
||||
final String initialText;
|
||||
final void Function(String value) onSubmit;
|
||||
|
||||
@override
|
||||
State<MathInputTextField> createState() => _MathInputTextFieldState();
|
||||
}
|
||||
|
||||
class _MathInputTextFieldState extends State<MathInputTextField> {
|
||||
late final TextEditingController textEditingController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
textEditingController = TextEditingController(
|
||||
text: widget.initialText,
|
||||
);
|
||||
textEditingController.selection = TextSelection(
|
||||
baseOffset: 0,
|
||||
extentOffset: widget.initialText.length,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: 240,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyFormTextInput(
|
||||
autoFocus: true,
|
||||
textAlign: TextAlign.left,
|
||||
controller: textEditingController,
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
vertical: 8.0,
|
||||
horizontal: 4.0,
|
||||
),
|
||||
onEditingComplete: () =>
|
||||
widget.onSubmit(textEditingController.text),
|
||||
),
|
||||
),
|
||||
const HSpace(4.0),
|
||||
FlowyButton(
|
||||
text: FlowyText(LocaleKeys.button_Done.tr()),
|
||||
useIntrinsicWidth: true,
|
||||
onTap: () => widget.onSubmit(textEditingController.text),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _IgnoreParentPointer extends StatelessWidget {
|
||||
const _IgnoreParentPointer({
|
||||
required this.child,
|
||||
});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () {},
|
||||
onTapDown: (_) {},
|
||||
onDoubleTap: () {},
|
||||
onLongPress: () {},
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
+74
@@ -0,0 +1,74 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/plugins.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
final ToolbarItem inlineMathEquationItem = ToolbarItem(
|
||||
id: 'editor.inline_math_equation',
|
||||
group: 2,
|
||||
isActive: onlyShowInSingleSelectionAndTextType,
|
||||
builder: (context, editorState) {
|
||||
final selection = editorState.selection!;
|
||||
final nodes = editorState.getNodesInSelection(selection);
|
||||
final isHighlight = nodes.allSatisfyInSelection(selection, (delta) {
|
||||
return delta.everyAttributes(
|
||||
(attributes) => attributes[InlineMathEquationKeys.formula] != null,
|
||||
);
|
||||
});
|
||||
return IconItemWidget(
|
||||
iconBuilder: (_) => svgWidget(
|
||||
'editor/math',
|
||||
size: const Size.square(16),
|
||||
color: isHighlight ? Colors.lightBlue : Colors.white,
|
||||
),
|
||||
isHighlight: isHighlight,
|
||||
tooltip: LocaleKeys.document_plugins_createInlineMathEquation.tr(),
|
||||
onPressed: () async {
|
||||
final selection = editorState.selection;
|
||||
if (selection == null || selection.isCollapsed) {
|
||||
return;
|
||||
}
|
||||
final node = editorState.getNodeAtPath(selection.start.path);
|
||||
final delta = node?.delta;
|
||||
if (node == null || delta == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
final transaction = editorState.transaction;
|
||||
if (isHighlight) {
|
||||
final formula = delta
|
||||
.slice(selection.startIndex, selection.endIndex)
|
||||
.whereType<TextInsert>()
|
||||
.firstOrNull
|
||||
?.attributes?[InlineMathEquationKeys.formula];
|
||||
assert(formula != null);
|
||||
if (formula == null) {
|
||||
return;
|
||||
}
|
||||
// clear the format
|
||||
transaction.replaceText(
|
||||
node,
|
||||
selection.startIndex,
|
||||
selection.length,
|
||||
formula,
|
||||
attributes: {},
|
||||
);
|
||||
} else {
|
||||
final text = editorState.getTextInSelection(selection).join();
|
||||
transaction.replaceText(
|
||||
node,
|
||||
selection.startIndex,
|
||||
selection.length,
|
||||
'\$',
|
||||
attributes: {
|
||||
InlineMathEquationKeys.formula: text,
|
||||
},
|
||||
);
|
||||
}
|
||||
await editorState.apply(transaction);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
-2
@@ -24,8 +24,6 @@ class MentionBlockKeys {
|
||||
static const mention = 'mention';
|
||||
static const type = 'type'; // MentionType, String
|
||||
static const pageId = 'page_id';
|
||||
static const pageType = 'page_type';
|
||||
static const pageName = 'page_name';
|
||||
}
|
||||
|
||||
class InlinePageReferenceService {
|
||||
|
||||
+6
-5
@@ -32,7 +32,7 @@ class MentionPageBlock extends StatefulWidget {
|
||||
|
||||
class _MentionPageBlockState extends State<MentionPageBlock> {
|
||||
late final EditorState editorState;
|
||||
late final Future<ViewPB?> viewPBFuture;
|
||||
late Future<ViewPB?> viewPBFuture;
|
||||
ViewListener? viewListener;
|
||||
|
||||
@override
|
||||
@@ -45,6 +45,7 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
|
||||
..start(
|
||||
onViewUpdated: (p0) {
|
||||
pageMemorizer[p0.id] = p0;
|
||||
viewPBFuture = fetchView(widget.pageId);
|
||||
editorState.reload();
|
||||
},
|
||||
);
|
||||
@@ -111,11 +112,10 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
|
||||
}
|
||||
|
||||
Future<ViewPB?> fetchView(String pageId) async {
|
||||
final views = await ViewBackendService().fetchViews((_, __) => true);
|
||||
final flattenViews = views.expand((e) => [e.$1, ...e.$2]).toList();
|
||||
final view = flattenViews.firstWhereOrNull(
|
||||
(element) => element.id == pageId,
|
||||
final view = await ViewBackendService.getView(pageId).then(
|
||||
(value) => value.swap().toOption().toNullable(),
|
||||
);
|
||||
|
||||
if (view == null) {
|
||||
// try to fetch from trash
|
||||
final trashViews = await TrashService().readTrash();
|
||||
@@ -129,6 +129,7 @@ class _MentionPageBlockState extends State<MentionPageBlock> {
|
||||
..name = trash.name;
|
||||
}
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -141,13 +141,13 @@ class EditorMigration {
|
||||
}
|
||||
const backgroundColor = 'backgroundColor';
|
||||
if (attributes.containsKey(backgroundColor)) {
|
||||
attributes[FlowyRichTextKeys.highlightColor] =
|
||||
attributes[AppFlowyRichTextKeys.highlightColor] =
|
||||
attributes[backgroundColor];
|
||||
attributes.remove(backgroundColor);
|
||||
}
|
||||
const color = 'color';
|
||||
if (attributes.containsKey(color)) {
|
||||
attributes[FlowyRichTextKeys.textColor] = attributes[color];
|
||||
attributes[AppFlowyRichTextKeys.textColor] = attributes[color];
|
||||
attributes.remove(color);
|
||||
}
|
||||
return attributes;
|
||||
|
||||
+1
-1
@@ -157,7 +157,7 @@ class _SmartEditBlockComponentWidgetState
|
||||
var width = double.infinity;
|
||||
final editorSize = editorState.renderBox?.size;
|
||||
final padding = editorState.editorStyle.padding;
|
||||
if (editorSize != null && padding != null) {
|
||||
if (editorSize != null) {
|
||||
width = editorSize.width - padding.left - padding.right;
|
||||
}
|
||||
return width;
|
||||
|
||||
+13
-11
@@ -1,22 +1,24 @@
|
||||
export 'actions/block_action_list.dart';
|
||||
export 'actions/option_action.dart';
|
||||
export 'callout/callout_block_component.dart';
|
||||
export 'code_block/code_block_component.dart';
|
||||
export 'code_block/code_block_shortcut_event.dart';
|
||||
export 'header/cover_editor_bloc.dart';
|
||||
export 'header/document_header_node_widget.dart';
|
||||
export 'header/custom_cover_picker.dart';
|
||||
export 'emoji_picker/emoji_menu_item.dart';
|
||||
export 'extensions/flowy_tint_extension.dart';
|
||||
export 'database/database_view_block_component.dart';
|
||||
export 'database/inline_database_menu_item.dart';
|
||||
export 'database/referenced_database_menu_item.dart';
|
||||
export 'database/database_view_block_component.dart';
|
||||
export 'emoji_picker/emoji_menu_item.dart';
|
||||
export 'extensions/flowy_tint_extension.dart';
|
||||
export 'header/cover_editor_bloc.dart';
|
||||
export 'header/custom_cover_picker.dart';
|
||||
export 'header/document_header_node_widget.dart';
|
||||
export 'image/image_menu.dart';
|
||||
export 'image/image_selection_menu.dart';
|
||||
export 'inline_math_equation/inline_math_equation.dart';
|
||||
export 'inline_math_equation/inline_math_equation_toolbar_item.dart';
|
||||
export 'math_equation/math_equation_block_component.dart';
|
||||
export 'openai/widgets/auto_completion_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_node_widget.dart';
|
||||
export 'openai/widgets/smart_edit_toolbar_item.dart';
|
||||
export 'outline/outline_block_component.dart';
|
||||
export 'toggle/toggle_block_component.dart';
|
||||
export 'toggle/toggle_block_shortcut_event.dart';
|
||||
export 'outline/outline_block_component.dart';
|
||||
export 'image/image_menu.dart';
|
||||
export 'image/image_selection_menu.dart';
|
||||
export 'actions/option_action.dart';
|
||||
export 'actions/block_action_list.dart';
|
||||
+2
-2
@@ -84,7 +84,7 @@ class _ToggleListBlockComponentWidgetState
|
||||
extends State<ToggleListBlockComponentWidget>
|
||||
with
|
||||
SelectableMixin,
|
||||
DefaultSelectable,
|
||||
DefaultSelectableMixin,
|
||||
BlockComponentConfigurable,
|
||||
BackgroundColorMixin {
|
||||
// the key used to forward focus to the richtext child
|
||||
@@ -141,7 +141,7 @@ class _ToggleListBlockComponentWidgetState
|
||||
width: 4.0,
|
||||
),
|
||||
Expanded(
|
||||
child: FlowyRichText(
|
||||
child: AppFlowyRichText(
|
||||
key: forwardKey,
|
||||
node: widget.node,
|
||||
editorState: editorState,
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_math_equation/inline_math_equation.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/inline_page/inline_page_reference.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/mention/mention_block.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg, Log;
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -28,30 +29,35 @@ class EditorStyleCustomizer {
|
||||
EditorStyle desktop() {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
final fontFamily = context.read<DocumentAppearanceCubit>().state.fontFamily;
|
||||
return EditorStyle.desktop(
|
||||
padding: padding,
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
cursorColor: theme.colorScheme.primary,
|
||||
textStyleConfiguration: TextStyleConfiguration(
|
||||
text: TextStyle(
|
||||
fontFamily: 'Poppins',
|
||||
text: baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
color: theme.colorScheme.onBackground,
|
||||
height: 1.5,
|
||||
),
|
||||
bold: const TextStyle(
|
||||
fontFamily: 'Poppins-Bold',
|
||||
bold: baseTextStyle(fontFamily, fontWeight: FontWeight.bold).copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
italic: const TextStyle(fontStyle: FontStyle.italic),
|
||||
underline: const TextStyle(decoration: TextDecoration.underline),
|
||||
strikethrough: const TextStyle(decoration: TextDecoration.lineThrough),
|
||||
href: TextStyle(
|
||||
italic: baseTextStyle(fontFamily).copyWith(
|
||||
fontStyle: FontStyle.italic,
|
||||
),
|
||||
underline: baseTextStyle(fontFamily).copyWith(
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
strikethrough: baseTextStyle(fontFamily).copyWith(
|
||||
decoration: TextDecoration.lineThrough,
|
||||
),
|
||||
href: baseTextStyle(fontFamily).copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
code: GoogleFonts.robotoMono(
|
||||
textStyle: TextStyle(
|
||||
textStyle: baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.red,
|
||||
@@ -66,30 +72,32 @@ class EditorStyleCustomizer {
|
||||
EditorStyle mobile() {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
final fontFamily = context.read<DocumentAppearanceCubit>().state.fontFamily;
|
||||
|
||||
return EditorStyle.desktop(
|
||||
padding: padding,
|
||||
backgroundColor: theme.colorScheme.surface,
|
||||
cursorColor: theme.colorScheme.primary,
|
||||
textStyleConfiguration: TextStyleConfiguration(
|
||||
text: TextStyle(
|
||||
fontFamily: 'poppins',
|
||||
text: baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
color: theme.colorScheme.onBackground,
|
||||
height: 1.5,
|
||||
),
|
||||
bold: const TextStyle(
|
||||
fontFamily: 'poppins-Bold',
|
||||
bold: baseTextStyle(fontFamily).copyWith(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
italic: const TextStyle(fontStyle: FontStyle.italic),
|
||||
underline: const TextStyle(decoration: TextDecoration.underline),
|
||||
strikethrough: const TextStyle(decoration: TextDecoration.lineThrough),
|
||||
href: TextStyle(
|
||||
italic: baseTextStyle(fontFamily).copyWith(fontStyle: FontStyle.italic),
|
||||
underline: baseTextStyle(fontFamily)
|
||||
.copyWith(decoration: TextDecoration.underline),
|
||||
strikethrough: baseTextStyle(fontFamily)
|
||||
.copyWith(decoration: TextDecoration.lineThrough),
|
||||
href: baseTextStyle(fontFamily).copyWith(
|
||||
color: theme.colorScheme.primary,
|
||||
decoration: TextDecoration.underline,
|
||||
),
|
||||
code: GoogleFonts.robotoMono(
|
||||
textStyle: TextStyle(
|
||||
textStyle: baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
fontWeight: FontWeight.normal,
|
||||
color: Colors.red,
|
||||
@@ -119,8 +127,8 @@ class EditorStyleCustomizer {
|
||||
TextStyle codeBlockStyleBuilder() {
|
||||
final theme = Theme.of(context);
|
||||
final fontSize = context.read<DocumentAppearanceCubit>().state.fontSize;
|
||||
return TextStyle(
|
||||
fontFamily: 'poppins',
|
||||
final fontFamily = context.read<DocumentAppearanceCubit>().state.fontFamily;
|
||||
return baseTextStyle(fontFamily).copyWith(
|
||||
fontSize: fontSize,
|
||||
height: 1.5,
|
||||
color: theme.colorScheme.onBackground,
|
||||
@@ -157,14 +165,33 @@ class EditorStyleCustomizer {
|
||||
);
|
||||
}
|
||||
|
||||
TextStyle baseTextStyle(
|
||||
String fontFamily, {
|
||||
FontWeight? fontWeight,
|
||||
}) {
|
||||
try {
|
||||
return GoogleFonts.getFont(
|
||||
fontFamily,
|
||||
fontWeight: fontWeight,
|
||||
);
|
||||
} on Exception {
|
||||
return GoogleFonts.getFont('Poppins');
|
||||
}
|
||||
}
|
||||
|
||||
InlineSpan customizeAttributeDecorator(
|
||||
TextInsert textInsert,
|
||||
BuildContext context,
|
||||
Node node,
|
||||
int index,
|
||||
TextInsert text,
|
||||
TextSpan textSpan,
|
||||
) {
|
||||
final attributes = textInsert.attributes;
|
||||
final attributes = text.attributes;
|
||||
if (attributes == null) {
|
||||
return textSpan;
|
||||
}
|
||||
|
||||
// customize the inline mention block, like inline page
|
||||
final mention = attributes[MentionBlockKeys.mention] as Map?;
|
||||
if (mention != null) {
|
||||
final type = mention[MentionBlockKeys.type];
|
||||
@@ -178,6 +205,21 @@ class EditorStyleCustomizer {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// customize the inline math equation block
|
||||
final formula = attributes[InlineMathEquationKeys.formula] as String?;
|
||||
if (formula != null) {
|
||||
return WidgetSpan(
|
||||
alignment: PlaceholderAlignment.middle,
|
||||
child: InlineMathEquation(
|
||||
node: node,
|
||||
index: index,
|
||||
formula: formula,
|
||||
textStyle: style().textStyleConfiguration.text,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return textSpan;
|
||||
}
|
||||
}
|
||||
+34
-11
@@ -1,30 +1,53 @@
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
const String _kDocumentAppearanceFontSize = 'kDocumentAppearanceFontSize';
|
||||
|
||||
class DocumentAppearance {
|
||||
const DocumentAppearance({
|
||||
required this.fontSize,
|
||||
required this.fontFamily,
|
||||
});
|
||||
|
||||
final double fontSize;
|
||||
// Will be supported...
|
||||
// final String fontName;
|
||||
final String fontFamily;
|
||||
|
||||
DocumentAppearance copyWith({double? fontSize}) {
|
||||
DocumentAppearance copyWith({
|
||||
double? fontSize,
|
||||
String? fontFamily,
|
||||
}) {
|
||||
return DocumentAppearance(
|
||||
fontSize: fontSize ?? this.fontSize,
|
||||
fontFamily: fontFamily ?? this.fontFamily,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
|
||||
DocumentAppearanceCubit() : super(const DocumentAppearance(fontSize: 16.0));
|
||||
DocumentAppearanceCubit()
|
||||
: super(const DocumentAppearance(fontSize: 16.0, fontFamily: 'Poppins'));
|
||||
|
||||
void fetch() async {
|
||||
Future<void> fetch() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
final fontSize = prefs.getDouble(_kDocumentAppearanceFontSize) ?? 16.0;
|
||||
final fontSize =
|
||||
prefs.getDouble(KVKeys.kDocumentAppearanceFontSize) ?? 16.0;
|
||||
final fontFamily =
|
||||
prefs.getString(KVKeys.kDocumentAppearanceFontFamily) ?? 'Poppins';
|
||||
|
||||
if (isClosed) {
|
||||
return;
|
||||
}
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
fontSize: fontSize,
|
||||
fontFamily: fontFamily,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> syncFontSize(double fontSize) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setDouble(KVKeys.kDocumentAppearanceFontSize, fontSize);
|
||||
|
||||
if (isClosed) {
|
||||
return;
|
||||
@@ -37,9 +60,9 @@ class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
|
||||
);
|
||||
}
|
||||
|
||||
void syncFontSize(double fontSize) async {
|
||||
Future<void> syncFontFamily(String fontFamily) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
prefs.setDouble(_kDocumentAppearanceFontSize, fontSize);
|
||||
prefs.setString(KVKeys.kDocumentAppearanceFontFamily, fontFamily);
|
||||
|
||||
if (isClosed) {
|
||||
return;
|
||||
@@ -47,7 +70,7 @@ class DocumentAppearanceCubit extends Cubit<DocumentAppearance> {
|
||||
|
||||
emit(
|
||||
state.copyWith(
|
||||
fontSize: fontSize,
|
||||
fontFamily: fontFamily,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/plugins/document/application/share_bloc.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||
import 'package:appflowy/workspace/application/view/view_listener.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/toast.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-document2/entities.pb.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/view.pb.dart';
|
||||
|
||||
@@ -6,14 +6,14 @@ import 'package:appflowy/plugins/database_view/application/field/field_service.d
|
||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy/user/application/auth/supabase_auth_service.dart';
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_impl.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_impl.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:appflowy/plugins/document/application/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/user/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/workspace/prelude.dart';
|
||||
import 'package:appflowy/workspace/application/edit_panel/edit_panel_bloc.dart';
|
||||
@@ -32,26 +32,35 @@ import 'package:get_it/get_it.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class DependencyResolver {
|
||||
static Future<void> resolve(GetIt getIt) async {
|
||||
static Future<void> resolve(
|
||||
GetIt getIt,
|
||||
IntegrationMode mode,
|
||||
) async {
|
||||
_resolveUserDeps(getIt);
|
||||
|
||||
_resolveHomeDeps(getIt);
|
||||
|
||||
_resolveFolderDeps(getIt);
|
||||
|
||||
_resolveDocDeps(getIt);
|
||||
|
||||
_resolveGridDeps(getIt);
|
||||
|
||||
_resolveCommonService(getIt);
|
||||
_resolveCommonService(getIt, mode);
|
||||
}
|
||||
}
|
||||
|
||||
void _resolveCommonService(GetIt getIt) async {
|
||||
void _resolveCommonService(
|
||||
GetIt getIt,
|
||||
IntegrationMode mode,
|
||||
) async {
|
||||
// getIt.registerFactory<KeyValueStorage>(() => RustKeyValue());
|
||||
getIt.registerFactory<KeyValueStorage>(() => DartKeyValue());
|
||||
getIt.registerFactory<FilePickerService>(() => FilePicker());
|
||||
getIt.registerFactory<ApplicationDataStorage>(() => ApplicationDataStorage());
|
||||
if (mode.isTest) {
|
||||
getIt.registerFactory<ApplicationDataStorage>(
|
||||
() => MockApplicationDataStorage(),
|
||||
);
|
||||
} else {
|
||||
getIt.registerFactory<ApplicationDataStorage>(
|
||||
() => ApplicationDataStorage(),
|
||||
);
|
||||
}
|
||||
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
() async {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/workspace/application/settings/settings_location_cubit.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:appflowy_backend/appflowy_backend.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -18,8 +17,14 @@ abstract class EntryPoint {
|
||||
Widget create(LaunchConfiguration config);
|
||||
}
|
||||
|
||||
class FlowyRunnerContext {
|
||||
final Directory applicationDataDirectory;
|
||||
|
||||
FlowyRunnerContext({required this.applicationDataDirectory});
|
||||
}
|
||||
|
||||
class FlowyRunner {
|
||||
static Future<void> run(
|
||||
static Future<FlowyRunnerContext> run(
|
||||
EntryPoint f,
|
||||
IntegrationMode mode, {
|
||||
LaunchConfiguration config = const LaunchConfiguration(
|
||||
@@ -32,44 +37,42 @@ class FlowyRunner {
|
||||
// Specify the env
|
||||
initGetIt(getIt, mode, f, config);
|
||||
|
||||
final directory = await getIt<ApplicationDataStorage>()
|
||||
.getPath()
|
||||
.then((value) => Directory(value));
|
||||
|
||||
// final directory = await appFlowyDocumentDirectory();
|
||||
final applicationDataDirectory =
|
||||
await getIt<ApplicationDataStorage>().getPath().then(
|
||||
(value) => Directory(value),
|
||||
);
|
||||
|
||||
// add task
|
||||
final launcher = getIt<AppLauncher>();
|
||||
launcher.addTasks(
|
||||
[
|
||||
// handle platform errors.
|
||||
const PlatformErrorCatcherTask(),
|
||||
// this task should be first task, for handling platform errors.
|
||||
// don't catch errors in test mode
|
||||
if (!mode.isUnitTest) const PlatformErrorCatcherTask(),
|
||||
// localization
|
||||
const InitLocalizationTask(),
|
||||
// init the app window
|
||||
const InitAppWindowTask(),
|
||||
// Init Rust SDK
|
||||
InitRustSDKTask(directory: directory),
|
||||
InitRustSDKTask(directory: applicationDataDirectory),
|
||||
// Load Plugins, like document, grid ...
|
||||
const PluginLoadTask(),
|
||||
|
||||
// init the app widget
|
||||
// ignore in test mode
|
||||
if (!mode.isTest()) ...[
|
||||
if (!mode.isUnitTest) ...[
|
||||
const HotKeyTask(),
|
||||
InitSupabaseTask(
|
||||
url: Env.supabaseUrl,
|
||||
anonKey: Env.supabaseAnonKey,
|
||||
key: Env.supabaseKey,
|
||||
jwtSecret: Env.supabaseJwtSecret,
|
||||
collabTable: Env.supabaseCollabTable,
|
||||
),
|
||||
InitSupabaseTask(),
|
||||
const InitAppWidgetTask(),
|
||||
const InitPlatformServiceTask()
|
||||
],
|
||||
],
|
||||
);
|
||||
await launcher.launch(); // execute the tasks
|
||||
|
||||
return FlowyRunnerContext(
|
||||
applicationDataDirectory: applicationDataDirectory,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +97,7 @@ Future<void> initGetIt(
|
||||
);
|
||||
getIt.registerSingleton<PluginSandbox>(PluginSandbox());
|
||||
|
||||
await DependencyResolver.resolve(getIt);
|
||||
await DependencyResolver.resolve(getIt, env);
|
||||
}
|
||||
|
||||
class LaunchContext {
|
||||
@@ -145,23 +148,24 @@ class AppLauncher {
|
||||
enum IntegrationMode {
|
||||
develop,
|
||||
release,
|
||||
test,
|
||||
integrationTest,
|
||||
}
|
||||
unitTest,
|
||||
integrationTest;
|
||||
|
||||
extension IntegrationEnvExt on IntegrationMode {
|
||||
bool isTest() {
|
||||
return this == IntegrationMode.test;
|
||||
}
|
||||
// test mode
|
||||
bool get isTest => isUnitTest || isIntegrationTest;
|
||||
bool get isUnitTest => this == IntegrationMode.unitTest;
|
||||
bool get isIntegrationTest => this == IntegrationMode.integrationTest;
|
||||
|
||||
bool isIntegrationTest() {
|
||||
return this == IntegrationMode.integrationTest;
|
||||
}
|
||||
// release mode
|
||||
bool get isRelease => this == IntegrationMode.release;
|
||||
|
||||
// develop mode
|
||||
bool get isDevelop => this == IntegrationMode.develop;
|
||||
}
|
||||
|
||||
IntegrationMode integrationEnv() {
|
||||
if (Platform.environment.containsKey('FLUTTER_TEST')) {
|
||||
return IntegrationMode.test;
|
||||
return IntegrationMode.unitTest;
|
||||
}
|
||||
|
||||
if (kReleaseMode) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide Log;
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -79,8 +80,13 @@ class ApplicationWidget extends StatelessWidget {
|
||||
final cubit = AppearanceSettingsCubit(appearanceSetting)
|
||||
..readLocaleWhenAppLaunch(context);
|
||||
|
||||
return BlocProvider(
|
||||
create: (context) => cubit,
|
||||
return MultiBlocProvider(
|
||||
providers: [
|
||||
BlocProvider.value(value: cubit),
|
||||
BlocProvider<DocumentAppearanceCubit>(
|
||||
create: (_) => DocumentAppearanceCubit()..fetch(),
|
||||
),
|
||||
],
|
||||
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
||||
builder: (context, state) => MaterialApp(
|
||||
builder: overlayManagerBuilder(),
|
||||
|
||||
@@ -8,5 +8,6 @@ class InitLocalizationTask extends LaunchTask {
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
await EasyLocalization.ensureInitialized();
|
||||
EasyLocalization.logger.enableBuildModes = [];
|
||||
}
|
||||
}
|
||||
@@ -23,31 +23,29 @@ class InitRustSDKTask extends LaunchTask {
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
final dir = directory ?? await appFlowyApplicationDataDirectory();
|
||||
|
||||
context.getIt<FlowySDK>().setEnv(getAppFlowyEnv());
|
||||
final env = getAppFlowyEnv();
|
||||
context.getIt<FlowySDK>().setEnv(env);
|
||||
await context.getIt<FlowySDK>().init(dir);
|
||||
}
|
||||
}
|
||||
|
||||
AppFlowyEnv getAppFlowyEnv() {
|
||||
final postgresConfig = PostgresConfiguration(
|
||||
url: Env.supabaseDb,
|
||||
password: Env.supabaseDbPassword,
|
||||
port: int.parse(Env.supabaseDbPort),
|
||||
user_name: Env.supabaseDbUser,
|
||||
);
|
||||
|
||||
final supabaseConfig = SupabaseConfiguration(
|
||||
url: Env.supabaseUrl,
|
||||
key: Env.supabaseKey,
|
||||
jwt_secret: Env.supabaseJwtSecret,
|
||||
);
|
||||
|
||||
final collabTableConfig =
|
||||
CollabTableConfig(enable: true, table_name: Env.supabaseCollabTable);
|
||||
|
||||
final supbaseDBConfig = SupabaseDBConfig(
|
||||
url: Env.supabaseUrl,
|
||||
key: Env.supabaseKey,
|
||||
jwt_secret: Env.supabaseJwtSecret,
|
||||
collab_table_config: collabTableConfig,
|
||||
postgres_config: postgresConfig,
|
||||
);
|
||||
|
||||
return AppFlowyEnv(
|
||||
supabase_config: supabaseConfig,
|
||||
supabase_db_config: supbaseDBConfig,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -62,7 +60,7 @@ Future<Directory> appFlowyApplicationDataDirectory() async {
|
||||
case IntegrationMode.release:
|
||||
final Directory documentsDir = await getApplicationSupportDirectory();
|
||||
return Directory(path.join(documentsDir.path, 'data')).create();
|
||||
case IntegrationMode.test:
|
||||
case IntegrationMode.unitTest:
|
||||
case IntegrationMode.integrationTest:
|
||||
return Directory(path.join(Directory.current.path, '.sandbox'));
|
||||
}
|
||||
|
||||
@@ -1,49 +1,37 @@
|
||||
import 'package:appflowy/core/config/config.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import '../startup.dart';
|
||||
|
||||
bool isSupabaseEnable = false;
|
||||
bool isSupabaseInitialized = false;
|
||||
|
||||
class InitSupabaseTask extends LaunchTask {
|
||||
const InitSupabaseTask({
|
||||
required this.url,
|
||||
required this.anonKey,
|
||||
required this.key,
|
||||
required this.jwtSecret,
|
||||
this.collabTable = "",
|
||||
});
|
||||
|
||||
final String url;
|
||||
final String anonKey;
|
||||
final String key;
|
||||
final String jwtSecret;
|
||||
final String collabTable;
|
||||
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
if (url.isEmpty || anonKey.isEmpty || jwtSecret.isEmpty || key.isEmpty) {
|
||||
isSupabaseEnable = false;
|
||||
Log.info('Supabase config is empty, skip init supabase.');
|
||||
if (!isSupabaseEnable) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSupabaseInitialized) {
|
||||
return;
|
||||
}
|
||||
await Supabase.initialize(
|
||||
url: url,
|
||||
anonKey: anonKey,
|
||||
url: Env.supabaseUrl,
|
||||
anonKey: Env.supabaseAnonKey,
|
||||
debug: false,
|
||||
);
|
||||
|
||||
await Config.setSupabaseConfig(
|
||||
url: url,
|
||||
key: key,
|
||||
secret: jwtSecret,
|
||||
anonKey: anonKey,
|
||||
url: Env.supabaseUrl,
|
||||
key: Env.supabaseKey,
|
||||
secret: Env.supabaseJwtSecret,
|
||||
anonKey: Env.supabaseAnonKey,
|
||||
pgPassword: Env.supabaseDbPassword,
|
||||
pgPort: Env.supabaseDbPort,
|
||||
pgUrl: Env.supabaseDb,
|
||||
pgUser: Env.supabaseDbUser,
|
||||
);
|
||||
isSupabaseEnable = true;
|
||||
isSupabaseInitialized = true;
|
||||
}
|
||||
}
|
||||
@@ -19,17 +19,14 @@ class InitAppWindowTask extends LaunchTask with WindowListener {
|
||||
@override
|
||||
Future<void> initialize(LaunchContext context) async {
|
||||
// Don't initialize on mobile or web.
|
||||
if (!defaultTargetPlatform.isDesktop) {
|
||||
if (!defaultTargetPlatform.isDesktop || context.env.isIntegrationTest) {
|
||||
return;
|
||||
}
|
||||
|
||||
await windowManager.ensureInitialized();
|
||||
windowManager.addListener(this);
|
||||
|
||||
Size windowSize = await WindowSizeManager().getSize();
|
||||
if (context.env.isIntegrationTest()) {
|
||||
windowSize = const Size(1600, 1200);
|
||||
}
|
||||
final windowSize = await WindowSizeManager().getSize();
|
||||
|
||||
final windowOptions = WindowOptions(
|
||||
size: windowSize,
|
||||
|
||||
@@ -14,6 +14,6 @@ class AuthError {
|
||||
..code = -10003;
|
||||
|
||||
static final supabaseGetUserError = FlowyError()
|
||||
..msg = 'supabase sign in with oauth error'
|
||||
..code = -10003;
|
||||
..msg = 'unable to get user from supabase'
|
||||
..code = -10004;
|
||||
}
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/startup/tasks/prelude.dart';
|
||||
import 'package:appflowy/env/env.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';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
@@ -112,13 +110,12 @@ class SupabaseAuthService implements AuthService {
|
||||
final completer = Completer<Either<FlowyError, UserProfilePB>>();
|
||||
late final StreamSubscription<AuthState> subscription;
|
||||
subscription = _auth.onAuthStateChange.listen((event) async {
|
||||
if (event.event != AuthChangeEvent.signedIn) {
|
||||
final user = event.session?.user;
|
||||
if (event.event != AuthChangeEvent.signedIn || user == null) {
|
||||
completer.complete(left(AuthError.supabaseSignInWithOauthError));
|
||||
} else {
|
||||
final user = await getSupabaseUser();
|
||||
final Either<FlowyError, UserProfilePB> response = await user.fold(
|
||||
(l) => left(l),
|
||||
(r) async => await setupAuth(map: {AuthServiceMapKeys.uuid: r.id}),
|
||||
final Either<FlowyError, UserProfilePB> response = await setupAuth(
|
||||
map: {AuthServiceMapKeys.uuid: user.id},
|
||||
);
|
||||
completer.complete(response);
|
||||
}
|
||||
@@ -164,16 +161,21 @@ class SupabaseAuthService implements AuthService {
|
||||
return _appFlowyAuthService.signUpAsGuest();
|
||||
}
|
||||
|
||||
// @override
|
||||
// Future<Either<FlowyError, UserProfilePB>> getUser() async {
|
||||
// final loginType = await getIt<KeyValueStorage>()
|
||||
// .get(KVKeys.loginType)
|
||||
// .then((value) => value.toOption().toNullable());
|
||||
// if (!isSupabaseEnable || (loginType != null && loginType != 'supabase')) {
|
||||
// return _appFlowyAuthService.getUser();
|
||||
// }
|
||||
// final user = await getSupabaseUser();
|
||||
// return user.map((r) => r.toUserProfile());
|
||||
// }
|
||||
|
||||
@override
|
||||
Future<Either<FlowyError, UserProfilePB>> getUser() async {
|
||||
final loginType = await getIt<KeyValueStorage>()
|
||||
.get(KVKeys.loginType)
|
||||
.then((value) => value.toOption().toNullable());
|
||||
if (!isSupabaseEnable || (loginType != null && loginType != 'supabase')) {
|
||||
return _appFlowyAuthService.getUser();
|
||||
}
|
||||
final user = await getSupabaseUser();
|
||||
return user.map((r) => r.toUserProfile());
|
||||
return UserBackendService.getCurrentUserProfile();
|
||||
}
|
||||
|
||||
Future<Either<FlowyError, User>> getSupabaseUser() async {
|
||||
@@ -197,14 +199,6 @@ class SupabaseAuthService implements AuthService {
|
||||
}
|
||||
}
|
||||
|
||||
extension on User {
|
||||
UserProfilePB toUserProfile() {
|
||||
return UserProfilePB()
|
||||
..email = email ?? ''
|
||||
..token = this.id;
|
||||
}
|
||||
}
|
||||
|
||||
extension on String {
|
||||
Provider toProvider() {
|
||||
switch (this) {
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/prelude.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -12,7 +13,6 @@ import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
import '../../../generated/locale_keys.g.dart';
|
||||
import '../../../startup/startup.dart';
|
||||
import '../../../workspace/application/settings/settings_location_cubit.dart';
|
||||
import '../../../workspace/presentation/home/toast.dart';
|
||||
|
||||
enum _FolderPage {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import 'package:appflowy/startup/tasks/supabase_task.dart';
|
||||
import 'package:appflowy/env/env.dart';
|
||||
import 'package:appflowy/user/application/auth/auth_service.dart';
|
||||
import 'package:appflowy_backend/dispatch/dispatch.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
@@ -96,7 +96,7 @@ class SplashScreen extends StatelessWidget {
|
||||
}
|
||||
|
||||
Future<void> _registerIfNeeded() async {
|
||||
final result = await UserEventCheckUser().send();
|
||||
final result = await UserEventGetUserProfile().send();
|
||||
if (!result.isLeft()) {
|
||||
await getIt<AuthService>().signUpAsGuest();
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
|
||||
part 'appearance.freezed.dart';
|
||||
|
||||
@@ -36,10 +37,10 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
|
||||
/// Update selected theme in the user's settings and emit an updated state
|
||||
/// with the AppTheme named [themeName].
|
||||
void setTheme(String themeName) {
|
||||
Future<void> setTheme(String themeName) async {
|
||||
_setting.theme = themeName;
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(appTheme: AppTheme.fromName(themeName)));
|
||||
emit(state.copyWith(appTheme: await AppTheme.fromName(themeName)));
|
||||
}
|
||||
|
||||
/// Update the theme mode in the user's settings and emit an updated state.
|
||||
@@ -49,11 +50,27 @@ class AppearanceSettingsCubit extends Cubit<AppearanceSettingsState> {
|
||||
emit(state.copyWith(themeMode: themeMode));
|
||||
}
|
||||
|
||||
/// Toggle the theme mode
|
||||
void toggleThemeMode() {
|
||||
final currentThemeMode = state.themeMode;
|
||||
setThemeMode(
|
||||
currentThemeMode == ThemeMode.light ? ThemeMode.dark : ThemeMode.light,
|
||||
);
|
||||
}
|
||||
|
||||
/// Update selected font in the user's settings and emit an updated state
|
||||
/// with the font name.
|
||||
void setFontFamily(String fontFamilyName) {
|
||||
_setting.font = fontFamilyName;
|
||||
_saveAppearanceSettings();
|
||||
emit(state.copyWith(font: fontFamilyName));
|
||||
}
|
||||
|
||||
/// Updates the current locale and notify the listeners the locale was
|
||||
/// changed. Fallback to [en] locale if [newLocale] is not supported.
|
||||
void setLocale(BuildContext context, Locale newLocale) {
|
||||
if (!context.supportedLocales.contains(newLocale)) {
|
||||
Log.warn("Unsupported locale: $newLocale, Fallback to locale: en");
|
||||
// Log.warn("Unsupported locale: $newLocale, Fallback to locale: en");
|
||||
newLocale = const Locale('en');
|
||||
}
|
||||
|
||||
@@ -182,7 +199,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
double menuOffset,
|
||||
) {
|
||||
return AppearanceSettingsState(
|
||||
appTheme: AppTheme.fromName(themeName),
|
||||
appTheme: AppTheme.fallback,
|
||||
font: font,
|
||||
monospaceFont: monospaceFont,
|
||||
themeMode: _themeModeFromPB(themeModePB),
|
||||
@@ -341,14 +358,24 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
}
|
||||
|
||||
TextStyle _getFontStyle({
|
||||
String? fontFamily,
|
||||
required String fontFamily,
|
||||
double? fontSize,
|
||||
FontWeight? fontWeight,
|
||||
Color? fontColor,
|
||||
double? letterSpacing,
|
||||
double? lineHeight,
|
||||
}) =>
|
||||
TextStyle(
|
||||
}) {
|
||||
try {
|
||||
return GoogleFonts.getFont(
|
||||
fontFamily,
|
||||
fontSize: fontSize ?? FontSizes.s12,
|
||||
color: fontColor,
|
||||
fontWeight: fontWeight ?? FontWeight.w500,
|
||||
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
|
||||
height: lineHeight,
|
||||
);
|
||||
} catch (e) {
|
||||
return TextStyle(
|
||||
fontFamily: fontFamily,
|
||||
fontSize: fontSize ?? FontSizes.s12,
|
||||
color: fontColor,
|
||||
@@ -357,6 +384,8 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
letterSpacing: (fontSize ?? FontSizes.s12) * (letterSpacing ?? 0.005),
|
||||
height: lineHeight,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
TextTheme _getTextTheme({
|
||||
required String fontFamily,
|
||||
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../startup/tasks/prelude.dart';
|
||||
|
||||
const appFlowyDataFolder = "AppFlowyDataDoNotRename";
|
||||
|
||||
class ApplicationDataStorage {
|
||||
ApplicationDataStorage();
|
||||
String? _cachePath;
|
||||
|
||||
/// Set the custom path to store the data.
|
||||
/// If the path is not exists, the path will be created.
|
||||
/// If the path is invalid, the path will be set to the default path.
|
||||
Future<void> setCustomPath(String path) async {
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
|
||||
Log.info('LocalFileStorage is not supported on this platform.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
// remove the prefix `/Volumes/*`
|
||||
path = path.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
|
||||
} else if (Platform.isWindows) {
|
||||
path = path.replaceAll('/', '\\');
|
||||
}
|
||||
|
||||
// If the path is not ends with `AppFlowyData`, we will append the
|
||||
// `AppFlowyData` to the path. If the path is ends with `AppFlowyData`,
|
||||
// which means the path is the custom path.
|
||||
if (p.basename(path) != appFlowyDataFolder) {
|
||||
path = p.join(path, appFlowyDataFolder);
|
||||
}
|
||||
|
||||
// create the directory if not exists.
|
||||
final directory = Directory(path);
|
||||
if (!directory.existsSync()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
||||
setPath(path);
|
||||
}
|
||||
|
||||
Future<void> setPath(String path) async {
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
|
||||
Log.info('LocalFileStorage is not supported on this platform.');
|
||||
return;
|
||||
}
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.pathLocation, path);
|
||||
// clear the cache path, and not set the cache path to the new path because the set path may be invalid
|
||||
_cachePath = null;
|
||||
}
|
||||
|
||||
Future<String> getPath() async {
|
||||
if (_cachePath != null) {
|
||||
return _cachePath!;
|
||||
}
|
||||
|
||||
final response = await getIt<KeyValueStorage>().get(KVKeys.pathLocation);
|
||||
String path = await response.fold(
|
||||
(error) async {
|
||||
// return the default path if the path is not set
|
||||
final directory = await appFlowyApplicationDataDirectory();
|
||||
return directory.path;
|
||||
},
|
||||
(path) => path,
|
||||
);
|
||||
_cachePath = path;
|
||||
|
||||
// if the path is not exists means the path is invalid, so we should clear the kv store
|
||||
if (!Directory(path).existsSync()) {
|
||||
await getIt<KeyValueStorage>().clear();
|
||||
final directory = await appFlowyApplicationDataDirectory();
|
||||
path = directory.path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
class MockApplicationDataStorage extends ApplicationDataStorage {
|
||||
MockApplicationDataStorage();
|
||||
|
||||
// this value will be clear after setup
|
||||
// only for the initial step
|
||||
@visibleForTesting
|
||||
static String? initialPath;
|
||||
|
||||
@override
|
||||
Future<String> getPath() async {
|
||||
final path = initialPath;
|
||||
if (path != null) {
|
||||
initialPath = null;
|
||||
await super.setPath(path);
|
||||
return Future.value(path);
|
||||
}
|
||||
return super.getPath();
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
export 'settings_dialog_bloc.dart';
|
||||
export 'application_data_storage.dart';
|
||||
+2
-83
@@ -1,13 +1,8 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/core/config/kv.dart';
|
||||
import 'package:appflowy/core/config/kv_keys.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy/workspace/application/settings/application_data_storage.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
import '../../../startup/tasks/prelude.dart';
|
||||
|
||||
@@ -27,7 +22,7 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
|
||||
|
||||
Future<void> resetDataStoragePathToApplicationDefault() async {
|
||||
final directory = await appFlowyApplicationDataDirectory();
|
||||
await getIt<ApplicationDataStorage>()._setPath(directory.path);
|
||||
await getIt<ApplicationDataStorage>().setPath(directory.path);
|
||||
emit(SettingsLocationState.didReceivedPath(directory.path));
|
||||
}
|
||||
|
||||
@@ -41,79 +36,3 @@ class SettingsLocationCubit extends Cubit<SettingsLocationState> {
|
||||
emit(SettingsLocationState.didReceivedPath(path));
|
||||
}
|
||||
}
|
||||
|
||||
const appFlowyDataFolder = "AppFlowyDataDoNotRename";
|
||||
|
||||
class ApplicationDataStorage {
|
||||
ApplicationDataStorage();
|
||||
String? _cachePath;
|
||||
|
||||
/// Set the custom path to store the data.
|
||||
/// If the path is not exists, the path will be created.
|
||||
/// If the path is invalid, the path will be set to the default path.
|
||||
Future<void> setCustomPath(String path) async {
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
|
||||
Log.info('LocalFileStorage is not supported on this platform.');
|
||||
return;
|
||||
}
|
||||
|
||||
if (Platform.isMacOS) {
|
||||
// remove the prefix `/Volumes/*`
|
||||
path = path.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
|
||||
} else if (Platform.isWindows) {
|
||||
path = path.replaceAll('/', '\\');
|
||||
}
|
||||
|
||||
// If the path is not ends with `AppFlowyData`, we will append the
|
||||
// `AppFlowyData` to the path. If the path is ends with `AppFlowyData`,
|
||||
// which means the path is the custom path.
|
||||
if (p.basename(path) != appFlowyDataFolder) {
|
||||
path = p.join(path, appFlowyDataFolder);
|
||||
}
|
||||
|
||||
// create the directory if not exists.
|
||||
final directory = Directory(path);
|
||||
if (!directory.existsSync()) {
|
||||
await directory.create(recursive: true);
|
||||
}
|
||||
|
||||
_setPath(path);
|
||||
}
|
||||
|
||||
Future<void> _setPath(String path) async {
|
||||
if (kIsWeb || Platform.isAndroid || Platform.isIOS) {
|
||||
Log.info('LocalFileStorage is not supported on this platform.');
|
||||
return;
|
||||
}
|
||||
|
||||
await getIt<KeyValueStorage>().set(KVKeys.pathLocation, path);
|
||||
// clear the cache path, and not set the cache path to the new path because the set path may be invalid
|
||||
_cachePath = null;
|
||||
}
|
||||
|
||||
Future<String> getPath() async {
|
||||
if (_cachePath != null) {
|
||||
return _cachePath!;
|
||||
}
|
||||
|
||||
final response = await getIt<KeyValueStorage>().get(KVKeys.pathLocation);
|
||||
String path = await response.fold(
|
||||
(error) async {
|
||||
// return the default path if the path is not set
|
||||
final directory = await appFlowyApplicationDataDirectory();
|
||||
return directory.path;
|
||||
},
|
||||
(path) => path,
|
||||
);
|
||||
_cachePath = path;
|
||||
|
||||
// if the path is not exists means the path is invalid, so we should clear the kv store
|
||||
if (!Directory(path).existsSync()) {
|
||||
await getIt<KeyValueStorage>().clear();
|
||||
final directory = await appFlowyApplicationDataDirectory();
|
||||
path = directory.path;
|
||||
}
|
||||
|
||||
return path;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy/workspace/application/home/home_setting_bloc.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hotkey_manager/hotkey_manager.dart';
|
||||
@@ -25,6 +26,21 @@ class HomeHotKeys extends StatelessWidget {
|
||||
.add(const HomeSettingEvent.collapseMenu());
|
||||
},
|
||||
);
|
||||
|
||||
final HotKey hotKeyForToggleThemeMode = HotKey(
|
||||
KeyCode.keyL,
|
||||
modifiers: [
|
||||
Platform.isMacOS ? KeyModifier.meta : KeyModifier.control,
|
||||
KeyModifier.shift,
|
||||
],
|
||||
scope: HotKeyScope.inapp,
|
||||
);
|
||||
hotKeyManager.register(
|
||||
hotKeyForToggleThemeMode,
|
||||
keyDownHandler: (_) {
|
||||
context.read<AppearanceSettingsCubit>().toggleThemeMode();
|
||||
},
|
||||
);
|
||||
return child;
|
||||
}
|
||||
}
|
||||
+1
-2
@@ -5,12 +5,11 @@ import 'dart:typed_data';
|
||||
import 'package:appflowy/plugins/document/application/document_data_pb_extension.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/editor_plugins/migration/editor_migration.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_service.dart';
|
||||
import 'package:appflowy/workspace/application/settings/share/import_service.dart';
|
||||
import 'package:appflowy/workspace/presentation/home/menu/app/header/import/import_type.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder2/protobuf.dart';
|
||||
import 'package:flowy_infra/file_picker/file_picker_service.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/container.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/color_generator/color_generator.dart';
|
||||
import 'package:appflowy/workspace/application/menu/menu_user_bloc.dart';
|
||||
@@ -103,7 +104,10 @@ class MenuUser extends StatelessWidget {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SettingsDialog(userProfile);
|
||||
return BlocProvider<DocumentAppearanceCubit>.value(
|
||||
value: BlocProvider.of<DocumentAppearanceCubit>(context),
|
||||
child: SettingsDialog(userProfile),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
|
||||
Loaded 100 of 432 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user