From 516e894c6abd706ff54ed2f03e506870ab2281ec Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 22 Jul 2022 09:42:10 +0800 Subject: [PATCH 01/22] chore: borad ui --- .../presentation/plugins/board/board.dart | 2 +- .../app_flowy/packages/flowy_board/.gitignore | 30 + .../app_flowy/packages/flowy_board/.metadata | 36 ++ .../packages/flowy_board/CHANGELOG.md | 3 + .../app_flowy/packages/flowy_board/LICENSE | 1 + .../app_flowy/packages/flowy_board/README.md | 15 + .../flowy_board/analysis_options.yaml | 4 + .../packages/flowy_board/example/.gitignore | 47 ++ .../packages/flowy_board/example/README.md | 16 + .../flowy_board/example/analysis_options.yaml | 29 + .../flowy_board/example/lib/main.dart | 63 ++ .../flowy_board/example/linux/.gitignore | 1 + .../flowy_board/example/linux/CMakeLists.txt | 138 +++++ .../example/linux/flutter/CMakeLists.txt | 88 +++ .../flutter/generated_plugin_registrant.cc | 15 + .../flutter/generated_plugin_registrant.h | 15 + .../linux/flutter/generated_plugins.cmake | 24 + .../flowy_board/example/linux/main.cc | 6 + .../example/linux/my_application.cc | 104 ++++ .../example/linux/my_application.h | 18 + .../flowy_board/example/macos/.gitignore | 7 + .../macos/Flutter/Flutter-Debug.xcconfig | 2 + .../macos/Flutter/Flutter-Release.xcconfig | 2 + .../Flutter/GeneratedPluginRegistrant.swift | 12 + .../flowy_board/example/macos/Podfile | 40 ++ .../macos/Runner.xcodeproj/project.pbxproj | 572 ++++++++++++++++++ .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 +++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 9 + .../AppIcon.appiconset/Contents.json | 68 +++ .../AppIcon.appiconset/app_icon_1024.png | Bin 0 -> 46993 bytes .../AppIcon.appiconset/app_icon_128.png | Bin 0 -> 3276 bytes .../AppIcon.appiconset/app_icon_16.png | Bin 0 -> 1429 bytes .../AppIcon.appiconset/app_icon_256.png | Bin 0 -> 5933 bytes .../AppIcon.appiconset/app_icon_32.png | Bin 0 -> 1243 bytes .../AppIcon.appiconset/app_icon_512.png | Bin 0 -> 14800 bytes .../AppIcon.appiconset/app_icon_64.png | Bin 0 -> 1874 bytes .../macos/Runner/Base.lproj/MainMenu.xib | 343 +++++++++++ .../macos/Runner/Configs/AppInfo.xcconfig | 14 + .../macos/Runner/Configs/Debug.xcconfig | 2 + .../macos/Runner/Configs/Release.xcconfig | 2 + .../macos/Runner/Configs/Warnings.xcconfig | 13 + .../macos/Runner/DebugProfile.entitlements | 12 + .../example/macos/Runner/Info.plist | 32 + .../macos/Runner/MainFlutterWindow.swift | 15 + .../example/macos/Runner/Release.entitlements | 8 + .../packages/flowy_board/example/pubspec.yaml | 84 +++ .../flowy_board/example/test/widget_test.dart | 27 + .../flowy_board/example/windows/.gitignore | 17 + .../example/windows/CMakeLists.txt | 101 ++++ .../example/windows/flutter/CMakeLists.txt | 104 ++++ .../flutter/generated_plugin_registrant.cc | 14 + .../flutter/generated_plugin_registrant.h | 15 + .../windows/flutter/generated_plugins.cmake | 24 + .../example/windows/runner/CMakeLists.txt | 32 + .../example/windows/runner/Runner.rc | 121 ++++ .../example/windows/runner/flutter_window.cpp | 61 ++ .../example/windows/runner/flutter_window.h | 33 + .../example/windows/runner/main.cpp | 43 ++ .../example/windows/runner/resource.h | 16 + .../windows/runner/resources/app_icon.ico | Bin 0 -> 33772 bytes .../windows/runner/runner.exe.manifest | 20 + .../example/windows/runner/utils.cpp | 64 ++ .../example/windows/runner/utils.h | 19 + .../example/windows/runner/win32_window.cpp | 245 ++++++++ .../example/windows/runner/win32_window.h | 98 +++ .../packages/flowy_board/lib/flowy_board.dart | 8 + .../lib/flowy_board_method_channel.dart | 17 + .../lib/flowy_board_platform_interface.dart | 29 + .../lib/src/widgets/board_overlay.dart | 410 +++++++++++++ .../lib/src/widgets/transitions.dart | 113 ++++ .../packages/flowy_board/linux/CMakeLists.txt | 47 ++ .../flowy_board/linux/flowy_board_plugin.cc | 70 +++ .../include/flowy_board/flowy_board_plugin.h | 26 + .../macos/Classes/FlowyBoardPlugin.swift | 19 + .../flowy_board/macos/flowy_board.podspec | 23 + .../packages/flowy_board/pubspec.yaml | 73 +++ .../test/flowy_board_method_channel_test.dart | 24 + .../flowy_board/test/flowy_board_test.dart | 29 + .../packages/flowy_board/windows/.gitignore | 17 + .../flowy_board/windows/CMakeLists.txt | 53 ++ .../windows/flowy_board_plugin.cpp | 59 ++ .../flowy_board/windows/flowy_board_plugin.h | 32 + .../windows/flowy_board_plugin_c_api.cpp | 12 + .../flowy_board/flowy_board_plugin_c_api.h | 23 + .../windows/flutter/generated_plugins.cmake | 8 + frontend/app_flowy/pubspec.yaml | 1 + 89 files changed, 4148 insertions(+), 1 deletion(-) create mode 100644 frontend/app_flowy/packages/flowy_board/.gitignore create mode 100644 frontend/app_flowy/packages/flowy_board/.metadata create mode 100644 frontend/app_flowy/packages/flowy_board/CHANGELOG.md create mode 100644 frontend/app_flowy/packages/flowy_board/LICENSE create mode 100644 frontend/app_flowy/packages/flowy_board/README.md create mode 100644 frontend/app_flowy/packages/flowy_board/analysis_options.yaml create mode 100644 frontend/app_flowy/packages/flowy_board/example/.gitignore create mode 100644 frontend/app_flowy/packages/flowy_board/example/README.md create mode 100644 frontend/app_flowy/packages/flowy_board/example/analysis_options.yaml create mode 100644 frontend/app_flowy/packages/flowy_board/example/lib/main.dart create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/.gitignore create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/CMakeLists.txt create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/flutter/CMakeLists.txt create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.h create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugins.cmake create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/main.cc create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/my_application.cc create mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/my_application.h create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/.gitignore create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Podfile create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/AppDelegate.swift create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Base.lproj/MainMenu.xib create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/AppInfo.xcconfig create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Debug.xcconfig create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Release.xcconfig create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Warnings.xcconfig create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/DebugProfile.entitlements create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Info.plist create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/MainFlutterWindow.swift create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Runner/Release.entitlements create mode 100644 frontend/app_flowy/packages/flowy_board/example/pubspec.yaml create mode 100644 frontend/app_flowy/packages/flowy_board/example/test/widget_test.dart create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/.gitignore create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/CMakeLists.txt create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/flutter/CMakeLists.txt create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.cc create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.h create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugins.cmake create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/CMakeLists.txt create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/Runner.rc create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.cpp create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.h create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/main.cpp create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/resource.h create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/resources/app_icon.ico create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/runner.exe.manifest create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.cpp create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.h create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.cpp create mode 100644 frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.h create mode 100644 frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_overlay.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/transitions.dart create mode 100644 frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt create mode 100644 frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc create mode 100644 frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h create mode 100644 frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift create mode 100644 frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec create mode 100644 frontend/app_flowy/packages/flowy_board/pubspec.yaml create mode 100644 frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart create mode 100644 frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart create mode 100644 frontend/app_flowy/packages/flowy_board/windows/.gitignore create mode 100644 frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt create mode 100644 frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp create mode 100644 frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h create mode 100644 frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp create mode 100644 frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h diff --git a/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart b/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart index ab58176e50..30e8d2743c 100644 --- a/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart +++ b/frontend/app_flowy/lib/workspace/presentation/plugins/board/board.dart @@ -28,7 +28,7 @@ class BoardPluginBuilder implements PluginBuilder { class BoardPluginConfig implements PluginConfig { @override - bool get creatable => false; + bool get creatable => true; } class BoardPlugin extends Plugin { diff --git a/frontend/app_flowy/packages/flowy_board/.gitignore b/frontend/app_flowy/packages/flowy_board/.gitignore new file mode 100644 index 0000000000..96486fd930 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/.gitignore @@ -0,0 +1,30 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock. +/pubspec.lock +**/doc/api/ +.dart_tool/ +.packages +build/ diff --git a/frontend/app_flowy/packages/flowy_board/.metadata b/frontend/app_flowy/packages/flowy_board/.metadata new file mode 100644 index 0000000000..2e1d1339c3 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/.metadata @@ -0,0 +1,36 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled. + +version: + revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + channel: stable + +project_type: plugin + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: linux + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: macos + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + - platform: windows + create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/frontend/app_flowy/packages/flowy_board/CHANGELOG.md b/frontend/app_flowy/packages/flowy_board/CHANGELOG.md new file mode 100644 index 0000000000..41cc7d8192 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/CHANGELOG.md @@ -0,0 +1,3 @@ +## 0.0.1 + +* TODO: Describe initial release. diff --git a/frontend/app_flowy/packages/flowy_board/LICENSE b/frontend/app_flowy/packages/flowy_board/LICENSE new file mode 100644 index 0000000000..ba75c69f7f --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/frontend/app_flowy/packages/flowy_board/README.md b/frontend/app_flowy/packages/flowy_board/README.md new file mode 100644 index 0000000000..fab2cf6e57 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/README.md @@ -0,0 +1,15 @@ +# flowy_board + +A new Flutter plugin project. + +## Getting Started + +This project is a starting point for a Flutter +[plug-in package](https://flutter.dev/developing-packages/), +a specialized package that includes platform-specific implementation code for +Android and/or iOS. + +For help getting started with Flutter development, view the +[online documentation](https://flutter.dev/docs), which offers tutorials, +samples, guidance on mobile development, and a full API reference. + diff --git a/frontend/app_flowy/packages/flowy_board/analysis_options.yaml b/frontend/app_flowy/packages/flowy_board/analysis_options.yaml new file mode 100644 index 0000000000..a5744c1cfb --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/analysis_options.yaml @@ -0,0 +1,4 @@ +include: package:flutter_lints/flutter.yaml + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/frontend/app_flowy/packages/flowy_board/example/.gitignore b/frontend/app_flowy/packages/flowy_board/example/.gitignore new file mode 100644 index 0000000000..a8e938c083 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/.gitignore @@ -0,0 +1,47 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ +migrate_working_dir/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json + +# Android Studio will place build artifacts here +/android/app/debug +/android/app/profile +/android/app/release diff --git a/frontend/app_flowy/packages/flowy_board/example/README.md b/frontend/app_flowy/packages/flowy_board/example/README.md new file mode 100644 index 0000000000..66dda99692 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/README.md @@ -0,0 +1,16 @@ +# flowy_board_example + +Demonstrates how to use the flowy_board plugin. + +## Getting Started + +This project is a starting point for a Flutter application. + +A few resources to get you started if this is your first Flutter project: + +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) + +For help getting started with Flutter development, view the +[online documentation](https://docs.flutter.dev/), which offers tutorials, +samples, guidance on mobile development, and a full API reference. diff --git a/frontend/app_flowy/packages/flowy_board/example/analysis_options.yaml b/frontend/app_flowy/packages/flowy_board/example/analysis_options.yaml new file mode 100644 index 0000000000..61b6c4de17 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/analysis_options.yaml @@ -0,0 +1,29 @@ +# This file configures the analyzer, which statically analyzes Dart code to +# check for errors, warnings, and lints. +# +# The issues identified by the analyzer are surfaced in the UI of Dart-enabled +# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be +# invoked from the command line by running `flutter analyze`. + +# The following line activates a set of recommended lints for Flutter apps, +# packages, and plugins designed to encourage good coding practices. +include: package:flutter_lints/flutter.yaml + +linter: + # The lint rules applied to this project can be customized in the + # section below to disable rules from the `package:flutter_lints/flutter.yaml` + # included above or to enable additional rules. A list of all available lints + # and their documentation is published at + # https://dart-lang.github.io/linter/lints/index.html. + # + # Instead of disabling a lint rule for the entire project in the + # section below, it can also be suppressed for a single line of code + # or a specific dart file by using the `// ignore: name_of_lint` and + # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # producing the lint. + rules: + # avoid_print: false # Uncomment to disable the `avoid_print` rule + # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule + +# Additional information about this file can be found at +# https://dart.dev/guides/language/analysis-options diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/main.dart b/frontend/app_flowy/packages/flowy_board/example/lib/main.dart new file mode 100644 index 0000000000..eecf56057f --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/lib/main.dart @@ -0,0 +1,63 @@ +import 'package:flutter/material.dart'; +import 'dart:async'; + +import 'package:flutter/services.dart'; +import 'package:flowy_board/flowy_board.dart'; + +void main() { + runApp(const MyApp()); +} + +class MyApp extends StatefulWidget { + const MyApp({Key? key}) : super(key: key); + + @override + State createState() => _MyAppState(); +} + +class _MyAppState extends State { + String _platformVersion = 'Unknown'; + final _flowyBoardPlugin = FlowyBoard(); + + @override + void initState() { + super.initState(); + initPlatformState(); + } + + // Platform messages are asynchronous, so we initialize in an async method. + Future initPlatformState() async { + String platformVersion; + // Platform messages may fail, so we use a try/catch PlatformException. + // We also handle the message potentially returning null. + try { + platformVersion = + await _flowyBoardPlugin.getPlatformVersion() ?? 'Unknown platform version'; + } on PlatformException { + platformVersion = 'Failed to get platform version.'; + } + + // If the widget was removed from the tree while the asynchronous platform + // message was in flight, we want to discard the reply rather than calling + // setState to update our non-existent appearance. + if (!mounted) return; + + setState(() { + _platformVersion = platformVersion; + }); + } + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + appBar: AppBar( + title: const Text('Plugin example app'), + ), + body: Center( + child: Text('Running on: $_platformVersion\n'), + ), + ), + ); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/.gitignore b/frontend/app_flowy/packages/flowy_board/example/linux/.gitignore new file mode 100644 index 0000000000..d3896c9844 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/.gitignore @@ -0,0 +1 @@ +flutter/ephemeral diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/example/linux/CMakeLists.txt new file mode 100644 index 0000000000..f10cda2c6c --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/CMakeLists.txt @@ -0,0 +1,138 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.10) +project(runner LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "flowy_board_example") +# The unique GTK application identifier for this application. See: +# https://wiki.gnome.org/HowDoI/ChooseApplicationID +set(APPLICATION_ID "com.example.flowy_board") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Load bundled libraries from the lib/ directory relative to the binary. +set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") + +# Root filesystem for cross-building. +if(FLUTTER_TARGET_PLATFORM_SYSROOT) + set(CMAKE_SYSROOT ${FLUTTER_TARGET_PLATFORM_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH ${CMAKE_SYSROOT}) + set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) + set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) + set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +endif() + +# Define build configuration options. +if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") +endif() + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_14) + target_compile_options(${TARGET} PRIVATE -Wall -Werror) + target_compile_options(${TARGET} PRIVATE "$<$>:-O3>") + target_compile_definitions(${TARGET} PRIVATE "$<$>:NDEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) + +add_definitions(-DAPPLICATION_ID="${APPLICATION_ID}") + +# Define the application target. To change its name, change BINARY_NAME above, +# not the value here, or `flutter run` will no longer work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} + "main.cc" + "my_application.cc" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Add dependency libraries. Add any application-specific dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter) +target_link_libraries(${BINARY_NAME} PRIVATE PkgConfig::GTK) + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) + +# Only the install-generated bundle's copy of the executable will launch +# correctly, since the resources must in the right relative locations. To avoid +# people trying to run the unbundled copy, put it in a subdirectory instead of +# the default top-level location. +set_target_properties(${BINARY_NAME} + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/intermediates_do_not_run" +) + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# By default, "installing" just makes a relocatable bundle in the build +# directory. +set(BUILD_BUNDLE_DIR "${PROJECT_BINARY_DIR}/bundle") +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +# Start with a clean build bundle directory every time. +install(CODE " + file(REMOVE_RECURSE \"${BUILD_BUNDLE_DIR}/\") + " COMPONENT Runtime) + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) + install(FILES "${bundled_library}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endforeach(bundled_library) + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +if(NOT CMAKE_BUILD_TYPE MATCHES "Debug") + install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/CMakeLists.txt new file mode 100644 index 0000000000..d5bd01648a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/CMakeLists.txt @@ -0,0 +1,88 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.10) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. + +# Serves the same purpose as list(TRANSFORM ... PREPEND ...), +# which isn't available in 3.10. +function(list_prepend LIST_NAME PREFIX) + set(NEW_LIST "") + foreach(element ${${LIST_NAME}}) + list(APPEND NEW_LIST "${PREFIX}${element}") + endforeach(element) + set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) +endfunction() + +# === Flutter Library === +# System-level dependencies. +find_package(PkgConfig REQUIRED) +pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) +pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) +pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) + +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "fl_basic_message_channel.h" + "fl_binary_codec.h" + "fl_binary_messenger.h" + "fl_dart_project.h" + "fl_engine.h" + "fl_json_message_codec.h" + "fl_json_method_codec.h" + "fl_message_codec.h" + "fl_method_call.h" + "fl_method_channel.h" + "fl_method_codec.h" + "fl_method_response.h" + "fl_plugin_registrar.h" + "fl_plugin_registry.h" + "fl_standard_message_codec.h" + "fl_standard_method_codec.h" + "fl_string_codec.h" + "fl_value.h" + "fl_view.h" + "flutter_linux.h" +) +list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") +target_link_libraries(flutter INTERFACE + PkgConfig::GTK + PkgConfig::GLIB + PkgConfig::GIO +) +add_dependencies(flutter flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/_phony_ + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" + ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} +) diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..8881f303d7 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) flowy_board_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyBoardPlugin"); + flowy_board_plugin_register_with_registrar(flowy_board_registrar); +} diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.h b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000..e0f0a47bc0 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void fl_register_plugins(FlPluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugins.cmake b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugins.cmake new file mode 100644 index 0000000000..dfc1c21713 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flowy_board +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/main.cc b/frontend/app_flowy/packages/flowy_board/example/linux/main.cc new file mode 100644 index 0000000000..e7c5c54370 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/main.cc @@ -0,0 +1,6 @@ +#include "my_application.h" + +int main(int argc, char** argv) { + g_autoptr(MyApplication) app = my_application_new(); + return g_application_run(G_APPLICATION(app), argc, argv); +} diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/my_application.cc b/frontend/app_flowy/packages/flowy_board/example/linux/my_application.cc new file mode 100644 index 0000000000..305813bfa8 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/my_application.cc @@ -0,0 +1,104 @@ +#include "my_application.h" + +#include +#ifdef GDK_WINDOWING_X11 +#include +#endif + +#include "flutter/generated_plugin_registrant.h" + +struct _MyApplication { + GtkApplication parent_instance; + char** dart_entrypoint_arguments; +}; + +G_DEFINE_TYPE(MyApplication, my_application, GTK_TYPE_APPLICATION) + +// Implements GApplication::activate. +static void my_application_activate(GApplication* application) { + MyApplication* self = MY_APPLICATION(application); + GtkWindow* window = + GTK_WINDOW(gtk_application_window_new(GTK_APPLICATION(application))); + + // Use a header bar when running in GNOME as this is the common style used + // by applications and is the setup most users will be using (e.g. Ubuntu + // desktop). + // If running on X and not using GNOME then just use a traditional title bar + // in case the window manager does more exotic layout, e.g. tiling. + // If running on Wayland assume the header bar will work (may need changing + // if future cases occur). + gboolean use_header_bar = TRUE; +#ifdef GDK_WINDOWING_X11 + GdkScreen* screen = gtk_window_get_screen(window); + if (GDK_IS_X11_SCREEN(screen)) { + const gchar* wm_name = gdk_x11_screen_get_window_manager_name(screen); + if (g_strcmp0(wm_name, "GNOME Shell") != 0) { + use_header_bar = FALSE; + } + } +#endif + if (use_header_bar) { + GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); + gtk_widget_show(GTK_WIDGET(header_bar)); + gtk_header_bar_set_title(header_bar, "flowy_board_example"); + gtk_header_bar_set_show_close_button(header_bar, TRUE); + gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); + } else { + gtk_window_set_title(window, "flowy_board_example"); + } + + gtk_window_set_default_size(window, 1280, 720); + gtk_widget_show(GTK_WIDGET(window)); + + g_autoptr(FlDartProject) project = fl_dart_project_new(); + fl_dart_project_set_dart_entrypoint_arguments(project, self->dart_entrypoint_arguments); + + FlView* view = fl_view_new(project); + gtk_widget_show(GTK_WIDGET(view)); + gtk_container_add(GTK_CONTAINER(window), GTK_WIDGET(view)); + + fl_register_plugins(FL_PLUGIN_REGISTRY(view)); + + gtk_widget_grab_focus(GTK_WIDGET(view)); +} + +// Implements GApplication::local_command_line. +static gboolean my_application_local_command_line(GApplication* application, gchar*** arguments, int* exit_status) { + MyApplication* self = MY_APPLICATION(application); + // Strip out the first argument as it is the binary name. + self->dart_entrypoint_arguments = g_strdupv(*arguments + 1); + + g_autoptr(GError) error = nullptr; + if (!g_application_register(application, nullptr, &error)) { + g_warning("Failed to register: %s", error->message); + *exit_status = 1; + return TRUE; + } + + g_application_activate(application); + *exit_status = 0; + + return TRUE; +} + +// Implements GObject::dispose. +static void my_application_dispose(GObject* object) { + MyApplication* self = MY_APPLICATION(object); + g_clear_pointer(&self->dart_entrypoint_arguments, g_strfreev); + G_OBJECT_CLASS(my_application_parent_class)->dispose(object); +} + +static void my_application_class_init(MyApplicationClass* klass) { + G_APPLICATION_CLASS(klass)->activate = my_application_activate; + G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_OBJECT_CLASS(klass)->dispose = my_application_dispose; +} + +static void my_application_init(MyApplication* self) {} + +MyApplication* my_application_new() { + return MY_APPLICATION(g_object_new(my_application_get_type(), + "application-id", APPLICATION_ID, + "flags", G_APPLICATION_NON_UNIQUE, + nullptr)); +} diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/my_application.h b/frontend/app_flowy/packages/flowy_board/example/linux/my_application.h new file mode 100644 index 0000000000..72271d5e41 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/linux/my_application.h @@ -0,0 +1,18 @@ +#ifndef FLUTTER_MY_APPLICATION_H_ +#define FLUTTER_MY_APPLICATION_H_ + +#include + +G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, + GtkApplication) + +/** + * my_application_new: + * + * Creates a new Flutter-based application. + * + * Returns: a new #MyApplication. + */ +MyApplication* my_application_new(); + +#endif // FLUTTER_MY_APPLICATION_H_ diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/.gitignore b/frontend/app_flowy/packages/flowy_board/example/macos/.gitignore new file mode 100644 index 0000000000..746adbb6b9 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/.gitignore @@ -0,0 +1,7 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/dgph +**/xcuserdata/ diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000..4b81f9b2d2 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000..5caa9d1579 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift new file mode 100644 index 0000000000..aaacabcec9 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -0,0 +1,12 @@ +// +// Generated file. Do not edit. +// + +import FlutterMacOS +import Foundation + +import flowy_board + +func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + FlowyBoardPlugin.register(with: registry.registrar(forPlugin: "FlowyBoardPlugin")) +} diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Podfile b/frontend/app_flowy/packages/flowy_board/example/macos/Podfile new file mode 100644 index 0000000000..dade8dfad0 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Podfile @@ -0,0 +1,40 @@ +platform :osx, '10.11' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_macos_podfile_setup + +target 'Runner' do + use_frameworks! + use_modular_headers! + + flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_macos_build_settings(target) + end +end diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..9ce6f7ca08 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,572 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* flowy_board_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "flowy_board_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* flowy_board_example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* flowy_board_example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename && \"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh embed\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000..cd0ca25ddd --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..1d526a16ed --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/AppDelegate.swift b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/AppDelegate.swift new file mode 100644 index 0000000000..d53ef64377 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..a2ec33f19f --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "app_icon_64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "app_icon_1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png new file mode 100644 index 0000000000000000000000000000000000000000..3c4935a7ca84f0976aca34b7f2895d65fb94d1ea GIT binary patch literal 46993 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 literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png new file mode 100644 index 0000000000000000000000000000000000000000..ed4cc16421680a50164ba74381b4b35ceaa0ccfc GIT binary patch literal 3276 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 N;OXk;vd$@?2>?>Ex^Vyi literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png new file mode 100644 index 0000000000000000000000000000000000000000..bcbf36df2f2aaaa0a63c7dabc94e600184229d0d GIT binary patch literal 5933 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 f$KZHO*s&vt|9^w-6P?|#0pRK8NSwWJ?9znhg z3{`3j3=J&|48MRv4KElNN(~qoUL`OvSj}Ky5HFasE6@fg!ItFh?!xdN1Q+aGJ{c&& zS>O>_%)r1c48n{Iv*t(u1=&kHeO=ifbFy+6aSK)V_AxLppYn8Z42d|rc6w}vOsL55 z`t&mC&y2@JTEyg!eDiFX^k#CC!jq%>erB=yHqUP0XcDOTw6ko}L zX;EmMrq(fKk*eygEuA616;0)>@A{TK|55PV@70 z$OfzS*(VJxQev3J?yY?O=ul(v`fp}?u9z`JK3ugibK>)DyCwImZOF4d{xK%%Ks1*} zv$oa)9anR%lXIBUqYnhLmT>VOzHfNP?ZwJNZ!5$s9M08RynIvaXw>@G^T9@r9^KH1 zVy??F&uuk)bH9Y4pQY!hP58i_H6 znl-NcuCpLV6ZWU;4C zu@9exF&OZi`Bovq_m%T+WhU2kvkz@^_LpycBvqm3bMpLw8X-Or5sL>0AKE1$(k_L=_Zc=CUq#=x1-QZf)G7nHu@fmsQ1eN_N3+nTEz`4HI4Z6uVlE zJH+X&det8JU?tO?upcM4Z=cV!JV;yF>FfL5Q$M|W_2Z!P`S=}Wzp|_1^#d%e?_H`> zV@%vA$+bFVqhw9`U;TfP|5|PD{||OiYdor8P*i??|NJcb%kzT_73*7WE?Ua5hAnR2 z=7WE=PhTlJ#ZeRznjTUb;`E(wkMZrj4e|Hilz-mK>9cZHQY**5TUPw~u}k;u73KI}xAx!0m-)GVia|x^d3p~s_9gh83jA&Ra<8rM%`>U3x69t&NzbwWY}7Ar?)FK#IZ0z|d0H0EkRO w3{9;}4Xg|ebq&m|3=9_N6z8I7$jwj5OsmAL;bP(Gi$Dzwp00i_>zopr02+f8CIA2c literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png new file mode 100644 index 0000000000000000000000000000000000000000..e71a726136a47ed24125c7efc79d68a4a01961b4 GIT binary patch literal 14800 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/AppInfo.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 0000000000..588195548a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = flowy_board_example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = com.example.flowyBoardExample + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Debug.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 0000000000..36b0fd9464 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Release.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 0000000000..dff4f49561 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Warnings.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 0000000000..42bcbf4780 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/DebugProfile.entitlements b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 0000000000..dddb8a30c8 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.server + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Info.plist b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Info.plist new file mode 100644 index 0000000000..4789daa6a4 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Info.plist @@ -0,0 +1,32 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/MainFlutterWindow.swift b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 0000000000..2722837ec9 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Release.entitlements b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Release.entitlements new file mode 100644 index 0000000000..852fa1a472 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Release.entitlements @@ -0,0 +1,8 @@ + + + + + com.apple.security.app-sandbox + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml b/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml new file mode 100644 index 0000000000..364ab9918c --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml @@ -0,0 +1,84 @@ +name: flowy_board_example +description: Demonstrates how to use the flowy_board plugin. + +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +environment: + sdk: ">=2.17.6 <3.0.0" + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + flowy_board: + # When depending on this package from a real application you should use: + # flowy_board: ^x.y.z + # See https://dart.dev/tools/pub/dependencies#version-constraints + # The example app is bundled with the plugin so we use a path dependency on + # the parent directory to use the current plugin's version. + path: ../ + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/frontend/app_flowy/packages/flowy_board/example/test/widget_test.dart b/frontend/app_flowy/packages/flowy_board/example/test/widget_test.dart new file mode 100644 index 0000000000..313f88f30d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/test/widget_test.dart @@ -0,0 +1,27 @@ +// This is a basic Flutter widget test. +// +// To perform an interaction with a widget in your test, use the WidgetTester +// utility in the flutter_test package. For example, you can send tap and scroll +// gestures. You can also use WidgetTester to find child widgets in the widget +// tree, read text, and verify that the values of widget properties are correct. + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:flowy_board_example/main.dart'; + +void main() { + testWidgets('Verify Platform version', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // Verify that platform version is retrieved. + expect( + find.byWidgetPredicate( + (Widget widget) => widget is Text && + widget.data!.startsWith('Running on:'), + ), + findsOneWidget, + ); + }); +} diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/.gitignore b/frontend/app_flowy/packages/flowy_board/example/windows/.gitignore new file mode 100644 index 0000000000..d492d0d98c --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ephemeral/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/example/windows/CMakeLists.txt new file mode 100644 index 0000000000..be7b6a81d7 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/CMakeLists.txt @@ -0,0 +1,101 @@ +# Project-level configuration. +cmake_minimum_required(VERSION 3.14) +project(flowy_board_example LANGUAGES CXX) + +# The name of the executable created for the application. Change this to change +# the on-disk name of your application. +set(BINARY_NAME "flowy_board_example") + +# Explicitly opt in to modern CMake behaviors to avoid warnings with recent +# versions of CMake. +cmake_policy(SET CMP0063 NEW) + +# Define build configuration option. +get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(IS_MULTICONFIG) + set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" + CACHE STRING "" FORCE) +else() + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + set(CMAKE_BUILD_TYPE "Debug" CACHE + STRING "Flutter build mode" FORCE) + set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS + "Debug" "Profile" "Release") + endif() +endif() +# Define settings for the Profile build mode. +set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") +set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") +set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") +set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") + +# Use Unicode for all projects. +add_definitions(-DUNICODE -D_UNICODE) + +# Compilation settings that should be applied to most targets. +# +# Be cautious about adding new options here, as plugins use this function by +# default. In most cases, you should add new options to specific targets instead +# of modifying this function. +function(APPLY_STANDARD_SETTINGS TARGET) + target_compile_features(${TARGET} PUBLIC cxx_std_17) + target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") + target_compile_options(${TARGET} PRIVATE /EHsc) + target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") + target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") +endfunction() + +# Flutter library and tool build rules. +set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") +add_subdirectory(${FLUTTER_MANAGED_DIR}) + +# Application build; see runner/CMakeLists.txt. +add_subdirectory("runner") + +# Generated plugin build rules, which manage building the plugins and adding +# them to the application. +include(flutter/generated_plugins.cmake) + + +# === Installation === +# Support files are copied into place next to the executable, so that it can +# run in place. This is done instead of making a separate bundle (as on Linux) +# so that building and running from within Visual Studio will work. +set(BUILD_BUNDLE_DIR "$") +# Make the "install" step default, as it's required to run. +set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) +if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) + set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) +endif() + +set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") +set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") + +install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + COMPONENT Runtime) + +install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + +if(PLUGIN_BUNDLED_LIBRARIES) + install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) +endif() + +# Fully re-copy the assets directory on each build to avoid having stale files +# from a previous install. +set(FLUTTER_ASSET_DIR_NAME "flutter_assets") +install(CODE " + file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") + " COMPONENT Runtime) +install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" + DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) + +# Install the AOT library on non-Debug builds only. +install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" + CONFIGURATIONS Profile;Release + COMPONENT Runtime) diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/CMakeLists.txt new file mode 100644 index 0000000000..930d2071a3 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/CMakeLists.txt @@ -0,0 +1,104 @@ +# This file controls Flutter-level build steps. It should not be edited. +cmake_minimum_required(VERSION 3.14) + +set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") + +# Configuration provided via flutter tool. +include(${EPHEMERAL_DIR}/generated_config.cmake) + +# TODO: Move the rest of this into files in ephemeral. See +# https://github.com/flutter/flutter/issues/57146. +set(WRAPPER_ROOT "${EPHEMERAL_DIR}/cpp_client_wrapper") + +# === Flutter Library === +set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/flutter_windows.dll") + +# Published to parent scope for install step. +set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) +set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) +set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) +set(AOT_LIBRARY "${PROJECT_DIR}/build/windows/app.so" PARENT_SCOPE) + +list(APPEND FLUTTER_LIBRARY_HEADERS + "flutter_export.h" + "flutter_windows.h" + "flutter_messenger.h" + "flutter_plugin_registrar.h" + "flutter_texture_registrar.h" +) +list(TRANSFORM FLUTTER_LIBRARY_HEADERS PREPEND "${EPHEMERAL_DIR}/") +add_library(flutter INTERFACE) +target_include_directories(flutter INTERFACE + "${EPHEMERAL_DIR}" +) +target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}.lib") +add_dependencies(flutter flutter_assemble) + +# === Wrapper === +list(APPEND CPP_WRAPPER_SOURCES_CORE + "core_implementations.cc" + "standard_codec.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_CORE PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_PLUGIN + "plugin_registrar.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_PLUGIN PREPEND "${WRAPPER_ROOT}/") +list(APPEND CPP_WRAPPER_SOURCES_APP + "flutter_engine.cc" + "flutter_view_controller.cc" +) +list(TRANSFORM CPP_WRAPPER_SOURCES_APP PREPEND "${WRAPPER_ROOT}/") + +# Wrapper sources needed for a plugin. +add_library(flutter_wrapper_plugin STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} +) +apply_standard_settings(flutter_wrapper_plugin) +set_target_properties(flutter_wrapper_plugin PROPERTIES + POSITION_INDEPENDENT_CODE ON) +set_target_properties(flutter_wrapper_plugin PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_link_libraries(flutter_wrapper_plugin PUBLIC flutter) +target_include_directories(flutter_wrapper_plugin PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_plugin flutter_assemble) + +# Wrapper sources needed for the runner. +add_library(flutter_wrapper_app STATIC + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_APP} +) +apply_standard_settings(flutter_wrapper_app) +target_link_libraries(flutter_wrapper_app PUBLIC flutter) +target_include_directories(flutter_wrapper_app PUBLIC + "${WRAPPER_ROOT}/include" +) +add_dependencies(flutter_wrapper_app flutter_assemble) + +# === Flutter tool backend === +# _phony_ is a non-existent file to force this command to run every time, +# since currently there's no way to get a full input/output list from the +# flutter tool. +set(PHONY_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/_phony_") +set_source_files_properties("${PHONY_OUTPUT}" PROPERTIES SYMBOLIC TRUE) +add_custom_command( + OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} + ${PHONY_OUTPUT} + COMMAND ${CMAKE_COMMAND} -E env + ${FLUTTER_TOOL_ENVIRONMENT} + "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.bat" + windows-x64 $ + VERBATIM +) +add_custom_target(flutter_assemble DEPENDS + "${FLUTTER_LIBRARY}" + ${FLUTTER_LIBRARY_HEADERS} + ${CPP_WRAPPER_SOURCES_CORE} + ${CPP_WRAPPER_SOURCES_PLUGIN} + ${CPP_WRAPPER_SOURCES_APP} +) diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.cc b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..0f2f5a79bb --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.cc @@ -0,0 +1,14 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + +#include + +void RegisterPlugins(flutter::PluginRegistry* registry) { + FlowyBoardPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlowyBoardPluginCApi")); +} diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.h b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.h new file mode 100644 index 0000000000..dc139d85a9 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.h @@ -0,0 +1,15 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#ifndef GENERATED_PLUGIN_REGISTRANT_ +#define GENERATED_PLUGIN_REGISTRANT_ + +#include + +// Registers Flutter plugins. +void RegisterPlugins(flutter::PluginRegistry* registry); + +#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugins.cmake b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugins.cmake new file mode 100644 index 0000000000..f4e524195d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugins.cmake @@ -0,0 +1,24 @@ +# +# Generated file, do not edit. +# + +list(APPEND FLUTTER_PLUGIN_LIST + flowy_board +) + +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + +set(PLUGIN_BUNDLED_LIBRARIES) + +foreach(plugin ${FLUTTER_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) + target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) + list(APPEND PLUGIN_BUNDLED_LIBRARIES $) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) +endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/example/windows/runner/CMakeLists.txt new file mode 100644 index 0000000000..b9e550fba8 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.14) +project(runner LANGUAGES CXX) + +# Define the application target. To change its name, change BINARY_NAME in the +# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer +# work. +# +# Any new source files that you add to the application should be added here. +add_executable(${BINARY_NAME} WIN32 + "flutter_window.cpp" + "main.cpp" + "utils.cpp" + "win32_window.cpp" + "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" + "Runner.rc" + "runner.exe.manifest" +) + +# Apply the standard set of build settings. This can be removed for applications +# that need different build settings. +apply_standard_settings(${BINARY_NAME}) + +# Disable Windows macros that collide with C++ standard library functions. +target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") + +# Add dependency libraries and include directories. Add any application-specific +# dependencies here. +target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) +target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") + +# Run the Flutter tool portions of the build. This must not be removed. +add_dependencies(${BINARY_NAME} flutter_assemble) diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/Runner.rc b/frontend/app_flowy/packages/flowy_board/example/windows/runner/Runner.rc new file mode 100644 index 0000000000..f8dc624c95 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/Runner.rc @@ -0,0 +1,121 @@ +// Microsoft Visual C++ generated resource script. +// +#pragma code_page(65001) +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (United States) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_APP_ICON ICON "resources\\app_icon.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +#ifdef FLUTTER_BUILD_NUMBER +#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER +#else +#define VERSION_AS_NUMBER 1,0,0 +#endif + +#ifdef FLUTTER_BUILD_NAME +#define VERSION_AS_STRING #FLUTTER_BUILD_NAME +#else +#define VERSION_AS_STRING "1.0.0" +#endif + +VS_VERSION_INFO VERSIONINFO + FILEVERSION VERSION_AS_NUMBER + PRODUCTVERSION VERSION_AS_NUMBER + FILEFLAGSMASK VS_FFI_FILEFLAGSMASK +#ifdef _DEBUG + FILEFLAGS VS_FF_DEBUG +#else + FILEFLAGS 0x0L +#endif + FILEOS VOS__WINDOWS32 + FILETYPE VFT_APP + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904e4" + BEGIN + VALUE "CompanyName", "com.example" "\0" + VALUE "FileDescription", "flowy_board_example" "\0" + VALUE "FileVersion", VERSION_AS_STRING "\0" + VALUE "InternalName", "flowy_board_example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" + VALUE "OriginalFilename", "flowy_board_example.exe" "\0" + VALUE "ProductName", "flowy_board_example" "\0" + VALUE "ProductVersion", VERSION_AS_STRING "\0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1252 + END +END + +#endif // English (United States) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.cpp b/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.cpp new file mode 100644 index 0000000000..b43b9095ea --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.cpp @@ -0,0 +1,61 @@ +#include "flutter_window.h" + +#include + +#include "flutter/generated_plugin_registrant.h" + +FlutterWindow::FlutterWindow(const flutter::DartProject& project) + : project_(project) {} + +FlutterWindow::~FlutterWindow() {} + +bool FlutterWindow::OnCreate() { + if (!Win32Window::OnCreate()) { + return false; + } + + RECT frame = GetClientArea(); + + // The size here must match the window dimensions to avoid unnecessary surface + // creation / destruction in the startup path. + flutter_controller_ = std::make_unique( + frame.right - frame.left, frame.bottom - frame.top, project_); + // Ensure that basic setup of the controller was successful. + if (!flutter_controller_->engine() || !flutter_controller_->view()) { + return false; + } + RegisterPlugins(flutter_controller_->engine()); + SetChildContent(flutter_controller_->view()->GetNativeWindow()); + return true; +} + +void FlutterWindow::OnDestroy() { + if (flutter_controller_) { + flutter_controller_ = nullptr; + } + + Win32Window::OnDestroy(); +} + +LRESULT +FlutterWindow::MessageHandler(HWND hwnd, UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + // Give Flutter, including plugins, an opportunity to handle window messages. + if (flutter_controller_) { + std::optional result = + flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, + lparam); + if (result) { + return *result; + } + } + + switch (message) { + case WM_FONTCHANGE: + flutter_controller_->engine()->ReloadSystemFonts(); + break; + } + + return Win32Window::MessageHandler(hwnd, message, wparam, lparam); +} diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.h b/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.h new file mode 100644 index 0000000000..6da0652f05 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.h @@ -0,0 +1,33 @@ +#ifndef RUNNER_FLUTTER_WINDOW_H_ +#define RUNNER_FLUTTER_WINDOW_H_ + +#include +#include + +#include + +#include "win32_window.h" + +// A window that does nothing but host a Flutter view. +class FlutterWindow : public Win32Window { + public: + // Creates a new FlutterWindow hosting a Flutter view running |project|. + explicit FlutterWindow(const flutter::DartProject& project); + virtual ~FlutterWindow(); + + protected: + // Win32Window: + bool OnCreate() override; + void OnDestroy() override; + LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, + LPARAM const lparam) noexcept override; + + private: + // The project to run. + flutter::DartProject project_; + + // The Flutter instance hosted by this window. + std::unique_ptr flutter_controller_; +}; + +#endif // RUNNER_FLUTTER_WINDOW_H_ diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/main.cpp b/frontend/app_flowy/packages/flowy_board/example/windows/runner/main.cpp new file mode 100644 index 0000000000..b59eac7d92 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/main.cpp @@ -0,0 +1,43 @@ +#include +#include +#include + +#include "flutter_window.h" +#include "utils.h" + +int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, + _In_ wchar_t *command_line, _In_ int show_command) { + // Attach to console when present (e.g., 'flutter run') or create a + // new console when running with a debugger. + if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { + CreateAndAttachConsole(); + } + + // Initialize COM, so that it is available for use in the library and/or + // plugins. + ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); + + flutter::DartProject project(L"data"); + + std::vector command_line_arguments = + GetCommandLineArguments(); + + project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); + + FlutterWindow window(project); + Win32Window::Point origin(10, 10); + Win32Window::Size size(1280, 720); + if (!window.CreateAndShow(L"flowy_board_example", origin, size)) { + return EXIT_FAILURE; + } + window.SetQuitOnClose(true); + + ::MSG msg; + while (::GetMessage(&msg, nullptr, 0, 0)) { + ::TranslateMessage(&msg); + ::DispatchMessage(&msg); + } + + ::CoUninitialize(); + return EXIT_SUCCESS; +} diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/resource.h b/frontend/app_flowy/packages/flowy_board/example/windows/runner/resource.h new file mode 100644 index 0000000000..66a65d1e4a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/resource.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by Runner.rc +// +#define IDI_APP_ICON 101 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 102 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1001 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/resources/app_icon.ico b/frontend/app_flowy/packages/flowy_board/example/windows/runner/resources/app_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..c04e20caf6370ebb9253ad831cc31de4a9c965f6 GIT binary patch literal 33772 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 literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/runner.exe.manifest b/frontend/app_flowy/packages/flowy_board/example/windows/runner/runner.exe.manifest new file mode 100644 index 0000000000..c977c4a425 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/runner.exe.manifest @@ -0,0 +1,20 @@ + + + + + PerMonitorV2 + + + + + + + + + + + + + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.cpp b/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.cpp new file mode 100644 index 0000000000..f5bf9fa0f5 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.cpp @@ -0,0 +1,64 @@ +#include "utils.h" + +#include +#include +#include +#include + +#include + +void CreateAndAttachConsole() { + if (::AllocConsole()) { + FILE *unused; + if (freopen_s(&unused, "CONOUT$", "w", stdout)) { + _dup2(_fileno(stdout), 1); + } + if (freopen_s(&unused, "CONOUT$", "w", stderr)) { + _dup2(_fileno(stdout), 2); + } + std::ios::sync_with_stdio(); + FlutterDesktopResyncOutputStreams(); + } +} + +std::vector GetCommandLineArguments() { + // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. + int argc; + wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); + if (argv == nullptr) { + return std::vector(); + } + + std::vector command_line_arguments; + + // Skip the first argument as it's the binary name. + for (int i = 1; i < argc; i++) { + command_line_arguments.push_back(Utf8FromUtf16(argv[i])); + } + + ::LocalFree(argv); + + return command_line_arguments; +} + +std::string Utf8FromUtf16(const wchar_t* utf16_string) { + if (utf16_string == nullptr) { + return std::string(); + } + int target_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, nullptr, 0, nullptr, nullptr); + std::string utf8_string; + if (target_length == 0 || target_length > utf8_string.max_size()) { + return utf8_string; + } + utf8_string.resize(target_length); + int converted_length = ::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, + -1, utf8_string.data(), + target_length, nullptr, nullptr); + if (converted_length == 0) { + return std::string(); + } + return utf8_string; +} diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.h b/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.h new file mode 100644 index 0000000000..3879d54755 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.h @@ -0,0 +1,19 @@ +#ifndef RUNNER_UTILS_H_ +#define RUNNER_UTILS_H_ + +#include +#include + +// Creates a console for the process, and redirects stdout and stderr to +// it for both the runner and the Flutter library. +void CreateAndAttachConsole(); + +// Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string +// encoded in UTF-8. Returns an empty std::string on failure. +std::string Utf8FromUtf16(const wchar_t* utf16_string); + +// Gets the command line arguments passed in as a std::vector, +// encoded in UTF-8. Returns an empty std::vector on failure. +std::vector GetCommandLineArguments(); + +#endif // RUNNER_UTILS_H_ diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.cpp b/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.cpp new file mode 100644 index 0000000000..c10f08dc7d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.cpp @@ -0,0 +1,245 @@ +#include "win32_window.h" + +#include + +#include "resource.h" + +namespace { + +constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW"; + +// The number of Win32Window objects that currently exist. +static int g_active_window_count = 0; + +using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd); + +// Scale helper to convert logical scaler values to physical using passed in +// scale factor +int Scale(int source, double scale_factor) { + return static_cast(source * scale_factor); +} + +// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module. +// This API is only needed for PerMonitor V1 awareness mode. +void EnableFullDpiSupportIfAvailable(HWND hwnd) { + HMODULE user32_module = LoadLibraryA("User32.dll"); + if (!user32_module) { + return; + } + auto enable_non_client_dpi_scaling = + reinterpret_cast( + GetProcAddress(user32_module, "EnableNonClientDpiScaling")); + if (enable_non_client_dpi_scaling != nullptr) { + enable_non_client_dpi_scaling(hwnd); + FreeLibrary(user32_module); + } +} + +} // namespace + +// Manages the Win32Window's window class registration. +class WindowClassRegistrar { + public: + ~WindowClassRegistrar() = default; + + // Returns the singleton registar instance. + static WindowClassRegistrar* GetInstance() { + if (!instance_) { + instance_ = new WindowClassRegistrar(); + } + return instance_; + } + + // Returns the name of the window class, registering the class if it hasn't + // previously been registered. + const wchar_t* GetWindowClass(); + + // Unregisters the window class. Should only be called if there are no + // instances of the window. + void UnregisterWindowClass(); + + private: + WindowClassRegistrar() = default; + + static WindowClassRegistrar* instance_; + + bool class_registered_ = false; +}; + +WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr; + +const wchar_t* WindowClassRegistrar::GetWindowClass() { + if (!class_registered_) { + WNDCLASS window_class{}; + window_class.hCursor = LoadCursor(nullptr, IDC_ARROW); + window_class.lpszClassName = kWindowClassName; + window_class.style = CS_HREDRAW | CS_VREDRAW; + window_class.cbClsExtra = 0; + window_class.cbWndExtra = 0; + window_class.hInstance = GetModuleHandle(nullptr); + window_class.hIcon = + LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON)); + window_class.hbrBackground = 0; + window_class.lpszMenuName = nullptr; + window_class.lpfnWndProc = Win32Window::WndProc; + RegisterClass(&window_class); + class_registered_ = true; + } + return kWindowClassName; +} + +void WindowClassRegistrar::UnregisterWindowClass() { + UnregisterClass(kWindowClassName, nullptr); + class_registered_ = false; +} + +Win32Window::Win32Window() { + ++g_active_window_count; +} + +Win32Window::~Win32Window() { + --g_active_window_count; + Destroy(); +} + +bool Win32Window::CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size) { + Destroy(); + + const wchar_t* window_class = + WindowClassRegistrar::GetInstance()->GetWindowClass(); + + const POINT target_point = {static_cast(origin.x), + static_cast(origin.y)}; + HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST); + UINT dpi = FlutterDesktopGetDpiForMonitor(monitor); + double scale_factor = dpi / 96.0; + + HWND window = CreateWindow( + window_class, title.c_str(), WS_OVERLAPPEDWINDOW | WS_VISIBLE, + Scale(origin.x, scale_factor), Scale(origin.y, scale_factor), + Scale(size.width, scale_factor), Scale(size.height, scale_factor), + nullptr, nullptr, GetModuleHandle(nullptr), this); + + if (!window) { + return false; + } + + return OnCreate(); +} + +// static +LRESULT CALLBACK Win32Window::WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + if (message == WM_NCCREATE) { + auto window_struct = reinterpret_cast(lparam); + SetWindowLongPtr(window, GWLP_USERDATA, + reinterpret_cast(window_struct->lpCreateParams)); + + auto that = static_cast(window_struct->lpCreateParams); + EnableFullDpiSupportIfAvailable(window); + that->window_handle_ = window; + } else if (Win32Window* that = GetThisFromHandle(window)) { + return that->MessageHandler(window, message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + +LRESULT +Win32Window::MessageHandler(HWND hwnd, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept { + switch (message) { + case WM_DESTROY: + window_handle_ = nullptr; + Destroy(); + if (quit_on_close_) { + PostQuitMessage(0); + } + return 0; + + case WM_DPICHANGED: { + auto newRectSize = reinterpret_cast(lparam); + LONG newWidth = newRectSize->right - newRectSize->left; + LONG newHeight = newRectSize->bottom - newRectSize->top; + + SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth, + newHeight, SWP_NOZORDER | SWP_NOACTIVATE); + + return 0; + } + case WM_SIZE: { + RECT rect = GetClientArea(); + if (child_content_ != nullptr) { + // Size and position the child window. + MoveWindow(child_content_, rect.left, rect.top, rect.right - rect.left, + rect.bottom - rect.top, TRUE); + } + return 0; + } + + case WM_ACTIVATE: + if (child_content_ != nullptr) { + SetFocus(child_content_); + } + return 0; + } + + return DefWindowProc(window_handle_, message, wparam, lparam); +} + +void Win32Window::Destroy() { + OnDestroy(); + + if (window_handle_) { + DestroyWindow(window_handle_); + window_handle_ = nullptr; + } + if (g_active_window_count == 0) { + WindowClassRegistrar::GetInstance()->UnregisterWindowClass(); + } +} + +Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept { + return reinterpret_cast( + GetWindowLongPtr(window, GWLP_USERDATA)); +} + +void Win32Window::SetChildContent(HWND content) { + child_content_ = content; + SetParent(content, window_handle_); + RECT frame = GetClientArea(); + + MoveWindow(content, frame.left, frame.top, frame.right - frame.left, + frame.bottom - frame.top, true); + + SetFocus(child_content_); +} + +RECT Win32Window::GetClientArea() { + RECT frame; + GetClientRect(window_handle_, &frame); + return frame; +} + +HWND Win32Window::GetHandle() { + return window_handle_; +} + +void Win32Window::SetQuitOnClose(bool quit_on_close) { + quit_on_close_ = quit_on_close; +} + +bool Win32Window::OnCreate() { + // No-op; provided for subclasses. + return true; +} + +void Win32Window::OnDestroy() { + // No-op; provided for subclasses. +} diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.h b/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.h new file mode 100644 index 0000000000..17ba431125 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.h @@ -0,0 +1,98 @@ +#ifndef RUNNER_WIN32_WINDOW_H_ +#define RUNNER_WIN32_WINDOW_H_ + +#include + +#include +#include +#include + +// A class abstraction for a high DPI-aware Win32 Window. Intended to be +// inherited from by classes that wish to specialize with custom +// rendering and input handling +class Win32Window { + public: + struct Point { + unsigned int x; + unsigned int y; + Point(unsigned int x, unsigned int y) : x(x), y(y) {} + }; + + struct Size { + unsigned int width; + unsigned int height; + Size(unsigned int width, unsigned int height) + : width(width), height(height) {} + }; + + Win32Window(); + virtual ~Win32Window(); + + // Creates and shows a win32 window with |title| and position and size using + // |origin| and |size|. New windows are created on the default monitor. Window + // sizes are specified to the OS in physical pixels, hence to ensure a + // consistent size to will treat the width height passed in to this function + // as logical pixels and scale to appropriate for the default monitor. Returns + // true if the window was created successfully. + bool CreateAndShow(const std::wstring& title, + const Point& origin, + const Size& size); + + // Release OS resources associated with window. + void Destroy(); + + // Inserts |content| into the window tree. + void SetChildContent(HWND content); + + // Returns the backing Window handle to enable clients to set icon and other + // window properties. Returns nullptr if the window has been destroyed. + HWND GetHandle(); + + // If true, closing this window will quit the application. + void SetQuitOnClose(bool quit_on_close); + + // Return a RECT representing the bounds of the current client area. + RECT GetClientArea(); + + protected: + // Processes and route salient window messages for mouse handling, + // size change and DPI. Delegates handling of these to member overloads that + // inheriting classes can handle. + virtual LRESULT MessageHandler(HWND window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Called when CreateAndShow is called, allowing subclass window-related + // setup. Subclasses should return false if setup fails. + virtual bool OnCreate(); + + // Called when Destroy is called. + virtual void OnDestroy(); + + private: + friend class WindowClassRegistrar; + + // OS callback called by message pump. Handles the WM_NCCREATE message which + // is passed when the non-client area is being created and enables automatic + // non-client DPI scaling so that the non-client area automatically + // responsponds to changes in DPI. All other messages are handled by + // MessageHandler. + static LRESULT CALLBACK WndProc(HWND const window, + UINT const message, + WPARAM const wparam, + LPARAM const lparam) noexcept; + + // Retrieves a class instance pointer for |window| + static Win32Window* GetThisFromHandle(HWND const window) noexcept; + + bool quit_on_close_ = false; + + // window handle for top level window. + HWND window_handle_ = nullptr; + + // window handle for hosted content. + HWND child_content_ = nullptr; +}; + +#endif // RUNNER_WIN32_WINDOW_H_ diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart new file mode 100644 index 0000000000..f4bed642b6 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart @@ -0,0 +1,8 @@ + +import 'flowy_board_platform_interface.dart'; + +class FlowyBoard { + Future getPlatformVersion() { + return FlowyBoardPlatform.instance.getPlatformVersion(); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart new file mode 100644 index 0000000000..f0acb6faa0 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart @@ -0,0 +1,17 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/services.dart'; + +import 'flowy_board_platform_interface.dart'; + +/// An implementation of [FlowyBoardPlatform] that uses method channels. +class MethodChannelFlowyBoard extends FlowyBoardPlatform { + /// The method channel used to interact with the native platform. + @visibleForTesting + final methodChannel = const MethodChannel('flowy_board'); + + @override + Future getPlatformVersion() async { + final version = await methodChannel.invokeMethod('getPlatformVersion'); + return version; + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart new file mode 100644 index 0000000000..2f8d314b3b --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart @@ -0,0 +1,29 @@ +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import 'flowy_board_method_channel.dart'; + +abstract class FlowyBoardPlatform extends PlatformInterface { + /// Constructs a FlowyBoardPlatform. + FlowyBoardPlatform() : super(token: _token); + + static final Object _token = Object(); + + static FlowyBoardPlatform _instance = MethodChannelFlowyBoard(); + + /// The default instance of [FlowyBoardPlatform] to use. + /// + /// Defaults to [MethodChannelFlowyBoard]. + static FlowyBoardPlatform get instance => _instance; + + /// Platform-specific implementations should set this with their own + /// platform-specific class that extends [FlowyBoardPlatform] when + /// they register themselves. + static set instance(FlowyBoardPlatform instance) { + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + Future getPlatformVersion() { + throw UnimplementedError('platformVersion() has not been implemented.'); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_overlay.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_overlay.dart new file mode 100644 index 0000000000..e974bfd838 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_overlay.dart @@ -0,0 +1,410 @@ +import 'dart:collection'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/scheduler.dart'; + +class BoardOverlayEntry { + /// This entry will include the widget built by this builder in the overlay at + /// the entry's position. + /// The builder will be called again after calling [markNeedsBuild] on this entry. + final WidgetBuilder builder; + + /// Whether this entry occludes the entire overlay. + /// + /// If an entry claims to be opaque, then, for efficiency, the overlay will + /// skip building entries below that entry unless they have [maintainState] + /// set. + bool get opaque => _opaque; + bool _opaque; + + BoardOverlayState? _overlay; + final GlobalKey<_OverlayEntryWidgetState> _key = + GlobalKey<_OverlayEntryWidgetState>(); + + set opaque(bool value) { + if (_opaque == value) return; + + _opaque = value; + assert(_overlay != null); + _overlay!._didChangeEntryOpacity(); + } + + BoardOverlayEntry({ + required this.builder, + bool opaque = false, + }) : _opaque = opaque; + + /// If this method is called while the [SchedulerBinding.schedulerPhase] is + /// [SchedulerPhase.persistentCallbacks], i.e. during the build, layout, or + /// paint phases (see [WidgetsBinding.drawFrame]), then the removal is + /// delayed until the post-frame callbacks phase. Otherwise the removal is done synchronously. + void remove() { + assert(_overlay != null, 'Should only call once'); + final BoardOverlayState overlay = _overlay!; + _overlay = null; + if (SchedulerBinding.instance.schedulerPhase == + SchedulerPhase.persistentCallbacks) { + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + overlay._remove(this); + }); + } else { + overlay._remove(this); + } + } + + /// Cause this entry to rebuild during the next pipeline flush. + /// You need to call this function if the output of [builder] has changed. + void markNeedsBuild() { + _key.currentState?._markNeedsBuild(); + } +} + +/// A [Stack] of entries that can be managed independently. +/// +/// Overlays let independent child widgets "float" visual elements on top of +/// other widgets by inserting them into the overlay's [Stack]. The overlay lets +/// each of these widgets manage their participation in the overlay using +/// [OverlayEntry] objects. +class BoardOverlay extends StatefulWidget { + final List initialEntries; + + const BoardOverlay({ + this.initialEntries = const [], + Key? key, + }) : super(key: key); + + static BoardOverlayState of(BuildContext context, + {Widget? debugRequiredFor}) { + final BoardOverlayState? result = + context.findAncestorStateOfType(); + assert(() { + if (debugRequiredFor != null && result == null) { + final String additional = context.widget != debugRequiredFor + ? '\nThe context from which that widget was searching for an overlay was:\n $context' + : ''; + throw FlutterError('No Overlay widget found.\n' + '${debugRequiredFor.runtimeType} widgets require an Overlay widget ancestor for correct operation.\n' + 'The most common way to add an Overlay to an application is to include a MaterialApp or Navigator widget in the runApp() call.\n' + 'The specific widget that failed to find an overlay was:\n' + ' $debugRequiredFor' + '$additional'); + } + return true; + }()); + return result!; + } + + @override + BoardOverlayState createState() => BoardOverlayState(); +} + +class BoardOverlayState extends State + with TickerProviderStateMixin { + final List _entries = []; + + @override + void initState() { + super.initState(); + insertAll(widget.initialEntries); + } + + /// Insert the given entry into the overlay. + /// + /// If [above] is non-null, the entry is inserted just above [above]. + /// Otherwise, the entry is inserted on top. + void insert(BoardOverlayEntry entry, {BoardOverlayEntry? above}) { + assert(entry._overlay == null); + assert( + above == null || (above._overlay == this && _entries.contains(above))); + entry._overlay = this; + setState(() { + final int index = + above == null ? _entries.length : _entries.indexOf(above) + 1; + _entries.insert(index, entry); + }); + } + + /// Insert all the entries in the given iterable. + /// + /// If [above] is non-null, the entries are inserted just above [above]. + /// Otherwise, the entries are inserted on top. + void insertAll(Iterable entries, + {BoardOverlayEntry? above}) { + assert( + above == null || (above._overlay == this && _entries.contains(above))); + if (entries.isEmpty) return; + for (BoardOverlayEntry entry in entries) { + assert(entry._overlay == null); + entry._overlay = this; + } + setState(() { + final int index = + above == null ? _entries.length : _entries.indexOf(above) + 1; + _entries.insertAll(index, entries); + }); + } + + void _remove(BoardOverlayEntry entry) { + if (mounted) { + _entries.remove(entry); + setState(() { + /* entry was removed */ + }); + } + } + + void _didChangeEntryOpacity() { + setState(() { + // We use the opacity of the entry in our build function, which means we + // our state has changed. + }); + } + + @override + Widget build(BuildContext context) { + // These lists are filled backwards. For the offstage children that + // does not matter since they aren't rendered, but for the onstage + // children we reverse the list below before adding it to the tree. + final List onstageChildren = []; + final List offstageChildren = []; + bool onstage = true; + for (int i = _entries.length - 1; i >= 0; i -= 1) { + final BoardOverlayEntry entry = _entries[i]; + if (onstage) { + onstageChildren.add(_OverlayEntryWidget(entry)); + if (entry.opaque) onstage = false; + } + } + return _BoardStack( + onstage: Stack( + fit: StackFit.passthrough, + //HanSheng changed it to passthrough so that this widget doesn't change layout constraints + children: onstageChildren.reversed.toList(growable: false), + ), + offstage: offstageChildren, + ); + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties + .add(DiagnosticsProperty>('entries', _entries)); + } +} + +class _OverlayEntryWidget extends StatefulWidget { + _OverlayEntryWidget(this.entry) : super(key: entry._key); + + final BoardOverlayEntry entry; + + @override + _OverlayEntryWidgetState createState() => _OverlayEntryWidgetState(); +} + +class _OverlayEntryWidgetState extends State<_OverlayEntryWidget> { + @override + Widget build(BuildContext context) { + return widget.entry.builder(context); + } + + void _markNeedsBuild() { + setState(() {}); + } +} + +/// A widget that has one [onstage] child which is visible, and one or more +/// [offstage] widgets which are kept alive, and are built, but are not laid out +/// or painted. +/// +/// The onstage widget must be a Stack. +/// +/// For convenience, it is legal to use [Positioned] widgets around the offstage +/// widgets. +class _BoardStack extends RenderObjectWidget { + final Stack? onstage; + final List offstage; + + const _BoardStack({ + required this.offstage, + this.onstage, + }); + + @override + _BoardStackElement createElement() => _BoardStackElement(this); + + @override + _RenderBoardObject createRenderObject(BuildContext context) => + _RenderBoardObject(); +} + +class _BoardStackElement extends RenderObjectElement { + Element? _onstage; + static final Object _onstageSlot = Object(); + late List _offstage; + final Set _forgottenOffstageChildren = HashSet(); + + _BoardStackElement(_BoardStack widget) + : assert(!debugChildrenHaveDuplicateKeys(widget, widget.offstage)), + super(widget); + + @override + _BoardStack get widget => super.widget as _BoardStack; + + @override + _RenderBoardObject get renderObject => + super.renderObject as _RenderBoardObject; + + @override + void insertRenderObjectChild(RenderBox child, dynamic slot) { + assert(renderObject.debugValidateChild(child)); + if (slot == _onstageSlot) { + assert(child is RenderStack); + renderObject.child = child as RenderStack?; + } else { + assert(slot == null || slot is Element); + renderObject.insert(child, after: slot?.renderObject); + } + } + + @override + void moveRenderObjectChild(RenderBox child, dynamic oldSlot, dynamic slot) { + if (slot == _onstageSlot) { + renderObject.remove(child); + assert(child is RenderStack); + renderObject.child = child as RenderStack?; + } else { + assert(slot == null || slot is Element); + if (renderObject.child == child) { + renderObject.child = null; + renderObject.insert(child, after: slot?.renderObject); + } else { + renderObject.move(child, after: slot?.renderObject); + } + } + } + + @override + void removeRenderObjectChild(RenderBox child, dynamic slot) { + if (renderObject.child == child) { + renderObject.child = null; + } else { + renderObject.remove(child); + } + } + + @override + void visitChildren(ElementVisitor visitor) { + if (_onstage != null) visitor(_onstage!); + for (Element child in _offstage) { + if (!_forgottenOffstageChildren.contains(child)) visitor(child); + } + } + + @override + void debugVisitOnstageChildren(ElementVisitor visitor) { + if (_onstage != null) visitor(_onstage!); + } + + @override + void forgetChild(Element child) { + if (child == _onstage) { + _onstage = null; + } else { + assert(_offstage.contains(child)); + assert(!_forgottenOffstageChildren.contains(child)); + _forgottenOffstageChildren.add(child); + } + super.forgetChild(child); + } + + @override + void mount(Element? parent, dynamic newSlot) { + super.mount(parent, newSlot); + _onstage = updateChild(_onstage, widget.onstage, _onstageSlot); + _offstage = []; + } + + @override + void update(_BoardStack newWidget) { + super.update(newWidget); + assert(widget == newWidget); + _onstage = updateChild(_onstage, widget.onstage, _onstageSlot); + _offstage = updateChildren(_offstage, widget.offstage, + forgottenChildren: _forgottenOffstageChildren); + _forgottenOffstageChildren.clear(); + } +} + +// A render object which lays out and paints one subtree while keeping a list +// of other subtrees alive but not laid out or painted. +// +// The subtree that is laid out and painted must be a [RenderStack]. +// +// This class uses [StackParentData] objects for its parent data so that the +// children of its primary subtree's stack can be moved to this object's list +// of zombie children without changing their parent data objects. +class _RenderBoardObject extends RenderBox + with + RenderObjectWithChildMixin, + RenderProxyBoxMixin, + ContainerRenderObjectMixin { + @override + void setupParentData(RenderObject child) { + if (child.parentData is! StackParentData) { + child.parentData = StackParentData(); + } + } + + @override + void redepthChildren() { + if (child != null) redepthChild(child!); + super.redepthChildren(); + } + + @override + void visitChildren(RenderObjectVisitor visitor) { + if (child != null) visitor(child!); + super.visitChildren(visitor); + } + + @override + List debugDescribeChildren() { + final List children = []; + + if (child != null) children.add(child!.toDiagnosticsNode(name: 'onstage')); + + if (firstChild != null) { + RenderBox child = firstChild!; + + int count = 1; + while (true) { + children.add( + child.toDiagnosticsNode( + name: 'offstage $count', + style: DiagnosticsTreeStyle.offstage, + ), + ); + if (child == lastChild) break; + final StackParentData childParentData = + child.parentData! as StackParentData; + child = childParentData.nextSibling!; + count += 1; + } + } else { + children.add( + DiagnosticsNode.message( + 'no offstage children', + style: DiagnosticsTreeStyle.offstage, + ), + ); + } + return children; + } + + @override + void visitChildrenForSemantics(RenderObjectVisitor visitor) { + if (child != null) visitor(child!); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/transitions.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/transitions.dart new file mode 100644 index 0000000000..525006af9c --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/transitions.dart @@ -0,0 +1,113 @@ +import 'package:flutter/widgets.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/rendering.dart'; + +class SizeTransitionWithIntrinsicSize extends SingleChildRenderObjectWidget { + /// Creates a size transition with its intrinsic width/height taking [sizeFactor] + /// into account. + /// + /// The [axis] argument defaults to [Axis.vertical]. + /// The [axisAlignment] defaults to 0.0, which centers the child along the + /// main axis during the transition. + SizeTransitionWithIntrinsicSize({ + this.axis = Axis.vertical, + required this.sizeFactor, + double axisAlignment = 0.0, + Widget? child, + Key? key, + }) : super( + key: key, + child: SizeTransition( + axis: axis, + sizeFactor: sizeFactor, + axisAlignment: axisAlignment, + child: child, + )); + + final Axis axis; + final Animation sizeFactor; + + @override + RenderSizeTransitionWithIntrinsicSize createRenderObject( + BuildContext context) { + return RenderSizeTransitionWithIntrinsicSize( + axis: axis, + sizeFactor: sizeFactor, + ); + } + + @override + void updateRenderObject(BuildContext context, + RenderSizeTransitionWithIntrinsicSize renderObject) { + renderObject + ..axis = axis + ..sizeFactor = sizeFactor; + } + + @override + void debugFillProperties(DiagnosticPropertiesBuilder properties) { + super.debugFillProperties(properties); + properties.add(DiagnosticsProperty('axis', axis)); + properties + .add(DiagnosticsProperty>('sizeFactor', sizeFactor)); + } +} + +class RenderSizeTransitionWithIntrinsicSize extends RenderProxyBox { + Axis axis; + Animation sizeFactor; + + RenderSizeTransitionWithIntrinsicSize({ + this.axis = Axis.vertical, + required this.sizeFactor, + RenderBox? child, + }) : super(child); + + @override + double computeMinIntrinsicWidth(double height) { + final child = this.child; + if (child != null) { + double childWidth = child.getMinIntrinsicWidth(height); + return axis == Axis.horizontal + ? childWidth * sizeFactor.value + : childWidth; + } + return 0.0; + } + + @override + double computeMaxIntrinsicWidth(double height) { + final child = this.child; + if (child != null) { + double childWidth = child.getMaxIntrinsicWidth(height); + return axis == Axis.horizontal + ? childWidth * sizeFactor.value + : childWidth; + } + return 0.0; + } + + @override + double computeMinIntrinsicHeight(double width) { + final child = this.child; + if (child != null) { + double childHeight = child.getMinIntrinsicHeight(width); + return axis == Axis.vertical + ? childHeight * sizeFactor.value + : childHeight; + } + return 0.0; + } + + @override + double computeMaxIntrinsicHeight(double width) { + final child = this.child; + if (child != null) { + double childHeight = child.getMaxIntrinsicHeight(width); + return axis == Axis.vertical + ? childHeight * sizeFactor.value + : childHeight; + } + return 0.0; + } +} diff --git a/frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt new file mode 100644 index 0000000000..ce0512464c --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt @@ -0,0 +1,47 @@ +# The Flutter tooling requires that developers have CMake 3.10 or later +# installed. You should not increase this version, as doing so will cause +# the plugin to fail to compile for some customers of the plugin. +cmake_minimum_required(VERSION 3.10) + +# Project-level configuration. +set(PROJECT_NAME "flowy_board") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed. +set(PLUGIN_NAME "flowy_board_plugin") + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +# +# Any new source files that you add to the plugin should be added here. +add_library(${PLUGIN_NAME} SHARED + "flowy_board_plugin.cc" +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) +target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(flowy_board_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc b/frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc new file mode 100644 index 0000000000..9e5d65cc4e --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc @@ -0,0 +1,70 @@ +#include "include/flowy_board/flowy_board_plugin.h" + +#include +#include +#include + +#include + +#define FLOWY_BOARD_PLUGIN(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj), flowy_board_plugin_get_type(), \ + FlowyBoardPlugin)) + +struct _FlowyBoardPlugin { + GObject parent_instance; +}; + +G_DEFINE_TYPE(FlowyBoardPlugin, flowy_board_plugin, g_object_get_type()) + +// Called when a method call is received from Flutter. +static void flowy_board_plugin_handle_method_call( + FlowyBoardPlugin* self, + FlMethodCall* method_call) { + g_autoptr(FlMethodResponse) response = nullptr; + + const gchar* method = fl_method_call_get_name(method_call); + + if (strcmp(method, "getPlatformVersion") == 0) { + struct utsname uname_data = {}; + uname(&uname_data); + g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version); + g_autoptr(FlValue) result = fl_value_new_string(version); + response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); + } else { + response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); + } + + fl_method_call_respond(method_call, response, nullptr); +} + +static void flowy_board_plugin_dispose(GObject* object) { + G_OBJECT_CLASS(flowy_board_plugin_parent_class)->dispose(object); +} + +static void flowy_board_plugin_class_init(FlowyBoardPluginClass* klass) { + G_OBJECT_CLASS(klass)->dispose = flowy_board_plugin_dispose; +} + +static void flowy_board_plugin_init(FlowyBoardPlugin* self) {} + +static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, + gpointer user_data) { + FlowyBoardPlugin* plugin = FLOWY_BOARD_PLUGIN(user_data); + flowy_board_plugin_handle_method_call(plugin, method_call); +} + +void flowy_board_plugin_register_with_registrar(FlPluginRegistrar* registrar) { + FlowyBoardPlugin* plugin = FLOWY_BOARD_PLUGIN( + g_object_new(flowy_board_plugin_get_type(), nullptr)); + + g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); + g_autoptr(FlMethodChannel) channel = + fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), + "flowy_board", + FL_METHOD_CODEC(codec)); + fl_method_channel_set_method_call_handler(channel, method_call_cb, + g_object_ref(plugin), + g_object_unref); + + g_object_unref(plugin); +} diff --git a/frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h b/frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h new file mode 100644 index 0000000000..076b022a5a --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h @@ -0,0 +1,26 @@ +#ifndef FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ +#define FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ + +#include + +G_BEGIN_DECLS + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) +#else +#define FLUTTER_PLUGIN_EXPORT +#endif + +typedef struct _FlowyBoardPlugin FlowyBoardPlugin; +typedef struct { + GObjectClass parent_class; +} FlowyBoardPluginClass; + +FLUTTER_PLUGIN_EXPORT GType flowy_board_plugin_get_type(); + +FLUTTER_PLUGIN_EXPORT void flowy_board_plugin_register_with_registrar( + FlPluginRegistrar* registrar); + +G_END_DECLS + +#endif // FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ diff --git a/frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift b/frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift new file mode 100644 index 0000000000..c7f8d0260d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift @@ -0,0 +1,19 @@ +import Cocoa +import FlutterMacOS + +public class FlowyBoardPlugin: NSObject, FlutterPlugin { + public static func register(with registrar: FlutterPluginRegistrar) { + let channel = FlutterMethodChannel(name: "flowy_board", binaryMessenger: registrar.messenger) + let instance = FlowyBoardPlugin() + registrar.addMethodCallDelegate(instance, channel: channel) + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "getPlatformVersion": + result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) + default: + result(FlutterMethodNotImplemented) + } + } +} diff --git a/frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec b/frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec new file mode 100644 index 0000000000..06d567ad65 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec @@ -0,0 +1,23 @@ +# +# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. +# Run `pod lib lint flowy_board.podspec` to validate before publishing. +# +Pod::Spec.new do |s| + s.name = 'flowy_board' + s.version = '0.0.1' + s.summary = 'A new Flutter plugin project.' + s.description = <<-DESC +A new Flutter plugin project. + DESC + s.homepage = 'http://example.com' + s.license = { :file => '../LICENSE' } + s.author = { 'Your Company' => 'email@example.com' } + + s.source = { :path => '.' } + s.source_files = 'Classes/**/*' + s.dependency 'FlutterMacOS' + + s.platform = :osx, '10.11' + s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } + s.swift_version = '5.0' +end diff --git a/frontend/app_flowy/packages/flowy_board/pubspec.yaml b/frontend/app_flowy/packages/flowy_board/pubspec.yaml new file mode 100644 index 0000000000..52600bde71 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/pubspec.yaml @@ -0,0 +1,73 @@ +name: flowy_board +description: A new Flutter plugin project. +version: 0.0.1 +homepage: + +environment: + sdk: ">=2.17.6 <3.0.0" + flutter: ">=2.5.0" + +dependencies: + flutter: + sdk: flutter + plugin_platform_interface: ^2.0.2 + +dev_dependencies: + flutter_test: + sdk: flutter + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + # This section identifies this Flutter project as a plugin project. + # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) + # which should be registered in the plugin registry. This is required for + # using method channels. + # The Android 'package' specifies package in which the registered class is. + # This is required for using method channels on Android. + # The 'ffiPlugin' specifies that native code should be built and bundled. + # This is required for using `dart:ffi`. + # All these are used by the tooling to maintain consistency when + # adding or updating assets for this project. + plugin: + platforms: + linux: + pluginClass: FlowyBoardPlugin + macos: + pluginClass: FlowyBoardPlugin + windows: + pluginClass: FlowyBoardPluginCApi + + # To add assets to your plugin package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.dev/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # To add custom fonts to your plugin package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.dev/custom-fonts/#from-packages diff --git a/frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart b/frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart new file mode 100644 index 0000000000..52e5d5713d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart @@ -0,0 +1,24 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flowy_board/flowy_board_method_channel.dart'; + +void main() { + MethodChannelFlowyBoard platform = MethodChannelFlowyBoard(); + const MethodChannel channel = MethodChannel('flowy_board'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + channel.setMockMethodCallHandler((MethodCall methodCall) async { + return '42'; + }); + }); + + tearDown(() { + channel.setMockMethodCallHandler(null); + }); + + test('getPlatformVersion', () async { + expect(await platform.getPlatformVersion(), '42'); + }); +} diff --git a/frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart b/frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart new file mode 100644 index 0000000000..1158119a60 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart @@ -0,0 +1,29 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flowy_board/flowy_board.dart'; +import 'package:flowy_board/flowy_board_platform_interface.dart'; +import 'package:flowy_board/flowy_board_method_channel.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +class MockFlowyBoardPlatform + with MockPlatformInterfaceMixin + implements FlowyBoardPlatform { + + @override + Future getPlatformVersion() => Future.value('42'); +} + +void main() { + final FlowyBoardPlatform initialPlatform = FlowyBoardPlatform.instance; + + test('$MethodChannelFlowyBoard is the default instance', () { + expect(initialPlatform, isInstanceOf()); + }); + + test('getPlatformVersion', () async { + FlowyBoard flowyBoardPlugin = FlowyBoard(); + MockFlowyBoardPlatform fakePlatform = MockFlowyBoardPlatform(); + FlowyBoardPlatform.instance = fakePlatform; + + expect(await flowyBoardPlugin.getPlatformVersion(), '42'); + }); +} diff --git a/frontend/app_flowy/packages/flowy_board/windows/.gitignore b/frontend/app_flowy/packages/flowy_board/windows/.gitignore new file mode 100644 index 0000000000..b3eb2be169 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/windows/.gitignore @@ -0,0 +1,17 @@ +flutter/ + +# Visual Studio user-specific files. +*.suo +*.user +*.userosscache +*.sln.docstates + +# Visual Studio build-related files. +x64/ +x86/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ diff --git a/frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt new file mode 100644 index 0000000000..193c5908ec --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt @@ -0,0 +1,53 @@ +# The Flutter tooling requires that developers have a version of Visual Studio +# installed that includes CMake 3.14 or later. You should not increase this +# version, as doing so will cause the plugin to fail to compile for some +# customers of the plugin. +cmake_minimum_required(VERSION 3.14) + +# Project-level configuration. +set(PROJECT_NAME "flowy_board") +project(${PROJECT_NAME} LANGUAGES CXX) + +# This value is used when generating builds using this plugin, so it must +# not be changed +set(PLUGIN_NAME "flowy_board_plugin") + +# Any new source files that you add to the plugin should be added here. +list(APPEND PLUGIN_SOURCES + "flowy_board_plugin.cpp" + "flowy_board_plugin.h" +) + +# Define the plugin library target. Its name must not be changed (see comment +# on PLUGIN_NAME above). +add_library(${PLUGIN_NAME} SHARED + "include/flowy_board/flowy_board_plugin_c_api.h" + "flowy_board_plugin_c_api.cpp" + ${PLUGIN_SOURCES} +) + +# Apply a standard set of build settings that are configured in the +# application-level CMakeLists.txt. This can be removed for plugins that want +# full control over build settings. +apply_standard_settings(${PLUGIN_NAME}) + +# Symbols are hidden by default to reduce the chance of accidental conflicts +# between plugins. This should not be removed; any symbols that should be +# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. +set_target_properties(${PLUGIN_NAME} PROPERTIES + CXX_VISIBILITY_PRESET hidden) +target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) + +# Source include directories and library dependencies. Add any plugin-specific +# dependencies here. +target_include_directories(${PLUGIN_NAME} INTERFACE + "${CMAKE_CURRENT_SOURCE_DIR}/include") +target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) + +# List of absolute paths to libraries that should be bundled with the plugin. +# This list could contain prebuilt libraries, or libraries created by an +# external build triggered from this build file. +set(flowy_board_bundled_libraries + "" + PARENT_SCOPE +) diff --git a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp new file mode 100644 index 0000000000..f54aaaa86b --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp @@ -0,0 +1,59 @@ +#include "flowy_board_plugin.h" + +// This must be included before many other Windows headers. +#include + +// For getPlatformVersion; remove unless needed for your plugin implementation. +#include + +#include +#include +#include + +#include +#include + +namespace flowy_board { + +// static +void FlowyBoardPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarWindows *registrar) { + auto channel = + std::make_unique>( + registrar->messenger(), "flowy_board", + &flutter::StandardMethodCodec::GetInstance()); + + auto plugin = std::make_unique(); + + channel->SetMethodCallHandler( + [plugin_pointer = plugin.get()](const auto &call, auto result) { + plugin_pointer->HandleMethodCall(call, std::move(result)); + }); + + registrar->AddPlugin(std::move(plugin)); +} + +FlowyBoardPlugin::FlowyBoardPlugin() {} + +FlowyBoardPlugin::~FlowyBoardPlugin() {} + +void FlowyBoardPlugin::HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result) { + if (method_call.method_name().compare("getPlatformVersion") == 0) { + std::ostringstream version_stream; + version_stream << "Windows "; + if (IsWindows10OrGreater()) { + version_stream << "10+"; + } else if (IsWindows8OrGreater()) { + version_stream << "8"; + } else if (IsWindows7OrGreater()) { + version_stream << "7"; + } + result->Success(flutter::EncodableValue(version_stream.str())); + } else { + result->NotImplemented(); + } +} + +} // namespace flowy_board diff --git a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h new file mode 100644 index 0000000000..27eeafed7b --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h @@ -0,0 +1,32 @@ +#ifndef FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ +#define FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ + +#include +#include + +#include + +namespace flowy_board { + +class FlowyBoardPlugin : public flutter::Plugin { + public: + static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); + + FlowyBoardPlugin(); + + virtual ~FlowyBoardPlugin(); + + // Disallow copy and assign. + FlowyBoardPlugin(const FlowyBoardPlugin&) = delete; + FlowyBoardPlugin& operator=(const FlowyBoardPlugin&) = delete; + + private: + // Called when a method is called on this plugin's channel from Dart. + void HandleMethodCall( + const flutter::MethodCall &method_call, + std::unique_ptr> result); +}; + +} // namespace flowy_board + +#endif // FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ diff --git a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp new file mode 100644 index 0000000000..7e966de43f --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp @@ -0,0 +1,12 @@ +#include "include/flowy_board/flowy_board_plugin_c_api.h" + +#include + +#include "flowy_board_plugin.h" + +void FlowyBoardPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar) { + flowy_board::FlowyBoardPlugin::RegisterWithRegistrar( + flutter::PluginRegistrarManager::GetInstance() + ->GetRegistrar(registrar)); +} diff --git a/frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h b/frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h new file mode 100644 index 0000000000..9ad37dc483 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h @@ -0,0 +1,23 @@ +#ifndef FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_C_API_H_ +#define FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_C_API_H_ + +#include + +#ifdef FLUTTER_PLUGIN_IMPL +#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) +#else +#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) +#endif + +#if defined(__cplusplus) +extern "C" { +#endif + +FLUTTER_PLUGIN_EXPORT void FlowyBoardPluginCApiRegisterWithRegistrar( + FlutterDesktopPluginRegistrarRef registrar); + +#if defined(__cplusplus) +} // extern "C" +#endif + +#endif // FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_C_API_H_ diff --git a/frontend/app_flowy/packages/flowy_sdk/example/windows/flutter/generated_plugins.cmake b/frontend/app_flowy/packages/flowy_sdk/example/windows/flutter/generated_plugins.cmake index d71dbaabab..33c0fd0169 100644 --- a/frontend/app_flowy/packages/flowy_sdk/example/windows/flutter/generated_plugins.cmake +++ b/frontend/app_flowy/packages/flowy_sdk/example/windows/flutter/generated_plugins.cmake @@ -6,6 +6,9 @@ list(APPEND FLUTTER_PLUGIN_LIST flowy_sdk ) +list(APPEND FLUTTER_FFI_PLUGIN_LIST +) + set(PLUGIN_BUNDLED_LIBRARIES) foreach(plugin ${FLUTTER_PLUGIN_LIST}) @@ -14,3 +17,8 @@ foreach(plugin ${FLUTTER_PLUGIN_LIST}) list(APPEND PLUGIN_BUNDLED_LIBRARIES $) list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) endforeach(plugin) + +foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) + add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) + list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) +endforeach(ffi_plugin) diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index 8dbc3b4c70..7e9c38cf5b 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -77,6 +77,7 @@ dependencies: reorderables: ^0.5.0 linked_scroll_controller: ^0.2.0 + dev_dependencies: flutter_lints: ^1.0.0 flutter_test: From a4b4b20cfc95052622693db8d248d0fc03f3a42c Mon Sep 17 00:00:00 2001 From: appflowy Date: Wed, 3 Aug 2022 10:24:14 +0800 Subject: [PATCH 02/22] chore: config board ui --- .../flowy_board/example/.vscode/launch.json | 37 ++ .../flowy_board/example/lib/main.dart | 68 ++- .../example/lib/multi_board_list_example.dart | 78 +++ .../lib/single_board_list_example.dart | 60 ++ .../flowy_board/example/macos/Podfile.lock | 22 + .../macos/Runner.xcodeproj/project.pbxproj | 62 ++- .../contents.xcworkspacedata | 3 + .../packages/flowy_board/example/pubspec.yaml | 2 +- .../packages/flowy_board/lib/flowy_board.dart | 11 +- .../{widgets => rendering}/board_overlay.dart | 0 .../flowy_board/lib/src/utils/log.dart | 28 + .../flowy_board/lib/src/widgets/board.dart | 149 +++++ .../widgets/board_column/board_column.dart | 142 +++++ .../widgets/board_column/data_controller.dart | 85 +++ .../lib/src/widgets/column_container.dart | 91 +++ .../lib/src/widgets/flex/drag_state.dart | 171 ++++++ .../lib/src/widgets/flex/drag_target.dart | 369 ++++++++++++ .../lib/src/widgets/flex/reorder_flex.dart | 524 ++++++++++++++++++ .../src/widgets/flex/reorder_flex_ext.dart | 78 +++ .../lib/src/widgets/flex/reorder_mixin.dart | 65 +++ .../widgets/phantom/phantom_controller.dart | 338 +++++++++++ .../src/widgets/phantom/phantom_state.dart | 109 ++++ .../packages/flowy_board/pubspec.yaml | 4 +- .../test/flowy_board_method_channel_test.dart | 24 - .../flowy_board/test/flowy_board_test.dart | 29 - 25 files changed, 2450 insertions(+), 99 deletions(-) create mode 100644 frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json create mode 100644 frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart create mode 100644 frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart create mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock rename frontend/app_flowy/packages/flowy_board/lib/src/{widgets => rendering}/board_overlay.dart (100%) create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart delete mode 100644 frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart delete mode 100644 frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart diff --git a/frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json b/frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json new file mode 100644 index 0000000000..3652b92f27 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json @@ -0,0 +1,37 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "name": "example", + "cwd": "example", + "request": "launch", + "env": { + "Dart_LOG": "true" + }, + "type": "dart" + }, + { + "name": "example (profile mode)", + "cwd": "example", + "request": "launch", + "type": "dart", + "env": { + "Dart_LOG": "true" + }, + "flutterMode": "profile" + }, + { + "name": "example (release mode)", + "cwd": "example", + "request": "launch", + "type": "dart", + "env": { + "Dart_LOG": "true" + }, + "flutterMode": "release" + } + ] +} \ No newline at end of file diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/main.dart b/frontend/app_flowy/packages/flowy_board/example/lib/main.dart index eecf56057f..975cbb6ca8 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/main.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/main.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:flowy_board/flowy_board.dart'; +import 'single_board_list_example.dart'; +import 'multi_board_list_example.dart'; void main() { runApp(const MyApp()); @@ -16,48 +14,46 @@ class MyApp extends StatefulWidget { } class _MyAppState extends State { - String _platformVersion = 'Unknown'; - final _flowyBoardPlugin = FlowyBoard(); + int _currentIndex = 0; + final _bottomNavigationColor = Colors.blue; + + final List _examples = [ + const MultiBoardListExample(), + const SingleBoardListExample(), + ]; @override void initState() { super.initState(); - initPlatformState(); - } - - // Platform messages are asynchronous, so we initialize in an async method. - Future initPlatformState() async { - String platformVersion; - // Platform messages may fail, so we use a try/catch PlatformException. - // We also handle the message potentially returning null. - try { - platformVersion = - await _flowyBoardPlugin.getPlatformVersion() ?? 'Unknown platform version'; - } on PlatformException { - platformVersion = 'Failed to get platform version.'; - } - - // If the widget was removed from the tree while the asynchronous platform - // message was in flight, we want to discard the reply rather than calling - // setState to update our non-existent appearance. - if (!mounted) return; - - setState(() { - _platformVersion = platformVersion; - }); } @override Widget build(BuildContext context) { return MaterialApp( home: Scaffold( - appBar: AppBar( - title: const Text('Plugin example app'), - ), - body: Center( - child: Text('Running on: $_platformVersion\n'), - ), - ), + appBar: AppBar( + title: const Text('FlowyBoard example'), + ), + body: _examples[_currentIndex], + bottomNavigationBar: BottomNavigationBar( + fixedColor: _bottomNavigationColor, + showSelectedLabels: true, + showUnselectedLabels: false, + currentIndex: _currentIndex, + items: [ + BottomNavigationBarItem( + icon: Icon(Icons.grid_on, color: _bottomNavigationColor), + label: "MultiBoardList"), + BottomNavigationBarItem( + icon: Icon(Icons.grid_on, color: _bottomNavigationColor), + label: "SingleBoardList"), + ], + onTap: (int index) { + setState(() { + _currentIndex = index; + }); + }, + )), ); } } diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart new file mode 100644 index 0000000000..3574745ae2 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart @@ -0,0 +1,78 @@ +import 'package:flowy_board/flowy_board.dart'; +import 'package:flutter/material.dart'; + +class MultiBoardListExample extends StatefulWidget { + const MultiBoardListExample({Key? key}) : super(key: key); + + @override + State createState() => _MultiBoardListExampleState(); +} + +class _MultiBoardListExampleState extends State { + final BoardDataController boardData = BoardDataController(); + + @override + void initState() { + final column1 = BoardColumnData(id: "1", items: [ + TextItem("a"), + TextItem("b"), + TextItem("c"), + TextItem("d"), + ]); + final column2 = BoardColumnData(id: "2", items: [ + TextItem("1"), + TextItem("2"), + TextItem("3"), + TextItem("4"), + TextItem("5"), + ]); + + final column3 = BoardColumnData(id: "3", items: [ + TextItem("A"), + TextItem("B"), + TextItem("C"), + TextItem("D"), + ]); + + boardData.setColumnData(column1); + boardData.setColumnData(column2); + boardData.setColumnData(column3); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Board( + dataController: boardData, + background: Container(color: Colors.red), + builder: (context, item) { + return _RowWidget(item: item as TextItem, key: ObjectKey(item)); + }, + ); + } +} + +class _RowWidget extends StatelessWidget { + final TextItem item; + const _RowWidget({Key? key, required this.item}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + key: ObjectKey(item), + height: 60, + color: Colors.green, + child: Center(child: Text(item.s)), + ); + } +} + +class TextItem extends ColumnItem { + final String s; + + TextItem(this.s); + + @override + String get id => s; +} diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart new file mode 100644 index 0000000000..fd61b5b836 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:flowy_board/flowy_board.dart'; + +class SingleBoardListExample extends StatefulWidget { + const SingleBoardListExample({Key? key}) : super(key: key); + + @override + State createState() => _SingleBoardListExampleState(); +} + +class _SingleBoardListExampleState extends State { + final BoardDataController boardData = BoardDataController(); + + @override + void initState() { + final column = BoardColumnData(id: "1", items: [ + TextItem("a"), + TextItem("b"), + TextItem("c"), + TextItem("d"), + ]); + + boardData.setColumnData(column); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Board( + dataController: boardData, + builder: (context, item) { + return _RowWidget(item: item as TextItem, key: ObjectKey(item)); + }, + ); + } +} + +class _RowWidget extends StatelessWidget { + final TextItem item; + const _RowWidget({Key? key, required this.item}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + key: ObjectKey(item), + height: 60, + color: Colors.green, + child: Center(child: Text(item.s)), + ); + } +} + +class TextItem extends ColumnItem { + final String s; + + TextItem(this.s); + + @override + String get id => throw UnimplementedError(); +} diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock b/frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock new file mode 100644 index 0000000000..18dcf94df9 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock @@ -0,0 +1,22 @@ +PODS: + - flowy_board (0.0.1): + - FlutterMacOS + - FlutterMacOS (1.0.0) + +DEPENDENCIES: + - flowy_board (from `Flutter/ephemeral/.symlinks/plugins/flowy_board/macos`) + - FlutterMacOS (from `Flutter/ephemeral`) + +EXTERNAL SOURCES: + flowy_board: + :path: Flutter/ephemeral/.symlinks/plugins/flowy_board/macos + FlutterMacOS: + :path: Flutter/ephemeral + +SPEC CHECKSUMS: + flowy_board: e93adfa305df65f1ac860f2cf9dc7188f50c9b66 + FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 + +PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c + +COCOAPODS: 1.11.3 diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj index 9ce6f7ca08..d60feedcfe 100644 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj @@ -21,6 +21,7 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ + 2AD48017E3EAE6142B6E265B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 368626E4047E7783820AEC34 /* Pods_Runner.framework */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; @@ -52,9 +53,10 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 02BDE4CD9C63CA2562B2FDD1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* flowy_board_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "flowy_board_example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* flowy_board_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flowy_board_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -66,8 +68,11 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 368626E4047E7783820AEC34 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 59D896B3478D0A2144E570BB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + F368AC3EE3CE4F2FCEC85166 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -75,6 +80,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 2AD48017E3EAE6142B6E265B /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -99,6 +105,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, + 6BBA375F5E43A645EC061EA0 /* Pods */, ); sourceTree = ""; }; @@ -145,9 +152,21 @@ path = Runner; sourceTree = ""; }; + 6BBA375F5E43A645EC061EA0 /* Pods */ = { + isa = PBXGroup; + children = ( + F368AC3EE3CE4F2FCEC85166 /* Pods-Runner.debug.xcconfig */, + 02BDE4CD9C63CA2562B2FDD1 /* Pods-Runner.release.xcconfig */, + 59D896B3478D0A2144E570BB /* Pods-Runner.profile.xcconfig */, + ); + name = Pods; + path = Pods; + sourceTree = ""; + }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( + 368626E4047E7783820AEC34 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -159,11 +178,13 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( + DF704441B638BDD90A6EF3BC /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, + B89DDA2E6C13BED8F107400D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -270,6 +291,45 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; + B89DDA2E6C13BED8F107400D /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + DF704441B638BDD90A6EF3BC /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata index 1d526a16ed..21a3cc14c7 100644 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -4,4 +4,7 @@ + + diff --git a/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml b/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml index 364ab9918c..8504fc5208 100644 --- a/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml +++ b/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml @@ -6,7 +6,7 @@ description: Demonstrates how to use the flowy_board plugin. publish_to: 'none' # Remove this line if you wish to publish to pub.dev environment: - sdk: ">=2.17.6 <3.0.0" + sdk: ">=2.17.1 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart index f4bed642b6..0611432b4a 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart @@ -1,8 +1,5 @@ +library flowy_board; -import 'flowy_board_platform_interface.dart'; - -class FlowyBoard { - Future getPlatformVersion() { - return FlowyBoardPlatform.instance.getPlatformVersion(); - } -} +export 'src/widgets/board_column/board_column.dart'; +export 'src/widgets/board_column/data_controller.dart'; +export 'src/widgets/board.dart'; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_overlay.dart b/frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_overlay.dart rename to frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart b/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart new file mode 100644 index 0000000000..b9f766f961 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +// ignore: constant_identifier_names +const DART_LOG = "Dart_LOG"; + +class Log { + // static const enableLog = bool.hasEnvironment(DART_LOG); + // static final shared = Log(); + static const enableLog = true; + + static void info(String? message) { + if (enableLog) { + debugPrint('ℹ️[Info]=> $message'); + } + } + + static void debug(String? message) { + if (enableLog) { + debugPrint('🐛[Debug]=> $message'); + } + } + + static void trace(String? message) { + if (enableLog) { + // debugPrint('❗️[Trace]=> $message'); + } + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart new file mode 100644 index 0000000000..9305ba760d --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -0,0 +1,149 @@ +import 'dart:collection'; +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import '../../flowy_board.dart'; + +import 'column_container.dart'; +import 'flex/reorder_flex.dart'; +import 'phantom/phantom_controller.dart'; + +class Board extends StatelessWidget { + /// The direction to use as the main axis. + final Axis direction = Axis.vertical; + + /// How much space to place between children in a run in the main axis. + /// Defaults to 10.0. + final double spacing; + + /// How much space to place between the runs themselves in the cross axis. + /// Defaults to 0.0. + final double runSpacing; + + final Widget? background; + + final BoardColumnItemWidgetBuilder builder; + + /// + final BoardDataController dataController; + + /// + final BoardPhantomController passthroughPhantomContorller; + + Board({ + required this.dataController, + required this.builder, + this.spacing = 10.0, + this.runSpacing = 0.0, + this.background, + Key? key, + }) : passthroughPhantomContorller = + BoardPhantomController(delegate: dataController), + super(key: key); + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: dataController, + child: Consumer( + builder: (context, notifier, child) { + List children = []; + List acceptColumns = + dataController.columnControllers.keys.toList(); + + dataController.columnControllers.forEach((columnId, dataController) { + Widget child = + buildBoardColumn(columnId, acceptColumns, dataController); + if (children.isEmpty) { + // children.add(SizedBox(width: spacing)); + } + // if (background != null) { + // child = Stack(children: [ + // background!, + // child, + // ]); + // } + // children.add(Expanded(key: ValueKey(columnId), child: child)); + children.add(child); + // children.add(SizedBox(width: spacing)); + }); + + return BoardColumnContainer( + onReorder: (fromIndex, toIndex) {}, + boardDataController: dataController, + children: children, + ); + + // return Row( + // crossAxisAlignment: CrossAxisAlignment.start, + // mainAxisAlignment: MainAxisAlignment.spaceEvenly, + // children: children, + // ); + }, + ), + ); + } + + /// + Widget buildBoardColumn( + String columnId, + List acceptColumns, + BoardColumnDataController dataController, + ) { + return ChangeNotifierProvider.value( + key: ValueKey(columnId), + value: dataController, + child: Consumer( + builder: (context, value, child) { + return SizedBox( + width: 200, + child: BoardColumnWidget( + header: Container(color: Colors.yellow, height: 30), + builder: builder, + acceptColumns: acceptColumns, + dataController: dataController, + scrollController: ScrollController(), + onReorder: (_, int fromIndex, int toIndex) { + dataController.move(fromIndex, toIndex); + }, + phantomController: passthroughPhantomContorller, + ), + ); + }, + ), + ); + } +} + +class BoardDataController extends ChangeNotifier + with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource { + final LinkedHashMap columnDatas = LinkedHashMap(); + final LinkedHashMap columnControllers = + LinkedHashMap(); + + BoardDataController(); + + void setColumnData(BoardColumnData columnData) { + final controller = BoardColumnDataController(columnData: columnData); + columnDatas[columnData.id] = columnData; + columnControllers[columnData.id] = controller; + } + + @override + List get props { + return [columnDatas.values]; + } + + @override + BoardColumnDataController? controller(String columnId) { + return columnControllers[columnId]; + } + + @override + String get identifier => '$BoardDataController'; + + @override + List get items => columnDatas.values.toList(); +} + +class BoardDataIdentifier {} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart new file mode 100644 index 0000000000..4d14d2d521 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart @@ -0,0 +1,142 @@ +import 'package:flutter/material.dart'; + +import '../../rendering/board_overlay.dart'; +import '../../utils/log.dart'; +import '../phantom/phantom_controller.dart'; +import '../flex/reorder_flex.dart'; +import '../flex/drag_state.dart'; +import '../flex/reorder_flex_ext.dart'; +import 'data_controller.dart'; + +typedef OnDragStarted = void Function(int index); +typedef OnDragEnded = void Function(String listId); +typedef OnReorder = void Function(String listId, int fromIndex, int toIndex); +typedef OnDeleted = void Function(String listId, int deletedIndex); +typedef OnInserted = void Function(String listId, int insertedIndex); +typedef OnPassedInPhantom = void Function( + String listId, + FlexDragTargetData dragTargetData, + int phantomIndex, +); + +typedef BoardColumnItemWidgetBuilder = Widget Function( + BuildContext context, ColumnItem item); + +class BoardColumnWidget extends StatefulWidget { + final Widget? header; + final Widget? footer; + final BoardColumnDataController dataController; + final ScrollController? scrollController; + final ReorderFlexConfig config; + + final OnDragStarted? onDragStarted; + final OnReorder onReorder; + final OnDragEnded? onDragEnded; + + final BoardPhantomController phantomController; + + String get columnId => dataController.identifier; + + final List acceptColumns; + + final BoardColumnItemWidgetBuilder builder; + + const BoardColumnWidget({ + Key? key, + this.header, + this.footer, + required this.builder, + required this.onReorder, + required this.dataController, + required this.phantomController, + required this.acceptColumns, + this.config = const ReorderFlexConfig(), + this.onDragStarted, + this.scrollController, + this.onDragEnded, + }) : super(key: key); + + @override + State createState() => _BoardColumnWidgetState(); +} + +class _BoardColumnWidgetState extends State { + final GlobalKey _columnOverlayKey = + GlobalKey(debugLabel: '$BoardColumnWidget overlay key'); + + late BoardOverlayEntry _overlayEntry; + + @override + void initState() { + _overlayEntry = BoardOverlayEntry( + builder: (BuildContext context) { + final children = widget.dataController.items + .map((item) => _buildWidget(context, item)) + .toList(); + + final dragTargetExtension = ReorderFlextDragTargetExtension( + reorderFlexId: widget.columnId, + delegate: widget.phantomController, + acceptReorderFlexIds: widget.acceptColumns, + draggableTargetBuilder: PhantomReorderDraggableBuilder(), + ); + + return ReorderFlex( + key: widget.key, + header: widget.header, + footer: widget.footer, + scrollController: widget.scrollController, + config: widget.config, + onDragStarted: (index) { + widget.phantomController.columnStartDragging(widget.columnId); + widget.onDragStarted?.call(index); + }, + onReorder: ((fromIndex, toIndex) { + if (widget.phantomController.isFromColumn(widget.columnId)) { + widget.onReorder(widget.columnId, fromIndex, toIndex); + widget.phantomController.transformIndex(fromIndex, toIndex); + } + }), + onDragEnded: () { + widget.phantomController.columnEndDragging(widget.columnId); + widget.onDragEnded?.call(widget.columnId); + _printItems(widget.dataController); + }, + dataSource: widget.dataController, + dragTargetExtension: dragTargetExtension, + children: children, + ); + }, + opaque: false); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BoardOverlay( + key: _columnOverlayKey, + initialEntries: [_overlayEntry], + ); + } + + Widget _buildWidget(BuildContext context, ColumnItem item) { + if (item is PhantomColumnItem) { + return PassthroughPhantomWidget( + key: UniqueKey(), + opacity: widget.config.draggingWidgetOpacity, + passthroughPhantomContext: item.phantomContext, + ); + } else { + return widget.builder(context, item); + } + } +} + +void _printItems(BoardColumnDataController dataController) { + String msg = ''; + for (var element in dataController.items) { + msg = '$msg$element,'; + } + + Log.debug(msg); +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart new file mode 100644 index 0000000000..b890c7da73 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart @@ -0,0 +1,85 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; +import '../../utils/log.dart'; +import '../flex/reorder_flex.dart'; + +abstract class ColumnItem extends ReoderFlextItem { + String get id; + + bool get isPhantom => false; + + @override + String toString() { + return id; + } +} + +class BoardColumnData extends ReoderFlextItem with EquatableMixin { + final String id; + final List items; + + BoardColumnData({ + required this.id, + required this.items, + }); + + @override + List get props => [id, ...items]; + + @override + String toString() { + return 'Column$id'; + } +} + +class BoardColumnDataController extends ChangeNotifier + with EquatableMixin, ReoderFlextDataSource { + final BoardColumnData columnData; + + BoardColumnDataController({ + required this.columnData, + }); + + @override + List get props => columnData.props; + + ColumnItem removeAt(int index) { + Log.debug('[$BoardColumnDataController] $columnData remove item at $index'); + final item = columnData.items.removeAt(index); + notifyListeners(); + return item; + } + + void move(int fromIndex, int toIndex) { + if (fromIndex == toIndex) { + return; + } + Log.debug( + '[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex'); + final item = columnData.items.removeAt(fromIndex); + columnData.items.insert(toIndex, item); + notifyListeners(); + } + + void insert(int index, ColumnItem item, {bool notify = true}) { + Log.debug('[$BoardColumnDataController] $columnData insert item at $index'); + columnData.items.insert(index, item); + if (notify) { + notifyListeners(); + } + } + + void replace(int index, ColumnItem item) { + Log.debug( + '[$BoardColumnDataController] $columnData replace item at $index'); + columnData.items.removeAt(index); + columnData.items.insert(index, item); + notifyListeners(); + } + + @override + List get items => columnData.items; + + @override + String get identifier => columnData.id; +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart new file mode 100644 index 0000000000..f8057fb1c2 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; + +import '../rendering/board_overlay.dart'; +import 'flex/reorder_flex.dart'; +import 'board.dart'; + +class BoardColumnContainer extends StatefulWidget { + final Widget? header; + final Widget? footer; + final ScrollController? scrollController; + final OnDragStarted? onDragStarted; + final OnReorder onReorder; + final OnDragEnded? onDragEnded; + final BoardDataController boardDataController; + final List children; + final EdgeInsets? padding; + final Widget? background; + final ReorderFlexConfig config; + + const BoardColumnContainer({ + required this.boardDataController, + required this.onReorder, + required this.children, + this.onDragStarted, + this.onDragEnded, + this.header, + this.footer, + this.scrollController, + this.padding, + this.background, + this.config = const ReorderFlexConfig(), + Key? key, + }) : super(key: key); + + @override + State createState() => _BoardColumnContainerState(); +} + +class _BoardColumnContainerState extends State { + final GlobalKey _columnContainerOverlayKey = + GlobalKey(debugLabel: '$BoardColumnContainer overlay key'); + late BoardOverlayEntry _overlayEntry; + + @override + void initState() { + _overlayEntry = BoardOverlayEntry( + builder: (BuildContext context) { + Widget reorderFlex = ReorderFlex( + key: widget.key, + header: widget.header, + footer: widget.footer, + scrollController: widget.scrollController, + config: widget.config, + onDragStarted: (index) {}, + onReorder: ((fromIndex, toIndex) {}), + onDragEnded: () {}, + dataSource: widget.boardDataController, + direction: Axis.horizontal, + children: widget.children, + ); + + if (widget.padding != null) { + reorderFlex = Padding( + padding: widget.padding!, + child: reorderFlex, + ); + } + + return Expanded( + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + Container( + color: Colors.red, + ), + reorderFlex + ], + )); + }, + opaque: false); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BoardOverlay( + key: _columnContainerOverlayKey, + initialEntries: [_overlayEntry], + ); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart new file mode 100644 index 0000000000..e75d1f74cd --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart @@ -0,0 +1,171 @@ +import 'package:flutter/material.dart'; + +import '../../utils/log.dart'; +import 'drag_target.dart'; +import 'reorder_flex.dart'; + +/// [FlexDragTargetData] is used to store the custom dragging data. It can be used to +/// locate the index of the dragging widget in the [BoardList]. +class FlexDragTargetData extends DragTargetData { + /// The index of the dragging target in the boardList. + @override + final int draggingIndex; + + final DraggingState state; + + Widget? get draggingWidget => state.draggingWidget; + + Size? get draggingFeedbackSize => state.feedbackSize; + + /// Indicate the dargging come from which [ReorderFlex]. + final DraggingReorderFlex draggingReorderFlex; + + ReoderFlextItem get columnItem => + draggingReorderFlex.itemAtIndex(draggingIndex); + + String get reorderFlexId => draggingReorderFlex.id; + + FlexDragTargetData({ + required this.draggingIndex, + required this.state, + required this.draggingReorderFlex, + }); +} + +abstract class DraggingReorderFlex { + String get id; + ReoderFlextItem itemAtIndex(int index); +} + +class DraggingState { + final String id; + + /// The member of widget.children currently being dragged. + /// + /// Null if no drag is underway. + Widget? _draggingWidget; + + Widget? get draggingWidget => _draggingWidget; + + /// The last computed size of the feedback widget being dragged. + Size? _draggingFeedbackSize = Size.zero; + + Size? get feedbackSize => _draggingFeedbackSize; + + /// The location that the dragging widget occupied before it started to drag. + int dragStartIndex = -1; + + /// The index that the dragging widget most recently left. + /// This is used to show an animation of the widget's position. + int phantomIndex = -1; + + /// The index that the dragging widget currently occupies. + int currentIndex = -1; + + /// The widget to move the dragging widget too after the current index. + int nextIndex = 0; + + /// Whether or not we are currently scrolling this view to show a widget. + bool scrolling = false; + + /// The additional margin to place around a computed drop area. + static const double _dropAreaMargin = 0.0; + + DraggingState(this.id); + + Size get dropAreaSize { + if (_draggingFeedbackSize == null) { + return Size.zero; + } + return _draggingFeedbackSize! + + const Offset(_dropAreaMargin, _dropAreaMargin); + } + + void startDragging(Widget draggingWidget, int draggingWidgetIndex, + Size? draggingWidgetSize) { + /// + assert(draggingWidgetIndex >= 0); + + _draggingWidget = draggingWidget; + phantomIndex = draggingWidgetIndex; + dragStartIndex = draggingWidgetIndex; + currentIndex = draggingWidgetIndex; + _draggingFeedbackSize = draggingWidgetSize; + } + + void endDragging() { + dragStartIndex = -1; + phantomIndex = -1; + currentIndex = -1; + _draggingWidget = null; + } + + /// When the phantomIndex and currentIndex are the same, it means the dragging + /// widget did move to the destination location. + void removePhantom() { + phantomIndex = currentIndex; + } + + /// The dragging widget overlaps with the phantom widget. + bool isOverlapWithPhantom() { + return currentIndex != phantomIndex; + } + + bool isPhantomAboveDragTarget() { + return currentIndex > phantomIndex; + } + + bool isPhantomBelowDragTarget() { + return currentIndex < phantomIndex; + } + + bool didDragTargetMoveToNext() { + return currentIndex == nextIndex; + } + + /// Set the currentIndex to nextIndex + void moveDragTargetToNext() { + Log.trace('moveDragTargetToNext: $nextIndex'); + currentIndex = nextIndex; + } + + void updateNextIndex(int index) { + assert(index >= 0); + Log.trace('updateNextIndex: $index'); + nextIndex = index; + } + + bool isNotDragging() { + return dragStartIndex == -1; + } + + bool isDragging() { + return !isNotDragging(); + } + + /// When the _dragStartIndex less than the _currentIndex, it means the + /// dragTarget is going down to the end of the list. + bool isDragTargetMovingDown() { + return dragStartIndex < currentIndex; + } + + /// The index represents the widget original index of the list. + int calculateShiftedIndex(int index) { + int shiftedIndex = index; + if (index == dragStartIndex) { + shiftedIndex = phantomIndex; + } else if (index > dragStartIndex && index <= phantomIndex) { + /// phantom move up + shiftedIndex--; + } else if (index < dragStartIndex && index >= phantomIndex) { + /// phantom move down + shiftedIndex++; + } + return shiftedIndex; + } + + @override + String toString() { + return 'DragStartIndex: $dragStartIndex, PhantomIndex: $phantomIndex, CurrentIndex: $currentIndex, NextIndex: $nextIndex'; + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart new file mode 100644 index 0000000000..6bd6015111 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -0,0 +1,369 @@ +import 'package:flutter/material.dart'; + +abstract class DragTargetData { + int get draggingIndex; +} + +abstract class ReorderDraggableTargetBuilder { + Widget? build( + BuildContext context, + Widget child, + DragTargetOnStarted onDragStarted, + DragTargetOnEnded onDragEnded, + DragTargetWillAccpet onWillAccept); +} + +typedef DragTargetWillAccpet = bool Function( + T dragTargetData); +typedef DragTargetOnStarted = void Function(Widget, int, Size?); +typedef DragTargetOnEnded = void Function( + T dragTargetData); + +/// [ReorderDragTarget] is a [DragTarget] that carries the index information of +/// the child. +/// +/// The size of the [ReorderDragTarget] will become zero when it start dragging. +/// +class ReorderDragTarget extends StatefulWidget { + final Widget child; + final T dragTargetData; + + final GlobalObjectKey _indexGlobalKey; + + /// Called when dragTarget is being dragging. + final DragTargetOnStarted onDragStarted; + + final DragTargetOnEnded onDragEnded; + + /// Called to determine whether this widget is interested in receiving a given + /// piece of data being dragged over this drag target. + /// + /// [toAccept] represents the dragTarget index, which is the value passed in + /// when creating the [ReorderDragTarget]. + final DragTargetWillAccpet onWillAccept; + + /// Called when an acceptable piece of data was dropped over this drag target. + /// + /// Equivalent to [onAcceptWithDetails], but only includes the data. + final void Function(T dragTargetData)? onAccept; + + /// Called when a given piece of data being dragged over this target leaves + /// the target. + final void Function(T dragTargetData)? onLeave; + + final ReorderDraggableTargetBuilder? draggableTargetBuilder; + + ReorderDragTarget({ + Key? key, + required this.child, + required this.dragTargetData, + required this.onDragStarted, + required this.onDragEnded, + required this.onWillAccept, + this.onAccept, + this.onLeave, + this.draggableTargetBuilder, + }) : _indexGlobalKey = GlobalObjectKey(child.key!), + super(key: key); + + @override + State> createState() => _ReorderDragTargetState(); +} + +class _ReorderDragTargetState + extends State> { + /// Return the dragTarget's size + Size? _draggingFeedbackSize = Size.zero; + + @override + Widget build(BuildContext context) { + Widget dragTarget = DragTarget( + builder: _buildDraggableWidget, + onWillAccept: (dragTargetData) { + assert(dragTargetData != null); + if (dragTargetData == null) return false; + return widget.onWillAccept(dragTargetData); + }, + onAccept: widget.onAccept, + onLeave: (dragTargetData) { + assert(dragTargetData != null); + if (dragTargetData != null) { + widget.onLeave?.call(dragTargetData); + } + }, + ); + + dragTarget = KeyedSubtree(key: widget._indexGlobalKey, child: dragTarget); + return dragTarget; + } + + Widget _buildDraggableWidget( + BuildContext context, + List acceptedCandidates, + List rejectedCandidates, + ) { + Widget feedbackBuilder = Builder(builder: (BuildContext context) { + BoxConstraints contentSizeConstraints = + BoxConstraints.loose(_draggingFeedbackSize!); + return _buildDraggableFeedback( + context, + contentSizeConstraints, + widget.child, + ); + }); + + final draggableWidget = widget.draggableTargetBuilder?.build( + context, + widget.child, + widget.onDragStarted, + widget.onDragEnded, + widget.onWillAccept, + ) ?? + LongPressDraggable( + maxSimultaneousDrags: 1, + data: widget.dragTargetData, + ignoringFeedbackSemantics: false, + feedback: feedbackBuilder, + childWhenDragging: IgnorePointerWidget(child: widget.child), + onDragStarted: () { + _draggingFeedbackSize = widget._indexGlobalKey.currentContext?.size; + widget.onDragStarted( + widget.child, + widget.dragTargetData.draggingIndex, + _draggingFeedbackSize, + ); + }, + dragAnchorStrategy: childDragAnchorStrategy, + + /// When the drag ends inside a DragTarget widget, the drag + /// succeeds, and we reorder the widget into position appropriately. + onDragCompleted: () { + widget.onDragEnded(widget.dragTargetData); + }, + + /// When the drag does not end inside a DragTarget widget, the + /// drag fails, but we still reorder the widget to the last position it + /// had been dragged to. + onDraggableCanceled: (Velocity velocity, Offset offset) => + widget.onDragEnded(widget.dragTargetData), + child: widget.child, + ); + + return draggableWidget; + } + + Widget _buildDraggableFeedback( + BuildContext context, BoxConstraints constraints, Widget child) { + return Transform( + transform: Matrix4.rotationZ(0), + alignment: FractionalOffset.topLeft, + child: Material( + elevation: 3.0, + color: Colors.transparent, + borderRadius: BorderRadius.zero, + child: ConstrainedBox(constraints: constraints, child: child), + ), + ); + } +} + +class DragAnimationController { + // How long an animation to reorder an element in the list takes. + final Duration reorderAnimationDuration; + + // How long an animation to scroll to an off-screen element in the + // list takes. + final Duration scrollAnimationDuration; + + // This controls the entrance of the dragging widget into a new place. + late AnimationController entranceController; + + // This controls the 'phantom' of the dragging widget, which is left behind + // where the widget used to be. + late AnimationController phantomController; + + DragAnimationController({ + required this.reorderAnimationDuration, + required this.scrollAnimationDuration, + required TickerProvider vsync, + required void Function(AnimationStatus) entranceAnimateStatusChanged, + }) { + entranceController = AnimationController( + value: 1.0, vsync: vsync, duration: reorderAnimationDuration); + phantomController = AnimationController( + value: 0, vsync: vsync, duration: reorderAnimationDuration); + entranceController.addStatusListener(entranceAnimateStatusChanged); + } + + bool get isEntranceAnimationCompleted => entranceController.isCompleted; + + void startDargging() { + entranceController.value = 1.0; + } + + void animateToNext() { + phantomController.reverse(from: 1.0); + entranceController.forward(from: 0.0); + } + + void reverseAnimation() { + phantomController.reverse(from: 0.1); + entranceController.reverse(from: 0.0); + } + + void dispose() { + entranceController.dispose(); + phantomController.dispose(); + } +} + +class IgnorePointerWidget extends StatelessWidget { + final Widget? child; + final bool useIntrinsicSize; + const IgnorePointerWidget({ + required this.child, + this.useIntrinsicSize = false, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final sizedChild = useIntrinsicSize + ? child + : SizedBox(width: 0.0, height: 0.0, child: child); + return IgnorePointer( + ignoring: true, + child: Opacity( + opacity: 0, + child: sizedChild, + ), + ); + } +} + +class PhantomWidget extends StatelessWidget { + final Widget? child; + final double opacity; + const PhantomWidget({ + this.child, + this.opacity = 1.0, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return Opacity( + opacity: opacity, + child: child, + ); + } +} + +class PhantomAnimateContorller { + // How long an animation to reorder an element in the list takes. + final Duration reorderAnimationDuration; + late AnimationController appearController; + late AnimationController disappearController; + + PhantomAnimateContorller({ + required TickerProvider vsync, + required this.reorderAnimationDuration, + required void Function(AnimationStatus) appearAnimateStatusChanged, + }) { + appearController = AnimationController( + value: 1.0, vsync: vsync, duration: reorderAnimationDuration); + disappearController = AnimationController( + value: 0, vsync: vsync, duration: reorderAnimationDuration); + appearController.addStatusListener(appearAnimateStatusChanged); + } + + bool get isAppearAnimationCompleted => appearController.isCompleted; + + void animateToNext() { + disappearController.reverse(from: 1.0); + appearController.forward(from: 0.0); + } + + void performReorderAnimation() { + disappearController.reverse(from: 0.1); + appearController.reverse(from: 0.0); + } + + void dispose() { + appearController.dispose(); + disappearController.dispose(); + } +} + +abstract class FakeDragTargetEventTrigger { + void fakeOnDragStarted(VoidCallback callback); + void fakeOnDragEnded(VoidCallback callback); +} + +abstract class FakeDragTargetEventData { + Size? get feedbackSize; + int get index; + DragTargetData get dragTargetData; +} + +class FakeDragTarget extends StatefulWidget { + final FakeDragTargetEventTrigger eventTrigger; + final FakeDragTargetEventData eventData; + final DragTargetOnStarted onDragStarted; + final DragTargetOnEnded onDragEnded; + final DragTargetWillAccpet onWillAccept; + final Widget child; + const FakeDragTarget({ + Key? key, + required this.eventTrigger, + required this.eventData, + required this.onDragStarted, + required this.onDragEnded, + required this.onWillAccept, + required this.child, + }) : super(key: key); + + @override + State> createState() => _FakeDragTargetState(); +} + +class _FakeDragTargetState + extends State> { + bool isDragging = false; + + @override + void initState() { + widget.eventTrigger.fakeOnDragStarted(() { + if (mounted) { + setState(() { + widget.onWillAccept(widget.eventData.dragTargetData as T); + + widget.onDragStarted( + widget.child, + widget.eventData.index, + widget.eventData.feedbackSize, + ); + + isDragging = true; + }); + } + }); + + widget.eventTrigger.fakeOnDragEnded(() { + if (mounted) { + widget.onDragEnded(widget.eventData.dragTargetData as T); + } + }); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + if (isDragging) { + return IgnorePointerWidget(child: widget.child); + } else { + return IgnorePointerWidget(useIntrinsicSize: true, child: widget.child); + } + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart new file mode 100644 index 0000000000..5de75526d1 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -0,0 +1,524 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; + +import '../../utils/log.dart'; +import 'reorder_mixin.dart'; +import 'drag_target.dart'; +import 'drag_state.dart'; +import 'reorder_flex_ext.dart'; + +typedef OnDragStarted = void Function(int index); +typedef OnDragEnded = void Function(); +typedef OnReorder = void Function(int fromIndex, int toIndex); +typedef OnDeleted = void Function(int deletedIndex); +typedef OnInserted = void Function(int insertedIndex); +typedef OnReveivePassedInPhantom = void Function( + FlexDragTargetData dragTargetData, int phantomIndex); + +abstract class ReoderFlextDataSource { + String get identifier; + List get items; +} + +abstract class ReoderFlextItem {} + +class ReorderFlexConfig { + final bool needsLongPressDraggable = true; + final double draggingWidgetOpacity = 0.2; + final Duration reorderAnimationDuration = const Duration(milliseconds: 250); + final Duration scrollAnimationDuration = const Duration(milliseconds: 250); + const ReorderFlexConfig(); +} + +class ReorderFlex extends StatefulWidget with DraggingReorderFlex { + final Widget? header; + final Widget? footer; + final ReorderFlexConfig config; + + final List children; + final EdgeInsets? padding; + final Axis direction; + final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.spaceEvenly; + final ScrollController? scrollController; + + final OnDragStarted? onDragStarted; + final OnReorder onReorder; + final OnDragEnded? onDragEnded; + + final ReoderFlextDataSource dataSource; + + final ReorderFlextDragTargetExtension? dragTargetExtension; + + const ReorderFlex({ + Key? key, + this.header, + this.footer, + this.scrollController, + required this.dataSource, + required this.children, + required this.config, + required this.onReorder, + this.onDragStarted, + this.onDragEnded, + this.dragTargetExtension, + // ignore: unused_element + this.padding, + this.direction = Axis.vertical, + }) : super(key: key); + + @override + State createState() => ReorderFlexState(); + + @override + String get id => dataSource.identifier; + + @override + ReoderFlextItem itemAtIndex(int index) { + return dataSource.items[index]; + } +} + +class ReorderFlexState extends State + with ReorderFlexMinxi, TickerProviderStateMixin { + /// Controls scrolls and measures scroll progress. + late ScrollController _scrollController; + ScrollPosition? _attachedScrollPosition; + + /// Whether or not we are currently scrolling this view to show a widget. + bool _scrolling = false; + + late DraggingState dragState; + late DragAnimationController _dragAnimationController; + + @override + void initState() { + dragState = DraggingState(widget.id); + + _dragAnimationController = DragAnimationController( + reorderAnimationDuration: widget.config.reorderAnimationDuration, + scrollAnimationDuration: widget.config.scrollAnimationDuration, + entranceAnimateStatusChanged: (status) { + if (status == AnimationStatus.completed) { + setState(() => _requestAnimationToNextIndex()); + } + }, + vsync: this, + ); + + super.initState(); + } + + @override + void didChangeDependencies() { + if (_attachedScrollPosition != null) { + _scrollController.detach(_attachedScrollPosition!); + _attachedScrollPosition = null; + } + + _scrollController = widget.scrollController ?? + PrimaryScrollController.of(context) ?? + ScrollController(); + + if (_scrollController.hasClients) { + _attachedScrollPosition = Scrollable.of(context)?.position; + } else { + _attachedScrollPosition = null; + } + + if (_attachedScrollPosition != null) { + _scrollController.attach(_attachedScrollPosition!); + } + super.didChangeDependencies(); + } + + @override + Widget build(BuildContext context) { + final List children = []; + if (widget.header != null) { + children.add(widget.header!); + } + + for (int i = 0; i < widget.children.length; i += 1) { + Widget child = widget.children[i]; + final wrapChild = _wrap(child, i); + children.add(wrapChild); + } + + if (widget.footer != null) { + children.add(widget.footer!); + } + + return _wrapScrollView( + child: _wrapContainer(children), + ); + } + + @override + void dispose() { + if (_attachedScrollPosition != null) { + _scrollController.detach(_attachedScrollPosition!); + _attachedScrollPosition = null; + } + + _dragAnimationController.dispose(); + super.dispose(); + } + + void _requestAnimationToNextIndex({bool isAcceptingNewTarget = false}) { + /// Update the dragState and animate to the next index if the current + /// dragging animation is completed. Otherwise, it will get called again + /// when the animation finishs. + + if (_dragAnimationController.isEntranceAnimationCompleted) { + dragState.removePhantom(); + + if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) { + return; + } + + dragState.moveDragTargetToNext(); + _dragAnimationController.animateToNext(); + } + } + + /// [child]: the child will be wrapped with dartTarget + /// [childIndex]: the index of the child in a list + Widget _wrap(Widget child, int childIndex) { + return Builder(builder: (context) { + final dragTarget = _buildDragTarget(context, child, childIndex); + int shiftedIndex = childIndex; + + if (dragState.isOverlapWithPhantom()) { + shiftedIndex = dragState.calculateShiftedIndex(childIndex); + } + + Log.trace( + 'Rebuild: Column${dragState.id} ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); + final currentIndex = dragState.currentIndex; + final dragPhantomIndex = dragState.phantomIndex; + + if (shiftedIndex == currentIndex || childIndex == dragPhantomIndex) { + Widget dragSpace; + if (dragState.draggingWidget != null) { + if (dragState.draggingWidget is PhantomWidget) { + dragSpace = dragState.draggingWidget!; + } else { + dragSpace = PhantomWidget( + opacity: widget.config.draggingWidgetOpacity, + child: dragState.draggingWidget, + ); + } + } else { + dragSpace = SizedBox.fromSize(size: dragState.dropAreaSize); + } + + /// Return the dragTarget it is not start dragging. The size of the + /// dragTarget is the same as the the passed in child. + /// + if (dragState.isNotDragging()) { + return _buildDraggingContainer(children: [dragTarget]); + } + + /// Determine the size of the drop area to show under the dragging widget. + final feedbackSize = dragState.feedbackSize; + Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize); + Widget disappearSpace = _makeDisappearSpace(dragSpace, feedbackSize); + + /// When start dragging, the dragTarget, [BoardDragTarget], will + /// return a [IgnorePointerWidget] which size is zero. + if (dragState.isPhantomAboveDragTarget()) { + //the phantom is moving down, i.e. the tile below the phantom is moving up + Log.trace('index:$childIndex item moving up / phantom moving down'); + if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { + return _buildDraggingContainer(children: [ + disappearSpace, + dragTarget, + appearSpace, + ]); + } else if (shiftedIndex == currentIndex) { + return _buildDraggingContainer(children: [ + dragTarget, + appearSpace, + ]); + } else if (childIndex == dragPhantomIndex) { + return _buildDraggingContainer( + children: shiftedIndex <= childIndex + ? [dragTarget, disappearSpace] + : [disappearSpace, dragTarget]); + } + } + + /// + if (dragState.isPhantomBelowDragTarget()) { + //the phantom is moving up, i.e. the tile above the phantom is moving down + Log.trace('index:$childIndex item moving down / phantom moving up'); + if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { + return _buildDraggingContainer(children: [ + appearSpace, + dragTarget, + disappearSpace, + ]); + } else if (shiftedIndex == currentIndex) { + return _buildDraggingContainer(children: [ + appearSpace, + dragTarget, + ]); + } else if (childIndex == dragPhantomIndex) { + return _buildDraggingContainer( + children: shiftedIndex >= childIndex + ? [disappearSpace, dragTarget] + : [dragTarget, disappearSpace]); + } + } + + assert(!dragState.isOverlapWithPhantom()); + + List children = []; + if (dragState.isDragTargetMovingDown()) { + children.addAll([dragTarget, appearSpace]); + } else { + children.addAll([appearSpace, dragTarget]); + } + return _buildDraggingContainer(children: children); + } + + /// We still wrap dragTarget with a container so that widget's depths are + /// the same and it prevent's layout alignment issue + return _buildDraggingContainer(children: [dragTarget]); + }); + } + + ReorderDragTarget _buildDragTarget( + BuildContext builderContext, Widget child, int childIndex) { + return ReorderDragTarget( + dragTargetData: FlexDragTargetData( + draggingIndex: childIndex, + state: dragState, + draggingReorderFlex: widget, + ), + onDragStarted: (draggingWidget, draggingIndex, size) { + Log.debug("Column${widget.dataSource.identifier} start dragging"); + _startDragging(draggingWidget, draggingIndex, size); + widget.onDragStarted?.call(draggingIndex); + }, + onDragEnded: (dragTargetData) { + Log.debug("Column${widget.dataSource.identifier} end dragging"); + + setState(() { + _onReordered( + dragState.dragStartIndex, + dragState.currentIndex, + ); + dragState.endDragging(); + widget.onDragEnded?.call(); + }); + }, + onWillAccept: (FlexDragTargetData dragTargetData) { + Log.debug( + '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); + assert(widget.dataSource.items.length > childIndex); + + if (_requestDragExtensionToHanlder( + dragTargetData, + (extension) { + extension.onWillAccept( + this, + builderContext, + dragTargetData, + dragState.isDragging(), + dragTargetData.draggingIndex, + childIndex, + ); + }, + )) { + return true; + } else { + final dragIndex = dragTargetData.draggingIndex; + return onWillAccept(builderContext, dragIndex, childIndex); + } + }, + onAccept: (dragTargetData) { + _requestDragExtensionToHanlder( + dragTargetData, + (extension) => extension.onAccept(dragTargetData), + ); + }, + onLeave: (dragTargetData) { + _requestDragExtensionToHanlder( + dragTargetData, + (extension) => extension.onLeave(dragTargetData), + ); + }, + draggableTargetBuilder: + widget.dragTargetExtension?.draggableTargetBuilder, + child: child, + ); + } + + bool _requestDragExtensionToHanlder( + FlexDragTargetData dragTargetData, + void Function(ReorderFlextDragTargetExtension) callback, + ) { + final extension = widget.dragTargetExtension; + if (extension != null && extension.canHandler(dragTargetData)) { + callback(extension); + return true; + } else { + return false; + } + } + + Widget _makeAppearSpace(Widget child, Size? feedbackSize) { + return makeAppearingWidget( + child, + _dragAnimationController.entranceController, + feedbackSize, + widget.direction, + ); + } + + Widget _makeDisappearSpace(Widget child, Size? feedbackSize) { + return makeDisappearingWidget( + child, + _dragAnimationController.phantomController, + feedbackSize, + widget.direction, + ); + } + + void _startDragging( + Widget draggingWidget, + int dragIndex, + Size? feedbackSize, + ) { + setState(() { + dragState.startDragging(draggingWidget, dragIndex, feedbackSize); + _dragAnimationController.startDargging(); + }); + } + + bool onWillAccept(BuildContext context, int? dragIndex, int childIndex) { + /// The [willAccept] will be true if the dargTarget is the widget that gets + /// dragged and it is dragged on top of the other dragTargets. + bool willAccept = + dragState.dragStartIndex == dragIndex && dragIndex != childIndex; + setState(() { + if (willAccept) { + int shiftedIndex = dragState.calculateShiftedIndex(childIndex); + dragState.updateNextIndex(shiftedIndex); + } else { + dragState.updateNextIndex(childIndex); + } + + _requestAnimationToNextIndex(isAcceptingNewTarget: true); + }); + + _scrollTo(context); + + /// If the target is not the original starting point, then we will accept the drop. + return willAccept; + } + + void _onReordered(int fromIndex, int toIndex) { + if (fromIndex != toIndex) { + widget.onReorder.call(fromIndex, toIndex); + } + + _dragAnimationController.reverseAnimation(); + } + + Widget _wrapScrollView({required Widget child}) { + if (widget.scrollController != null && + PrimaryScrollController.of(context) == null) { + return child; + } else { + return SingleChildScrollView( + scrollDirection: widget.direction, + padding: widget.padding, + controller: _scrollController, + child: child, + ); + } + } + + Widget _wrapContainer(List children) { + switch (widget.direction) { + case Axis.horizontal: + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: widget.mainAxisAlignment, + children: children, + ); + case Axis.vertical: + default: + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: widget.mainAxisAlignment, + children: children, + ); + } + } + + Widget _buildDraggingContainer({required List children}) { + switch (widget.direction) { + case Axis.horizontal: + return Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: children, + ); + case Axis.vertical: + default: + return Column( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: children, + ); + } + } + +// Scrolls to a target context if that context is not on the screen. + void _scrollTo(BuildContext context) { + if (_scrolling) return; + final RenderObject contextObject = context.findRenderObject()!; + final RenderAbstractViewport viewport = + RenderAbstractViewport.of(contextObject)!; + // If and only if the current scroll offset falls in-between the offsets + // necessary to reveal the selected context at the top or bottom of the + // screen, then it is already on-screen. + final double margin = widget.direction == Axis.horizontal + ? dragState.dropAreaSize.width + : dragState.dropAreaSize.height; + if (_scrollController.hasClients) { + final double scrollOffset = _scrollController.offset; + final double topOffset = max( + _scrollController.position.minScrollExtent, + viewport.getOffsetToReveal(contextObject, 0.0).offset - margin, + ); + final double bottomOffset = min( + _scrollController.position.maxScrollExtent, + viewport.getOffsetToReveal(contextObject, 1.0).offset + margin, + ); + final bool onScreen = + scrollOffset <= topOffset && scrollOffset >= bottomOffset; + + // If the context is off screen, then we request a scroll to make it visible. + if (!onScreen) { + _scrolling = true; + _scrollController.position + .animateTo( + scrollOffset < bottomOffset ? bottomOffset : topOffset, + duration: _dragAnimationController.scrollAnimationDuration, + curve: Curves.easeInOut, + ) + .then((void value) { + setState(() { + _scrolling = false; + }); + }); + } + } + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart new file mode 100644 index 0000000000..00ecf6fd1b --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; + +import '../../utils/log.dart'; +import 'drag_state.dart'; +import 'drag_target.dart'; +import 'reorder_flex.dart'; + +abstract class DragTargetExtensionDelegate { + bool acceptNewDragTargetData( + String columnId, + FlexDragTargetData dragTargetData, + int index, + ); + void updateDragTargetData( + String columnId, + FlexDragTargetData dragTargetData, + int index, + ); +} + +class ReorderFlextDragTargetExtension { + final String reorderFlexId; + final List acceptReorderFlexIds; + final DragTargetExtensionDelegate delegate; + final ReorderDraggableTargetBuilder? draggableTargetBuilder; + + ReorderFlextDragTargetExtension({ + required this.reorderFlexId, + required this.delegate, + required this.acceptReorderFlexIds, + this.draggableTargetBuilder, + }); + + bool canHandler(FlexDragTargetData dragTargetData) { + /// If the columnId equal to the dragTargetData's columnId, + /// it means the dragTarget is dragging on the top of its own list. + /// Otherwise, it means the dargTarget was moved to another list. + /// + if (!acceptReorderFlexIds.contains(dragTargetData.reorderFlexId)) { + return false; + } + + return reorderFlexId != dragTargetData.reorderFlexId; + } + + bool onWillAccept( + ReorderFlexState reorderFlexState, + BuildContext context, + FlexDragTargetData dragTargetData, + bool isDragging, + int dragIndex, + int itemIndex, + ) { + final isNewDragTarget = delegate.acceptNewDragTargetData( + reorderFlexId, dragTargetData, itemIndex); + + if (isNewDragTarget == false) { + delegate.updateDragTargetData(reorderFlexId, dragTargetData, itemIndex); + reorderFlexState.onWillAccept(context, dragIndex, itemIndex); + } else { + Log.debug( + '[$ReorderFlextDragTargetExtension] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' + 'to Column$reorderFlexId:$itemIndex'); + } + + return true; + } + + void onAccept(FlexDragTargetData dragTargetData) { + Log.trace( + '[$ReorderFlextDragTargetExtension] Column$reorderFlexId on onAccept'); + } + + void onLeave(FlexDragTargetData dragTargetData) { + Log.trace( + '[$ReorderFlextDragTargetExtension] Column$reorderFlexId on leave'); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart new file mode 100644 index 0000000000..76b7d834a4 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart @@ -0,0 +1,65 @@ +import 'package:flutter/widgets.dart'; + +import '../transitions.dart'; + +mixin ReorderFlexMinxi { + @protected + Widget makeAppearingWidget( + Widget child, + AnimationController entranceController, + Size? draggingFeedbackSize, + Axis direction, + ) { + if (null == draggingFeedbackSize) { + return SizeTransitionWithIntrinsicSize( + sizeFactor: entranceController, + axis: direction, + child: FadeTransition( + opacity: entranceController, + child: child, + ), + ); + } else { + var transition = SizeTransition( + sizeFactor: entranceController, + axis: direction, + child: FadeTransition(opacity: entranceController, child: child), + ); + + BoxConstraints contentSizeConstraints = + BoxConstraints.loose(draggingFeedbackSize); + return ConstrainedBox( + constraints: contentSizeConstraints, child: transition); + } + } + + @protected + Widget makeDisappearingWidget( + Widget child, + AnimationController phantomController, + Size? draggingFeedbackSize, + Axis direction, + ) { + if (null == draggingFeedbackSize) { + return SizeTransitionWithIntrinsicSize( + sizeFactor: phantomController, + axis: direction, + child: FadeTransition( + opacity: phantomController, + child: child, + ), + ); + } else { + var transition = SizeTransition( + sizeFactor: phantomController, + axis: direction, + child: FadeTransition(opacity: phantomController, child: child), + ); + + BoxConstraints contentSizeConstraints = + BoxConstraints.loose(draggingFeedbackSize); + return ConstrainedBox( + constraints: contentSizeConstraints, child: transition); + } + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart new file mode 100644 index 0000000000..fa11597428 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -0,0 +1,338 @@ +import 'package:flutter/material.dart'; +import '../../../flowy_board.dart'; +import '../../utils/log.dart'; +import '../flex/drag_state.dart'; +import '../flex/drag_target.dart'; +import '../flex/reorder_flex_ext.dart'; +import 'phantom_state.dart'; + +abstract class BoardPhantomControllerDelegate { + BoardColumnDataController? controller(String columnId); +} + +mixin ColumnDataPhantomMixim { + BoardColumnDataController? get; +} + +class BoardPhantomController extends DragTargetExtensionDelegate { + final BoardPhantomControllerDelegate delegate; + + PhantomRecord? phantomRecord; + + final columnsState = ColumnPassthroughStateController(); + + BoardPhantomController({required this.delegate}); + + bool get hasPhantom => phantomRecord != null; + + bool isFromColumn(String columnId) { + if (phantomRecord != null) { + return phantomRecord!.fromColumnId == columnId; + } else { + return true; + } + } + + void transformIndex(int fromIndex, int toIndex) { + if (phantomRecord == null) { + return; + } + assert(phantomRecord!.fromColumnIndex == fromIndex); + phantomRecord?.updateFromColumnIndex(toIndex); + } + + void columnStartDragging(String columnId) { + columnsState.setColumnIsDragging(columnId, false); + } + + void columnEndDragging(String columnId) { + columnsState.setColumnIsDragging(columnId, true); + if (phantomRecord != null) { + if (phantomRecord!.fromColumnId == columnId) { + columnsState.notifyDidRemovePhantom(phantomRecord!.toColumnId); + } + } + _swapColumnData(); + } + + void _swapColumnData() { + if (phantomRecord == null) { + return; + } + + if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) { + return; + } + + Log.debug("[$BoardPhantomController] move ${phantomRecord.toString()}"); + + final item = delegate + .controller(phantomRecord!.fromColumnId) + ?.removeAt(phantomRecord!.fromColumnIndex); + + assert(item != null); + assert(delegate + .controller(phantomRecord!.toColumnId) + ?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem); + + delegate + .controller(phantomRecord!.toColumnId) + ?.replace(phantomRecord!.toColumnIndex, item!); + + phantomRecord = null; + } + + @override + bool acceptNewDragTargetData( + String columnId, FlexDragTargetData dragTargetData, int index) { + if (phantomRecord == null) { + _updatePhantomRecord(columnId, dragTargetData, index); + _insertPhantom(columnId, dragTargetData, index); + + return true; + } + + final isDifferentDragTarget = phantomRecord!.toColumnId != columnId; + Log.debug( + '[$BoardPhantomController] Set inserted column id: $columnId, different target: $isDifferentDragTarget'); + if (isDifferentDragTarget) { + /// Remove the phantom in the previous column. + _removePhantom(phantomRecord!.toColumnId); + + /// Update the record and insert the phantom to new column. + _updatePhantomRecord(columnId, dragTargetData, index); + _insertPhantom(columnId, dragTargetData, index); + } + + return isDifferentDragTarget; + } + + @override + void updateDragTargetData( + String columnId, FlexDragTargetData dragTargetData, int index) { + phantomRecord?.updateInsertedIndex(index); + + assert(phantomRecord != null); + if (phantomRecord!.toColumnId == columnId) { + /// Update the existing phantom index + _updatePhantom(phantomRecord!.toColumnId, dragTargetData, index); + } + } + + void _updatePhantom( + String toColumnId, + FlexDragTargetData dragTargetData, + int phantomIndex, + ) { + final items = delegate.controller(toColumnId)?.items; + if (items == null) { + return; + } + + final index = items.indexWhere((item) => item.isPhantom); + assert(index != -1); + if (index != -1) { + if (index != phantomIndex) { + Log.debug( + '[$BoardPhantomController] move phantom $toColumnId:$index to $toColumnId:$phantomIndex'); + final item = items.removeAt(index); + items.insert(phantomIndex, item); + } + } + } + + void _removePhantom(String columnId) { + final items = delegate.controller(columnId)?.items; + if (items == null) { + return; + } + + final index = items.indexWhere((item) => item.isPhantom); + assert(index != -1); + if (index != -1) { + items.removeAt(index); + Log.debug( + '[$BoardPhantomController] Column$columnId remove phantom, current count: ${items.length}'); + columnsState.notifyDidRemovePhantom(columnId); + columnsState.removeColumnListener(columnId); + } + } + + void _insertPhantom( + String toColumnId, + FlexDragTargetData dragTargetData, + int phantomIndex, + ) { + final items = delegate.controller(toColumnId)?.items; + if (items == null) { + return; + } + + final phantomContext = PassthroughPhantomContext( + index: phantomIndex, + dragTargetData: dragTargetData, + ); + columnsState.addColumnListener(toColumnId, phantomContext); + + Log.debug( + '[$BoardPhantomController] Column$toColumnId insert phantom at $phantomIndex'); + delegate + .controller(toColumnId) + ?.insert(phantomIndex, PhantomColumnItem(phantomContext)); + + WidgetsBinding.instance.addPostFrameCallback((_) { + Future.delayed(const Duration(microseconds: 100), () { + columnsState.notifyDidInsertPhantom(toColumnId); + }); + }); + } + + void _updatePhantomRecord( + String columnId, + FlexDragTargetData dragTargetData, + int index, + ) { + phantomRecord = PhantomRecord( + toColumnId: columnId, + toColumnIndex: index, + item: dragTargetData.columnItem as ColumnItem, + fromColumnId: dragTargetData.reorderFlexId, + fromColumnIndex: dragTargetData.draggingIndex, + ); + } +} + +class PhantomRecord { + final ColumnItem item; + final String fromColumnId; + int fromColumnIndex; + + final String toColumnId; + int toColumnIndex; + + PhantomRecord({ + required this.item, + required this.toColumnId, + required this.toColumnIndex, + required this.fromColumnId, + required this.fromColumnIndex, + }); + + void updateFromColumnIndex(int index) { + if (fromColumnIndex == index) { + return; + } + Log.info( + '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + fromColumnIndex = index; + } + + void updateInsertedIndex(int index) { + if (toColumnIndex == index) { + return; + } + + Log.info( + '[$PhantomRecord] Update Column$toColumnId phantom position to $index'); + toColumnIndex = index; + } + + @override + String toString() { + return '$fromColumnId:$fromColumnIndex to $toColumnId:$toColumnIndex'; + } +} + +class PhantomColumnItem extends ColumnItem { + final PassthroughPhantomContext phantomContext; + + PhantomColumnItem(PassthroughPhantomContext insertedPhantom) + : phantomContext = insertedPhantom; + + @override + bool get isPhantom => true; + + @override + String get id => phantomContext.itemData.id; + + Size? get feedbackSize => phantomContext.feedbackSize; + + Widget get draggingWidget => phantomContext.draggingWidget == null + ? const SizedBox() + : phantomContext.draggingWidget!; +} + +class PassthroughPhantomContext extends FakeDragTargetEventTrigger + with FakeDragTargetEventData, PassthroughPhantomListener { + @override + int index; + + @override + final FlexDragTargetData dragTargetData; + + @override + Size? get feedbackSize => dragTargetData.state.feedbackSize; + + Widget? get draggingWidget => dragTargetData.draggingWidget; + + ColumnItem get itemData => dragTargetData.columnItem as ColumnItem; + + @override + VoidCallback? onInserted; + + @override + VoidCallback? onDragEnded; + + PassthroughPhantomContext({ + required this.index, + required this.dragTargetData, + }); + + @override + void fakeOnDragEnded(VoidCallback callback) { + onDragEnded = callback; + } + + @override + void fakeOnDragStarted(VoidCallback callback) { + onInserted = callback; + } +} + +class PassthroughPhantomWidget extends PhantomWidget { + final PassthroughPhantomContext passthroughPhantomContext; + + PassthroughPhantomWidget({ + required double opacity, + required this.passthroughPhantomContext, + Key? key, + }) : super( + child: passthroughPhantomContext.draggingWidget, + opacity: opacity, + key: key, + ); +} + +class PhantomReorderDraggableBuilder extends ReorderDraggableTargetBuilder { + @override + Widget? build( + BuildContext context, + Widget child, + DragTargetOnStarted onDragStarted, + DragTargetOnEnded onDragEnded, + DragTargetWillAccpet onWillAccept, + ) { + if (child is PassthroughPhantomWidget) { + return FakeDragTarget( + eventTrigger: child.passthroughPhantomContext, + eventData: child.passthroughPhantomContext, + onDragStarted: onDragStarted, + onDragEnded: onDragEnded, + onWillAccept: onWillAccept, + child: child, + ); + } else { + return null; + } + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart new file mode 100644 index 0000000000..b78c567d81 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart @@ -0,0 +1,109 @@ +import 'phantom_controller.dart'; +import 'package:flutter/material.dart'; + +class ColumnPassthroughStateController { + final _states = {}; + + void setColumnIsDragging(String columnId, bool isDragging) { + _stateWithId(columnId).isDragging = isDragging; + } + + bool isDragging(String columnId) { + return _stateWithId(columnId).isDragging; + } + + void addColumnListener(String columnId, PassthroughPhantomListener listener) { + _stateWithId(columnId).notifier.addListener( + onInserted: (c) => listener.onInserted?.call(), + onDeleted: () => listener.onDragEnded?.call(), + ); + } + + void removeColumnListener(String columnId) { + _stateWithId(columnId).notifier.dispose(); + _states.remove(columnId); + } + + void notifyDidInsertPhantom(String columnId) { + _stateWithId(columnId).notifier.insert(); + } + + void notifyDidRemovePhantom(String columnId) { + _stateWithId(columnId).notifier.remove(); + } + + ColumnPassthrougPhantomhState _stateWithId(String columnId) { + var state = _states[columnId]; + if (state == null) { + state = ColumnPassthrougPhantomhState(); + _states[columnId] = state; + } + return state; + } +} + +class ColumnPassthrougPhantomhState { + bool isDragging = false; + final notifier = PassthroughPhantomNotifier(); +} + +abstract class PassthroughPhantomListener { + VoidCallback? get onInserted; + VoidCallback? get onDragEnded; +} + +class PassthroughPhantomNotifier { + final insertNotifier = PhantomInsertNotifier(); + + final removeNotifier = PhantomDeleteNotifier(); + + void insert() { + insertNotifier.insert(); + } + + void remove() { + removeNotifier.remove(); + } + + void addListener({ + void Function(PassthroughPhantomContext? insertedPhantom)? onInserted, + void Function()? onDeleted, + }) { + if (onInserted != null) { + insertNotifier.addListener(() { + onInserted(insertNotifier.insertedPhantom); + }); + } + + if (onDeleted != null) { + removeNotifier.addListener(() { + onDeleted(); + }); + } + } + + void dispose() { + insertNotifier.dispose(); + removeNotifier.dispose(); + } +} + +class PhantomInsertNotifier extends ChangeNotifier { + PassthroughPhantomContext? insertedPhantom; + + void insert() { + notifyListeners(); + } +} + +class PhantomDeleteNotifier extends ChangeNotifier { + // int deletedIndex = -1; + + void remove() { + // if (this.deletedIndex != deletedIndex) { + // this.deletedIndex = deletedIndex; + // notifyListeners(); + // } + notifyListeners(); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/pubspec.yaml b/frontend/app_flowy/packages/flowy_board/pubspec.yaml index 52600bde71..f38763fc04 100644 --- a/frontend/app_flowy/packages/flowy_board/pubspec.yaml +++ b/frontend/app_flowy/packages/flowy_board/pubspec.yaml @@ -4,13 +4,15 @@ version: 0.0.1 homepage: environment: - sdk: ">=2.17.6 <3.0.0" + sdk: ">=2.17.1 <3.0.0" flutter: ">=2.5.0" dependencies: flutter: sdk: flutter plugin_platform_interface: ^2.0.2 + equatable: ^2.0.3 + provider: ^6.0.1 dev_dependencies: flutter_test: diff --git a/frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart b/frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart deleted file mode 100644 index 52e5d5713d..0000000000 --- a/frontend/app_flowy/packages/flowy_board/test/flowy_board_method_channel_test.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:flowy_board/flowy_board_method_channel.dart'; - -void main() { - MethodChannelFlowyBoard platform = MethodChannelFlowyBoard(); - const MethodChannel channel = MethodChannel('flowy_board'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return '42'; - }); - }); - - tearDown(() { - channel.setMockMethodCallHandler(null); - }); - - test('getPlatformVersion', () async { - expect(await platform.getPlatformVersion(), '42'); - }); -} diff --git a/frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart b/frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart deleted file mode 100644 index 1158119a60..0000000000 --- a/frontend/app_flowy/packages/flowy_board/test/flowy_board_test.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_test/flutter_test.dart'; -import 'package:flowy_board/flowy_board.dart'; -import 'package:flowy_board/flowy_board_platform_interface.dart'; -import 'package:flowy_board/flowy_board_method_channel.dart'; -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -class MockFlowyBoardPlatform - with MockPlatformInterfaceMixin - implements FlowyBoardPlatform { - - @override - Future getPlatformVersion() => Future.value('42'); -} - -void main() { - final FlowyBoardPlatform initialPlatform = FlowyBoardPlatform.instance; - - test('$MethodChannelFlowyBoard is the default instance', () { - expect(initialPlatform, isInstanceOf()); - }); - - test('getPlatformVersion', () async { - FlowyBoard flowyBoardPlugin = FlowyBoard(); - MockFlowyBoardPlatform fakePlatform = MockFlowyBoardPlatform(); - FlowyBoardPlatform.instance = fakePlatform; - - expect(await flowyBoardPlugin.getPlatformVersion(), '42'); - }); -} From dbc5de29685b828c2ec3d3e9b1148740f5767bbb Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 4 Aug 2022 14:18:34 +0800 Subject: [PATCH 03/22] chore: update board layout, support footer and header --- .../example/lib/multi_board_list_example.dart | 15 +- .../lib/single_board_list_example.dart | 2 +- .../lib/src/rendering/board_overlay.dart | 3 +- .../flowy_board/lib/src/widgets/board.dart | 59 ++++---- .../widgets/board_column/board_column.dart | 140 ++++++++++-------- .../lib/src/widgets/column_container.dart | 33 ++--- .../lib/src/widgets/flex/drag_target.dart | 7 +- .../widgets/flex/drag_target_inteceptor.dart | 114 ++++++++++++++ .../lib/src/widgets/flex/reorder_flex.dart | 85 +++++------ .../src/widgets/flex/reorder_flex_ext.dart | 78 ---------- .../widgets/phantom/phantom_controller.dart | 6 +- 11 files changed, 296 insertions(+), 246 deletions(-) create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart delete mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart index 3574745ae2..a7a74c8fa3 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart @@ -46,9 +46,22 @@ class _MultiBoardListExampleState extends State { return Board( dataController: boardData, background: Container(color: Colors.red), - builder: (context, item) { + footBuilder: (context, columnData) { + return Container( + color: Colors.purple, + height: 30, + ); + }, + headerBuilder: (context, columnData) { + return Container( + color: Colors.yellow, + height: 30, + ); + }, + cardBuilder: (context, item) { return _RowWidget(item: item as TextItem, key: ObjectKey(item)); }, + columnConstraints: const BoxConstraints.tightFor(width: 240), ); } } diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart index fd61b5b836..e8bfedb344 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart @@ -28,7 +28,7 @@ class _SingleBoardListExampleState extends State { Widget build(BuildContext context) { return Board( dataController: boardData, - builder: (context, item) { + cardBuilder: (context, item) { return _RowWidget(item: item as TextItem, key: ObjectKey(item)); }, ); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart b/frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart index e974bfd838..f2c99e057b 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart @@ -13,8 +13,7 @@ class BoardOverlayEntry { /// Whether this entry occludes the entire overlay. /// /// If an entry claims to be opaque, then, for efficiency, the overlay will - /// skip building entries below that entry unless they have [maintainState] - /// set. + /// skip building entries below that entry. bool get opaque => _opaque; bool _opaque; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index 9305ba760d..5daffdb47b 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -20,25 +20,37 @@ class Board extends StatelessWidget { /// Defaults to 0.0. final double runSpacing; + /// final Widget? background; - final BoardColumnItemWidgetBuilder builder; + /// + final BoardColumnCardBuilder cardBuilder; + + /// + final BoardColumnHeaderBuilder? headerBuilder; + + /// + final BoardColumnFooterBuilder? footBuilder; /// final BoardDataController dataController; + final BoxConstraints columnConstraints; + /// - final BoardPhantomController passthroughPhantomContorller; + final BoardPhantomController phantomController; Board({ required this.dataController, - required this.builder, + required this.cardBuilder, this.spacing = 10.0, this.runSpacing = 0.0, this.background, + this.footBuilder, + this.headerBuilder, + this.columnConstraints = const BoxConstraints(maxWidth: 200), Key? key, - }) : passthroughPhantomContorller = - BoardPhantomController(delegate: dataController), + }) : phantomController = BoardPhantomController(delegate: dataController), super(key: key); @override @@ -51,41 +63,25 @@ class Board extends StatelessWidget { List acceptColumns = dataController.columnControllers.keys.toList(); - dataController.columnControllers.forEach((columnId, dataController) { - Widget child = - buildBoardColumn(columnId, acceptColumns, dataController); - if (children.isEmpty) { - // children.add(SizedBox(width: spacing)); - } - // if (background != null) { - // child = Stack(children: [ - // background!, - // child, - // ]); - // } - // children.add(Expanded(key: ValueKey(columnId), child: child)); + dataController.columnControllers.forEach((columnId, controller) { + Widget child = _buildColumn(columnId, acceptColumns, controller); children.add(child); - // children.add(SizedBox(width: spacing)); }); return BoardColumnContainer( onReorder: (fromIndex, toIndex) {}, boardDataController: dataController, + background: background, + spacing: spacing, children: children, ); - - // return Row( - // crossAxisAlignment: CrossAxisAlignment.start, - // mainAxisAlignment: MainAxisAlignment.spaceEvenly, - // children: children, - // ); }, ), ); } /// - Widget buildBoardColumn( + Widget _buildColumn( String columnId, List acceptColumns, BoardColumnDataController dataController, @@ -95,18 +91,19 @@ class Board extends StatelessWidget { value: dataController, child: Consumer( builder: (context, value, child) { - return SizedBox( - width: 200, + return ConstrainedBox( + constraints: columnConstraints, child: BoardColumnWidget( - header: Container(color: Colors.yellow, height: 30), - builder: builder, + headerBuilder: headerBuilder, + footBuilder: footBuilder, + cardBuilder: cardBuilder, acceptColumns: acceptColumns, dataController: dataController, scrollController: ScrollController(), onReorder: (_, int fromIndex, int toIndex) { dataController.move(fromIndex, toIndex); }, - phantomController: passthroughPhantomContorller, + phantomController: phantomController, ), ); }, diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart index 4d14d2d521..92b4959fe7 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart @@ -4,34 +4,33 @@ import '../../rendering/board_overlay.dart'; import '../../utils/log.dart'; import '../phantom/phantom_controller.dart'; import '../flex/reorder_flex.dart'; -import '../flex/drag_state.dart'; -import '../flex/reorder_flex_ext.dart'; +import '../flex/drag_target_inteceptor.dart'; import 'data_controller.dart'; -typedef OnDragStarted = void Function(int index); -typedef OnDragEnded = void Function(String listId); -typedef OnReorder = void Function(String listId, int fromIndex, int toIndex); -typedef OnDeleted = void Function(String listId, int deletedIndex); -typedef OnInserted = void Function(String listId, int insertedIndex); -typedef OnPassedInPhantom = void Function( - String listId, - FlexDragTargetData dragTargetData, - int phantomIndex, -); +typedef OnColumnDragStarted = void Function(int index); +typedef OnColumnDragEnded = void Function(String listId); +typedef OnColumnReorder = void Function( + String listId, int fromIndex, int toIndex); +typedef OnColumnDeleted = void Function(String listId, int deletedIndex); +typedef OnColumnInserted = void Function(String listId, int insertedIndex); -typedef BoardColumnItemWidgetBuilder = Widget Function( +typedef BoardColumnCardBuilder = Widget Function( BuildContext context, ColumnItem item); +typedef BoardColumnHeaderBuilder = Widget Function( + BuildContext context, BoardColumnData columnData); + +typedef BoardColumnFooterBuilder = Widget Function( + BuildContext context, BoardColumnData columnData); + class BoardColumnWidget extends StatefulWidget { - final Widget? header; - final Widget? footer; final BoardColumnDataController dataController; final ScrollController? scrollController; final ReorderFlexConfig config; - final OnDragStarted? onDragStarted; - final OnReorder onReorder; - final OnDragEnded? onDragEnded; + final OnColumnDragStarted? onDragStarted; + final OnColumnReorder onReorder; + final OnColumnDragEnded? onDragEnded; final BoardPhantomController phantomController; @@ -39,18 +38,25 @@ class BoardColumnWidget extends StatefulWidget { final List acceptColumns; - final BoardColumnItemWidgetBuilder builder; + final BoardColumnCardBuilder cardBuilder; + + final BoardColumnHeaderBuilder? headerBuilder; + + final BoardColumnFooterBuilder? footBuilder; + + final double? spacing; const BoardColumnWidget({ Key? key, - this.header, - this.footer, - required this.builder, + this.headerBuilder, + this.footBuilder, + required this.cardBuilder, required this.onReorder, required this.dataController, required this.phantomController, required this.acceptColumns, this.config = const ReorderFlexConfig(), + this.spacing, this.onDragStarted, this.scrollController, this.onDragEnded, @@ -69,45 +75,59 @@ class _BoardColumnWidgetState extends State { @override void initState() { _overlayEntry = BoardOverlayEntry( - builder: (BuildContext context) { - final children = widget.dataController.items - .map((item) => _buildWidget(context, item)) - .toList(); + builder: (BuildContext context) { + final children = widget.dataController.items + .map((item) => _buildWidget(context, item)) + .toList(); - final dragTargetExtension = ReorderFlextDragTargetExtension( - reorderFlexId: widget.columnId, - delegate: widget.phantomController, - acceptReorderFlexIds: widget.acceptColumns, - draggableTargetBuilder: PhantomReorderDraggableBuilder(), - ); + final header = widget.headerBuilder + ?.call(context, widget.dataController.columnData); - return ReorderFlex( - key: widget.key, - header: widget.header, - footer: widget.footer, - scrollController: widget.scrollController, - config: widget.config, - onDragStarted: (index) { - widget.phantomController.columnStartDragging(widget.columnId); - widget.onDragStarted?.call(index); - }, - onReorder: ((fromIndex, toIndex) { - if (widget.phantomController.isFromColumn(widget.columnId)) { - widget.onReorder(widget.columnId, fromIndex, toIndex); - widget.phantomController.transformIndex(fromIndex, toIndex); - } - }), - onDragEnded: () { - widget.phantomController.columnEndDragging(widget.columnId); - widget.onDragEnded?.call(widget.columnId); - _printItems(widget.dataController); - }, - dataSource: widget.dataController, - dragTargetExtension: dragTargetExtension, - children: children, - ); - }, - opaque: false); + final footer = + widget.footBuilder?.call(context, widget.dataController.columnData); + + final interceptor = CrossReorderFlexDragTargetInterceptor( + reorderFlexId: widget.columnId, + delegate: widget.phantomController, + acceptReorderFlexIds: widget.acceptColumns, + draggableTargetBuilder: PhantomDraggableBuilder(), + ); + + final reorderFlex = ReorderFlex( + key: widget.key, + scrollController: widget.scrollController, + config: widget.config, + onDragStarted: (index) { + widget.phantomController.columnStartDragging(widget.columnId); + widget.onDragStarted?.call(index); + }, + onReorder: ((fromIndex, toIndex) { + if (widget.phantomController.isFromColumn(widget.columnId)) { + widget.onReorder(widget.columnId, fromIndex, toIndex); + widget.phantomController.transformIndex(fromIndex, toIndex); + } + }), + onDragEnded: () { + widget.phantomController.columnEndDragging(widget.columnId); + widget.onDragEnded?.call(widget.columnId); + _printItems(widget.dataController); + }, + dataSource: widget.dataController, + interceptor: interceptor, + spacing: widget.spacing, + children: children, + ); + + return Column( + children: [ + if (header != null) header, + Expanded(child: reorderFlex), + if (footer != null) footer, + ], + ); + }, + opaque: false, + ); super.initState(); } @@ -127,7 +147,7 @@ class _BoardColumnWidgetState extends State { passthroughPhantomContext: item.phantomContext, ); } else { - return widget.builder(context, item); + return widget.cardBuilder(context, item); } } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart index f8057fb1c2..e6b6a50e50 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart @@ -1,12 +1,10 @@ import 'package:flutter/material.dart'; +import '../../flowy_board.dart'; import '../rendering/board_overlay.dart'; import 'flex/reorder_flex.dart'; -import 'board.dart'; class BoardColumnContainer extends StatefulWidget { - final Widget? header; - final Widget? footer; final ScrollController? scrollController; final OnDragStarted? onDragStarted; final OnReorder onReorder; @@ -15,6 +13,7 @@ class BoardColumnContainer extends StatefulWidget { final List children; final EdgeInsets? padding; final Widget? background; + final double spacing; final ReorderFlexConfig config; const BoardColumnContainer({ @@ -23,11 +22,10 @@ class BoardColumnContainer extends StatefulWidget { required this.children, this.onDragStarted, this.onDragEnded, - this.header, - this.footer, this.scrollController, this.padding, this.background, + this.spacing = 0.0, this.config = const ReorderFlexConfig(), Key? key, }) : super(key: key); @@ -47,8 +45,6 @@ class _BoardColumnContainerState extends State { builder: (BuildContext context) { Widget reorderFlex = ReorderFlex( key: widget.key, - header: widget.header, - footer: widget.footer, scrollController: widget.scrollController, config: widget.config, onDragStarted: (index) {}, @@ -56,6 +52,7 @@ class _BoardColumnContainerState extends State { onDragEnded: () {}, dataSource: widget.boardDataController, direction: Axis.horizontal, + spacing: widget.spacing, children: widget.children, ); @@ -65,17 +62,7 @@ class _BoardColumnContainerState extends State { child: reorderFlex, ); } - - return Expanded( - child: Stack( - alignment: AlignmentDirectional.center, - children: [ - Container( - color: Colors.red, - ), - reorderFlex - ], - )); + return _wrapStack(reorderFlex); }, opaque: false); super.initState(); @@ -88,4 +75,14 @@ class _BoardColumnContainerState extends State { initialEntries: [_overlayEntry], ); } + + Widget _wrapStack(Widget child) { + return Stack( + alignment: AlignmentDirectional.topStart, + children: [ + if (widget.background != null) widget.background!, + child, + ], + ); + } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index 6bd6015111..f6dfbb9812 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -4,7 +4,7 @@ abstract class DragTargetData { int get draggingIndex; } -abstract class ReorderDraggableTargetBuilder { +abstract class ReorderFlexDraggableTargetBuilder { Widget? build( BuildContext context, Widget child, @@ -51,7 +51,7 @@ class ReorderDragTarget extends StatefulWidget { /// the target. final void Function(T dragTargetData)? onLeave; - final ReorderDraggableTargetBuilder? draggableTargetBuilder; + final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder; ReorderDragTarget({ Key? key, @@ -158,9 +158,10 @@ class _ReorderDragTargetState transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, child: Material( - elevation: 3.0, + elevation: 6.0, color: Colors.transparent, borderRadius: BorderRadius.zero, + clipBehavior: Clip.hardEdge, child: ConstrainedBox(constraints: constraints, child: child), ), ); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart new file mode 100644 index 0000000000..8b8917bb59 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -0,0 +1,114 @@ +import 'package:flutter/material.dart'; + +import '../../utils/log.dart'; +import 'drag_state.dart'; +import 'drag_target.dart'; +import 'reorder_flex.dart'; + +abstract class ReorderFlexDragTargetInterceptor { + bool canHandler(FlexDragTargetData dragTargetData); + + bool onWillAccept( + BuildContext context, + ReorderFlexState reorderFlexState, + FlexDragTargetData dragTargetData, + int itemIndex, + ); + + void onAccept(FlexDragTargetData dragTargetData); + + void onLeave(FlexDragTargetData dragTargetData); + + ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder; +} + +abstract class CrossReorderFlexDragTargetDelegate { + bool acceptNewDragTargetData( + String columnId, + FlexDragTargetData dragTargetData, + int index, + ); + void updateDragTargetData( + String columnId, + FlexDragTargetData dragTargetData, + int index, + ); +} + +class CrossReorderFlexDragTargetInterceptor + extends ReorderFlexDragTargetInterceptor { + final String reorderFlexId; + final List acceptReorderFlexIds; + final CrossReorderFlexDragTargetDelegate delegate; + @override + final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder; + + CrossReorderFlexDragTargetInterceptor({ + required this.reorderFlexId, + required this.delegate, + required this.acceptReorderFlexIds, + this.draggableTargetBuilder, + }); + + @override + bool canHandler(FlexDragTargetData dragTargetData) { + if (acceptReorderFlexIds.isEmpty) { + return true; + } + + if (acceptReorderFlexIds.contains(dragTargetData.reorderFlexId)) { + /// If the columnId equal to the dragTargetData's columnId, + /// it means the dragTarget is dragging on the top of its own list. + /// Otherwise, it means the dargTarget was moved to another list. + return reorderFlexId != dragTargetData.reorderFlexId; + } else { + return false; + } + } + + @override + bool onWillAccept( + BuildContext context, + ReorderFlexState reorderFlexState, + FlexDragTargetData dragTargetData, + int itemIndex, + ) { + final isNewDragTarget = delegate.acceptNewDragTargetData( + reorderFlexId, + dragTargetData, + itemIndex, + ); + + if (isNewDragTarget == false) { + delegate.updateDragTargetData( + reorderFlexId, + dragTargetData, + itemIndex, + ); + + reorderFlexState.onWillAccept( + context, + dragTargetData.draggingIndex, + itemIndex, + ); + } else { + Log.debug( + '[$CrossReorderFlexDragTargetInterceptor] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' + 'to Column$reorderFlexId:$itemIndex'); + } + + return true; + } + + @override + void onAccept(FlexDragTargetData dragTargetData) { + Log.trace( + '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); + } + + @override + void onLeave(FlexDragTargetData dragTargetData) { + Log.trace( + '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index 5de75526d1..4c10772571 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -2,12 +2,11 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; - import '../../utils/log.dart'; import 'reorder_mixin.dart'; import 'drag_target.dart'; import 'drag_state.dart'; -import 'reorder_flex_ext.dart'; +import 'drag_target_inteceptor.dart'; typedef OnDragStarted = void Function(int index); typedef OnDragEnded = void Function(); @@ -33,14 +32,13 @@ class ReorderFlexConfig { } class ReorderFlex extends StatefulWidget with DraggingReorderFlex { - final Widget? header; - final Widget? footer; final ReorderFlexConfig config; final List children; final EdgeInsets? padding; + final double? spacing; final Axis direction; - final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.spaceEvenly; + final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start; final ScrollController? scrollController; final OnDragStarted? onDragStarted; @@ -49,12 +47,10 @@ class ReorderFlex extends StatefulWidget with DraggingReorderFlex { final ReoderFlextDataSource dataSource; - final ReorderFlextDragTargetExtension? dragTargetExtension; + final ReorderFlexDragTargetInterceptor? interceptor; const ReorderFlex({ Key? key, - this.header, - this.footer, this.scrollController, required this.dataSource, required this.children, @@ -62,9 +58,9 @@ class ReorderFlex extends StatefulWidget with DraggingReorderFlex { required this.onReorder, this.onDragStarted, this.onDragEnded, - this.dragTargetExtension, - // ignore: unused_element + this.interceptor, this.padding, + this.spacing, this.direction = Axis.vertical, }) : super(key: key); @@ -136,23 +132,19 @@ class ReorderFlexState extends State @override Widget build(BuildContext context) { final List children = []; - if (widget.header != null) { - children.add(widget.header!); - } for (int i = 0; i < widget.children.length; i += 1) { Widget child = widget.children[i]; + if (widget.spacing != null) { + children.add(SizedBox(width: widget.spacing!)); + } + final wrapChild = _wrap(child, i); children.add(wrapChild); } - if (widget.footer != null) { - children.add(widget.footer!); - } - - return _wrapScrollView( - child: _wrapContainer(children), - ); + final child = _wrapContainer(children); + return _wrapScrollView(child: child); } @override @@ -316,54 +308,49 @@ class ReorderFlexState extends State }); }, onWillAccept: (FlexDragTargetData dragTargetData) { - Log.debug( - '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); assert(widget.dataSource.items.length > childIndex); - if (_requestDragExtensionToHanlder( + if (_interceptDragTarget( dragTargetData, - (extension) { - extension.onWillAccept( - this, - builderContext, - dragTargetData, - dragState.isDragging(), - dragTargetData.draggingIndex, - childIndex, - ); - }, + (interceptor) => interceptor.onWillAccept( + builderContext, + this, + dragTargetData, + childIndex, + ), )) { return true; } else { + Log.debug( + '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); final dragIndex = dragTargetData.draggingIndex; return onWillAccept(builderContext, dragIndex, childIndex); } }, onAccept: (dragTargetData) { - _requestDragExtensionToHanlder( + _interceptDragTarget( dragTargetData, - (extension) => extension.onAccept(dragTargetData), + (interceptor) => interceptor.onAccept(dragTargetData), ); }, onLeave: (dragTargetData) { - _requestDragExtensionToHanlder( + _interceptDragTarget( dragTargetData, - (extension) => extension.onLeave(dragTargetData), + (interceptor) => interceptor.onLeave(dragTargetData), ); }, - draggableTargetBuilder: - widget.dragTargetExtension?.draggableTargetBuilder, + draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, child: child, ); } - bool _requestDragExtensionToHanlder( + bool _interceptDragTarget( FlexDragTargetData dragTargetData, - void Function(ReorderFlextDragTargetExtension) callback, + void Function(ReorderFlexDragTargetInterceptor) callback, ) { - final extension = widget.dragTargetExtension; - if (extension != null && extension.canHandler(dragTargetData)) { - callback(extension); + final interceptor = widget.interceptor; + if (interceptor != null && interceptor.canHandler(dragTargetData)) { + callback(interceptor); return true; } else { return false; @@ -466,14 +453,16 @@ class ReorderFlexState extends State case Axis.horizontal: return Row( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: widget.mainAxisAlignment, children: children, ); case Axis.vertical: default: return Column( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: widget.mainAxisAlignment, children: children, ); } @@ -514,9 +503,7 @@ class ReorderFlexState extends State curve: Curves.easeInOut, ) .then((void value) { - setState(() { - _scrolling = false; - }); + setState(() => _scrolling = false); }); } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart deleted file mode 100644 index 00ecf6fd1b..0000000000 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex_ext.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../utils/log.dart'; -import 'drag_state.dart'; -import 'drag_target.dart'; -import 'reorder_flex.dart'; - -abstract class DragTargetExtensionDelegate { - bool acceptNewDragTargetData( - String columnId, - FlexDragTargetData dragTargetData, - int index, - ); - void updateDragTargetData( - String columnId, - FlexDragTargetData dragTargetData, - int index, - ); -} - -class ReorderFlextDragTargetExtension { - final String reorderFlexId; - final List acceptReorderFlexIds; - final DragTargetExtensionDelegate delegate; - final ReorderDraggableTargetBuilder? draggableTargetBuilder; - - ReorderFlextDragTargetExtension({ - required this.reorderFlexId, - required this.delegate, - required this.acceptReorderFlexIds, - this.draggableTargetBuilder, - }); - - bool canHandler(FlexDragTargetData dragTargetData) { - /// If the columnId equal to the dragTargetData's columnId, - /// it means the dragTarget is dragging on the top of its own list. - /// Otherwise, it means the dargTarget was moved to another list. - /// - if (!acceptReorderFlexIds.contains(dragTargetData.reorderFlexId)) { - return false; - } - - return reorderFlexId != dragTargetData.reorderFlexId; - } - - bool onWillAccept( - ReorderFlexState reorderFlexState, - BuildContext context, - FlexDragTargetData dragTargetData, - bool isDragging, - int dragIndex, - int itemIndex, - ) { - final isNewDragTarget = delegate.acceptNewDragTargetData( - reorderFlexId, dragTargetData, itemIndex); - - if (isNewDragTarget == false) { - delegate.updateDragTargetData(reorderFlexId, dragTargetData, itemIndex); - reorderFlexState.onWillAccept(context, dragIndex, itemIndex); - } else { - Log.debug( - '[$ReorderFlextDragTargetExtension] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' - 'to Column$reorderFlexId:$itemIndex'); - } - - return true; - } - - void onAccept(FlexDragTargetData dragTargetData) { - Log.trace( - '[$ReorderFlextDragTargetExtension] Column$reorderFlexId on onAccept'); - } - - void onLeave(FlexDragTargetData dragTargetData) { - Log.trace( - '[$ReorderFlextDragTargetExtension] Column$reorderFlexId on leave'); - } -} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index fa11597428..d852a84fd7 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -3,7 +3,7 @@ import '../../../flowy_board.dart'; import '../../utils/log.dart'; import '../flex/drag_state.dart'; import '../flex/drag_target.dart'; -import '../flex/reorder_flex_ext.dart'; +import '../flex/drag_target_inteceptor.dart'; import 'phantom_state.dart'; abstract class BoardPhantomControllerDelegate { @@ -14,7 +14,7 @@ mixin ColumnDataPhantomMixim { BoardColumnDataController? get; } -class BoardPhantomController extends DragTargetExtensionDelegate { +class BoardPhantomController extends CrossReorderFlexDragTargetDelegate { final BoardPhantomControllerDelegate delegate; PhantomRecord? phantomRecord; @@ -313,7 +313,7 @@ class PassthroughPhantomWidget extends PhantomWidget { ); } -class PhantomReorderDraggableBuilder extends ReorderDraggableTargetBuilder { +class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { @override Widget? build( BuildContext context, From 9ea4c8b2605720d09cfb93c3c4bb3422a7243d8c Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 4 Aug 2022 18:19:53 +0800 Subject: [PATCH 04/22] chore: add overlap dragTarget interceptor --- .../flowy_board/lib/src/widgets/board.dart | 141 +++++++++++++++--- .../widgets/board_column/board_column.dart | 8 +- .../widgets/board_column/data_controller.dart | 7 +- .../lib/src/widgets/column_container.dart | 88 ----------- .../lib/src/widgets/flex/drag_state.dart | 18 ++- .../widgets/flex/drag_target_inteceptor.dart | 136 +++++++++++------ .../lib/src/widgets/flex/reorder_flex.dart | 35 +++-- .../widgets/phantom/phantom_controller.dart | 25 ++-- 8 files changed, 270 insertions(+), 188 deletions(-) delete mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index 5daffdb47b..2a49de26ba 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -4,7 +4,8 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../../flowy_board.dart'; -import 'column_container.dart'; +import '../rendering/board_overlay.dart'; +import 'flex/drag_target_inteceptor.dart'; import 'flex/reorder_flex.dart'; import 'phantom/phantom_controller.dart'; @@ -57,22 +58,24 @@ class Board extends StatelessWidget { Widget build(BuildContext context) { return ChangeNotifierProvider.value( value: dataController, - child: Consumer( + child: Consumer( builder: (context, notifier, child) { - List children = []; - List acceptColumns = - dataController.columnControllers.keys.toList(); + List children = dataController.columnDatas.map((columnData) { + final controller = dataController.columnController(columnData.id); - dataController.columnControllers.forEach((columnId, controller) { - Widget child = _buildColumn(columnId, acceptColumns, controller); - children.add(child); - }); + return _buildColumn( + columnData.id, + dataController.columnIds, + controller, + ); + }).toList(); return BoardColumnContainer( - onReorder: (fromIndex, toIndex) {}, + onReorder: dataController.onReorder, boardDataController: dataController, background: background, spacing: spacing, + delegate: phantomController, children: children, ); }, @@ -97,7 +100,7 @@ class Board extends StatelessWidget { headerBuilder: headerBuilder, footBuilder: footBuilder, cardBuilder: cardBuilder, - acceptColumns: acceptColumns, + acceptedColumns: acceptColumns, dataController: dataController, scrollController: ScrollController(), onReorder: (_, int fromIndex, int toIndex) { @@ -114,33 +117,133 @@ class Board extends StatelessWidget { class BoardDataController extends ChangeNotifier with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource { - final LinkedHashMap columnDatas = LinkedHashMap(); - final LinkedHashMap columnControllers = + final List _columnDatas = []; + + List get columnDatas => _columnDatas; + + List get columnIds => + _columnDatas.map((columnData) => columnData.id).toList(); + + final LinkedHashMap _columnControllers = LinkedHashMap(); BoardDataController(); void setColumnData(BoardColumnData columnData) { final controller = BoardColumnDataController(columnData: columnData); - columnDatas[columnData.id] = columnData; - columnControllers[columnData.id] = controller; + _columnDatas.add(columnData); + _columnControllers[columnData.id] = controller; + } + + BoardColumnDataController columnController(String columnId) { + return _columnControllers[columnId]!; + } + + void onReorder(int fromIndex, int toIndex) { + final columnData = _columnDatas.removeAt(fromIndex); + _columnDatas.insert(toIndex, columnData); + notifyListeners(); } @override List get props { - return [columnDatas.values]; + return [_columnDatas]; } @override BoardColumnDataController? controller(String columnId) { - return columnControllers[columnId]; + return _columnControllers[columnId]; } @override String get identifier => '$BoardDataController'; @override - List get items => columnDatas.values.toList(); + List get items => _columnDatas; } -class BoardDataIdentifier {} +class BoardColumnContainer extends StatefulWidget { + final ScrollController? scrollController; + final OnDragStarted? onDragStarted; + final OnReorder onReorder; + final OnDragEnded? onDragEnded; + final BoardDataController boardDataController; + final List children; + final Widget? background; + final double spacing; + final ReorderFlexConfig config; + + final OverlapReorderFlexDragTargetDelegate delegate; + + const BoardColumnContainer({ + required this.onReorder, + required this.children, + required this.delegate, + required this.boardDataController, + this.onDragStarted, + this.onDragEnded, + this.scrollController, + this.background, + this.spacing = 0.0, + this.config = const ReorderFlexConfig(), + Key? key, + }) : super(key: key); + + @override + State createState() => _BoardColumnContainerState(); +} + +class _BoardColumnContainerState extends State { + final GlobalKey _columnContainerOverlayKey = + GlobalKey(debugLabel: '$BoardColumnContainer overlay key'); + late BoardOverlayEntry _overlayEntry; + + @override + void initState() { + _overlayEntry = BoardOverlayEntry( + builder: (BuildContext context) { + final interceptor = OverlapReorderFlexDragTargetInteceptor( + reorderFlexId: widget.boardDataController.identifier, + acceptedReorderFlexId: widget.boardDataController.columnIds, + delegate: widget.delegate, + ); + + Widget reorderFlex = ReorderFlex( + key: widget.key, + scrollController: widget.scrollController, + config: widget.config, + onDragStarted: widget.onDragStarted, + onReorder: widget.onReorder, + onDragEnded: widget.onDragEnded, + dataSource: widget.boardDataController, + direction: Axis.horizontal, + spacing: widget.spacing, + interceptor: interceptor, + children: widget.children, + ); + + return _wrapStack(reorderFlex); + }, + opaque: false, + ); + super.initState(); + } + + @override + Widget build(BuildContext context) { + return BoardOverlay( + key: _columnContainerOverlayKey, + initialEntries: [_overlayEntry], + ); + } + + Widget _wrapStack(Widget child) { + return Stack( + alignment: AlignmentDirectional.topStart, + children: [ + if (widget.background != null) widget.background!, + child, + ], + ); + } +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart index 92b4959fe7..869adb4f8d 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart @@ -36,7 +36,7 @@ class BoardColumnWidget extends StatefulWidget { String get columnId => dataController.identifier; - final List acceptColumns; + final List acceptedColumns; final BoardColumnCardBuilder cardBuilder; @@ -54,7 +54,7 @@ class BoardColumnWidget extends StatefulWidget { required this.onReorder, required this.dataController, required this.phantomController, - required this.acceptColumns, + required this.acceptedColumns, this.config = const ReorderFlexConfig(), this.spacing, this.onDragStarted, @@ -89,7 +89,7 @@ class _BoardColumnWidgetState extends State { final interceptor = CrossReorderFlexDragTargetInterceptor( reorderFlexId: widget.columnId, delegate: widget.phantomController, - acceptReorderFlexIds: widget.acceptColumns, + acceptedReorderFlexIds: widget.acceptedColumns, draggableTargetBuilder: PhantomDraggableBuilder(), ); @@ -153,7 +153,7 @@ class _BoardColumnWidgetState extends State { } void _printItems(BoardColumnDataController dataController) { - String msg = ''; + String msg = 'Column${dataController.columnData} data: '; for (var element in dataController.items) { msg = '$msg$element,'; } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart index b890c7da73..d8442563ed 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart @@ -3,9 +3,7 @@ import 'package:flutter/material.dart'; import '../../utils/log.dart'; import '../flex/reorder_flex.dart'; -abstract class ColumnItem extends ReoderFlextItem { - String get id; - +abstract class ColumnItem extends ReoderFlexItem { bool get isPhantom => false; @override @@ -14,7 +12,8 @@ abstract class ColumnItem extends ReoderFlextItem { } } -class BoardColumnData extends ReoderFlextItem with EquatableMixin { +class BoardColumnData extends ReoderFlexItem with EquatableMixin { + @override final String id; final List items; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart deleted file mode 100644 index e6b6a50e50..0000000000 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/column_container.dart +++ /dev/null @@ -1,88 +0,0 @@ -import 'package:flutter/material.dart'; - -import '../../flowy_board.dart'; -import '../rendering/board_overlay.dart'; -import 'flex/reorder_flex.dart'; - -class BoardColumnContainer extends StatefulWidget { - final ScrollController? scrollController; - final OnDragStarted? onDragStarted; - final OnReorder onReorder; - final OnDragEnded? onDragEnded; - final BoardDataController boardDataController; - final List children; - final EdgeInsets? padding; - final Widget? background; - final double spacing; - final ReorderFlexConfig config; - - const BoardColumnContainer({ - required this.boardDataController, - required this.onReorder, - required this.children, - this.onDragStarted, - this.onDragEnded, - this.scrollController, - this.padding, - this.background, - this.spacing = 0.0, - this.config = const ReorderFlexConfig(), - Key? key, - }) : super(key: key); - - @override - State createState() => _BoardColumnContainerState(); -} - -class _BoardColumnContainerState extends State { - final GlobalKey _columnContainerOverlayKey = - GlobalKey(debugLabel: '$BoardColumnContainer overlay key'); - late BoardOverlayEntry _overlayEntry; - - @override - void initState() { - _overlayEntry = BoardOverlayEntry( - builder: (BuildContext context) { - Widget reorderFlex = ReorderFlex( - key: widget.key, - scrollController: widget.scrollController, - config: widget.config, - onDragStarted: (index) {}, - onReorder: ((fromIndex, toIndex) {}), - onDragEnded: () {}, - dataSource: widget.boardDataController, - direction: Axis.horizontal, - spacing: widget.spacing, - children: widget.children, - ); - - if (widget.padding != null) { - reorderFlex = Padding( - padding: widget.padding!, - child: reorderFlex, - ); - } - return _wrapStack(reorderFlex); - }, - opaque: false); - super.initState(); - } - - @override - Widget build(BuildContext context) { - return BoardOverlay( - key: _columnContainerOverlayKey, - initialEntries: [_overlayEntry], - ); - } - - Widget _wrapStack(Widget child) { - return Stack( - alignment: AlignmentDirectional.topStart, - children: [ - if (widget.background != null) widget.background!, - child, - ], - ); - } -} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart index e75d1f74cd..f7b174ebee 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart @@ -17,24 +17,32 @@ class FlexDragTargetData extends DragTargetData { Size? get draggingFeedbackSize => state.feedbackSize; - /// Indicate the dargging come from which [ReorderFlex]. + /// Indicate the dragTarget come from which [ReorderFlex]. final DraggingReorderFlex draggingReorderFlex; - ReoderFlextItem get columnItem => + final String dragTargetId; + + ReoderFlexItem get reorderFlexItem => draggingReorderFlex.itemAtIndex(draggingIndex); - String get reorderFlexId => draggingReorderFlex.id; + String get reorderFlexId => draggingReorderFlex.reorderFlexId; FlexDragTargetData({ + required this.dragTargetId, required this.draggingIndex, required this.state, required this.draggingReorderFlex, }); + + @override + String toString() { + return 'ReorderFlexId: $reorderFlexId, dragTargetId: $dragTargetId'; + } } abstract class DraggingReorderFlex { - String get id; - ReoderFlextItem itemAtIndex(int index); + String get reorderFlexId; + ReoderFlexItem itemAtIndex(int index); } class DraggingState { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index 8b8917bb59..4e551c057c 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -8,12 +8,13 @@ import 'reorder_flex.dart'; abstract class ReorderFlexDragTargetInterceptor { bool canHandler(FlexDragTargetData dragTargetData); - bool onWillAccept( - BuildContext context, - ReorderFlexState reorderFlexState, - FlexDragTargetData dragTargetData, - int itemIndex, - ); + bool onWillAccept({ + required BuildContext context, + required ReorderFlexState reorderFlexState, + required FlexDragTargetData dragTargetData, + required String dragTargetId, + required int dragTargetIndex, + }); void onAccept(FlexDragTargetData dragTargetData); @@ -22,6 +23,51 @@ abstract class ReorderFlexDragTargetInterceptor { ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder; } +abstract class OverlapReorderFlexDragTargetDelegate + extends CrossReorderFlexDragTargetDelegate {} + +class OverlapReorderFlexDragTargetInteceptor + extends ReorderFlexDragTargetInterceptor { + final String reorderFlexId; + final List acceptedReorderFlexId; + final OverlapReorderFlexDragTargetDelegate delegate; + + OverlapReorderFlexDragTargetInteceptor({ + required this.delegate, + required this.reorderFlexId, + required this.acceptedReorderFlexId, + }); + + @override + bool canHandler(FlexDragTargetData dragTargetData) { + return acceptedReorderFlexId.contains(dragTargetData.reorderFlexId); + } + + @override + ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder => null; + + @override + void onAccept(FlexDragTargetData dragTargetData) {} + + @override + void onLeave(FlexDragTargetData dragTargetData) {} + + @override + bool onWillAccept( + {required BuildContext context, + required ReorderFlexState reorderFlexState, + required FlexDragTargetData dragTargetData, + required String dragTargetId, + required int dragTargetIndex}) { + Log.trace('dragTargetData: $dragTargetData'); + Log.trace('currentDragTargetId: $dragTargetId'); + // + Log.debug('Switch to $dragTargetId'); + + return true; + } +} + abstract class CrossReorderFlexDragTargetDelegate { bool acceptNewDragTargetData( String columnId, @@ -38,7 +84,7 @@ abstract class CrossReorderFlexDragTargetDelegate { class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterceptor { final String reorderFlexId; - final List acceptReorderFlexIds; + final List acceptedReorderFlexIds; final CrossReorderFlexDragTargetDelegate delegate; @override final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder; @@ -46,17 +92,17 @@ class CrossReorderFlexDragTargetInterceptor CrossReorderFlexDragTargetInterceptor({ required this.reorderFlexId, required this.delegate, - required this.acceptReorderFlexIds, + required this.acceptedReorderFlexIds, this.draggableTargetBuilder, }); @override bool canHandler(FlexDragTargetData dragTargetData) { - if (acceptReorderFlexIds.isEmpty) { - return true; + if (acceptedReorderFlexIds.isEmpty) { + return false; } - if (acceptReorderFlexIds.contains(dragTargetData.reorderFlexId)) { + if (acceptedReorderFlexIds.contains(dragTargetData.reorderFlexId)) { /// If the columnId equal to the dragTargetData's columnId, /// it means the dragTarget is dragging on the top of its own list. /// Otherwise, it means the dargTarget was moved to another list. @@ -66,40 +112,6 @@ class CrossReorderFlexDragTargetInterceptor } } - @override - bool onWillAccept( - BuildContext context, - ReorderFlexState reorderFlexState, - FlexDragTargetData dragTargetData, - int itemIndex, - ) { - final isNewDragTarget = delegate.acceptNewDragTargetData( - reorderFlexId, - dragTargetData, - itemIndex, - ); - - if (isNewDragTarget == false) { - delegate.updateDragTargetData( - reorderFlexId, - dragTargetData, - itemIndex, - ); - - reorderFlexState.onWillAccept( - context, - dragTargetData.draggingIndex, - itemIndex, - ); - } else { - Log.debug( - '[$CrossReorderFlexDragTargetInterceptor] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' - 'to Column$reorderFlexId:$itemIndex'); - } - - return true; - } - @override void onAccept(FlexDragTargetData dragTargetData) { Log.trace( @@ -111,4 +123,38 @@ class CrossReorderFlexDragTargetInterceptor Log.trace( '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); } + + @override + bool onWillAccept( + {required BuildContext context, + required ReorderFlexState reorderFlexState, + required FlexDragTargetData dragTargetData, + required String dragTargetId, + required int dragTargetIndex}) { + final isNewDragTarget = delegate.acceptNewDragTargetData( + reorderFlexId, + dragTargetData, + dragTargetIndex, + ); + + if (isNewDragTarget == false) { + delegate.updateDragTargetData( + reorderFlexId, + dragTargetData, + dragTargetIndex, + ); + + reorderFlexState.onWillAccept( + context, + dragTargetData.draggingIndex, + dragTargetIndex, + ); + } else { + Log.debug( + '[$CrossReorderFlexDragTargetInterceptor] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' + 'to Column$reorderFlexId:$dragTargetIndex'); + } + + return true; + } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index 4c10772571..9ed78dcdb1 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -18,10 +18,12 @@ typedef OnReveivePassedInPhantom = void Function( abstract class ReoderFlextDataSource { String get identifier; - List get items; + List get items; } -abstract class ReoderFlextItem {} +abstract class ReoderFlexItem { + String get id; +} class ReorderFlexConfig { final bool needsLongPressDraggable = true; @@ -68,10 +70,10 @@ class ReorderFlex extends StatefulWidget with DraggingReorderFlex { State createState() => ReorderFlexState(); @override - String get id => dataSource.identifier; + String get reorderFlexId => dataSource.identifier; @override - ReoderFlextItem itemAtIndex(int index) { + ReoderFlexItem itemAtIndex(int index) { return dataSource.items[index]; } } @@ -90,7 +92,7 @@ class ReorderFlexState extends State @override void initState() { - dragState = DraggingState(widget.id); + dragState = DraggingState(widget.reorderFlexId); _dragAnimationController = DragAnimationController( reorderAnimationDuration: widget.config.reorderAnimationDuration, @@ -135,6 +137,7 @@ class ReorderFlexState extends State for (int i = 0; i < widget.children.length; i += 1) { Widget child = widget.children[i]; + if (widget.spacing != null) { children.add(SizedBox(width: widget.spacing!)); } @@ -283,12 +286,17 @@ class ReorderFlexState extends State } ReorderDragTarget _buildDragTarget( - BuildContext builderContext, Widget child, int childIndex) { + BuildContext builderContext, + Widget child, + int dragTargetIndex, + ) { + final ReoderFlexItem item = widget.dataSource.items[dragTargetIndex]; return ReorderDragTarget( dragTargetData: FlexDragTargetData( - draggingIndex: childIndex, + draggingIndex: dragTargetIndex, state: dragState, draggingReorderFlex: widget, + dragTargetId: item.id, ), onDragStarted: (draggingWidget, draggingIndex, size) { Log.debug("Column${widget.dataSource.identifier} start dragging"); @@ -308,15 +316,16 @@ class ReorderFlexState extends State }); }, onWillAccept: (FlexDragTargetData dragTargetData) { - assert(widget.dataSource.items.length > childIndex); + assert(widget.dataSource.items.length > dragTargetIndex); if (_interceptDragTarget( dragTargetData, (interceptor) => interceptor.onWillAccept( - builderContext, - this, - dragTargetData, - childIndex, + context: builderContext, + reorderFlexState: this, + dragTargetData: dragTargetData, + dragTargetId: item.id, + dragTargetIndex: dragTargetIndex, ), )) { return true; @@ -324,7 +333,7 @@ class ReorderFlexState extends State Log.debug( '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); final dragIndex = dragTargetData.draggingIndex; - return onWillAccept(builderContext, dragIndex, childIndex); + return onWillAccept(builderContext, dragIndex, dragTargetIndex); } }, onAccept: (dragTargetData) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index d852a84fd7..005169278a 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -14,7 +14,10 @@ mixin ColumnDataPhantomMixim { BoardColumnDataController? get; } -class BoardPhantomController extends CrossReorderFlexDragTargetDelegate { +class BoardPhantomController + with + CrossReorderFlexDragTargetDelegate, + OverlapReorderFlexDragTargetDelegate { final BoardPhantomControllerDelegate delegate; PhantomRecord? phantomRecord; @@ -142,17 +145,19 @@ class BoardPhantomController extends CrossReorderFlexDragTargetDelegate { } void _removePhantom(String columnId) { - final items = delegate.controller(columnId)?.items; - if (items == null) { - return; - } + final index = delegate + .controller(columnId) + ?.items + .indexWhere((item) => item.isPhantom); + + if (index == null) return; - final index = items.indexWhere((item) => item.isPhantom); assert(index != -1); + if (index != -1) { - items.removeAt(index); + delegate.controller(columnId)?.removeAt(index); Log.debug( - '[$BoardPhantomController] Column$columnId remove phantom, current count: ${items.length}'); + '[$BoardPhantomController] Column$columnId remove phantom, current count: ${delegate.controller(columnId)?.items.length}'); columnsState.notifyDidRemovePhantom(columnId); columnsState.removeColumnListener(columnId); } @@ -195,7 +200,7 @@ class BoardPhantomController extends CrossReorderFlexDragTargetDelegate { phantomRecord = PhantomRecord( toColumnId: columnId, toColumnIndex: index, - item: dragTargetData.columnItem as ColumnItem, + item: dragTargetData.reorderFlexItem as ColumnItem, fromColumnId: dragTargetData.reorderFlexId, fromColumnIndex: dragTargetData.draggingIndex, ); @@ -275,7 +280,7 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger Widget? get draggingWidget => dragTargetData.draggingWidget; - ColumnItem get itemData => dragTargetData.columnItem as ColumnItem; + ColumnItem get itemData => dragTargetData.reorderFlexItem as ColumnItem; @override VoidCallback? onInserted; From e8dad2ef030d12bc5d6fcbdd1b355d51fc303e39 Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 4 Aug 2022 20:55:02 +0800 Subject: [PATCH 05/22] chore: update --- .../packages/flowy_board/lib/flowy_board.dart | 1 + .../flowy_board/lib/src/widgets/board.dart | 199 +++++++----------- .../lib/src/widgets/board_data.dart | 55 +++++ .../widgets/flex/drag_target_inteceptor.dart | 34 +-- .../lib/src/widgets/flex/reorder_flex.dart | 5 +- .../widgets/phantom/phantom_controller.dart | 85 ++++---- 6 files changed, 193 insertions(+), 186 deletions(-) create mode 100644 frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart index 0611432b4a..16d2c612d3 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart @@ -3,3 +3,4 @@ library flowy_board; export 'src/widgets/board_column/board_column.dart'; export 'src/widgets/board_column/data_controller.dart'; export 'src/widgets/board.dart'; +export 'src/widgets/board_data.dart'; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index 2a49de26ba..f425bcd556 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -1,13 +1,10 @@ -import 'dart:collection'; -import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; -import '../../flowy_board.dart'; - -import '../rendering/board_overlay.dart'; import 'flex/drag_target_inteceptor.dart'; import 'flex/reorder_flex.dart'; import 'phantom/phantom_controller.dart'; +import '../../flowy_board.dart'; +import '../rendering/board_overlay.dart'; class Board extends StatelessWidget { /// The direction to use as the main axis. @@ -60,54 +57,17 @@ class Board extends StatelessWidget { value: dataController, child: Consumer( builder: (context, notifier, child) { - List children = dataController.columnDatas.map((columnData) { - final controller = dataController.columnController(columnData.id); - - return _buildColumn( - columnData.id, - dataController.columnIds, - controller, - ); - }).toList(); - - return BoardColumnContainer( - onReorder: dataController.onReorder, - boardDataController: dataController, + return BoardContent( + dataController: dataController, background: background, spacing: spacing, delegate: phantomController, - children: children, - ); - }, - ), - ); - } - - /// - Widget _buildColumn( - String columnId, - List acceptColumns, - BoardColumnDataController dataController, - ) { - return ChangeNotifierProvider.value( - key: ValueKey(columnId), - value: dataController, - child: Consumer( - builder: (context, value, child) { - return ConstrainedBox( - constraints: columnConstraints, - child: BoardColumnWidget( - headerBuilder: headerBuilder, - footBuilder: footBuilder, - cardBuilder: cardBuilder, - acceptedColumns: acceptColumns, - dataController: dataController, - scrollController: ScrollController(), - onReorder: (_, int fromIndex, int toIndex) { - dataController.move(fromIndex, toIndex); - }, - phantomController: phantomController, - ), + columnConstraints: columnConstraints, + cardBuilder: cardBuilder, + footBuilder: footBuilder, + headerBuilder: headerBuilder, + phantomController: phantomController, + onReorder: dataController.onReorder, ); }, ), @@ -115,96 +75,73 @@ class Board extends StatelessWidget { } } -class BoardDataController extends ChangeNotifier - with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource { - final List _columnDatas = []; - - List get columnDatas => _columnDatas; - - List get columnIds => - _columnDatas.map((columnData) => columnData.id).toList(); - - final LinkedHashMap _columnControllers = - LinkedHashMap(); - - BoardDataController(); - - void setColumnData(BoardColumnData columnData) { - final controller = BoardColumnDataController(columnData: columnData); - _columnDatas.add(columnData); - _columnControllers[columnData.id] = controller; - } - - BoardColumnDataController columnController(String columnId) { - return _columnControllers[columnId]!; - } - - void onReorder(int fromIndex, int toIndex) { - final columnData = _columnDatas.removeAt(fromIndex); - _columnDatas.insert(toIndex, columnData); - notifyListeners(); - } - - @override - List get props { - return [_columnDatas]; - } - - @override - BoardColumnDataController? controller(String columnId) { - return _columnControllers[columnId]; - } - - @override - String get identifier => '$BoardDataController'; - - @override - List get items => _columnDatas; -} - -class BoardColumnContainer extends StatefulWidget { +class BoardContent extends StatefulWidget { final ScrollController? scrollController; final OnDragStarted? onDragStarted; final OnReorder onReorder; final OnDragEnded? onDragEnded; - final BoardDataController boardDataController; - final List children; + final BoardDataController dataController; final Widget? background; final double spacing; final ReorderFlexConfig config; + final BoxConstraints columnConstraints; + + /// + final BoardColumnCardBuilder cardBuilder; + + /// + final BoardColumnHeaderBuilder? headerBuilder; + + /// + final BoardColumnFooterBuilder? footBuilder; final OverlapReorderFlexDragTargetDelegate delegate; - const BoardColumnContainer({ + final BoardPhantomController phantomController; + + const BoardContent({ required this.onReorder, - required this.children, required this.delegate, - required this.boardDataController, + required this.dataController, this.onDragStarted, this.onDragEnded, this.scrollController, this.background, this.spacing = 0.0, this.config = const ReorderFlexConfig(), + required this.columnConstraints, + required this.cardBuilder, + this.footBuilder, + this.headerBuilder, + required this.phantomController, Key? key, }) : super(key: key); @override - State createState() => _BoardColumnContainerState(); + State createState() => _BoardContentState(); } -class _BoardColumnContainerState extends State { +class _BoardContentState extends State { final GlobalKey _columnContainerOverlayKey = - GlobalKey(debugLabel: '$BoardColumnContainer overlay key'); + GlobalKey(debugLabel: '$BoardContent overlay key'); late BoardOverlayEntry _overlayEntry; @override void initState() { _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { + List children = + widget.dataController.columnDatas.map((columnData) { + return _buildColumn( + columnData.id, + widget.dataController.columnIds, + widget.dataController.columnController(columnData.id), + ); + }).toList(); + final interceptor = OverlapReorderFlexDragTargetInteceptor( - reorderFlexId: widget.boardDataController.identifier, - acceptedReorderFlexId: widget.boardDataController.columnIds, + reorderFlexId: widget.dataController.identifier, + acceptedReorderFlexId: widget.dataController.columnIds, delegate: widget.delegate, ); @@ -215,14 +152,20 @@ class _BoardColumnContainerState extends State { onDragStarted: widget.onDragStarted, onReorder: widget.onReorder, onDragEnded: widget.onDragEnded, - dataSource: widget.boardDataController, + dataSource: widget.dataController, direction: Axis.horizontal, spacing: widget.spacing, interceptor: interceptor, - children: widget.children, + children: children, ); - return _wrapStack(reorderFlex); + return Stack( + alignment: AlignmentDirectional.topStart, + children: [ + if (widget.background != null) widget.background!, + reorderFlex, + ], + ); }, opaque: false, ); @@ -237,13 +180,33 @@ class _BoardColumnContainerState extends State { ); } - Widget _wrapStack(Widget child) { - return Stack( - alignment: AlignmentDirectional.topStart, - children: [ - if (widget.background != null) widget.background!, - child, - ], + Widget _buildColumn( + String columnId, + List acceptColumns, + BoardColumnDataController dataController, + ) { + return ChangeNotifierProvider.value( + key: ValueKey(columnId), + value: dataController, + child: Consumer( + builder: (context, value, child) { + return ConstrainedBox( + constraints: widget.columnConstraints, + child: BoardColumnWidget( + headerBuilder: widget.headerBuilder, + footBuilder: widget.footBuilder, + cardBuilder: widget.cardBuilder, + acceptedColumns: acceptColumns, + dataController: dataController, + scrollController: ScrollController(), + onReorder: (_, int fromIndex, int toIndex) { + dataController.move(fromIndex, toIndex); + }, + phantomController: widget.phantomController, + ), + ); + }, + ), ); } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart new file mode 100644 index 0000000000..818258ed71 --- /dev/null +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart @@ -0,0 +1,55 @@ +import 'dart:collection'; + +import 'package:equatable/equatable.dart'; + +import '../../flowy_board.dart'; +import 'flex/reorder_flex.dart'; +import 'package:flutter/material.dart'; +import 'phantom/phantom_controller.dart'; + +class BoardDataController extends ChangeNotifier + with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource { + final List _columnDatas = []; + + List get columnDatas => _columnDatas; + + List get columnIds => + _columnDatas.map((columnData) => columnData.id).toList(); + + final LinkedHashMap _columnControllers = + LinkedHashMap(); + + BoardDataController(); + + void setColumnData(BoardColumnData columnData) { + final controller = BoardColumnDataController(columnData: columnData); + _columnDatas.add(columnData); + _columnControllers[columnData.id] = controller; + } + + BoardColumnDataController columnController(String columnId) { + return _columnControllers[columnId]!; + } + + void onReorder(int fromIndex, int toIndex) { + final columnData = _columnDatas.removeAt(fromIndex); + _columnDatas.insert(toIndex, columnData); + notifyListeners(); + } + + @override + List get props { + return [_columnDatas]; + } + + @override + BoardColumnDataController? controller(String columnId) { + return _columnControllers[columnId]; + } + + @override + String get identifier => '$BoardDataController'; + + @override + List get items => _columnDatas; +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index 4e551c057c..09a8e2b064 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -16,15 +16,14 @@ abstract class ReorderFlexDragTargetInterceptor { required int dragTargetIndex, }); - void onAccept(FlexDragTargetData dragTargetData); + void onAccept(FlexDragTargetData dragTargetData) {} - void onLeave(FlexDragTargetData dragTargetData); + void onLeave(FlexDragTargetData dragTargetData) {} - ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder; + ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder => null; } -abstract class OverlapReorderFlexDragTargetDelegate - extends CrossReorderFlexDragTargetDelegate {} +abstract class OverlapReorderFlexDragTargetDelegate {} class OverlapReorderFlexDragTargetInteceptor extends ReorderFlexDragTargetInterceptor { @@ -43,15 +42,6 @@ class OverlapReorderFlexDragTargetInteceptor return acceptedReorderFlexId.contains(dragTargetData.reorderFlexId); } - @override - ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder => null; - - @override - void onAccept(FlexDragTargetData dragTargetData) {} - - @override - void onLeave(FlexDragTargetData dragTargetData) {} - @override bool onWillAccept( {required BuildContext context, @@ -59,10 +49,9 @@ class OverlapReorderFlexDragTargetInteceptor required FlexDragTargetData dragTargetData, required String dragTargetId, required int dragTargetIndex}) { - Log.trace('dragTargetData: $dragTargetData'); - Log.trace('currentDragTargetId: $dragTargetId'); - // - Log.debug('Switch to $dragTargetId'); + if (dragTargetId == dragTargetData.reorderFlexId) { + Log.debug('remove all phantom'); + } return true; } @@ -70,12 +59,13 @@ class OverlapReorderFlexDragTargetInteceptor abstract class CrossReorderFlexDragTargetDelegate { bool acceptNewDragTargetData( - String columnId, + String reorderFlexId, FlexDragTargetData dragTargetData, int index, ); + void updateDragTargetData( - String columnId, + String reorderFlexId, FlexDragTargetData dragTargetData, int index, ); @@ -149,10 +139,6 @@ class CrossReorderFlexDragTargetInterceptor dragTargetData.draggingIndex, dragTargetIndex, ); - } else { - Log.debug( - '[$CrossReorderFlexDragTargetInterceptor] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' - 'to Column$reorderFlexId:$dragTargetIndex'); } return true; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index 9ed78dcdb1..b1d3c59a9f 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -317,7 +317,8 @@ class ReorderFlexState extends State }, onWillAccept: (FlexDragTargetData dragTargetData) { assert(widget.dataSource.items.length > dragTargetIndex); - + Log.debug( + '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); if (_interceptDragTarget( dragTargetData, (interceptor) => interceptor.onWillAccept( @@ -330,8 +331,6 @@ class ReorderFlexState extends State )) { return true; } else { - Log.debug( - '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); final dragIndex = dragTargetData.draggingIndex; return onWillAccept(builderContext, dragIndex, dragTargetIndex); } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index 005169278a..65c3cfbe16 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -14,10 +14,8 @@ mixin ColumnDataPhantomMixim { BoardColumnDataController? get; } -class BoardPhantomController - with - CrossReorderFlexDragTargetDelegate, - OverlapReorderFlexDragTargetDelegate { +class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate + with CrossReorderFlexDragTargetDelegate { final BoardPhantomControllerDelegate delegate; PhantomRecord? phantomRecord; @@ -85,43 +83,6 @@ class BoardPhantomController phantomRecord = null; } - @override - bool acceptNewDragTargetData( - String columnId, FlexDragTargetData dragTargetData, int index) { - if (phantomRecord == null) { - _updatePhantomRecord(columnId, dragTargetData, index); - _insertPhantom(columnId, dragTargetData, index); - - return true; - } - - final isDifferentDragTarget = phantomRecord!.toColumnId != columnId; - Log.debug( - '[$BoardPhantomController] Set inserted column id: $columnId, different target: $isDifferentDragTarget'); - if (isDifferentDragTarget) { - /// Remove the phantom in the previous column. - _removePhantom(phantomRecord!.toColumnId); - - /// Update the record and insert the phantom to new column. - _updatePhantomRecord(columnId, dragTargetData, index); - _insertPhantom(columnId, dragTargetData, index); - } - - return isDifferentDragTarget; - } - - @override - void updateDragTargetData( - String columnId, FlexDragTargetData dragTargetData, int index) { - phantomRecord?.updateInsertedIndex(index); - - assert(phantomRecord != null); - if (phantomRecord!.toColumnId == columnId) { - /// Update the existing phantom index - _updatePhantom(phantomRecord!.toColumnId, dragTargetData, index); - } - } - void _updatePhantom( String toColumnId, FlexDragTargetData dragTargetData, @@ -197,6 +158,10 @@ class BoardPhantomController FlexDragTargetData dragTargetData, int index, ) { + Log.debug( + '[$BoardPhantomController] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' + 'to Column$columnId:$index'); + phantomRecord = PhantomRecord( toColumnId: columnId, toColumnIndex: index, @@ -205,6 +170,42 @@ class BoardPhantomController fromColumnIndex: dragTargetData.draggingIndex, ); } + + @override + bool acceptNewDragTargetData( + String reorderFlexId, FlexDragTargetData dragTargetData, int index) { + if (phantomRecord == null) { + _updatePhantomRecord(reorderFlexId, dragTargetData, index); + _insertPhantom(reorderFlexId, dragTargetData, index); + return false; + } + + final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId; + Log.debug( + '[$BoardPhantomController] Set inserted column id: $reorderFlexId, is new target: $isNewDragTarget'); + if (isNewDragTarget) { + /// Remove the phantom in the previous column. + _removePhantom(phantomRecord!.toColumnId); + + /// Update the record and insert the phantom to new column. + _updatePhantomRecord(reorderFlexId, dragTargetData, index); + _insertPhantom(reorderFlexId, dragTargetData, index); + } + + return isNewDragTarget; + } + + @override + void updateDragTargetData( + String reorderFlexId, FlexDragTargetData dragTargetData, int index) { + phantomRecord?.updateInsertedIndex(index); + + assert(phantomRecord != null); + if (phantomRecord!.toColumnId == reorderFlexId) { + /// Update the existing phantom index + _updatePhantom(phantomRecord!.toColumnId, dragTargetData, index); + } + } } class PhantomRecord { @@ -234,6 +235,8 @@ class PhantomRecord { void updateInsertedIndex(int index) { if (toColumnIndex == index) { + Log.info( + '[$PhantomRecord] Column$toColumnId toColumnIndex: $toColumnIndex, index: $index'); return; } From 146cc5c5e9abfb97c97a588766aa2cce81b7ca60 Mon Sep 17 00:00:00 2001 From: appflowy Date: Thu, 4 Aug 2022 23:30:27 +0800 Subject: [PATCH 06/22] fix: fix some bugs --- .../flowy_board/lib/src/widgets/board.dart | 6 +- .../widgets/board_column/data_controller.dart | 47 ++++++----- .../lib/src/widgets/flex/drag_state.dart | 10 +-- .../widgets/flex/drag_target_inteceptor.dart | 14 ++-- .../lib/src/widgets/flex/reorder_flex.dart | 57 ++++++------- .../widgets/phantom/phantom_controller.dart | 83 ++++++------------- 6 files changed, 87 insertions(+), 130 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index f425bcd556..c3bbe9824f 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -122,16 +122,14 @@ class BoardContent extends StatefulWidget { } class _BoardContentState extends State { - final GlobalKey _columnContainerOverlayKey = - GlobalKey(debugLabel: '$BoardContent overlay key'); + final GlobalKey _columnContainerOverlayKey = GlobalKey(debugLabel: '$BoardContent overlay key'); late BoardOverlayEntry _overlayEntry; @override void initState() { _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { - List children = - widget.dataController.columnDatas.map((columnData) { + List children = widget.dataController.columnDatas.map((columnData) { return _buildColumn( columnData.id, widget.dataController.columnIds, diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart index d8442563ed..0eda4f3cc6 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart @@ -1,3 +1,5 @@ +import 'dart:collection'; + import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '../../utils/log.dart'; @@ -8,22 +10,26 @@ abstract class ColumnItem extends ReoderFlexItem { @override String toString() { - return id; + if (isPhantom) { + return 'phantom:$id'; + } else { + return id; + } } } class BoardColumnData extends ReoderFlexItem with EquatableMixin { @override final String id; - final List items; + final List _items; BoardColumnData({ required this.id, - required this.items, - }); + required List items, + }) : _items = items; @override - List get props => [id, ...items]; + List get props => [id, ..._items]; @override String toString() { @@ -31,8 +37,7 @@ class BoardColumnData extends ReoderFlexItem with EquatableMixin { } } -class BoardColumnDataController extends ChangeNotifier - with EquatableMixin, ReoderFlextDataSource { +class BoardColumnDataController extends ChangeNotifier with EquatableMixin, ReoderFlextDataSource { final BoardColumnData columnData; BoardColumnDataController({ @@ -42,10 +47,12 @@ class BoardColumnDataController extends ChangeNotifier @override List get props => columnData.props; - ColumnItem removeAt(int index) { + ColumnItem removeAt(int index, {bool notify = true}) { Log.debug('[$BoardColumnDataController] $columnData remove item at $index'); - final item = columnData.items.removeAt(index); - notifyListeners(); + final item = columnData._items.removeAt(index); + if (notify) { + notifyListeners(); + } return item; } @@ -53,31 +60,29 @@ class BoardColumnDataController extends ChangeNotifier if (fromIndex == toIndex) { return; } - Log.debug( - '[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex'); - final item = columnData.items.removeAt(fromIndex); - columnData.items.insert(toIndex, item); + Log.debug('[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex'); + final item = columnData._items.removeAt(fromIndex); + columnData._items.insert(toIndex, item); notifyListeners(); } void insert(int index, ColumnItem item, {bool notify = true}) { - Log.debug('[$BoardColumnDataController] $columnData insert item at $index'); - columnData.items.insert(index, item); + Log.debug('[$BoardColumnDataController] $columnData insert $item at $index'); + columnData._items.insert(index, item); if (notify) { notifyListeners(); } } void replace(int index, ColumnItem item) { - Log.debug( - '[$BoardColumnDataController] $columnData replace item at $index'); - columnData.items.removeAt(index); - columnData.items.insert(index, item); + final removedItem = columnData._items.removeAt(index); + columnData._items.insert(index, item); + Log.debug('[$BoardColumnDataController] $columnData replace $removedItem with $item at $index'); notifyListeners(); } @override - List get items => columnData.items; + List get items => UnmodifiableListView(columnData._items); @override String get identifier => columnData.id; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart index f7b174ebee..1de8b843a0 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart @@ -22,14 +22,14 @@ class FlexDragTargetData extends DragTargetData { final String dragTargetId; - ReoderFlexItem get reorderFlexItem => - draggingReorderFlex.itemAtIndex(draggingIndex); + final ReoderFlexItem reorderFlexItem; String get reorderFlexId => draggingReorderFlex.reorderFlexId; FlexDragTargetData({ required this.dragTargetId, required this.draggingIndex, + required this.reorderFlexItem, required this.state, required this.draggingReorderFlex, }); @@ -85,12 +85,10 @@ class DraggingState { if (_draggingFeedbackSize == null) { return Size.zero; } - return _draggingFeedbackSize! + - const Offset(_dropAreaMargin, _dropAreaMargin); + return _draggingFeedbackSize! + const Offset(_dropAreaMargin, _dropAreaMargin); } - void startDragging(Widget draggingWidget, int draggingWidgetIndex, - Size? draggingWidgetSize) { + void startDragging(Widget draggingWidget, int draggingWidgetIndex, Size? draggingWidgetSize) { /// assert(draggingWidgetIndex >= 0); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index 09a8e2b064..b1f933f577 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -25,8 +25,7 @@ abstract class ReorderFlexDragTargetInterceptor { abstract class OverlapReorderFlexDragTargetDelegate {} -class OverlapReorderFlexDragTargetInteceptor - extends ReorderFlexDragTargetInterceptor { +class OverlapReorderFlexDragTargetInteceptor extends ReorderFlexDragTargetInterceptor { final String reorderFlexId; final List acceptedReorderFlexId; final OverlapReorderFlexDragTargetDelegate delegate; @@ -71,8 +70,7 @@ abstract class CrossReorderFlexDragTargetDelegate { ); } -class CrossReorderFlexDragTargetInterceptor - extends ReorderFlexDragTargetInterceptor { +class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterceptor { final String reorderFlexId; final List acceptedReorderFlexIds; final CrossReorderFlexDragTargetDelegate delegate; @@ -104,14 +102,12 @@ class CrossReorderFlexDragTargetInterceptor @override void onAccept(FlexDragTargetData dragTargetData) { - Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); + Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); } @override void onLeave(FlexDragTargetData dragTargetData) { - Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); + Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); } @override @@ -134,7 +130,7 @@ class CrossReorderFlexDragTargetInterceptor dragTargetIndex, ); - reorderFlexState.onWillAccept( + reorderFlexState.handleOnWillAccept( context, dragTargetData.draggingIndex, dragTargetIndex, diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index b1d3c59a9f..242bd77785 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -13,8 +13,7 @@ typedef OnDragEnded = void Function(); typedef OnReorder = void Function(int fromIndex, int toIndex); typedef OnDeleted = void Function(int deletedIndex); typedef OnInserted = void Function(int insertedIndex); -typedef OnReveivePassedInPhantom = void Function( - FlexDragTargetData dragTargetData, int phantomIndex); +typedef OnReveivePassedInPhantom = void Function(FlexDragTargetData dragTargetData, int phantomIndex); abstract class ReoderFlextDataSource { String get identifier; @@ -78,8 +77,7 @@ class ReorderFlex extends StatefulWidget with DraggingReorderFlex { } } -class ReorderFlexState extends State - with ReorderFlexMinxi, TickerProviderStateMixin { +class ReorderFlexState extends State with ReorderFlexMinxi, TickerProviderStateMixin { /// Controls scrolls and measures scroll progress. late ScrollController _scrollController; ScrollPosition? _attachedScrollPosition; @@ -115,9 +113,7 @@ class ReorderFlexState extends State _attachedScrollPosition = null; } - _scrollController = widget.scrollController ?? - PrimaryScrollController.of(context) ?? - ScrollController(); + _scrollController = widget.scrollController ?? PrimaryScrollController.of(context) ?? ScrollController(); if (_scrollController.hasClients) { _attachedScrollPosition = Scrollable.of(context)?.position; @@ -239,9 +235,7 @@ class ReorderFlexState extends State ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex <= childIndex - ? [dragTarget, disappearSpace] - : [disappearSpace, dragTarget]); + children: shiftedIndex <= childIndex ? [dragTarget, disappearSpace] : [disappearSpace, dragTarget]); } } @@ -262,9 +256,7 @@ class ReorderFlexState extends State ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex >= childIndex - ? [disappearSpace, dragTarget] - : [dragTarget, disappearSpace]); + children: shiftedIndex >= childIndex ? [disappearSpace, dragTarget] : [dragTarget, disappearSpace]); } } @@ -290,21 +282,22 @@ class ReorderFlexState extends State Widget child, int dragTargetIndex, ) { - final ReoderFlexItem item = widget.dataSource.items[dragTargetIndex]; + final ReoderFlexItem reorderFlexItem = widget.dataSource.items[dragTargetIndex]; return ReorderDragTarget( dragTargetData: FlexDragTargetData( draggingIndex: dragTargetIndex, + reorderFlexItem: reorderFlexItem, state: dragState, draggingReorderFlex: widget, - dragTargetId: item.id, + dragTargetId: reorderFlexItem.id, ), onDragStarted: (draggingWidget, draggingIndex, size) { - Log.debug("Column${widget.dataSource.identifier} start dragging"); + Log.debug("[DragTarget] Column${widget.dataSource.identifier} start dragging"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); }, onDragEnded: (dragTargetData) { - Log.debug("Column${widget.dataSource.identifier} end dragging"); + Log.debug("[DragTarget]: Column${widget.dataSource.identifier} end dragging"); setState(() { _onReordered( @@ -317,22 +310,21 @@ class ReorderFlexState extends State }, onWillAccept: (FlexDragTargetData dragTargetData) { assert(widget.dataSource.items.length > dragTargetIndex); - Log.debug( - '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); + if (_interceptDragTarget( dragTargetData, (interceptor) => interceptor.onWillAccept( context: builderContext, reorderFlexState: this, dragTargetData: dragTargetData, - dragTargetId: item.id, + dragTargetId: reorderFlexItem.id, dragTargetIndex: dragTargetIndex, ), )) { return true; } else { final dragIndex = dragTargetData.draggingIndex; - return onWillAccept(builderContext, dragIndex, dragTargetIndex); + return handleOnWillAccept(builderContext, dragIndex, dragTargetIndex); } }, onAccept: (dragTargetData) { @@ -394,11 +386,14 @@ class ReorderFlexState extends State }); } - bool onWillAccept(BuildContext context, int? dragIndex, int childIndex) { + bool handleOnWillAccept(BuildContext context, int? dragIndex, int childIndex) { /// The [willAccept] will be true if the dargTarget is the widget that gets /// dragged and it is dragged on top of the other dragTargets. - bool willAccept = - dragState.dragStartIndex == dragIndex && dragIndex != childIndex; + /// + Log.trace( + '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); + + bool willAccept = dragState.dragStartIndex == dragIndex && dragIndex != childIndex; setState(() { if (willAccept) { int shiftedIndex = dragState.calculateShiftedIndex(childIndex); @@ -425,8 +420,7 @@ class ReorderFlexState extends State } Widget _wrapScrollView({required Widget child}) { - if (widget.scrollController != null && - PrimaryScrollController.of(context) == null) { + if (widget.scrollController != null && PrimaryScrollController.of(context) == null) { return child; } else { return SingleChildScrollView( @@ -480,14 +474,12 @@ class ReorderFlexState extends State void _scrollTo(BuildContext context) { if (_scrolling) return; final RenderObject contextObject = context.findRenderObject()!; - final RenderAbstractViewport viewport = - RenderAbstractViewport.of(contextObject)!; + final RenderAbstractViewport viewport = RenderAbstractViewport.of(contextObject)!; // If and only if the current scroll offset falls in-between the offsets // necessary to reveal the selected context at the top or bottom of the // screen, then it is already on-screen. - final double margin = widget.direction == Axis.horizontal - ? dragState.dropAreaSize.width - : dragState.dropAreaSize.height; + final double margin = + widget.direction == Axis.horizontal ? dragState.dropAreaSize.width : dragState.dropAreaSize.height; if (_scrollController.hasClients) { final double scrollOffset = _scrollController.offset; final double topOffset = max( @@ -498,8 +490,7 @@ class ReorderFlexState extends State _scrollController.position.maxScrollExtent, viewport.getOffsetToReveal(contextObject, 1.0).offset + margin, ); - final bool onScreen = - scrollOffset <= topOffset && scrollOffset >= bottomOffset; + final bool onScreen = scrollOffset <= topOffset && scrollOffset >= bottomOffset; // If the context is off screen, then we request a scroll to make it visible. if (!onScreen) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index 65c3cfbe16..b7074ed01a 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -14,8 +14,7 @@ mixin ColumnDataPhantomMixim { BoardColumnDataController? get; } -class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate - with CrossReorderFlexDragTargetDelegate { +class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with CrossReorderFlexDragTargetDelegate { final BoardPhantomControllerDelegate delegate; PhantomRecord? phantomRecord; @@ -64,22 +63,12 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) { return; } - - Log.debug("[$BoardPhantomController] move ${phantomRecord.toString()}"); - - final item = delegate - .controller(phantomRecord!.fromColumnId) - ?.removeAt(phantomRecord!.fromColumnIndex); - + final item = delegate.controller(phantomRecord!.fromColumnId)?.removeAt(phantomRecord!.fromColumnIndex); assert(item != null); - assert(delegate - .controller(phantomRecord!.toColumnId) - ?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem); - - delegate - .controller(phantomRecord!.toColumnId) - ?.replace(phantomRecord!.toColumnIndex, item!); + assert(delegate.controller(phantomRecord!.toColumnId)?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem); + delegate.controller(phantomRecord!.toColumnId)?.replace(phantomRecord!.toColumnIndex, item!); + Log.debug("[$BoardPhantomController] did move ${phantomRecord.toString()}"); phantomRecord = null; } @@ -88,28 +77,22 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate FlexDragTargetData dragTargetData, int phantomIndex, ) { - final items = delegate.controller(toColumnId)?.items; - if (items == null) { - return; - } + final columnDataController = delegate.controller(toColumnId); + final index = columnDataController?.items.indexWhere((item) => item.isPhantom); + if (index == null) return; - final index = items.indexWhere((item) => item.isPhantom); assert(index != -1); if (index != -1) { if (index != phantomIndex) { - Log.debug( - '[$BoardPhantomController] move phantom $toColumnId:$index to $toColumnId:$phantomIndex'); - final item = items.removeAt(index); - items.insert(phantomIndex, item); + // Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex'); + final item = columnDataController!.removeAt(index, notify: false); + columnDataController.insert(phantomIndex, item, notify: false); } } } void _removePhantom(String columnId) { - final index = delegate - .controller(columnId) - ?.items - .indexWhere((item) => item.isPhantom); + final index = delegate.controller(columnId)?.items.indexWhere((item) => item.isPhantom); if (index == null) return; @@ -139,15 +122,11 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate dragTargetData: dragTargetData, ); columnsState.addColumnListener(toColumnId, phantomContext); - - Log.debug( - '[$BoardPhantomController] Column$toColumnId insert phantom at $phantomIndex'); - delegate - .controller(toColumnId) - ?.insert(phantomIndex, PhantomColumnItem(phantomContext)); + Log.debug('$phantomContext'); + delegate.controller(toColumnId)?.insert(phantomIndex, PhantomColumnItem(phantomContext)); WidgetsBinding.instance.addPostFrameCallback((_) { - Future.delayed(const Duration(microseconds: 100), () { + Future.delayed(const Duration(milliseconds: 00), () { columnsState.notifyDidInsertPhantom(toColumnId); }); }); @@ -158,9 +137,8 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate FlexDragTargetData dragTargetData, int index, ) { - Log.debug( - '[$BoardPhantomController] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' - 'to Column$columnId:$index'); + // Log.debug('[$BoardPhantomController] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' + // 'to Column$columnId:$index'); phantomRecord = PhantomRecord( toColumnId: columnId, @@ -169,11 +147,11 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate fromColumnId: dragTargetData.reorderFlexId, fromColumnIndex: dragTargetData.draggingIndex, ); + Log.debug('[$BoardPhantomController] will move: $phantomRecord'); } @override - bool acceptNewDragTargetData( - String reorderFlexId, FlexDragTargetData dragTargetData, int index) { + bool acceptNewDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int index) { if (phantomRecord == null) { _updatePhantomRecord(reorderFlexId, dragTargetData, index); _insertPhantom(reorderFlexId, dragTargetData, index); @@ -181,8 +159,6 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate } final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId; - Log.debug( - '[$BoardPhantomController] Set inserted column id: $reorderFlexId, is new target: $isNewDragTarget'); if (isNewDragTarget) { /// Remove the phantom in the previous column. _removePhantom(phantomRecord!.toColumnId); @@ -196,8 +172,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate } @override - void updateDragTargetData( - String reorderFlexId, FlexDragTargetData dragTargetData, int index) { + void updateDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int index) { phantomRecord?.updateInsertedIndex(index); assert(phantomRecord != null); @@ -228,34 +203,29 @@ class PhantomRecord { if (fromColumnIndex == index) { return; } - Log.info( - '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + Log.debug('[$PhantomRecord] Update Column$fromColumnId remove position to $index'); fromColumnIndex = index; } void updateInsertedIndex(int index) { if (toColumnIndex == index) { - Log.info( - '[$PhantomRecord] Column$toColumnId toColumnIndex: $toColumnIndex, index: $index'); return; } - Log.info( - '[$PhantomRecord] Update Column$toColumnId phantom position to $index'); + Log.debug('[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); toColumnIndex = index; } @override String toString() { - return '$fromColumnId:$fromColumnIndex to $toColumnId:$toColumnIndex'; + return 'Column$fromColumnId:$fromColumnIndex to Column$toColumnId:$toColumnIndex'; } } class PhantomColumnItem extends ColumnItem { final PassthroughPhantomContext phantomContext; - PhantomColumnItem(PassthroughPhantomContext insertedPhantom) - : phantomContext = insertedPhantom; + PhantomColumnItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom; @override bool get isPhantom => true; @@ -265,9 +235,8 @@ class PhantomColumnItem extends ColumnItem { Size? get feedbackSize => phantomContext.feedbackSize; - Widget get draggingWidget => phantomContext.draggingWidget == null - ? const SizedBox() - : phantomContext.draggingWidget!; + Widget get draggingWidget => + phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!; } class PassthroughPhantomContext extends FakeDragTargetEventTrigger From c25237c7671a9286f3e90e39399cfae546454700 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 5 Aug 2022 09:49:16 +0800 Subject: [PATCH 07/22] fix: reorder bugs --- .../flowy_board/lib/src/widgets/board.dart | 74 +++++++++---------- .../lib/src/widgets/flex/drag_state.dart | 2 +- .../widgets/flex/drag_target_inteceptor.dart | 2 +- .../lib/src/widgets/flex/reorder_flex.dart | 12 +-- .../widgets/phantom/phantom_controller.dart | 9 ++- 5 files changed, 48 insertions(+), 51 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index c3bbe9824f..1e52e7a3aa 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -129,24 +129,16 @@ class _BoardContentState extends State { void initState() { _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { - List children = widget.dataController.columnDatas.map((columnData) { - return _buildColumn( - columnData.id, - widget.dataController.columnIds, - widget.dataController.columnController(columnData.id), - ); - }).toList(); - final interceptor = OverlapReorderFlexDragTargetInteceptor( reorderFlexId: widget.dataController.identifier, acceptedReorderFlexId: widget.dataController.columnIds, delegate: widget.delegate, ); - Widget reorderFlex = ReorderFlex( + final reorderFlex = ReorderFlex( key: widget.key, - scrollController: widget.scrollController, config: widget.config, + scrollController: widget.scrollController, onDragStarted: widget.onDragStarted, onReorder: widget.onReorder, onDragEnded: widget.onDragEnded, @@ -154,7 +146,7 @@ class _BoardContentState extends State { direction: Axis.horizontal, spacing: widget.spacing, interceptor: interceptor, - children: children, + children: _buildColumns(), ); return Stack( @@ -178,33 +170,37 @@ class _BoardContentState extends State { ); } - Widget _buildColumn( - String columnId, - List acceptColumns, - BoardColumnDataController dataController, - ) { - return ChangeNotifierProvider.value( - key: ValueKey(columnId), - value: dataController, - child: Consumer( - builder: (context, value, child) { - return ConstrainedBox( - constraints: widget.columnConstraints, - child: BoardColumnWidget( - headerBuilder: widget.headerBuilder, - footBuilder: widget.footBuilder, - cardBuilder: widget.cardBuilder, - acceptedColumns: acceptColumns, - dataController: dataController, - scrollController: ScrollController(), - onReorder: (_, int fromIndex, int toIndex) { - dataController.move(fromIndex, toIndex); - }, - phantomController: widget.phantomController, - ), - ); - }, - ), - ); + List _buildColumns() { + final acceptColumns = widget.dataController.columnIds; + + final List children = widget.dataController.columnDatas.map((columnData) { + final dataController = widget.dataController.columnController(columnData.id); + + return ChangeNotifierProvider.value( + key: ValueKey(columnData.id), + value: dataController, + child: Consumer( + builder: (context, value, child) { + return ConstrainedBox( + constraints: widget.columnConstraints, + child: BoardColumnWidget( + headerBuilder: widget.headerBuilder, + footBuilder: widget.footBuilder, + cardBuilder: widget.cardBuilder, + acceptedColumns: acceptColumns, + dataController: dataController, + scrollController: ScrollController(), + phantomController: widget.phantomController, + onReorder: (_, int fromIndex, int toIndex) { + dataController.move(fromIndex, toIndex); + }, + ), + ); + }, + ), + ); + }).toList(); + + return children; } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart index 1de8b843a0..a5496bc9d8 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart @@ -15,7 +15,7 @@ class FlexDragTargetData extends DragTargetData { Widget? get draggingWidget => state.draggingWidget; - Size? get draggingFeedbackSize => state.feedbackSize; + Size? get feedbackSize => state.feedbackSize; /// Indicate the dragTarget come from which [ReorderFlex]. final DraggingReorderFlex draggingReorderFlex; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index b1f933f577..0495eeae14 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -66,7 +66,7 @@ abstract class CrossReorderFlexDragTargetDelegate { void updateDragTargetData( String reorderFlexId, FlexDragTargetData dragTargetData, - int index, + int dragTargetIndex, ); } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index 242bd77785..a7c4940f6d 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -386,20 +386,20 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP }); } - bool handleOnWillAccept(BuildContext context, int? dragIndex, int childIndex) { + bool handleOnWillAccept(BuildContext context, int? dragIndex, int dragTargetIndex) { /// The [willAccept] will be true if the dargTarget is the widget that gets /// dragged and it is dragged on top of the other dragTargets. /// - Log.trace( - '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, count: ${widget.dataSource.items.length}'); + Log.debug( + '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); - bool willAccept = dragState.dragStartIndex == dragIndex && dragIndex != childIndex; + bool willAccept = dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; setState(() { if (willAccept) { - int shiftedIndex = dragState.calculateShiftedIndex(childIndex); + int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex); dragState.updateNextIndex(shiftedIndex); } else { - dragState.updateNextIndex(childIndex); + dragState.updateNextIndex(dragTargetIndex); } _requestAnimationToNextIndex(isAcceptingNewTarget: true); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index b7074ed01a..6e2a5195f3 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; import '../../../flowy_board.dart'; import '../../utils/log.dart'; import '../flex/drag_state.dart'; @@ -126,7 +127,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C delegate.controller(toColumnId)?.insert(phantomIndex, PhantomColumnItem(phantomContext)); WidgetsBinding.instance.addPostFrameCallback((_) { - Future.delayed(const Duration(milliseconds: 00), () { + Future.delayed(const Duration(milliseconds: 100), () { columnsState.notifyDidInsertPhantom(toColumnId); }); }); @@ -172,13 +173,13 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C } @override - void updateDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int index) { - phantomRecord?.updateInsertedIndex(index); + void updateDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex) { + phantomRecord?.updateInsertedIndex(dragTargetIndex); assert(phantomRecord != null); if (phantomRecord!.toColumnId == reorderFlexId) { /// Update the existing phantom index - _updatePhantom(phantomRecord!.toColumnId, dragTargetData, index); + _updatePhantom(phantomRecord!.toColumnId, dragTargetData, dragTargetIndex); } } } From a0d7f114c02b95ccbe5c09c77022edcc4108bd18 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 5 Aug 2022 10:29:39 +0800 Subject: [PATCH 08/22] fix: invalid drag start index --- .../lib/src/widgets/flex/drag_state.dart | 20 ++---- .../widgets/flex/drag_target_inteceptor.dart | 30 ++++---- .../lib/src/widgets/flex/reorder_flex.dart | 61 +++++++++------- .../widgets/phantom/phantom_controller.dart | 71 +++++++++++++------ 4 files changed, 108 insertions(+), 74 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart index a5496bc9d8..9a5725b6a4 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart @@ -17,21 +17,18 @@ class FlexDragTargetData extends DragTargetData { Size? get feedbackSize => state.feedbackSize; - /// Indicate the dragTarget come from which [ReorderFlex]. - final DraggingReorderFlex draggingReorderFlex; - final String dragTargetId; - final ReoderFlexItem reorderFlexItem; + final String reorderFlexId; - String get reorderFlexId => draggingReorderFlex.reorderFlexId; + final ReoderFlexItem reorderFlexItem; FlexDragTargetData({ required this.dragTargetId, required this.draggingIndex, + required this.reorderFlexId, required this.reorderFlexItem, required this.state, - required this.draggingReorderFlex, }); @override @@ -40,11 +37,6 @@ class FlexDragTargetData extends DragTargetData { } } -abstract class DraggingReorderFlex { - String get reorderFlexId; - ReoderFlexItem itemAtIndex(int index); -} - class DraggingState { final String id; @@ -85,10 +77,12 @@ class DraggingState { if (_draggingFeedbackSize == null) { return Size.zero; } - return _draggingFeedbackSize! + const Offset(_dropAreaMargin, _dropAreaMargin); + return _draggingFeedbackSize! + + const Offset(_dropAreaMargin, _dropAreaMargin); } - void startDragging(Widget draggingWidget, int draggingWidgetIndex, Size? draggingWidgetSize) { + void startDragging(Widget draggingWidget, int draggingWidgetIndex, + Size? draggingWidgetSize) { /// assert(draggingWidgetIndex >= 0); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index 0495eeae14..19c0c0f05d 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -25,7 +25,8 @@ abstract class ReorderFlexDragTargetInterceptor { abstract class OverlapReorderFlexDragTargetDelegate {} -class OverlapReorderFlexDragTargetInteceptor extends ReorderFlexDragTargetInterceptor { +class OverlapReorderFlexDragTargetInteceptor + extends ReorderFlexDragTargetInterceptor { final String reorderFlexId; final List acceptedReorderFlexId; final OverlapReorderFlexDragTargetDelegate delegate; @@ -49,7 +50,7 @@ class OverlapReorderFlexDragTargetInteceptor extends ReorderFlexDragTargetInterc required String dragTargetId, required int dragTargetIndex}) { if (dragTargetId == dragTargetData.reorderFlexId) { - Log.debug('remove all phantom'); + // Log.debug('remove all phantom'); } return true; @@ -60,7 +61,7 @@ abstract class CrossReorderFlexDragTargetDelegate { bool acceptNewDragTargetData( String reorderFlexId, FlexDragTargetData dragTargetData, - int index, + int dragTargetIndex, ); void updateDragTargetData( @@ -70,7 +71,8 @@ abstract class CrossReorderFlexDragTargetDelegate { ); } -class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterceptor { +class CrossReorderFlexDragTargetInterceptor + extends ReorderFlexDragTargetInterceptor { final String reorderFlexId; final List acceptedReorderFlexIds; final CrossReorderFlexDragTargetDelegate delegate; @@ -102,21 +104,24 @@ class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterce @override void onAccept(FlexDragTargetData dragTargetData) { - Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); + Log.trace( + '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); } @override void onLeave(FlexDragTargetData dragTargetData) { - Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); + Log.trace( + '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); } @override - bool onWillAccept( - {required BuildContext context, - required ReorderFlexState reorderFlexState, - required FlexDragTargetData dragTargetData, - required String dragTargetId, - required int dragTargetIndex}) { + bool onWillAccept({ + required BuildContext context, + required ReorderFlexState reorderFlexState, + required FlexDragTargetData dragTargetData, + required String dragTargetId, + required int dragTargetIndex, + }) { final isNewDragTarget = delegate.acceptNewDragTargetData( reorderFlexId, dragTargetData, @@ -132,7 +137,6 @@ class CrossReorderFlexDragTargetInterceptor extends ReorderFlexDragTargetInterce reorderFlexState.handleOnWillAccept( context, - dragTargetData.draggingIndex, dragTargetIndex, ); } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index a7c4940f6d..b451aaf0e6 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -13,7 +13,8 @@ typedef OnDragEnded = void Function(); typedef OnReorder = void Function(int fromIndex, int toIndex); typedef OnDeleted = void Function(int deletedIndex); typedef OnInserted = void Function(int insertedIndex); -typedef OnReveivePassedInPhantom = void Function(FlexDragTargetData dragTargetData, int phantomIndex); +typedef OnReveivePassedInPhantom = void Function( + FlexDragTargetData dragTargetData, int phantomIndex); abstract class ReoderFlextDataSource { String get identifier; @@ -32,7 +33,7 @@ class ReorderFlexConfig { const ReorderFlexConfig(); } -class ReorderFlex extends StatefulWidget with DraggingReorderFlex { +class ReorderFlex extends StatefulWidget { final ReorderFlexConfig config; final List children; @@ -68,16 +69,11 @@ class ReorderFlex extends StatefulWidget with DraggingReorderFlex { @override State createState() => ReorderFlexState(); - @override String get reorderFlexId => dataSource.identifier; - - @override - ReoderFlexItem itemAtIndex(int index) { - return dataSource.items[index]; - } } -class ReorderFlexState extends State with ReorderFlexMinxi, TickerProviderStateMixin { +class ReorderFlexState extends State + with ReorderFlexMinxi, TickerProviderStateMixin { /// Controls scrolls and measures scroll progress. late ScrollController _scrollController; ScrollPosition? _attachedScrollPosition; @@ -113,7 +109,9 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP _attachedScrollPosition = null; } - _scrollController = widget.scrollController ?? PrimaryScrollController.of(context) ?? ScrollController(); + _scrollController = widget.scrollController ?? + PrimaryScrollController.of(context) ?? + ScrollController(); if (_scrollController.hasClients) { _attachedScrollPosition = Scrollable.of(context)?.position; @@ -235,7 +233,9 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex <= childIndex ? [dragTarget, disappearSpace] : [disappearSpace, dragTarget]); + children: shiftedIndex <= childIndex + ? [dragTarget, disappearSpace] + : [disappearSpace, dragTarget]); } } @@ -256,7 +256,9 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex >= childIndex ? [disappearSpace, dragTarget] : [dragTarget, disappearSpace]); + children: shiftedIndex >= childIndex + ? [disappearSpace, dragTarget] + : [dragTarget, disappearSpace]); } } @@ -282,22 +284,25 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP Widget child, int dragTargetIndex, ) { - final ReoderFlexItem reorderFlexItem = widget.dataSource.items[dragTargetIndex]; + final ReoderFlexItem reorderFlexItem = + widget.dataSource.items[dragTargetIndex]; return ReorderDragTarget( dragTargetData: FlexDragTargetData( draggingIndex: dragTargetIndex, + reorderFlexId: widget.reorderFlexId, reorderFlexItem: reorderFlexItem, state: dragState, - draggingReorderFlex: widget, dragTargetId: reorderFlexItem.id, ), onDragStarted: (draggingWidget, draggingIndex, size) { - Log.debug("[DragTarget] Column${widget.dataSource.identifier} start dragging"); + Log.debug( + "[DragTarget] Column${widget.dataSource.identifier} start dragging"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); }, onDragEnded: (dragTargetData) { - Log.debug("[DragTarget]: Column${widget.dataSource.identifier} end dragging"); + Log.debug( + "[DragTarget]: Column${widget.dataSource.identifier} end dragging"); setState(() { _onReordered( @@ -323,8 +328,7 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP )) { return true; } else { - final dragIndex = dragTargetData.draggingIndex; - return handleOnWillAccept(builderContext, dragIndex, dragTargetIndex); + return handleOnWillAccept(builderContext, dragTargetIndex); } }, onAccept: (dragTargetData) { @@ -386,14 +390,17 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP }); } - bool handleOnWillAccept(BuildContext context, int? dragIndex, int dragTargetIndex) { + bool handleOnWillAccept(BuildContext context, int dragTargetIndex) { + final dragIndex = dragState.dragStartIndex; + /// The [willAccept] will be true if the dargTarget is the widget that gets /// dragged and it is dragged on top of the other dragTargets. /// Log.debug( '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); - bool willAccept = dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; + bool willAccept = + dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; setState(() { if (willAccept) { int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex); @@ -420,7 +427,8 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP } Widget _wrapScrollView({required Widget child}) { - if (widget.scrollController != null && PrimaryScrollController.of(context) == null) { + if (widget.scrollController != null && + PrimaryScrollController.of(context) == null) { return child; } else { return SingleChildScrollView( @@ -474,12 +482,14 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP void _scrollTo(BuildContext context) { if (_scrolling) return; final RenderObject contextObject = context.findRenderObject()!; - final RenderAbstractViewport viewport = RenderAbstractViewport.of(contextObject)!; + final RenderAbstractViewport viewport = + RenderAbstractViewport.of(contextObject)!; // If and only if the current scroll offset falls in-between the offsets // necessary to reveal the selected context at the top or bottom of the // screen, then it is already on-screen. - final double margin = - widget.direction == Axis.horizontal ? dragState.dropAreaSize.width : dragState.dropAreaSize.height; + final double margin = widget.direction == Axis.horizontal + ? dragState.dropAreaSize.width + : dragState.dropAreaSize.height; if (_scrollController.hasClients) { final double scrollOffset = _scrollController.offset; final double topOffset = max( @@ -490,7 +500,8 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP _scrollController.position.maxScrollExtent, viewport.getOffsetToReveal(contextObject, 1.0).offset + margin, ); - final bool onScreen = scrollOffset <= topOffset && scrollOffset >= bottomOffset; + final bool onScreen = + scrollOffset <= topOffset && scrollOffset >= bottomOffset; // If the context is off screen, then we request a scroll to make it visible. if (!onScreen) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index 6e2a5195f3..a7be370c71 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; import '../../../flowy_board.dart'; import '../../utils/log.dart'; import '../flex/drag_state.dart'; @@ -15,7 +14,8 @@ mixin ColumnDataPhantomMixim { BoardColumnDataController? get; } -class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with CrossReorderFlexDragTargetDelegate { +class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate + with CrossReorderFlexDragTargetDelegate { final BoardPhantomControllerDelegate delegate; PhantomRecord? phantomRecord; @@ -64,10 +64,16 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) { return; } - final item = delegate.controller(phantomRecord!.fromColumnId)?.removeAt(phantomRecord!.fromColumnIndex); + final item = delegate + .controller(phantomRecord!.fromColumnId) + ?.removeAt(phantomRecord!.fromColumnIndex); assert(item != null); - assert(delegate.controller(phantomRecord!.toColumnId)?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem); - delegate.controller(phantomRecord!.toColumnId)?.replace(phantomRecord!.toColumnIndex, item!); + assert(delegate + .controller(phantomRecord!.toColumnId) + ?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem); + delegate + .controller(phantomRecord!.toColumnId) + ?.replace(phantomRecord!.toColumnIndex, item!); Log.debug("[$BoardPhantomController] did move ${phantomRecord.toString()}"); phantomRecord = null; @@ -76,24 +82,28 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C void _updatePhantom( String toColumnId, FlexDragTargetData dragTargetData, - int phantomIndex, + int dragTargetIndex, ) { final columnDataController = delegate.controller(toColumnId); - final index = columnDataController?.items.indexWhere((item) => item.isPhantom); + final index = + columnDataController?.items.indexWhere((item) => item.isPhantom); if (index == null) return; assert(index != -1); if (index != -1) { - if (index != phantomIndex) { + if (index != dragTargetIndex) { // Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex'); final item = columnDataController!.removeAt(index, notify: false); - columnDataController.insert(phantomIndex, item, notify: false); + columnDataController.insert(dragTargetIndex, item, notify: false); } } } void _removePhantom(String columnId) { - final index = delegate.controller(columnId)?.items.indexWhere((item) => item.isPhantom); + final index = delegate + .controller(columnId) + ?.items + .indexWhere((item) => item.isPhantom); if (index == null) return; @@ -124,7 +134,9 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C ); columnsState.addColumnListener(toColumnId, phantomContext); Log.debug('$phantomContext'); - delegate.controller(toColumnId)?.insert(phantomIndex, PhantomColumnItem(phantomContext)); + delegate + .controller(toColumnId) + ?.insert(phantomIndex, PhantomColumnItem(phantomContext)); WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 100), () { @@ -152,10 +164,14 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C } @override - bool acceptNewDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int index) { + bool acceptNewDragTargetData( + String reorderFlexId, + FlexDragTargetData dragTargetData, + int dragTargetIndex, + ) { if (phantomRecord == null) { - _updatePhantomRecord(reorderFlexId, dragTargetData, index); - _insertPhantom(reorderFlexId, dragTargetData, index); + _updatePhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); + _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); return false; } @@ -165,21 +181,26 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with C _removePhantom(phantomRecord!.toColumnId); /// Update the record and insert the phantom to new column. - _updatePhantomRecord(reorderFlexId, dragTargetData, index); - _insertPhantom(reorderFlexId, dragTargetData, index); + _updatePhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); + _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); } return isNewDragTarget; } @override - void updateDragTargetData(String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex) { + void updateDragTargetData( + String reorderFlexId, + FlexDragTargetData dragTargetData, + int dragTargetIndex, + ) { phantomRecord?.updateInsertedIndex(dragTargetIndex); assert(phantomRecord != null); if (phantomRecord!.toColumnId == reorderFlexId) { /// Update the existing phantom index - _updatePhantom(phantomRecord!.toColumnId, dragTargetData, dragTargetIndex); + _updatePhantom( + phantomRecord!.toColumnId, dragTargetData, dragTargetIndex); } } } @@ -204,7 +225,8 @@ class PhantomRecord { if (fromColumnIndex == index) { return; } - Log.debug('[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + Log.debug( + '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); fromColumnIndex = index; } @@ -213,7 +235,8 @@ class PhantomRecord { return; } - Log.debug('[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); + Log.debug( + '[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); toColumnIndex = index; } @@ -226,7 +249,8 @@ class PhantomRecord { class PhantomColumnItem extends ColumnItem { final PassthroughPhantomContext phantomContext; - PhantomColumnItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom; + PhantomColumnItem(PassthroughPhantomContext insertedPhantom) + : phantomContext = insertedPhantom; @override bool get isPhantom => true; @@ -236,8 +260,9 @@ class PhantomColumnItem extends ColumnItem { Size? get feedbackSize => phantomContext.feedbackSize; - Widget get draggingWidget => - phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!; + Widget get draggingWidget => phantomContext.draggingWidget == null + ? const SizedBox() + : phantomContext.draggingWidget!; } class PassthroughPhantomContext extends FakeDragTargetEventTrigger From 3977925e8a9c87afabbc0294cf5573ca51b5b820 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 5 Aug 2022 11:59:20 +0800 Subject: [PATCH 09/22] chore: add documentation --- .../widgets/board_column/board_column.dart | 12 +--- .../widgets/board_column/data_controller.dart | 21 +++++-- .../lib/src/widgets/flex/drag_state.dart | 19 ++++--- .../lib/src/widgets/flex/drag_target.dart | 5 ++ .../widgets/flex/drag_target_inteceptor.dart | 18 ++---- .../widgets/phantom/phantom_controller.dart | 56 +++++++++++-------- .../src/widgets/phantom/phantom_state.dart | 10 ++-- 7 files changed, 79 insertions(+), 62 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart index 869adb4f8d..cb2f557d99 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import '../../rendering/board_overlay.dart'; -import '../../utils/log.dart'; import '../phantom/phantom_controller.dart'; import '../flex/reorder_flex.dart'; import '../flex/drag_target_inteceptor.dart'; @@ -110,7 +109,7 @@ class _BoardColumnWidgetState extends State { onDragEnded: () { widget.phantomController.columnEndDragging(widget.columnId); widget.onDragEnded?.call(widget.columnId); - _printItems(widget.dataController); + widget.dataController.debugPrintItems(); }, dataSource: widget.dataController, interceptor: interceptor, @@ -151,12 +150,3 @@ class _BoardColumnWidgetState extends State { } } } - -void _printItems(BoardColumnDataController dataController) { - String msg = 'Column${dataController.columnData} data: '; - for (var element in dataController.items) { - msg = '$msg$element,'; - } - - Log.debug(msg); -} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart index 0eda4f3cc6..c7ecf2169b 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart @@ -37,7 +37,8 @@ class BoardColumnData extends ReoderFlexItem with EquatableMixin { } } -class BoardColumnDataController extends ChangeNotifier with EquatableMixin, ReoderFlextDataSource { +class BoardColumnDataController extends ChangeNotifier + with EquatableMixin, ReoderFlextDataSource { final BoardColumnData columnData; BoardColumnDataController({ @@ -60,14 +61,16 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin, Reod if (fromIndex == toIndex) { return; } - Log.debug('[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex'); + Log.debug( + '[$BoardColumnDataController] $columnData move item from $fromIndex to $toIndex'); final item = columnData._items.removeAt(fromIndex); columnData._items.insert(toIndex, item); notifyListeners(); } void insert(int index, ColumnItem item, {bool notify = true}) { - Log.debug('[$BoardColumnDataController] $columnData insert $item at $index'); + Log.debug( + '[$BoardColumnDataController] $columnData insert $item at $index'); columnData._items.insert(index, item); if (notify) { notifyListeners(); @@ -77,10 +80,20 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin, Reod void replace(int index, ColumnItem item) { final removedItem = columnData._items.removeAt(index); columnData._items.insert(index, item); - Log.debug('[$BoardColumnDataController] $columnData replace $removedItem with $item at $index'); + Log.debug( + '[$BoardColumnDataController] $columnData replace $removedItem with $item at $index'); notifyListeners(); } + void debugPrintItems() { + String msg = '[$BoardColumnDataController] $columnData data: '; + for (var element in items) { + msg = '$msg$element,'; + } + + Log.debug(msg); + } + @override List get items => UnmodifiableListView(columnData._items); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart index 9a5725b6a4..8c4d8af4ce 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart @@ -4,18 +4,23 @@ import '../../utils/log.dart'; import 'drag_target.dart'; import 'reorder_flex.dart'; -/// [FlexDragTargetData] is used to store the custom dragging data. It can be used to -/// locate the index of the dragging widget in the [BoardList]. +/// [FlexDragTargetData] is used to store the custom dragging data. +/// +/// * [draggingIndex] the index of the dragTarget that is being dragged. +/// * [draggingWidget] the widget of the dragTarget that is being dragged. +/// * [reorderFlexId] the id of the [ReorderFlex] +/// * [reorderFlexItem] the item of the [ReorderFlex] +/// class FlexDragTargetData extends DragTargetData { /// The index of the dragging target in the boardList. @override final int draggingIndex; - final DraggingState state; + final DraggingState _state; - Widget? get draggingWidget => state.draggingWidget; + Widget? get draggingWidget => _state.draggingWidget; - Size? get feedbackSize => state.feedbackSize; + Size? get feedbackSize => _state.feedbackSize; final String dragTargetId; @@ -28,8 +33,8 @@ class FlexDragTargetData extends DragTargetData { required this.draggingIndex, required this.reorderFlexId, required this.reorderFlexItem, - required this.state, - }); + required DraggingState state, + }) : _state = state; @override String toString() { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index f6dfbb9812..fe086ca84a 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -13,9 +13,14 @@ abstract class ReorderFlexDraggableTargetBuilder { DragTargetWillAccpet onWillAccept); } +/// typedef DragTargetWillAccpet = bool Function( T dragTargetData); + +/// typedef DragTargetOnStarted = void Function(Widget, int, Size?); + +/// typedef DragTargetOnEnded = void Function( T dragTargetData); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index 19c0c0f05d..64addf7dbe 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -50,7 +50,9 @@ class OverlapReorderFlexDragTargetInteceptor required String dragTargetId, required int dragTargetIndex}) { if (dragTargetId == dragTargetData.reorderFlexId) { - // Log.debug('remove all phantom'); + Log.debug('remove all phantom'); + } else { + Log.debug('add phantom to $dragTargetId'); } return true; @@ -58,6 +60,7 @@ class OverlapReorderFlexDragTargetInteceptor } abstract class CrossReorderFlexDragTargetDelegate { + /// * [reorderFlexId] is the id that the [ReorderFlex] passed in. bool acceptNewDragTargetData( String reorderFlexId, FlexDragTargetData dragTargetData, @@ -66,7 +69,6 @@ abstract class CrossReorderFlexDragTargetDelegate { void updateDragTargetData( String reorderFlexId, - FlexDragTargetData dragTargetData, int dragTargetIndex, ); } @@ -129,16 +131,8 @@ class CrossReorderFlexDragTargetInterceptor ); if (isNewDragTarget == false) { - delegate.updateDragTargetData( - reorderFlexId, - dragTargetData, - dragTargetIndex, - ); - - reorderFlexState.handleOnWillAccept( - context, - dragTargetIndex, - ); + delegate.updateDragTargetData(reorderFlexId, dragTargetIndex); + reorderFlexState.handleOnWillAccept(context, dragTargetIndex); } return true; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index a7be370c71..d04c7eca6e 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -20,7 +20,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate PhantomRecord? phantomRecord; - final columnsState = ColumnPassthroughStateController(); + final columnsState = ColumnPhantomStateController(); BoardPhantomController({required this.delegate}); @@ -79,9 +79,11 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate phantomRecord = null; } + /// Update the column's phantom index if it exists. + /// [toColumnId] the id of the column + /// [dragTargetIndex] the index of the dragTarget void _updatePhantom( String toColumnId, - FlexDragTargetData dragTargetData, int dragTargetIndex, ) { final columnDataController = delegate.controller(toColumnId); @@ -99,6 +101,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate } } + /// Remove the phantom in the column if it contains phantom void _removePhantom(String columnId) { final index = delegate .controller(columnId) @@ -118,45 +121,51 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate } } + /// Insert the phantom into column + /// + /// * [toColumnId] id of the column + /// * [phantomIndex] the phantom occupies index void _insertPhantom( String toColumnId, FlexDragTargetData dragTargetData, int phantomIndex, ) { - final items = delegate.controller(toColumnId)?.items; - if (items == null) { - return; - } - final phantomContext = PassthroughPhantomContext( index: phantomIndex, dragTargetData: dragTargetData, ); columnsState.addColumnListener(toColumnId, phantomContext); - Log.debug('$phantomContext'); delegate .controller(toColumnId) ?.insert(phantomIndex, PhantomColumnItem(phantomContext)); WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 100), () { + Log.debug( + '[$BoardPhantomController] notify $toColumnId to insert phantom'); columnsState.notifyDidInsertPhantom(toColumnId); }); }); } - void _updatePhantomRecord( + /// Reset or initial the [PhantomRecord] + /// + /// + /// * [columnId] the id of the column + /// * [dragTargetData] + /// * [dragTargetIndex] + /// + void _resetPhantomRecord( String columnId, FlexDragTargetData dragTargetData, - int index, + int dragTargetIndex, ) { // Log.debug('[$BoardPhantomController] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' // 'to Column$columnId:$index'); phantomRecord = PhantomRecord( toColumnId: columnId, - toColumnIndex: index, - item: dragTargetData.reorderFlexItem as ColumnItem, + toColumnIndex: dragTargetIndex, fromColumnId: dragTargetData.reorderFlexId, fromColumnIndex: dragTargetData.draggingIndex, ); @@ -170,18 +179,15 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate int dragTargetIndex, ) { if (phantomRecord == null) { - _updatePhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); + _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); return false; } final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId; if (isNewDragTarget) { - /// Remove the phantom in the previous column. _removePhantom(phantomRecord!.toColumnId); - - /// Update the record and insert the phantom to new column. - _updatePhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); + _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); } @@ -191,7 +197,6 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate @override void updateDragTargetData( String reorderFlexId, - FlexDragTargetData dragTargetData, int dragTargetIndex, ) { phantomRecord?.updateInsertedIndex(dragTargetIndex); @@ -199,14 +204,20 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate assert(phantomRecord != null); if (phantomRecord!.toColumnId == reorderFlexId) { /// Update the existing phantom index - _updatePhantom( - phantomRecord!.toColumnId, dragTargetData, dragTargetIndex); + _updatePhantom(phantomRecord!.toColumnId, dragTargetIndex); } } } +/// Use [PhantomRecord] to record where to remove the column item and where to +/// insert the column item. +/// +/// [fromColumnId] the column that phantom comes from +/// [fromColumnIndex] the index of the phantom from the original column +/// [toColumnId] the column that the phantom moves into +/// [toColumnIndex] the index of the phantom moves into the column +/// class PhantomRecord { - final ColumnItem item; final String fromColumnId; int fromColumnIndex; @@ -214,7 +225,6 @@ class PhantomRecord { int toColumnIndex; PhantomRecord({ - required this.item, required this.toColumnId, required this.toColumnIndex, required this.fromColumnId, @@ -274,7 +284,7 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger final FlexDragTargetData dragTargetData; @override - Size? get feedbackSize => dragTargetData.state.feedbackSize; + Size? get feedbackSize => dragTargetData.feedbackSize; Widget? get draggingWidget => dragTargetData.draggingWidget; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart index b78c567d81..f26cc26f11 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart @@ -1,8 +1,8 @@ import 'phantom_controller.dart'; import 'package:flutter/material.dart'; -class ColumnPassthroughStateController { - final _states = {}; +class ColumnPhantomStateController { + final _states = {}; void setColumnIsDragging(String columnId, bool isDragging) { _stateWithId(columnId).isDragging = isDragging; @@ -32,17 +32,17 @@ class ColumnPassthroughStateController { _stateWithId(columnId).notifier.remove(); } - ColumnPassthrougPhantomhState _stateWithId(String columnId) { + ColumnState _stateWithId(String columnId) { var state = _states[columnId]; if (state == null) { - state = ColumnPassthrougPhantomhState(); + state = ColumnState(); _states[columnId] = state; } return state; } } -class ColumnPassthrougPhantomhState { +class ColumnState { bool isDragging = false; final notifier = PassthroughPhantomNotifier(); } From e592a09ec305ee6581b22f2f0c68d87e540baac8 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 5 Aug 2022 12:09:27 +0800 Subject: [PATCH 10/22] chore: remove phantom where dragTarget disappear --- .../lib/src/widgets/flex/drag_target_inteceptor.dart | 6 ++++-- .../flowy_board/lib/src/widgets/flex/reorder_flex.dart | 2 +- .../lib/src/widgets/phantom/phantom_controller.dart | 10 ++++++++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index 64addf7dbe..fc2fc8482c 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -23,7 +23,9 @@ abstract class ReorderFlexDragTargetInterceptor { ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder => null; } -abstract class OverlapReorderFlexDragTargetDelegate {} +abstract class OverlapReorderFlexDragTargetDelegate { + void dragTargetDidDisappear(); +} class OverlapReorderFlexDragTargetInteceptor extends ReorderFlexDragTargetInterceptor { @@ -50,7 +52,7 @@ class OverlapReorderFlexDragTargetInteceptor required String dragTargetId, required int dragTargetIndex}) { if (dragTargetId == dragTargetData.reorderFlexId) { - Log.debug('remove all phantom'); + delegate.dragTargetDidDisappear(); } else { Log.debug('add phantom to $dragTargetId'); } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index b451aaf0e6..6ffb310390 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -396,7 +396,7 @@ class ReorderFlexState extends State /// The [willAccept] will be true if the dargTarget is the widget that gets /// dragged and it is dragged on top of the other dragTargets. /// - Log.debug( + Log.trace( '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); bool willAccept = diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index d04c7eca6e..92b9f31043 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -207,6 +207,16 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate _updatePhantom(phantomRecord!.toColumnId, dragTargetIndex); } } + + @override + void dragTargetDidDisappear() { + if (phantomRecord == null) { + return; + } + + _removePhantom(phantomRecord!.toColumnId); + phantomRecord = null; + } } /// Use [PhantomRecord] to record where to remove the column item and where to From 2e0256f305e57c09fa9e0e22965bdf7e6b144111 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 5 Aug 2022 12:51:47 +0800 Subject: [PATCH 11/22] fix: inserted index out of boundr --- .../lib/src/widgets/board_column/data_controller.dart | 8 +++++++- .../lib/src/widgets/flex/drag_target_inteceptor.dart | 11 ++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart index c7ecf2169b..e7c528f772 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart @@ -71,7 +71,13 @@ class BoardColumnDataController extends ChangeNotifier void insert(int index, ColumnItem item, {bool notify = true}) { Log.debug( '[$BoardColumnDataController] $columnData insert $item at $index'); - columnData._items.insert(index, item); + + if (columnData._items.length > index) { + columnData._items.insert(index, item); + } else { + columnData._items.add(item); + } + if (notify) { notifyListeners(); } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index fc2fc8482c..776722b05d 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -25,6 +25,11 @@ abstract class ReorderFlexDragTargetInterceptor { abstract class OverlapReorderFlexDragTargetDelegate { void dragTargetDidDisappear(); + bool acceptNewDragTargetData( + String reorderFlexId, + FlexDragTargetData dragTargetData, + int dragTargetIndex, + ); } class OverlapReorderFlexDragTargetInteceptor @@ -54,7 +59,11 @@ class OverlapReorderFlexDragTargetInteceptor if (dragTargetId == dragTargetData.reorderFlexId) { delegate.dragTargetDidDisappear(); } else { - Log.debug('add phantom to $dragTargetId'); + delegate.acceptNewDragTargetData( + dragTargetId, + dragTargetData, + dragTargetIndex, + ); } return true; From 9e4dbc53f7833365c07239b977bd4d23de71d3c2 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 5 Aug 2022 20:33:00 +0800 Subject: [PATCH 12/22] chore: add documentation --- .../example/lib/multi_board_list_example.dart | 10 +- .../flowy_board/lib/src/widgets/board.dart | 88 ++++++++------ .../widgets/board_column/board_column.dart | 82 +++++++++---- .../widgets/board_column/data_controller.dart | 108 ++++++++++-------- .../lib/src/widgets/board_data.dart | 65 ++++++++++- .../lib/src/widgets/flex/drag_target.dart | 2 +- .../lib/src/widgets/flex/reorder_flex.dart | 16 +-- .../widgets/phantom/phantom_controller.dart | 105 +++++++---------- 8 files changed, 293 insertions(+), 183 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart index a7a74c8fa3..2e3ead4ee0 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart @@ -9,7 +9,7 @@ class MultiBoardListExample extends StatefulWidget { } class _MultiBoardListExampleState extends State { - final BoardDataController boardData = BoardDataController(); + final BoardDataController boardDataController = BoardDataController(); @override void initState() { @@ -34,9 +34,9 @@ class _MultiBoardListExampleState extends State { TextItem("D"), ]); - boardData.setColumnData(column1); - boardData.setColumnData(column2); - boardData.setColumnData(column3); + boardDataController.setColumnData(column1); + boardDataController.setColumnData(column2); + boardDataController.setColumnData(column3); super.initState(); } @@ -44,7 +44,7 @@ class _MultiBoardListExampleState extends State { @override Widget build(BuildContext context) { return Board( - dataController: boardData, + dataController: boardDataController, background: Container(color: Colors.red), footBuilder: (context, columnData) { return Container( diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index 1e52e7a3aa..2f27734e54 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -67,7 +67,7 @@ class Board extends StatelessWidget { footBuilder: footBuilder, headerBuilder: headerBuilder, phantomController: phantomController, - onReorder: dataController.onReorder, + onReorder: dataController.moveColumn, ); }, ), @@ -99,7 +99,7 @@ class BoardContent extends StatefulWidget { final BoardPhantomController phantomController; - const BoardContent({ + BoardContent({ required this.onReorder, required this.delegate, required this.dataController, @@ -107,22 +107,23 @@ class BoardContent extends StatefulWidget { this.onDragEnded, this.scrollController, this.background, - this.spacing = 0.0, - this.config = const ReorderFlexConfig(), + this.spacing = 10.0, required this.columnConstraints, required this.cardBuilder, this.footBuilder, this.headerBuilder, required this.phantomController, Key? key, - }) : super(key: key); + }) : config = ReorderFlexConfig(spacing: spacing), + super(key: key); @override State createState() => _BoardContentState(); } class _BoardContentState extends State { - final GlobalKey _columnContainerOverlayKey = GlobalKey(debugLabel: '$BoardContent overlay key'); + final GlobalKey _columnContainerOverlayKey = + GlobalKey(debugLabel: '$BoardContent overlay key'); late BoardOverlayEntry _overlayEntry; @override @@ -144,7 +145,6 @@ class _BoardContentState extends State { onDragEnded: widget.onDragEnded, dataSource: widget.dataController, direction: Axis.horizontal, - spacing: widget.spacing, interceptor: interceptor, children: _buildColumns(), ); @@ -171,36 +171,54 @@ class _BoardContentState extends State { } List _buildColumns() { - final acceptColumns = widget.dataController.columnIds; + final List children = widget.dataController.columnDatas.map( + (columnData) { + final dataSource = _BoardColumnDataSourceImpl( + columnId: columnData.id, + dataController: widget.dataController, + ); - final List children = widget.dataController.columnDatas.map((columnData) { - final dataController = widget.dataController.columnController(columnData.id); - - return ChangeNotifierProvider.value( - key: ValueKey(columnData.id), - value: dataController, - child: Consumer( - builder: (context, value, child) { - return ConstrainedBox( - constraints: widget.columnConstraints, - child: BoardColumnWidget( - headerBuilder: widget.headerBuilder, - footBuilder: widget.footBuilder, - cardBuilder: widget.cardBuilder, - acceptedColumns: acceptColumns, - dataController: dataController, - scrollController: ScrollController(), - phantomController: widget.phantomController, - onReorder: (_, int fromIndex, int toIndex) { - dataController.move(fromIndex, toIndex); - }, - ), - ); - }, - ), - ); - }).toList(); + return ChangeNotifierProvider.value( + key: ValueKey(columnData.id), + value: widget.dataController.columnController(columnData.id), + child: Consumer( + builder: (context, value, child) { + return ConstrainedBox( + constraints: widget.columnConstraints, + child: BoardColumnWidget( + headerBuilder: widget.headerBuilder, + footBuilder: widget.footBuilder, + cardBuilder: widget.cardBuilder, + dataSource: dataSource, + scrollController: ScrollController(), + phantomController: widget.phantomController, + onReorder: widget.dataController.moveColumnItem, + spacing: 10, + ), + ); + }, + ), + ); + }, + ).toList(); return children; } } + +class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource { + String columnId; + final BoardDataController dataController; + + _BoardColumnDataSourceImpl({ + required this.columnId, + required this.dataController, + }); + + @override + BoardColumnData get columnData => + dataController.columnController(columnId).columnData; + + @override + List get acceptedColumnIds => dataController.columnIds; +} diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart index cb2f557d99..78cb78950c 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart @@ -1,29 +1,67 @@ -import 'package:flutter/material.dart'; +import 'dart:collection'; +import 'package:flutter/material.dart'; import '../../rendering/board_overlay.dart'; +import '../../utils/log.dart'; import '../phantom/phantom_controller.dart'; import '../flex/reorder_flex.dart'; import '../flex/drag_target_inteceptor.dart'; import 'data_controller.dart'; typedef OnColumnDragStarted = void Function(int index); + typedef OnColumnDragEnded = void Function(String listId); + typedef OnColumnReorder = void Function( - String listId, int fromIndex, int toIndex); + String listId, + int fromIndex, + int toIndex, +); + typedef OnColumnDeleted = void Function(String listId, int deletedIndex); + typedef OnColumnInserted = void Function(String listId, int insertedIndex); typedef BoardColumnCardBuilder = Widget Function( - BuildContext context, ColumnItem item); + BuildContext context, + ColumnItem item, +); typedef BoardColumnHeaderBuilder = Widget Function( - BuildContext context, BoardColumnData columnData); + BuildContext context, + BoardColumnData columnData, +); typedef BoardColumnFooterBuilder = Widget Function( - BuildContext context, BoardColumnData columnData); + BuildContext context, + BoardColumnData columnData, +); +abstract class BoardColumnDataDataSource extends ReoderFlextDataSource { + BoardColumnData get columnData; + + List get acceptedColumnIds; + + @override + String get identifier => columnData.id; + + @override + UnmodifiableListView get items => columnData.items; + + void debugPrint() { + String msg = '[$BoardColumnDataDataSource] $columnData data: '; + for (var element in items) { + msg = '$msg$element,'; + } + + Log.debug(msg); + } +} + +/// [BoardColumnWidget] represents the column of the Board. +/// class BoardColumnWidget extends StatefulWidget { - final BoardColumnDataController dataController; + final BoardColumnDataDataSource dataSource; final ScrollController? scrollController; final ReorderFlexConfig config; @@ -33,9 +71,7 @@ class BoardColumnWidget extends StatefulWidget { final BoardPhantomController phantomController; - String get columnId => dataController.identifier; - - final List acceptedColumns; + String get columnId => dataSource.columnData.id; final BoardColumnCardBuilder cardBuilder; @@ -43,23 +79,20 @@ class BoardColumnWidget extends StatefulWidget { final BoardColumnFooterBuilder? footBuilder; - final double? spacing; - - const BoardColumnWidget({ + BoardColumnWidget({ Key? key, this.headerBuilder, this.footBuilder, required this.cardBuilder, required this.onReorder, - required this.dataController, + required this.dataSource, required this.phantomController, - required this.acceptedColumns, - this.config = const ReorderFlexConfig(), - this.spacing, this.onDragStarted, this.scrollController, this.onDragEnded, - }) : super(key: key); + double? spacing, + }) : config = ReorderFlexConfig(spacing: spacing), + super(key: key); @override State createState() => _BoardColumnWidgetState(); @@ -75,20 +108,20 @@ class _BoardColumnWidgetState extends State { void initState() { _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { - final children = widget.dataController.items + final children = widget.dataSource.columnData.items .map((item) => _buildWidget(context, item)) .toList(); - final header = widget.headerBuilder - ?.call(context, widget.dataController.columnData); + final header = + widget.headerBuilder?.call(context, widget.dataSource.columnData); final footer = - widget.footBuilder?.call(context, widget.dataController.columnData); + widget.footBuilder?.call(context, widget.dataSource.columnData); final interceptor = CrossReorderFlexDragTargetInterceptor( reorderFlexId: widget.columnId, delegate: widget.phantomController, - acceptedReorderFlexIds: widget.acceptedColumns, + acceptedReorderFlexIds: widget.dataSource.acceptedColumnIds, draggableTargetBuilder: PhantomDraggableBuilder(), ); @@ -109,11 +142,10 @@ class _BoardColumnWidgetState extends State { onDragEnded: () { widget.phantomController.columnEndDragging(widget.columnId); widget.onDragEnded?.call(widget.columnId); - widget.dataController.debugPrintItems(); + widget.dataSource.debugPrint(); }, - dataSource: widget.dataController, + dataSource: widget.dataSource, interceptor: interceptor, - spacing: widget.spacing, children: children, ); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart index e7c528f772..24d3cc1a96 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart @@ -9,36 +9,18 @@ abstract class ColumnItem extends ReoderFlexItem { bool get isPhantom => false; @override - String toString() { - if (isPhantom) { - return 'phantom:$id'; - } else { - return id; - } - } + String toString() => id; } -class BoardColumnData extends ReoderFlexItem with EquatableMixin { - @override - final String id; - final List _items; - - BoardColumnData({ - required this.id, - required List items, - }) : _items = items; - - @override - List get props => [id, ..._items]; - - @override - String toString() { - return 'Column$id'; - } -} - -class BoardColumnDataController extends ChangeNotifier - with EquatableMixin, ReoderFlextDataSource { +/// [BoardColumnDataController] is used to handle the [BoardColumnData]. +/// * Remove an item by calling [removeAt] method. +/// * Move item to another position by calling [move] method. +/// * Insert item to index by calling [insert] method +/// * Replace item at index by calling [replace] method. +/// +/// All there operations will notify listeners by default. +/// +class BoardColumnDataController extends ChangeNotifier with EquatableMixin { final BoardColumnData columnData; BoardColumnDataController({ @@ -48,7 +30,18 @@ class BoardColumnDataController extends ChangeNotifier @override List get props => columnData.props; + /// Returns the readonly List + UnmodifiableListView get items => + UnmodifiableListView(columnData.items); + + /// Remove the item at [index]. + /// * [index] the index of the item you want to remove + /// * [notify] the default value of [notify] is true, it will notify the + /// listener. Set to [false] if you do not want to notify the listeners. + /// ColumnItem removeAt(int index, {bool notify = true}) { + assert(index >= 0); + Log.debug('[$BoardColumnDataController] $columnData remove item at $index'); final item = columnData._items.removeAt(index); if (notify) { @@ -57,7 +50,16 @@ class BoardColumnDataController extends ChangeNotifier return item; } + int removeWhere(bool Function(ColumnItem) condition) { + return items.indexWhere(condition); + } + + /// Move the item from [fromIndex] to [toIndex]. It will do nothing if the + /// [fromIndex] equal to the [toIndex]. void move(int fromIndex, int toIndex) { + assert(fromIndex >= 0); + assert(toIndex >= 0); + if (fromIndex == toIndex) { return; } @@ -68,7 +70,12 @@ class BoardColumnDataController extends ChangeNotifier notifyListeners(); } + /// Insert an item to [index] and notify the listen if the value of [notify] + /// is true. + /// + /// The default value of [notify] is true. void insert(int index, ColumnItem item, {bool notify = true}) { + assert(index >= 0); Log.debug( '[$BoardColumnDataController] $columnData insert $item at $index'); @@ -83,26 +90,35 @@ class BoardColumnDataController extends ChangeNotifier } } - void replace(int index, ColumnItem item) { + /// Replace the item at index with the [newItem]. + void replace(int index, ColumnItem newItem) { final removedItem = columnData._items.removeAt(index); - columnData._items.insert(index, item); + columnData._items.insert(index, newItem); Log.debug( - '[$BoardColumnDataController] $columnData replace $removedItem with $item at $index'); + '[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index'); notifyListeners(); } - - void debugPrintItems() { - String msg = '[$BoardColumnDataController] $columnData data: '; - for (var element in items) { - msg = '$msg$element,'; - } - - Log.debug(msg); - } - - @override - List get items => UnmodifiableListView(columnData._items); - - @override - String get identifier => columnData.id; +} + +/// [BoardColumnData] represents the data of each Column of the Board. +class BoardColumnData extends ReoderFlexItem with EquatableMixin { + @override + final String id; + final List _items; + + BoardColumnData({ + required this.id, + required List items, + }) : _items = items; + + /// Returns the readonly List + UnmodifiableListView get items => UnmodifiableListView(_items); + + @override + List get props => [id, ..._items]; + + @override + String toString() { + return 'Column$id'; + } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart index 818258ed71..13918c1f4e 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart @@ -3,6 +3,7 @@ import 'dart:collection'; import 'package:equatable/equatable.dart'; import '../../flowy_board.dart'; +import '../utils/log.dart'; import 'flex/reorder_flex.dart'; import 'package:flutter/material.dart'; import 'phantom/phantom_controller.dart'; @@ -31,12 +32,35 @@ class BoardDataController extends ChangeNotifier return _columnControllers[columnId]!; } - void onReorder(int fromIndex, int toIndex) { + void moveColumn(int fromIndex, int toIndex) { final columnData = _columnDatas.removeAt(fromIndex); _columnDatas.insert(toIndex, columnData); notifyListeners(); } + void moveColumnItem(String columnId, int fromIndex, int toIndex) { + final columnController = _columnControllers[columnId]; + assert(columnController != null); + if (columnController != null) { + columnController.move(fromIndex, toIndex); + } + } + + @override + void swapColumnItem( + String fromColumnId, + int fromColumnIndex, + String toColumnId, + int toColumnIndex, + ) { + final item = columnController(fromColumnId).removeAt(fromColumnIndex); + + assert( + columnController(toColumnId).items[toColumnIndex] is PhantomColumnItem); + + columnController(toColumnId).replace(toColumnIndex, item); + } + @override List get props { return [_columnDatas]; @@ -51,5 +75,42 @@ class BoardDataController extends ChangeNotifier String get identifier => '$BoardDataController'; @override - List get items => _columnDatas; + UnmodifiableListView get items => + UnmodifiableListView(_columnDatas); + + @override + bool removePhantom(String columnId) { + final columnController = this.columnController(columnId); + final index = columnController.items.indexWhere((item) => item.isPhantom); + + final isExist = index != -1; + if (isExist) { + columnController.removeAt(index); + + Log.debug( + '[$BoardPhantomController] Column$columnId remove phantom, current count: ${columnController.items.length}'); + } + return isExist; + } + + @override + void updatePhantom(String columnId, int newIndex) { + final columnDataController = columnController(columnId); + final index = + columnDataController.items.indexWhere((item) => item.isPhantom); + + assert(index != -1); + if (index != -1) { + if (index != newIndex) { + // Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex'); + final item = columnDataController.removeAt(index, notify: false); + columnDataController.insert(newIndex, item, notify: false); + } + } + } + + @override + void insertPhantom(String columnId, int index, PhantomColumnItem item) { + columnController(columnId).insert(index, item); + } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index fe086ca84a..ed44d20c14 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -77,7 +77,7 @@ class ReorderDragTarget extends StatefulWidget { class _ReorderDragTargetState extends State> { - /// Return the dragTarget's size + /// Returns the dragTarget's size Size? _draggingFeedbackSize = Size.zero; @override diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index 6ffb310390..89aa00de8d 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -1,3 +1,4 @@ +import 'dart:collection'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -18,10 +19,11 @@ typedef OnReveivePassedInPhantom = void Function( abstract class ReoderFlextDataSource { String get identifier; - List get items; + UnmodifiableListView get items; } abstract class ReoderFlexItem { + /// [id] is used to identify the item String get id; } @@ -30,7 +32,9 @@ class ReorderFlexConfig { final double draggingWidgetOpacity = 0.2; final Duration reorderAnimationDuration = const Duration(milliseconds: 250); final Duration scrollAnimationDuration = const Duration(milliseconds: 250); - const ReorderFlexConfig(); + + final double? spacing; + const ReorderFlexConfig({this.spacing}); } class ReorderFlex extends StatefulWidget { @@ -38,7 +42,6 @@ class ReorderFlex extends StatefulWidget { final List children; final EdgeInsets? padding; - final double? spacing; final Axis direction; final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start; final ScrollController? scrollController; @@ -62,7 +65,6 @@ class ReorderFlex extends StatefulWidget { this.onDragEnded, this.interceptor, this.padding, - this.spacing, this.direction = Axis.vertical, }) : super(key: key); @@ -132,8 +134,8 @@ class ReorderFlexState extends State for (int i = 0; i < widget.children.length; i += 1) { Widget child = widget.children[i]; - if (widget.spacing != null) { - children.add(SizedBox(width: widget.spacing!)); + if (widget.config.spacing != null) { + children.add(SizedBox(width: widget.config.spacing!)); } final wrapChild = _wrap(child, i); @@ -203,7 +205,7 @@ class ReorderFlexState extends State dragSpace = SizedBox.fromSize(size: dragState.dropAreaSize); } - /// Return the dragTarget it is not start dragging. The size of the + /// Returns the dragTarget it is not start dragging. The size of the /// dragTarget is the same as the the passed in child. /// if (dragState.isNotDragging()) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index 92b9f31043..69f9fd85aa 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -8,24 +8,39 @@ import 'phantom_state.dart'; abstract class BoardPhantomControllerDelegate { BoardColumnDataController? controller(String columnId); -} -mixin ColumnDataPhantomMixim { - BoardColumnDataController? get; + bool removePhantom(String columnId); + + /// Insert the phantom into column + /// + /// * [toColumnId] id of the column + /// * [phantomIndex] the phantom occupies index + void insertPhantom( + String columnId, + int index, + PhantomColumnItem item, + ); + + /// Update the column's phantom index if it exists. + /// [toColumnId] the id of the column + /// [dragTargetIndex] the index of the dragTarget + void updatePhantom(String columnId, int newIndex); + + void swapColumnItem( + String fromColumnId, + int fromColumnIndex, + String toColumnId, + int toColumnIndex, + ); } class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate with CrossReorderFlexDragTargetDelegate { - final BoardPhantomControllerDelegate delegate; - PhantomRecord? phantomRecord; - + final BoardPhantomControllerDelegate delegate; final columnsState = ColumnPhantomStateController(); - BoardPhantomController({required this.delegate}); - bool get hasPhantom => phantomRecord != null; - bool isFromColumn(String columnId) { if (phantomRecord != null) { return phantomRecord!.fromColumnId == columnId; @@ -64,67 +79,25 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) { return; } - final item = delegate - .controller(phantomRecord!.fromColumnId) - ?.removeAt(phantomRecord!.fromColumnIndex); - assert(item != null); - assert(delegate - .controller(phantomRecord!.toColumnId) - ?.items[phantomRecord!.toColumnIndex] is PhantomColumnItem); - delegate - .controller(phantomRecord!.toColumnId) - ?.replace(phantomRecord!.toColumnIndex, item!); + delegate.swapColumnItem( + phantomRecord!.fromColumnId, + phantomRecord!.fromColumnIndex, + phantomRecord!.toColumnId, + phantomRecord!.toColumnIndex, + ); Log.debug("[$BoardPhantomController] did move ${phantomRecord.toString()}"); phantomRecord = null; } - /// Update the column's phantom index if it exists. - /// [toColumnId] the id of the column - /// [dragTargetIndex] the index of the dragTarget - void _updatePhantom( - String toColumnId, - int dragTargetIndex, - ) { - final columnDataController = delegate.controller(toColumnId); - final index = - columnDataController?.items.indexWhere((item) => item.isPhantom); - if (index == null) return; - - assert(index != -1); - if (index != -1) { - if (index != dragTargetIndex) { - // Log.debug('[$BoardPhantomController] update $toColumnId:$index to $toColumnId:$phantomIndex'); - final item = columnDataController!.removeAt(index, notify: false); - columnDataController.insert(dragTargetIndex, item, notify: false); - } - } - } - /// Remove the phantom in the column if it contains phantom void _removePhantom(String columnId) { - final index = delegate - .controller(columnId) - ?.items - .indexWhere((item) => item.isPhantom); - - if (index == null) return; - - assert(index != -1); - - if (index != -1) { - delegate.controller(columnId)?.removeAt(index); - Log.debug( - '[$BoardPhantomController] Column$columnId remove phantom, current count: ${delegate.controller(columnId)?.items.length}'); + if (delegate.removePhantom(columnId)) { columnsState.notifyDidRemovePhantom(columnId); columnsState.removeColumnListener(columnId); } } - /// Insert the phantom into column - /// - /// * [toColumnId] id of the column - /// * [phantomIndex] the phantom occupies index void _insertPhantom( String toColumnId, FlexDragTargetData dragTargetData, @@ -135,9 +108,12 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate dragTargetData: dragTargetData, ); columnsState.addColumnListener(toColumnId, phantomContext); - delegate - .controller(toColumnId) - ?.insert(phantomIndex, PhantomColumnItem(phantomContext)); + + delegate.insertPhantom( + toColumnId, + phantomIndex, + PhantomColumnItem(phantomContext), + ); WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 100), () { @@ -204,7 +180,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate assert(phantomRecord != null); if (phantomRecord!.toColumnId == reorderFlexId) { /// Update the existing phantom index - _updatePhantom(phantomRecord!.toColumnId, dragTargetIndex); + delegate.updatePhantom(phantomRecord!.toColumnId, dragTargetIndex); } } @@ -283,6 +259,11 @@ class PhantomColumnItem extends ColumnItem { Widget get draggingWidget => phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!; + + @override + String toString() { + return 'phantom:$id'; + } } class PassthroughPhantomContext extends FakeDragTargetEventTrigger From 8ca1cc0a21c602fc9e702aa0ebc001c9039df258 Mon Sep 17 00:00:00 2001 From: appflowy Date: Fri, 5 Aug 2022 23:53:50 +0800 Subject: [PATCH 13/22] chore: add documentation --- .../flowy_board/lib/src/widgets/board.dart | 10 +-- .../lib/src/widgets/flex/drag_state.dart | 22 +++--- .../lib/src/widgets/flex/drag_target.dart | 52 ++++--------- .../widgets/flex/drag_target_inteceptor.dart | 40 ++++++---- .../lib/src/widgets/flex/reorder_flex.dart | 77 ++++++++++--------- .../widgets/phantom/phantom_controller.dart | 35 +++++---- .../src/widgets/phantom/phantom_state.dart | 6 -- 7 files changed, 114 insertions(+), 128 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index 2f27734e54..3cce2b8b99 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -95,7 +95,7 @@ class BoardContent extends StatefulWidget { /// final BoardColumnFooterBuilder? footBuilder; - final OverlapReorderFlexDragTargetDelegate delegate; + final OverlapDragTargetDelegate delegate; final BoardPhantomController phantomController; @@ -122,15 +122,14 @@ class BoardContent extends StatefulWidget { } class _BoardContentState extends State { - final GlobalKey _columnContainerOverlayKey = - GlobalKey(debugLabel: '$BoardContent overlay key'); + final GlobalKey _columnContainerOverlayKey = GlobalKey(debugLabel: '$BoardContent overlay key'); late BoardOverlayEntry _overlayEntry; @override void initState() { _overlayEntry = BoardOverlayEntry( builder: (BuildContext context) { - final interceptor = OverlapReorderFlexDragTargetInteceptor( + final interceptor = OverlappingDragTargetInteceptor( reorderFlexId: widget.dataController.identifier, acceptedReorderFlexId: widget.dataController.columnIds, delegate: widget.delegate, @@ -216,8 +215,7 @@ class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource { }); @override - BoardColumnData get columnData => - dataController.columnController(columnId).columnData; + BoardColumnData get columnData => dataController.columnController(columnId).columnData; @override List get acceptedColumnIds => dataController.columnIds; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart index 8c4d8af4ce..f5f7250834 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart @@ -46,16 +46,12 @@ class DraggingState { final String id; /// The member of widget.children currently being dragged. - /// - /// Null if no drag is underway. Widget? _draggingWidget; Widget? get draggingWidget => _draggingWidget; /// The last computed size of the feedback widget being dragged. - Size? _draggingFeedbackSize = Size.zero; - - Size? get feedbackSize => _draggingFeedbackSize; + Size? feedbackSize = Size.zero; /// The location that the dragging widget occupied before it started to drag. int dragStartIndex = -1; @@ -79,15 +75,17 @@ class DraggingState { DraggingState(this.id); Size get dropAreaSize { - if (_draggingFeedbackSize == null) { + if (feedbackSize == null) { return Size.zero; } - return _draggingFeedbackSize! + - const Offset(_dropAreaMargin, _dropAreaMargin); + return feedbackSize! + const Offset(_dropAreaMargin, _dropAreaMargin); } - void startDragging(Widget draggingWidget, int draggingWidgetIndex, - Size? draggingWidgetSize) { + void startDragging( + Widget draggingWidget, + int draggingWidgetIndex, + Size? draggingWidgetSize, + ) { /// assert(draggingWidgetIndex >= 0); @@ -95,7 +93,7 @@ class DraggingState { phantomIndex = draggingWidgetIndex; dragStartIndex = draggingWidgetIndex; currentIndex = draggingWidgetIndex; - _draggingFeedbackSize = draggingWidgetSize; + feedbackSize = draggingWidgetSize; } void endDragging() { @@ -130,12 +128,10 @@ class DraggingState { /// Set the currentIndex to nextIndex void moveDragTargetToNext() { - Log.trace('moveDragTargetToNext: $nextIndex'); currentIndex = nextIndex; } void updateNextIndex(int index) { - assert(index >= 0); Log.trace('updateNextIndex: $index'); nextIndex = index; } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index ed44d20c14..44a177ab8f 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -5,24 +5,18 @@ abstract class DragTargetData { } abstract class ReorderFlexDraggableTargetBuilder { - Widget? build( - BuildContext context, - Widget child, - DragTargetOnStarted onDragStarted, - DragTargetOnEnded onDragEnded, - DragTargetWillAccpet onWillAccept); + Widget? build(BuildContext context, Widget child, DragTargetOnStarted onDragStarted, + DragTargetOnEnded onDragEnded, DragTargetWillAccpet onWillAccept); } /// -typedef DragTargetWillAccpet = bool Function( - T dragTargetData); +typedef DragTargetWillAccpet = bool Function(T dragTargetData); /// typedef DragTargetOnStarted = void Function(Widget, int, Size?); /// -typedef DragTargetOnEnded = void Function( - T dragTargetData); +typedef DragTargetOnEnded = void Function(T dragTargetData); /// [ReorderDragTarget] is a [DragTarget] that carries the index information of /// the child. @@ -75,8 +69,7 @@ class ReorderDragTarget extends StatefulWidget { State> createState() => _ReorderDragTargetState(); } -class _ReorderDragTargetState - extends State> { +class _ReorderDragTargetState extends State> { /// Returns the dragTarget's size Size? _draggingFeedbackSize = Size.zero; @@ -108,8 +101,7 @@ class _ReorderDragTargetState List rejectedCandidates, ) { Widget feedbackBuilder = Builder(builder: (BuildContext context) { - BoxConstraints contentSizeConstraints = - BoxConstraints.loose(_draggingFeedbackSize!); + BoxConstraints contentSizeConstraints = BoxConstraints.loose(_draggingFeedbackSize!); return _buildDraggableFeedback( context, contentSizeConstraints, @@ -149,21 +141,19 @@ class _ReorderDragTargetState /// When the drag does not end inside a DragTarget widget, the /// drag fails, but we still reorder the widget to the last position it /// had been dragged to. - onDraggableCanceled: (Velocity velocity, Offset offset) => - widget.onDragEnded(widget.dragTargetData), + onDraggableCanceled: (Velocity velocity, Offset offset) => widget.onDragEnded(widget.dragTargetData), child: widget.child, ); return draggableWidget; } - Widget _buildDraggableFeedback( - BuildContext context, BoxConstraints constraints, Widget child) { + Widget _buildDraggableFeedback(BuildContext context, BoxConstraints constraints, Widget child) { return Transform( transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, child: Material( - elevation: 6.0, + elevation: 2.0, color: Colors.transparent, borderRadius: BorderRadius.zero, clipBehavior: Clip.hardEdge, @@ -177,10 +167,6 @@ class DragAnimationController { // How long an animation to reorder an element in the list takes. final Duration reorderAnimationDuration; - // How long an animation to scroll to an off-screen element in the - // list takes. - final Duration scrollAnimationDuration; - // This controls the entrance of the dragging widget into a new place. late AnimationController entranceController; @@ -190,14 +176,11 @@ class DragAnimationController { DragAnimationController({ required this.reorderAnimationDuration, - required this.scrollAnimationDuration, required TickerProvider vsync, required void Function(AnimationStatus) entranceAnimateStatusChanged, }) { - entranceController = AnimationController( - value: 1.0, vsync: vsync, duration: reorderAnimationDuration); - phantomController = AnimationController( - value: 0, vsync: vsync, duration: reorderAnimationDuration); + entranceController = AnimationController(value: 1.0, vsync: vsync, duration: reorderAnimationDuration); + phantomController = AnimationController(value: 0, vsync: vsync, duration: reorderAnimationDuration); entranceController.addStatusListener(entranceAnimateStatusChanged); } @@ -234,9 +217,7 @@ class IgnorePointerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final sizedChild = useIntrinsicSize - ? child - : SizedBox(width: 0.0, height: 0.0, child: child); + final sizedChild = useIntrinsicSize ? child : SizedBox(width: 0.0, height: 0.0, child: child); return IgnorePointer( ignoring: true, child: Opacity( @@ -276,10 +257,8 @@ class PhantomAnimateContorller { required this.reorderAnimationDuration, required void Function(AnimationStatus) appearAnimateStatusChanged, }) { - appearController = AnimationController( - value: 1.0, vsync: vsync, duration: reorderAnimationDuration); - disappearController = AnimationController( - value: 0, vsync: vsync, duration: reorderAnimationDuration); + appearController = AnimationController(value: 1.0, vsync: vsync, duration: reorderAnimationDuration); + disappearController = AnimationController(value: 0, vsync: vsync, duration: reorderAnimationDuration); appearController.addStatusListener(appearAnimateStatusChanged); } @@ -333,8 +312,7 @@ class FakeDragTarget extends StatefulWidget { State> createState() => _FakeDragTargetState(); } -class _FakeDragTargetState - extends State> { +class _FakeDragTargetState extends State> { bool isDragging = false; @override diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart index 776722b05d..9f0f4eb984 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart @@ -5,9 +5,13 @@ import 'drag_state.dart'; import 'drag_target.dart'; import 'reorder_flex.dart'; -abstract class ReorderFlexDragTargetInterceptor { +/// [DragTargetInterceptor] is used to intercept the [DragTarget]'s +/// [onWillAccept], [OnAccept], and [onLeave] event. +abstract class DragTargetInterceptor { + /// Returns [yes] to receive the [DragTarget]'s event. bool canHandler(FlexDragTargetData dragTargetData); + /// Handle the [DragTarget]'s [onWillAccept] event. bool onWillAccept({ required BuildContext context, required ReorderFlexState reorderFlexState, @@ -16,29 +20,36 @@ abstract class ReorderFlexDragTargetInterceptor { required int dragTargetIndex, }); + /// Handle the [DragTarget]'s [onAccept] event. void onAccept(FlexDragTargetData dragTargetData) {} + /// Handle the [DragTarget]'s [onLeave] event. void onLeave(FlexDragTargetData dragTargetData) {} ReorderFlexDraggableTargetBuilder? get draggableTargetBuilder => null; } -abstract class OverlapReorderFlexDragTargetDelegate { - void dragTargetDidDisappear(); - bool acceptNewDragTargetData( +abstract class OverlapDragTargetDelegate { + void didReturnOriginalDragTarget(); + void didCrossOtherDragTarget( String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex, ); } -class OverlapReorderFlexDragTargetInteceptor - extends ReorderFlexDragTargetInterceptor { +/// [OverlappingDragTargetInteceptor] is used to receive the overlapping +/// [DragTarget] event. If a [DragTarget] child is [DragTarget], it will +/// receive the [DragTarget] event when being dragged. +/// +/// Receive the [DragTarget] event if the [acceptedReorderFlexId] contains +/// the passed in dragTarget' reorderFlexId. +class OverlappingDragTargetInteceptor extends DragTargetInterceptor { final String reorderFlexId; final List acceptedReorderFlexId; - final OverlapReorderFlexDragTargetDelegate delegate; + final OverlapDragTargetDelegate delegate; - OverlapReorderFlexDragTargetInteceptor({ + OverlappingDragTargetInteceptor({ required this.delegate, required this.reorderFlexId, required this.acceptedReorderFlexId, @@ -57,9 +68,9 @@ class OverlapReorderFlexDragTargetInteceptor required String dragTargetId, required int dragTargetIndex}) { if (dragTargetId == dragTargetData.reorderFlexId) { - delegate.dragTargetDidDisappear(); + delegate.didReturnOriginalDragTarget(); } else { - delegate.acceptNewDragTargetData( + delegate.didCrossOtherDragTarget( dragTargetId, dragTargetData, dragTargetIndex, @@ -84,8 +95,7 @@ abstract class CrossReorderFlexDragTargetDelegate { ); } -class CrossReorderFlexDragTargetInterceptor - extends ReorderFlexDragTargetInterceptor { +class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor { final String reorderFlexId; final List acceptedReorderFlexIds; final CrossReorderFlexDragTargetDelegate delegate; @@ -117,14 +127,12 @@ class CrossReorderFlexDragTargetInterceptor @override void onAccept(FlexDragTargetData dragTargetData) { - Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); + Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); } @override void onLeave(FlexDragTargetData dragTargetData) { - Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); + Log.trace('[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); } @override diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index 89aa00de8d..fb861ae3ba 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -14,26 +14,34 @@ typedef OnDragEnded = void Function(); typedef OnReorder = void Function(int fromIndex, int toIndex); typedef OnDeleted = void Function(int deletedIndex); typedef OnInserted = void Function(int insertedIndex); -typedef OnReveivePassedInPhantom = void Function( - FlexDragTargetData dragTargetData, int phantomIndex); +typedef OnReveivePassedInPhantom = void Function(FlexDragTargetData dragTargetData, int phantomIndex); abstract class ReoderFlextDataSource { + /// [identifier] represents the id the [ReorderFlex]. It must be unique. String get identifier; + + /// The number of [ReoderFlexItem]s will be displaied in the [ReorderFlex]. UnmodifiableListView get items; } +/// Each item displaied in the [ReorderFlex] required to implement the [ReoderFlexItem]. abstract class ReoderFlexItem { - /// [id] is used to identify the item + /// [id] is used to identify the item. It must be unique. String get id; } class ReorderFlexConfig { - final bool needsLongPressDraggable = true; + /// The opacity of the dragging widget final double draggingWidgetOpacity = 0.2; + + // How long an animation to reorder an element final Duration reorderAnimationDuration = const Duration(milliseconds: 250); + + // How long an animation to scroll to an off-screen element final Duration scrollAnimationDuration = const Duration(milliseconds: 250); final double? spacing; + const ReorderFlexConfig({this.spacing}); } @@ -42,17 +50,25 @@ class ReorderFlex extends StatefulWidget { final List children; final EdgeInsets? padding; + + /// [direction] How to place the children, default is Axis.vertical final Axis direction; final MainAxisAlignment mainAxisAlignment = MainAxisAlignment.start; + final ScrollController? scrollController; + /// [onDragStarted] is called when start dragging final OnDragStarted? onDragStarted; + + /// [onReorder] is called when dragTarget did end dragging final OnReorder onReorder; + + /// [onDragEnded] is called when dragTarget did end dragging final OnDragEnded? onDragEnded; final ReoderFlextDataSource dataSource; - final ReorderFlexDragTargetInterceptor? interceptor; + final DragTargetInterceptor? interceptor; const ReorderFlex({ Key? key, @@ -74,16 +90,20 @@ class ReorderFlex extends StatefulWidget { String get reorderFlexId => dataSource.identifier; } -class ReorderFlexState extends State - with ReorderFlexMinxi, TickerProviderStateMixin { +class ReorderFlexState extends State with ReorderFlexMinxi, TickerProviderStateMixin { /// Controls scrolls and measures scroll progress. late ScrollController _scrollController; + + /// Records the position of the [Scrollable] ScrollPosition? _attachedScrollPosition; /// Whether or not we are currently scrolling this view to show a widget. bool _scrolling = false; + /// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc. late DraggingState dragState; + + /// [_dragAnimationController] controls the dragging animations late DragAnimationController _dragAnimationController; @override @@ -92,7 +112,6 @@ class ReorderFlexState extends State _dragAnimationController = DragAnimationController( reorderAnimationDuration: widget.config.reorderAnimationDuration, - scrollAnimationDuration: widget.config.scrollAnimationDuration, entranceAnimateStatusChanged: (status) { if (status == AnimationStatus.completed) { setState(() => _requestAnimationToNextIndex()); @@ -111,9 +130,7 @@ class ReorderFlexState extends State _attachedScrollPosition = null; } - _scrollController = widget.scrollController ?? - PrimaryScrollController.of(context) ?? - ScrollController(); + _scrollController = widget.scrollController ?? PrimaryScrollController.of(context) ?? ScrollController(); if (_scrollController.hasClients) { _attachedScrollPosition = Scrollable.of(context)?.position; @@ -235,9 +252,7 @@ class ReorderFlexState extends State ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex <= childIndex - ? [dragTarget, disappearSpace] - : [disappearSpace, dragTarget]); + children: shiftedIndex <= childIndex ? [dragTarget, disappearSpace] : [disappearSpace, dragTarget]); } } @@ -258,9 +273,7 @@ class ReorderFlexState extends State ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex >= childIndex - ? [disappearSpace, dragTarget] - : [dragTarget, disappearSpace]); + children: shiftedIndex >= childIndex ? [disappearSpace, dragTarget] : [dragTarget, disappearSpace]); } } @@ -286,8 +299,7 @@ class ReorderFlexState extends State Widget child, int dragTargetIndex, ) { - final ReoderFlexItem reorderFlexItem = - widget.dataSource.items[dragTargetIndex]; + final ReoderFlexItem reorderFlexItem = widget.dataSource.items[dragTargetIndex]; return ReorderDragTarget( dragTargetData: FlexDragTargetData( draggingIndex: dragTargetIndex, @@ -297,14 +309,12 @@ class ReorderFlexState extends State dragTargetId: reorderFlexItem.id, ), onDragStarted: (draggingWidget, draggingIndex, size) { - Log.debug( - "[DragTarget] Column${widget.dataSource.identifier} start dragging"); + Log.debug("[DragTarget] Column${widget.dataSource.identifier} start dragging"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); }, onDragEnded: (dragTargetData) { - Log.debug( - "[DragTarget]: Column${widget.dataSource.identifier} end dragging"); + Log.debug("[DragTarget]: Column${widget.dataSource.identifier} end dragging"); setState(() { _onReordered( @@ -352,7 +362,7 @@ class ReorderFlexState extends State bool _interceptDragTarget( FlexDragTargetData dragTargetData, - void Function(ReorderFlexDragTargetInterceptor) callback, + void Function(DragTargetInterceptor) callback, ) { final interceptor = widget.interceptor; if (interceptor != null && interceptor.canHandler(dragTargetData)) { @@ -401,8 +411,7 @@ class ReorderFlexState extends State Log.trace( '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); - bool willAccept = - dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; + bool willAccept = dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; setState(() { if (willAccept) { int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex); @@ -429,8 +438,7 @@ class ReorderFlexState extends State } Widget _wrapScrollView({required Widget child}) { - if (widget.scrollController != null && - PrimaryScrollController.of(context) == null) { + if (widget.scrollController != null && PrimaryScrollController.of(context) == null) { return child; } else { return SingleChildScrollView( @@ -484,14 +492,12 @@ class ReorderFlexState extends State void _scrollTo(BuildContext context) { if (_scrolling) return; final RenderObject contextObject = context.findRenderObject()!; - final RenderAbstractViewport viewport = - RenderAbstractViewport.of(contextObject)!; + final RenderAbstractViewport viewport = RenderAbstractViewport.of(contextObject)!; // If and only if the current scroll offset falls in-between the offsets // necessary to reveal the selected context at the top or bottom of the // screen, then it is already on-screen. - final double margin = widget.direction == Axis.horizontal - ? dragState.dropAreaSize.width - : dragState.dropAreaSize.height; + final double margin = + widget.direction == Axis.horizontal ? dragState.dropAreaSize.width : dragState.dropAreaSize.height; if (_scrollController.hasClients) { final double scrollOffset = _scrollController.offset; final double topOffset = max( @@ -502,8 +508,7 @@ class ReorderFlexState extends State _scrollController.position.maxScrollExtent, viewport.getOffsetToReveal(contextObject, 1.0).offset + margin, ); - final bool onScreen = - scrollOffset <= topOffset && scrollOffset >= bottomOffset; + final bool onScreen = scrollOffset <= topOffset && scrollOffset >= bottomOffset; // If the context is off screen, then we request a scroll to make it visible. if (!onScreen) { @@ -511,7 +516,7 @@ class ReorderFlexState extends State _scrollController.position .animateTo( scrollOffset < bottomOffset ? bottomOffset : topOffset, - duration: _dragAnimationController.scrollAnimationDuration, + duration: widget.config.scrollAnimationDuration, curve: Curves.easeInOut, ) .then((void value) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index 69f9fd85aa..aa2d9550d3 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -34,8 +34,7 @@ abstract class BoardPhantomControllerDelegate { ); } -class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate - with CrossReorderFlexDragTargetDelegate { +class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorderFlexDragTargetDelegate { PhantomRecord? phantomRecord; final BoardPhantomControllerDelegate delegate; final columnsState = ColumnPhantomStateController(); @@ -117,8 +116,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate WidgetsBinding.instance.addPostFrameCallback((_) { Future.delayed(const Duration(milliseconds: 100), () { - Log.debug( - '[$BoardPhantomController] notify $toColumnId to insert phantom'); + Log.debug('[$BoardPhantomController] notify $toColumnId to insert phantom'); columnsState.notifyDidInsertPhantom(toColumnId); }); }); @@ -185,7 +183,7 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate } @override - void dragTargetDidDisappear() { + void didReturnOriginalDragTarget() { if (phantomRecord == null) { return; } @@ -193,6 +191,19 @@ class BoardPhantomController extends OverlapReorderFlexDragTargetDelegate _removePhantom(phantomRecord!.toColumnId); phantomRecord = null; } + + @override + void didCrossOtherDragTarget( + String reorderFlexId, + FlexDragTargetData dragTargetData, + int dragTargetIndex, + ) { + acceptNewDragTargetData( + reorderFlexId, + dragTargetData, + dragTargetIndex, + ); + } } /// Use [PhantomRecord] to record where to remove the column item and where to @@ -221,8 +232,7 @@ class PhantomRecord { if (fromColumnIndex == index) { return; } - Log.debug( - '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + Log.debug('[$PhantomRecord] Update Column$fromColumnId remove position to $index'); fromColumnIndex = index; } @@ -231,8 +241,7 @@ class PhantomRecord { return; } - Log.debug( - '[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); + Log.debug('[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); toColumnIndex = index; } @@ -245,8 +254,7 @@ class PhantomRecord { class PhantomColumnItem extends ColumnItem { final PassthroughPhantomContext phantomContext; - PhantomColumnItem(PassthroughPhantomContext insertedPhantom) - : phantomContext = insertedPhantom; + PhantomColumnItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom; @override bool get isPhantom => true; @@ -256,9 +264,8 @@ class PhantomColumnItem extends ColumnItem { Size? get feedbackSize => phantomContext.feedbackSize; - Widget get draggingWidget => phantomContext.draggingWidget == null - ? const SizedBox() - : phantomContext.draggingWidget!; + Widget get draggingWidget => + phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!; @override String toString() { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart index f26cc26f11..d33b53500d 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart @@ -97,13 +97,7 @@ class PhantomInsertNotifier extends ChangeNotifier { } class PhantomDeleteNotifier extends ChangeNotifier { - // int deletedIndex = -1; - void remove() { - // if (this.deletedIndex != deletedIndex) { - // this.deletedIndex = deletedIndex; - // notifyListeners(); - // } notifyListeners(); } } From 151e735d278f69cb8f2d2baa73383d334d008b9a Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 6 Aug 2022 11:21:25 +0800 Subject: [PATCH 14/22] chore: dart fmt --- .../flowy_board/lib/src/utils/log.dart | 2 +- .../lib/src/widgets/board_data.dart | 1 + .../lib/src/widgets/flex/reorder_flex.dart | 44 ++++++++++----- .../lib/src/widgets/flex/reorder_mixin.dart | 54 ++++++++++++------- 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart b/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart index b9f766f961..5d39219f2e 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart @@ -22,7 +22,7 @@ class Log { static void trace(String? message) { if (enableLog) { - // debugPrint('❗️[Trace]=> $message'); + debugPrint('❗️[Trace]=> $message'); } } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart index 13918c1f4e..0e095b1a09 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart @@ -8,6 +8,7 @@ import 'flex/reorder_flex.dart'; import 'package:flutter/material.dart'; import 'phantom/phantom_controller.dart'; +@protected class BoardDataController extends ChangeNotifier with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource { final List _columnDatas = []; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index fb861ae3ba..d2dc7583c8 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -14,7 +14,8 @@ typedef OnDragEnded = void Function(); typedef OnReorder = void Function(int fromIndex, int toIndex); typedef OnDeleted = void Function(int deletedIndex); typedef OnInserted = void Function(int insertedIndex); -typedef OnReveivePassedInPhantom = void Function(FlexDragTargetData dragTargetData, int phantomIndex); +typedef OnReveivePassedInPhantom = void Function( + FlexDragTargetData dragTargetData, int phantomIndex); abstract class ReoderFlextDataSource { /// [identifier] represents the id the [ReorderFlex]. It must be unique. @@ -90,7 +91,8 @@ class ReorderFlex extends StatefulWidget { String get reorderFlexId => dataSource.identifier; } -class ReorderFlexState extends State with ReorderFlexMinxi, TickerProviderStateMixin { +class ReorderFlexState extends State + with ReorderFlexMinxi, TickerProviderStateMixin { /// Controls scrolls and measures scroll progress. late ScrollController _scrollController; @@ -130,7 +132,9 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP _attachedScrollPosition = null; } - _scrollController = widget.scrollController ?? PrimaryScrollController.of(context) ?? ScrollController(); + _scrollController = widget.scrollController ?? + PrimaryScrollController.of(context) ?? + ScrollController(); if (_scrollController.hasClients) { _attachedScrollPosition = Scrollable.of(context)?.position; @@ -252,7 +256,9 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex <= childIndex ? [dragTarget, disappearSpace] : [disappearSpace, dragTarget]); + children: shiftedIndex <= childIndex + ? [dragTarget, disappearSpace] + : [disappearSpace, dragTarget]); } } @@ -273,7 +279,9 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP ]); } else if (childIndex == dragPhantomIndex) { return _buildDraggingContainer( - children: shiftedIndex >= childIndex ? [disappearSpace, dragTarget] : [dragTarget, disappearSpace]); + children: shiftedIndex >= childIndex + ? [disappearSpace, dragTarget] + : [dragTarget, disappearSpace]); } } @@ -299,7 +307,8 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP Widget child, int dragTargetIndex, ) { - final ReoderFlexItem reorderFlexItem = widget.dataSource.items[dragTargetIndex]; + final ReoderFlexItem reorderFlexItem = + widget.dataSource.items[dragTargetIndex]; return ReorderDragTarget( dragTargetData: FlexDragTargetData( draggingIndex: dragTargetIndex, @@ -309,12 +318,14 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP dragTargetId: reorderFlexItem.id, ), onDragStarted: (draggingWidget, draggingIndex, size) { - Log.debug("[DragTarget] Column${widget.dataSource.identifier} start dragging"); + Log.debug( + "[DragTarget] Column${widget.dataSource.identifier} start dragging item at $draggingIndex"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); }, onDragEnded: (dragTargetData) { - Log.debug("[DragTarget]: Column${widget.dataSource.identifier} end dragging"); + Log.debug( + "[DragTarget]: Column${widget.dataSource.identifier} end dragging"); setState(() { _onReordered( @@ -411,7 +422,8 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP Log.trace( '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); - bool willAccept = dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; + bool willAccept = + dragState.dragStartIndex == dragIndex && dragIndex != dragTargetIndex; setState(() { if (willAccept) { int shiftedIndex = dragState.calculateShiftedIndex(dragTargetIndex); @@ -438,7 +450,8 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP } Widget _wrapScrollView({required Widget child}) { - if (widget.scrollController != null && PrimaryScrollController.of(context) == null) { + if (widget.scrollController != null && + PrimaryScrollController.of(context) == null) { return child; } else { return SingleChildScrollView( @@ -492,12 +505,14 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP void _scrollTo(BuildContext context) { if (_scrolling) return; final RenderObject contextObject = context.findRenderObject()!; - final RenderAbstractViewport viewport = RenderAbstractViewport.of(contextObject)!; + final RenderAbstractViewport viewport = + RenderAbstractViewport.of(contextObject)!; // If and only if the current scroll offset falls in-between the offsets // necessary to reveal the selected context at the top or bottom of the // screen, then it is already on-screen. - final double margin = - widget.direction == Axis.horizontal ? dragState.dropAreaSize.width : dragState.dropAreaSize.height; + final double margin = widget.direction == Axis.horizontal + ? dragState.dropAreaSize.width + : dragState.dropAreaSize.height; if (_scrollController.hasClients) { final double scrollOffset = _scrollController.offset; final double topOffset = max( @@ -508,7 +523,8 @@ class ReorderFlexState extends State with ReorderFlexMinxi, TickerP _scrollController.position.maxScrollExtent, viewport.getOffsetToReveal(contextObject, 1.0).offset + margin, ); - final bool onScreen = scrollOffset <= topOffset && scrollOffset >= bottomOffset; + final bool onScreen = + scrollOffset <= topOffset && scrollOffset >= bottomOffset; // If the context is off screen, then we request a scroll to make it visible. if (!onScreen) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart index 76b7d834a4..1d9e764ade 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart @@ -6,60 +6,78 @@ mixin ReorderFlexMinxi { @protected Widget makeAppearingWidget( Widget child, - AnimationController entranceController, + AnimationController animationController, Size? draggingFeedbackSize, Axis direction, ) { + final sizeFactor = animationController.withLinearCurve(); if (null == draggingFeedbackSize) { return SizeTransitionWithIntrinsicSize( - sizeFactor: entranceController, + sizeFactor: sizeFactor, axis: direction, child: FadeTransition( - opacity: entranceController, + opacity: sizeFactor, child: child, ), ); } else { var transition = SizeTransition( - sizeFactor: entranceController, + sizeFactor: sizeFactor, axis: direction, - child: FadeTransition(opacity: entranceController, child: child), + child: FadeTransition(opacity: animationController, child: child), ); - BoxConstraints contentSizeConstraints = - BoxConstraints.loose(draggingFeedbackSize); - return ConstrainedBox( - constraints: contentSizeConstraints, child: transition); + BoxConstraints contentSizeConstraints = BoxConstraints.loose(draggingFeedbackSize); + return ConstrainedBox(constraints: contentSizeConstraints, child: transition); } } @protected Widget makeDisappearingWidget( Widget child, - AnimationController phantomController, + AnimationController animationController, Size? draggingFeedbackSize, Axis direction, ) { + final sizeFactor = animationController.withLinearCurve(); if (null == draggingFeedbackSize) { return SizeTransitionWithIntrinsicSize( - sizeFactor: phantomController, + sizeFactor: sizeFactor, axis: direction, child: FadeTransition( - opacity: phantomController, + opacity: sizeFactor, child: child, ), ); } else { var transition = SizeTransition( - sizeFactor: phantomController, + sizeFactor: sizeFactor, axis: direction, - child: FadeTransition(opacity: phantomController, child: child), + child: FadeTransition(opacity: animationController, child: child), ); - BoxConstraints contentSizeConstraints = - BoxConstraints.loose(draggingFeedbackSize); - return ConstrainedBox( - constraints: contentSizeConstraints, child: transition); + BoxConstraints contentSizeConstraints = BoxConstraints.loose(draggingFeedbackSize); + return ConstrainedBox(constraints: contentSizeConstraints, child: transition); } } } + +Animation withCurve(AnimationController animationController, Cubic curve) { + return CurvedAnimation( + parent: animationController, + curve: curve, + ); +} + +extension CurveAnimationController on AnimationController { + Animation withLinearCurve() { + return withCurve(Curves.linear); + } + + Animation withCurve(Curve curve) { + return CurvedAnimation( + parent: this, + curve: curve, + ); + } +} From c035c9cf930c9aadc41afa7af41d6da095bbece5 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sat, 6 Aug 2022 12:10:10 +0800 Subject: [PATCH 15/22] chore: config insert animation --- .../lib/src/widgets/flex/drag_target.dart | 164 ++++++++++-------- .../widgets/flex/drag_target_inteceptor.dart | 6 +- .../lib/src/widgets/flex/reorder_flex.dart | 30 ++-- .../widgets/phantom/phantom_controller.dart | 44 +++-- 4 files changed, 142 insertions(+), 102 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index 44a177ab8f..4ba0f01d18 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -1,25 +1,35 @@ import 'package:flutter/material.dart'; +import '../transitions.dart'; abstract class DragTargetData { int get draggingIndex; } abstract class ReorderFlexDraggableTargetBuilder { - Widget? build(BuildContext context, Widget child, DragTargetOnStarted onDragStarted, - DragTargetOnEnded onDragEnded, DragTargetWillAccpet onWillAccept); + Widget? build( + BuildContext context, + Widget child, + DragTargetOnStarted onDragStarted, + DragTargetOnEnded onDragEnded, + DragTargetWillAccpet onWillAccept, + AnimationController insertAnimationController, + AnimationController deleteAnimationController, + ); } /// -typedef DragTargetWillAccpet = bool Function(T dragTargetData); +typedef DragTargetWillAccpet = bool Function( + T dragTargetData); /// typedef DragTargetOnStarted = void Function(Widget, int, Size?); /// -typedef DragTargetOnEnded = void Function(T dragTargetData); +typedef DragTargetOnEnded = void Function( + T dragTargetData); /// [ReorderDragTarget] is a [DragTarget] that carries the index information of -/// the child. +/// the child. You could check out this link for more information. /// /// The size of the [ReorderDragTarget] will become zero when it start dragging. /// @@ -52,6 +62,9 @@ class ReorderDragTarget extends StatefulWidget { final ReorderFlexDraggableTargetBuilder? draggableTargetBuilder; + final AnimationController insertAnimationController; + final AnimationController deleteAnimationController; + ReorderDragTarget({ Key? key, required this.child, @@ -59,6 +72,8 @@ class ReorderDragTarget extends StatefulWidget { required this.onDragStarted, required this.onDragEnded, required this.onWillAccept, + required this.insertAnimationController, + required this.deleteAnimationController, this.onAccept, this.onLeave, this.draggableTargetBuilder, @@ -69,7 +84,8 @@ class ReorderDragTarget extends StatefulWidget { State> createState() => _ReorderDragTargetState(); } -class _ReorderDragTargetState extends State> { +class _ReorderDragTargetState + extends State> { /// Returns the dragTarget's size Size? _draggingFeedbackSize = Size.zero; @@ -101,7 +117,8 @@ class _ReorderDragTargetState extends State rejectedCandidates, ) { Widget feedbackBuilder = Builder(builder: (BuildContext context) { - BoxConstraints contentSizeConstraints = BoxConstraints.loose(_draggingFeedbackSize!); + BoxConstraints contentSizeConstraints = + BoxConstraints.loose(_draggingFeedbackSize!); return _buildDraggableFeedback( context, contentSizeConstraints, @@ -115,6 +132,8 @@ class _ReorderDragTargetState extends State( maxSimultaneousDrags: 1, @@ -141,14 +160,16 @@ class _ReorderDragTargetState extends State widget.onDragEnded(widget.dragTargetData), + onDraggableCanceled: (Velocity velocity, Offset offset) => + widget.onDragEnded(widget.dragTargetData), child: widget.child, ); return draggableWidget; } - Widget _buildDraggableFeedback(BuildContext context, BoxConstraints constraints, Widget child) { + Widget _buildDraggableFeedback( + BuildContext context, BoxConstraints constraints, Widget child) { return Transform( transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, @@ -163,7 +184,7 @@ class _ReorderDragTargetState extends State entranceController.isCompleted; + phantomController = AnimationController( + value: 0, vsync: vsync, duration: reorderAnimationDuration); + + insertController = AnimationController( + value: 0.0, vsync: vsync, duration: reorderAnimationDuration); + + deleteController = AnimationController( + value: 0.0, vsync: vsync, duration: reorderAnimationDuration); + } void startDargging() { entranceController.value = 1.0; @@ -203,6 +235,8 @@ class DragAnimationController { void dispose() { entranceController.dispose(); phantomController.dispose(); + insertController.dispose(); + deleteController.dispose(); } } @@ -217,7 +251,9 @@ class IgnorePointerWidget extends StatelessWidget { @override Widget build(BuildContext context) { - final sizedChild = useIntrinsicSize ? child : SizedBox(width: 0.0, height: 0.0, child: child); + final sizedChild = useIntrinsicSize + ? child + : SizedBox(width: 0.0, height: 0.0, child: child); return IgnorePointer( ignoring: true, child: Opacity( @@ -246,42 +282,7 @@ class PhantomWidget extends StatelessWidget { } } -class PhantomAnimateContorller { - // How long an animation to reorder an element in the list takes. - final Duration reorderAnimationDuration; - late AnimationController appearController; - late AnimationController disappearController; - - PhantomAnimateContorller({ - required TickerProvider vsync, - required this.reorderAnimationDuration, - required void Function(AnimationStatus) appearAnimateStatusChanged, - }) { - appearController = AnimationController(value: 1.0, vsync: vsync, duration: reorderAnimationDuration); - disappearController = AnimationController(value: 0, vsync: vsync, duration: reorderAnimationDuration); - appearController.addStatusListener(appearAnimateStatusChanged); - } - - bool get isAppearAnimationCompleted => appearController.isCompleted; - - void animateToNext() { - disappearController.reverse(from: 1.0); - appearController.forward(from: 0.0); - } - - void performReorderAnimation() { - disappearController.reverse(from: 0.1); - appearController.reverse(from: 0.0); - } - - void dispose() { - appearController.dispose(); - disappearController.dispose(); - } -} - abstract class FakeDragTargetEventTrigger { - void fakeOnDragStarted(VoidCallback callback); void fakeOnDragEnded(VoidCallback callback); } @@ -292,12 +293,15 @@ abstract class FakeDragTargetEventData { } class FakeDragTarget extends StatefulWidget { + final Duration animationDuration; final FakeDragTargetEventTrigger eventTrigger; final FakeDragTargetEventData eventData; final DragTargetOnStarted onDragStarted; final DragTargetOnEnded onDragEnded; final DragTargetWillAccpet onWillAccept; final Widget child; + final AnimationController insertAnimationController; + final AnimationController deleteAnimationController; const FakeDragTarget({ Key? key, required this.eventTrigger, @@ -305,34 +309,43 @@ class FakeDragTarget extends StatefulWidget { required this.onDragStarted, required this.onDragEnded, required this.onWillAccept, + required this.insertAnimationController, + required this.deleteAnimationController, required this.child, + this.animationDuration = const Duration(milliseconds: 250), }) : super(key: key); @override State> createState() => _FakeDragTargetState(); } -class _FakeDragTargetState extends State> { - bool isDragging = false; +class _FakeDragTargetState + extends State> + with TickerProviderStateMixin> { + bool simulateDragging = false; @override void initState() { - widget.eventTrigger.fakeOnDragStarted(() { - if (mounted) { - setState(() { - widget.onWillAccept(widget.eventData.dragTargetData as T); - - widget.onDragStarted( - widget.child, - widget.eventData.index, - widget.eventData.feedbackSize, - ); - - isDragging = true; + widget.insertAnimationController.addStatusListener( + (status) { + if (status != AnimationStatus.completed) return; + if (!mounted) return; + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + simulateDragging = true; + widget.deleteAnimationController.reverse(from: 1.0); + widget.onWillAccept(widget.eventData.dragTargetData as T); + widget.onDragStarted( + widget.child, + widget.eventData.index, + widget.eventData.feedbackSize, + ); + }); }); - } - }); + }, + ); + widget.insertAnimationController.forward(from: 0.0); widget.eventTrigger.fakeOnDragEnded(() { if (mounted) { widget.onDragEnded(widget.eventData.dragTargetData as T); @@ -344,10 +357,17 @@ class _FakeDragTargetState extends State /// [dragState] records the dragging state including dragStartIndex, and phantomIndex, etc. late DraggingState dragState; - /// [_dragAnimationController] controls the dragging animations - late DragAnimationController _dragAnimationController; + /// [_animation] controls the dragging animations + late DragTargetAnimation _animation; @override void initState() { dragState = DraggingState(widget.reorderFlexId); - _dragAnimationController = DragAnimationController( + _animation = DragTargetAnimation( reorderAnimationDuration: widget.config.reorderAnimationDuration, entranceAnimateStatusChanged: (status) { if (status == AnimationStatus.completed) { @@ -174,7 +174,7 @@ class ReorderFlexState extends State _attachedScrollPosition = null; } - _dragAnimationController.dispose(); + _animation.dispose(); super.dispose(); } @@ -183,7 +183,7 @@ class ReorderFlexState extends State /// dragging animation is completed. Otherwise, it will get called again /// when the animation finishs. - if (_dragAnimationController.isEntranceAnimationCompleted) { + if (_animation.entranceController.isCompleted) { dragState.removePhantom(); if (!isAcceptingNewTarget && dragState.didDragTargetMoveToNext()) { @@ -191,7 +191,7 @@ class ReorderFlexState extends State } dragState.moveDragTargetToNext(); - _dragAnimationController.animateToNext(); + _animation.animateToNext(); } } @@ -238,7 +238,7 @@ class ReorderFlexState extends State Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize); Widget disappearSpace = _makeDisappearSpace(dragSpace, feedbackSize); - /// When start dragging, the dragTarget, [BoardDragTarget], will + /// When start dragging, the dragTarget, [ReorderDragTarget], will /// return a [IgnorePointerWidget] which size is zero. if (dragState.isPhantomAboveDragTarget()) { //the phantom is moving down, i.e. the tile below the phantom is moving up @@ -337,6 +337,12 @@ class ReorderFlexState extends State }); }, onWillAccept: (FlexDragTargetData dragTargetData) { + Log.debug('Insert animation: ${_animation.deleteController.status}'); + + if (_animation.deleteController.isAnimating) { + return false; + } + assert(widget.dataSource.items.length > dragTargetIndex); if (_interceptDragTarget( @@ -366,6 +372,8 @@ class ReorderFlexState extends State (interceptor) => interceptor.onLeave(dragTargetData), ); }, + insertAnimationController: _animation.insertController, + deleteAnimationController: _animation.deleteController, draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, child: child, ); @@ -387,7 +395,7 @@ class ReorderFlexState extends State Widget _makeAppearSpace(Widget child, Size? feedbackSize) { return makeAppearingWidget( child, - _dragAnimationController.entranceController, + _animation.entranceController, feedbackSize, widget.direction, ); @@ -396,7 +404,7 @@ class ReorderFlexState extends State Widget _makeDisappearSpace(Widget child, Size? feedbackSize) { return makeDisappearingWidget( child, - _dragAnimationController.phantomController, + _animation.phantomController, feedbackSize, widget.direction, ); @@ -409,7 +417,7 @@ class ReorderFlexState extends State ) { setState(() { dragState.startDragging(draggingWidget, dragIndex, feedbackSize); - _dragAnimationController.startDargging(); + _animation.startDargging(); }); } @@ -446,7 +454,7 @@ class ReorderFlexState extends State widget.onReorder.call(fromIndex, toIndex); } - _dragAnimationController.reverseAnimation(); + _animation.reverseAnimation(); } Widget _wrapScrollView({required Widget child}) { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index aa2d9550d3..6a5b91bdd1 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -6,6 +6,7 @@ import '../flex/drag_target.dart'; import '../flex/drag_target_inteceptor.dart'; import 'phantom_state.dart'; +@protected abstract class BoardPhantomControllerDelegate { BoardColumnDataController? controller(String columnId); @@ -34,7 +35,9 @@ abstract class BoardPhantomControllerDelegate { ); } -class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorderFlexDragTargetDelegate { +@protected +class BoardPhantomController extends OverlapDragTargetDelegate + with CrossReorderFlexDragTargetDelegate { PhantomRecord? phantomRecord; final BoardPhantomControllerDelegate delegate; final columnsState = ColumnPhantomStateController(); @@ -60,6 +63,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder columnsState.setColumnIsDragging(columnId, false); } + /// Remove the phanton in the column when the column is end dragging. void columnEndDragging(String columnId) { columnsState.setColumnIsDragging(columnId, true); if (phantomRecord != null) { @@ -91,6 +95,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder /// Remove the phantom in the column if it contains phantom void _removePhantom(String columnId) { + // columnsState.notifyDidRemovePhantom(columnId); + // columnsState.removeColumnListener(columnId); if (delegate.removePhantom(columnId)) { columnsState.notifyDidRemovePhantom(columnId); columnsState.removeColumnListener(columnId); @@ -114,12 +120,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder PhantomColumnItem(phantomContext), ); - WidgetsBinding.instance.addPostFrameCallback((_) { - Future.delayed(const Duration(milliseconds: 100), () { - Log.debug('[$BoardPhantomController] notify $toColumnId to insert phantom'); - columnsState.notifyDidInsertPhantom(toColumnId); - }); - }); + columnsState.notifyDidInsertPhantom(toColumnId); } /// Reset or initial the [PhantomRecord] @@ -160,6 +161,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder final isNewDragTarget = phantomRecord!.toColumnId != reorderFlexId; if (isNewDragTarget) { + /// Remove the phantom when the dragTarget is moved from one column to another column. _removePhantom(phantomRecord!.toColumnId); _resetPhantomRecord(reorderFlexId, dragTargetData, dragTargetIndex); _insertPhantom(reorderFlexId, dragTargetData, dragTargetIndex); @@ -188,6 +190,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder return; } + /// Remove the phantom when the dragTarge is go back to the original column. _removePhantom(phantomRecord!.toColumnId); phantomRecord = null; } @@ -214,6 +217,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorder /// [toColumnId] the column that the phantom moves into /// [toColumnIndex] the index of the phantom moves into the column /// +@protected class PhantomRecord { final String fromColumnId; int fromColumnIndex; @@ -232,7 +236,8 @@ class PhantomRecord { if (fromColumnIndex == index) { return; } - Log.debug('[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + Log.debug( + '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); fromColumnIndex = index; } @@ -241,7 +246,8 @@ class PhantomRecord { return; } - Log.debug('[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); + Log.debug( + '[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); toColumnIndex = index; } @@ -254,7 +260,8 @@ class PhantomRecord { class PhantomColumnItem extends ColumnItem { final PassthroughPhantomContext phantomContext; - PhantomColumnItem(PassthroughPhantomContext insertedPhantom) : phantomContext = insertedPhantom; + PhantomColumnItem(PassthroughPhantomContext insertedPhantom) + : phantomContext = insertedPhantom; @override bool get isPhantom => true; @@ -264,8 +271,9 @@ class PhantomColumnItem extends ColumnItem { Size? get feedbackSize => phantomContext.feedbackSize; - Widget get draggingWidget => - phantomContext.draggingWidget == null ? const SizedBox() : phantomContext.draggingWidget!; + Widget get draggingWidget => phantomContext.draggingWidget == null + ? const SizedBox() + : phantomContext.draggingWidget!; @override String toString() { @@ -303,13 +311,9 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger void fakeOnDragEnded(VoidCallback callback) { onDragEnded = callback; } - - @override - void fakeOnDragStarted(VoidCallback callback) { - onInserted = callback; - } } +@protected class PassthroughPhantomWidget extends PhantomWidget { final PassthroughPhantomContext passthroughPhantomContext; @@ -324,7 +328,9 @@ class PassthroughPhantomWidget extends PhantomWidget { ); } +@protected class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { + PhantomDraggableBuilder(); @override Widget? build( BuildContext context, @@ -332,6 +338,8 @@ class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { DragTargetOnStarted onDragStarted, DragTargetOnEnded onDragEnded, DragTargetWillAccpet onWillAccept, + AnimationController insertAnimationController, + AnimationController deleteAnimationController, ) { if (child is PassthroughPhantomWidget) { return FakeDragTarget( @@ -340,6 +348,8 @@ class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { onDragStarted: onDragStarted, onDragEnded: onDragEnded, onWillAccept: onWillAccept, + insertAnimationController: insertAnimationController, + deleteAnimationController: deleteAnimationController, child: child, ); } else { From 621c9615a1498e6fba62ade0ed39c0058f7d95df Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 7 Aug 2022 10:46:31 +0800 Subject: [PATCH 16/22] chore: update import path --- .../example/lib/multi_board_list_example.dart | 19 ++++-- .../lib/single_board_list_example.dart | 2 +- .../packages/flowy_board/lib/flowy_board.dart | 5 +- .../flowy_board/lib/src/utils/log.dart | 4 +- .../flowy_board/lib/src/widgets/board.dart | 10 ++- .../widgets/board_column/board_column.dart | 2 +- ...controller.dart => board_column_data.dart} | 0 .../lib/src/widgets/board_data.dart | 43 ++++++++++-- .../lib/src/widgets/flex/drag_target.dart | 68 +++++++++++-------- .../lib/src/widgets/flex/reorder_mixin.dart | 15 ++-- .../widgets/phantom/phantom_controller.dart | 9 +-- 11 files changed, 116 insertions(+), 61 deletions(-) rename frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/{data_controller.dart => board_column_data.dart} (100%) diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart index 2e3ead4ee0..c9d7347319 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart @@ -9,7 +9,18 @@ class MultiBoardListExample extends StatefulWidget { } class _MultiBoardListExampleState extends State { - final BoardDataController boardDataController = BoardDataController(); + final BoardDataController boardDataController = BoardDataController( + onMoveColumn: (fromIndex, toIndex) { + debugPrint('Move column from $fromIndex to $toIndex'); + }, + onMoveColumnItem: (columnId, fromIndex, toIndex) { + debugPrint('Column:$columnId move item from $fromIndex to $toIndex'); + }, + onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) { + debugPrint( + 'Column:$fromColumnId move item at $fromIndex to column:$toColumnId $toIndex'); + }, + ); @override void initState() { @@ -34,9 +45,9 @@ class _MultiBoardListExampleState extends State { TextItem("D"), ]); - boardDataController.setColumnData(column1); - boardDataController.setColumnData(column2); - boardDataController.setColumnData(column3); + boardDataController.addColumn(column1); + boardDataController.addColumn(column2); + boardDataController.addColumn(column3); super.initState(); } diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart index e8bfedb344..64c4871e52 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart @@ -20,7 +20,7 @@ class _SingleBoardListExampleState extends State { TextItem("d"), ]); - boardData.setColumnData(column); + boardData.addColumn(column); super.initState(); } diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart index 16d2c612d3..c145be5440 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart @@ -1,6 +1,5 @@ library flowy_board; -export 'src/widgets/board_column/board_column.dart'; -export 'src/widgets/board_column/data_controller.dart'; -export 'src/widgets/board.dart'; +export 'src/widgets/board_column/board_column_data.dart'; export 'src/widgets/board_data.dart'; +export 'src/widgets/board.dart'; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart b/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart index 5d39219f2e..6f923ddf16 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart @@ -6,7 +6,7 @@ const DART_LOG = "Dart_LOG"; class Log { // static const enableLog = bool.hasEnvironment(DART_LOG); // static final shared = Log(); - static const enableLog = true; + static const enableLog = false; static void info(String? message) { if (enableLog) { @@ -22,7 +22,7 @@ class Log { static void trace(String? message) { if (enableLog) { - debugPrint('❗️[Trace]=> $message'); + // debugPrint('❗️[Trace]=> $message'); } } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart index 3cce2b8b99..bef98842c0 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart @@ -1,9 +1,11 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'board_column/board_column.dart'; +import 'board_column/board_column_data.dart'; +import 'board_data.dart'; import 'flex/drag_target_inteceptor.dart'; import 'flex/reorder_flex.dart'; import 'phantom/phantom_controller.dart'; -import '../../flowy_board.dart'; import '../rendering/board_overlay.dart'; class Board extends StatelessWidget { @@ -122,7 +124,8 @@ class BoardContent extends StatefulWidget { } class _BoardContentState extends State { - final GlobalKey _columnContainerOverlayKey = GlobalKey(debugLabel: '$BoardContent overlay key'); + final GlobalKey _columnContainerOverlayKey = + GlobalKey(debugLabel: '$BoardContent overlay key'); late BoardOverlayEntry _overlayEntry; @override @@ -215,7 +218,8 @@ class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource { }); @override - BoardColumnData get columnData => dataController.columnController(columnId).columnData; + BoardColumnData get columnData => + dataController.columnController(columnId).columnData; @override List get acceptedColumnIds => dataController.columnIds; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart index 78cb78950c..f95f4dae5c 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart @@ -6,7 +6,7 @@ import '../../utils/log.dart'; import '../phantom/phantom_controller.dart'; import '../flex/reorder_flex.dart'; import '../flex/drag_target_inteceptor.dart'; -import 'data_controller.dart'; +import 'board_column_data.dart'; typedef OnColumnDragStarted = void Function(int index); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column_data.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/data_controller.dart rename to frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column_data.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart index 0e095b1a09..a5033623fb 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart @@ -2,16 +2,33 @@ import 'dart:collection'; import 'package:equatable/equatable.dart'; -import '../../flowy_board.dart'; import '../utils/log.dart'; +import 'board_column/board_column_data.dart'; import 'flex/reorder_flex.dart'; import 'package:flutter/material.dart'; import 'phantom/phantom_controller.dart'; -@protected +typedef OnMoveColumn = void Function(int fromIndex, int toIndex); + +typedef OnMoveColumnItem = void Function( + String columnId, + int fromIndex, + int toIndex, +); + +typedef OnMoveColumnItemToColumn = void Function( + String fromColumnId, + int fromIndex, + String toColumnId, + int toIndex, +); + class BoardDataController extends ChangeNotifier with EquatableMixin, BoardPhantomControllerDelegate, ReoderFlextDataSource { final List _columnDatas = []; + final OnMoveColumn? onMoveColumn; + final OnMoveColumnItem? onMoveColumnItem; + final OnMoveColumnItemToColumn? onMoveColumnItemToColumn; List get columnDatas => _columnDatas; @@ -21,9 +38,13 @@ class BoardDataController extends ChangeNotifier final LinkedHashMap _columnControllers = LinkedHashMap(); - BoardDataController(); + BoardDataController({ + this.onMoveColumn, + this.onMoveColumnItem, + this.onMoveColumnItemToColumn, + }); - void setColumnData(BoardColumnData columnData) { + void addColumn(BoardColumnData columnData) { final controller = BoardColumnDataController(columnData: columnData); _columnDatas.add(columnData); _columnControllers[columnData.id] = controller; @@ -36,6 +57,7 @@ class BoardDataController extends ChangeNotifier void moveColumn(int fromIndex, int toIndex) { final columnData = _columnDatas.removeAt(fromIndex); _columnDatas.insert(toIndex, columnData); + onMoveColumn?.call(fromIndex, toIndex); notifyListeners(); } @@ -44,10 +66,12 @@ class BoardDataController extends ChangeNotifier assert(columnController != null); if (columnController != null) { columnController.move(fromIndex, toIndex); + onMoveColumnItem?.call(columnId, fromIndex, toIndex); } } @override + @protected void swapColumnItem( String fromColumnId, int fromColumnIndex, @@ -55,11 +79,17 @@ class BoardDataController extends ChangeNotifier int toColumnIndex, ) { final item = columnController(fromColumnId).removeAt(fromColumnIndex); - assert( columnController(toColumnId).items[toColumnIndex] is PhantomColumnItem); columnController(toColumnId).replace(toColumnIndex, item); + + onMoveColumnItemToColumn?.call( + fromColumnId, + fromColumnIndex, + toColumnId, + toColumnIndex, + ); } @override @@ -80,6 +110,7 @@ class BoardDataController extends ChangeNotifier UnmodifiableListView(_columnDatas); @override + @protected bool removePhantom(String columnId) { final columnController = this.columnController(columnId); final index = columnController.items.indexWhere((item) => item.isPhantom); @@ -95,6 +126,7 @@ class BoardDataController extends ChangeNotifier } @override + @protected void updatePhantom(String columnId, int newIndex) { final columnDataController = columnController(columnId); final index = @@ -111,6 +143,7 @@ class BoardDataController extends ChangeNotifier } @override + @protected void insertPhantom(String columnId, int index, PhantomColumnItem item) { columnController(columnId).insert(index, item); } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index 4ba0f01d18..c3b962408d 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -327,29 +327,16 @@ class _FakeDragTargetState @override void initState() { widget.insertAnimationController.addStatusListener( - (status) { - if (status != AnimationStatus.completed) return; - if (!mounted) return; - WidgetsBinding.instance.addPostFrameCallback((_) { - setState(() { - simulateDragging = true; - widget.deleteAnimationController.reverse(from: 1.0); - widget.onWillAccept(widget.eventData.dragTargetData as T); - widget.onDragStarted( - widget.child, - widget.eventData.index, - widget.eventData.feedbackSize, - ); - }); - }); - }, + _onInsertedAnimationStatusChanged, ); + /// Start insert animation widget.insertAnimationController.forward(from: 0.0); + widget.eventTrigger.fakeOnDragEnded(() { - if (mounted) { + WidgetsBinding.instance.addPostFrameCallback((_) { widget.onDragEnded(widget.eventData.dragTargetData as T); - } + }); }); super.initState(); @@ -357,17 +344,40 @@ class _FakeDragTargetState @override Widget build(BuildContext context) { - final child = IgnorePointerWidget( - useIntrinsicSize: !simulateDragging, child: widget.child); + if (simulateDragging) { + return SizeTransitionWithIntrinsicSize( + sizeFactor: widget.deleteAnimationController, + axis: Axis.vertical, + child: IgnorePointerWidget( + child: widget.child, + ), + ); + } else { + return SizeTransitionWithIntrinsicSize( + sizeFactor: widget.insertAnimationController, + axis: Axis.vertical, + child: IgnorePointerWidget( + useIntrinsicSize: true, + child: widget.child, + ), + ); + } + } - final animationController = simulateDragging - ? widget.deleteAnimationController - : widget.insertAnimationController; - - return SizeTransitionWithIntrinsicSize( - sizeFactor: animationController, - axis: Axis.vertical, - child: child, - ); + void _onInsertedAnimationStatusChanged(AnimationStatus status) { + if (status != AnimationStatus.completed) return; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + setState(() { + simulateDragging = true; + widget.deleteAnimationController.reverse(from: 1.0); + widget.onWillAccept(widget.eventData.dragTargetData as T); + widget.onDragStarted( + widget.child, + widget.eventData.index, + widget.eventData.feedbackSize, + ); + }); + }); } } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart index 1d9e764ade..a90ee6a83a 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart @@ -27,8 +27,10 @@ mixin ReorderFlexMinxi { child: FadeTransition(opacity: animationController, child: child), ); - BoxConstraints contentSizeConstraints = BoxConstraints.loose(draggingFeedbackSize); - return ConstrainedBox(constraints: contentSizeConstraints, child: transition); + BoxConstraints contentSizeConstraints = + BoxConstraints.loose(draggingFeedbackSize); + return ConstrainedBox( + constraints: contentSizeConstraints, child: transition); } } @@ -56,13 +58,16 @@ mixin ReorderFlexMinxi { child: FadeTransition(opacity: animationController, child: child), ); - BoxConstraints contentSizeConstraints = BoxConstraints.loose(draggingFeedbackSize); - return ConstrainedBox(constraints: contentSizeConstraints, child: transition); + BoxConstraints contentSizeConstraints = + BoxConstraints.loose(draggingFeedbackSize); + return ConstrainedBox( + constraints: contentSizeConstraints, child: transition); } } } -Animation withCurve(AnimationController animationController, Cubic curve) { +Animation withCurve( + AnimationController animationController, Cubic curve) { return CurvedAnimation( parent: animationController, curve: curve, diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index 6a5b91bdd1..9faece14f2 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart'; -import '../../../flowy_board.dart'; import '../../utils/log.dart'; +import '../board_column/board_column_data.dart'; import '../flex/drag_state.dart'; import '../flex/drag_target.dart'; import '../flex/drag_target_inteceptor.dart'; import 'phantom_state.dart'; -@protected abstract class BoardPhantomControllerDelegate { BoardColumnDataController? controller(String columnId); @@ -35,7 +34,6 @@ abstract class BoardPhantomControllerDelegate { ); } -@protected class BoardPhantomController extends OverlapDragTargetDelegate with CrossReorderFlexDragTargetDelegate { PhantomRecord? phantomRecord; @@ -95,8 +93,6 @@ class BoardPhantomController extends OverlapDragTargetDelegate /// Remove the phantom in the column if it contains phantom void _removePhantom(String columnId) { - // columnsState.notifyDidRemovePhantom(columnId); - // columnsState.removeColumnListener(columnId); if (delegate.removePhantom(columnId)) { columnsState.notifyDidRemovePhantom(columnId); columnsState.removeColumnListener(columnId); @@ -217,7 +213,6 @@ class BoardPhantomController extends OverlapDragTargetDelegate /// [toColumnId] the column that the phantom moves into /// [toColumnIndex] the index of the phantom moves into the column /// -@protected class PhantomRecord { final String fromColumnId; int fromColumnIndex; @@ -313,7 +308,6 @@ class PassthroughPhantomContext extends FakeDragTargetEventTrigger } } -@protected class PassthroughPhantomWidget extends PhantomWidget { final PassthroughPhantomContext passthroughPhantomContext; @@ -328,7 +322,6 @@ class PassthroughPhantomWidget extends PhantomWidget { ); } -@protected class PhantomDraggableBuilder extends ReorderFlexDraggableTargetBuilder { PhantomDraggableBuilder(); @override From 1ba24c00baa1610a536eca5839e57cdd72552523 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 7 Aug 2022 11:17:27 +0800 Subject: [PATCH 17/22] chore: fix reset phantom record bug --- .../example/lib/multi_board_list_example.dart | 5 ++-- .../lib/single_board_list_example.dart | 2 +- .../lib/src/widgets/board_data.dart | 2 +- .../lib/src/widgets/flex/drag_target.dart | 6 ++--- .../lib/src/widgets/flex/reorder_flex.dart | 11 +++++---- .../widgets/phantom/phantom_controller.dart | 24 ++++++++----------- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart index c9d7347319..95b0a64fc6 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart @@ -14,11 +14,10 @@ class _MultiBoardListExampleState extends State { debugPrint('Move column from $fromIndex to $toIndex'); }, onMoveColumnItem: (columnId, fromIndex, toIndex) { - debugPrint('Column:$columnId move item from $fromIndex to $toIndex'); + debugPrint('Move $columnId:$fromIndex to $columnId:$toIndex'); }, onMoveColumnItemToColumn: (fromColumnId, fromIndex, toColumnId, toIndex) { - debugPrint( - 'Column:$fromColumnId move item at $fromIndex to column:$toColumnId $toIndex'); + debugPrint('Move $fromColumnId:$fromIndex to $toColumnId:$toIndex'); }, ); diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart index 64c4871e52..e508896f9f 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart +++ b/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart @@ -56,5 +56,5 @@ class TextItem extends ColumnItem { TextItem(this.s); @override - String get id => throw UnimplementedError(); + String get id => s; } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart index a5033623fb..fe2fca2c92 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart @@ -120,7 +120,7 @@ class BoardDataController extends ChangeNotifier columnController.removeAt(index); Log.debug( - '[$BoardPhantomController] Column$columnId remove phantom, current count: ${columnController.items.length}'); + '[$BoardDataController] Column$columnId remove phantom, current count: ${columnController.items.length}'); } return isExist; } diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart index c3b962408d..fafdcef774 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart @@ -174,7 +174,7 @@ class _ReorderDragTargetState transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, child: Material( - elevation: 2.0, + elevation: 3.0, color: Colors.transparent, borderRadius: BorderRadius.zero, clipBehavior: Clip.hardEdge, @@ -212,10 +212,10 @@ class DragTargetAnimation { value: 0, vsync: vsync, duration: reorderAnimationDuration); insertController = AnimationController( - value: 0.0, vsync: vsync, duration: reorderAnimationDuration); + value: 0.0, vsync: vsync, duration: const Duration(milliseconds: 100)); deleteController = AnimationController( - value: 0.0, vsync: vsync, duration: reorderAnimationDuration); + value: 0.0, vsync: vsync, duration: const Duration(milliseconds: 10)); } void startDargging() { diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart index f8c587d08c..9066c987f0 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart @@ -328,10 +328,13 @@ class ReorderFlexState extends State "[DragTarget]: Column${widget.dataSource.identifier} end dragging"); setState(() { - _onReordered( - dragState.dragStartIndex, - dragState.currentIndex, - ); + if (dragTargetData.reorderFlexId == widget.reorderFlexId) { + _onReordered( + dragState.dragStartIndex, + dragState.currentIndex, + ); + } + dragState.endDragging(); widget.onDragEnded?.call(); }); diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart index 9faece14f2..6b68eefd52 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart @@ -64,26 +64,22 @@ class BoardPhantomController extends OverlapDragTargetDelegate /// Remove the phanton in the column when the column is end dragging. void columnEndDragging(String columnId) { columnsState.setColumnIsDragging(columnId, true); - if (phantomRecord != null) { - if (phantomRecord!.fromColumnId == columnId) { - columnsState.notifyDidRemovePhantom(phantomRecord!.toColumnId); - } - } - _swapColumnData(); - } + if (phantomRecord == null) return; - void _swapColumnData() { - if (phantomRecord == null) { + final fromColumnId = phantomRecord!.fromColumnId; + final toColumnId = phantomRecord!.toColumnId; + if (fromColumnId == columnId) { + columnsState.notifyDidRemovePhantom(toColumnId); + } + + if (columnsState.isDragging(fromColumnId) == false) { return; } - if (columnsState.isDragging(phantomRecord!.fromColumnId) == false) { - return; - } delegate.swapColumnItem( - phantomRecord!.fromColumnId, + fromColumnId, phantomRecord!.fromColumnIndex, - phantomRecord!.toColumnId, + toColumnId, phantomRecord!.toColumnIndex, ); From ae57216c0a041e3e1260fbecf071379663047525 Mon Sep 17 00:00:00 2001 From: appflowy Date: Sun, 7 Aug 2022 16:11:42 +0800 Subject: [PATCH 18/22] chore: add license --- .../app_flowy/packages/flowy_board/LICENSE | 662 +++++++++++++++++- 1 file changed, 661 insertions(+), 1 deletion(-) diff --git a/frontend/app_flowy/packages/flowy_board/LICENSE b/frontend/app_flowy/packages/flowy_board/LICENSE index ba75c69f7f..0ad25db4bd 100644 --- a/frontend/app_flowy/packages/flowy_board/LICENSE +++ b/frontend/app_flowy/packages/flowy_board/LICENSE @@ -1 +1,661 @@ -TODO: Add your license here. + GNU AFFERO GENERAL PUBLIC LICENSE + Version 3, 19 November 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU Affero General Public License is a free, copyleft license for +software and other kinds of works, specifically designed to ensure +cooperation with the community in the case of network server software. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +our General Public Licenses are intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + Developers that use our General Public Licenses protect your rights +with two steps: (1) assert copyright on the software, and (2) offer +you this License which gives you legal permission to copy, distribute +and/or modify the software. + + A secondary benefit of defending all users' freedom is that +improvements made in alternate versions of the program, if they +receive widespread use, become available for other developers to +incorporate. Many developers of free software are heartened and +encouraged by the resulting cooperation. However, in the case of +software used on network servers, this result may fail to come about. +The GNU General Public License permits making a modified version and +letting the public access it on a server without ever releasing its +source code to the public. + + The GNU Affero General Public License is designed specifically to +ensure that, in such cases, the modified source code becomes available +to the community. It requires the operator of a network server to +provide the source code of the modified version running there to the +users of that server. Therefore, public use of a modified version, on +a publicly accessible server, gives the public access to the source +code of the modified version. + + An older license, called the Affero General Public License and +published by Affero, was designed to accomplish similar goals. This is +a different license, not a version of the Affero GPL, but Affero has +released a new version of the Affero GPL which permits relicensing under +this license. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU Affero General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Remote Network Interaction; Use with the GNU General Public License. + + Notwithstanding any other provision of this License, if you modify the +Program, your modified version must prominently offer all users +interacting with it remotely through a computer network (if your version +supports such interaction) an opportunity to receive the Corresponding +Source of your version by providing access to the Corresponding Source +from a network server at no charge, through some standard or customary +means of facilitating copying of software. This Corresponding Source +shall include the Corresponding Source for any work covered by version 3 +of the GNU General Public License that is incorporated pursuant to the +following paragraph. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the work with which it is combined will remain governed by version +3 of the GNU General Public License. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU Affero General Public License from time to time. Such new versions +will be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU Affero General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU Affero General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU Affero General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If your software can interact with users remotely through a computer +network, you should also make sure that it provides a way for users to +get its source. For example, if your program is a web application, its +interface could display a "Source" link that leads users to an archive +of the code. There are many ways you could offer source, and different +solutions will be better for different programs; see section 13 for the +specific requirements. + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU AGPL, see +. From a333f558f41aa04eec386368d951c2586317236b Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 8 Aug 2022 10:27:58 +0800 Subject: [PATCH 19/22] chore: rename flowy_board to appflowy_board --- .github/workflows/dart_lint.yml | 2 +- .../.gitignore | 0 .../packages/appflowy_board/.metadata | 10 + .../packages/appflowy_board/CHANGELOG.md | 9 + .../{flowy_board => appflowy_board}/LICENSE | 0 .../packages/appflowy_board/README.md | 64 +++ .../analysis_options.yaml | 0 .../example/.gitignore | 2 + .../example/README.md | 4 +- .../example/analysis_options.yaml | 0 .../appflowy_board/example/android/.gitignore | 13 + .../example/android/app/build.gradle | 71 +++ .../android/app/src/debug/AndroidManifest.xml | 8 + .../android/app/src/main/AndroidManifest.xml | 34 ++ .../appflowy/board/example/MainActivity.kt | 6 + .../res/drawable-v21/launch_background.xml | 12 + .../main/res/drawable/launch_background.xml | 12 + .../src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 544 bytes .../src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 442 bytes .../src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 721 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1031 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 1443 bytes .../app/src/main/res/values-night/styles.xml | 18 + .../app/src/main/res/values/styles.xml | 18 + .../app/src/profile/AndroidManifest.xml | 8 + .../example/android/build.gradle | 31 ++ .../example/android/gradle.properties | 3 + .../gradle/wrapper/gradle-wrapper.properties | 6 + .../example/android/settings.gradle | 11 + .../appflowy_board/example/ios/.gitignore | 34 ++ .../ios/Flutter/AppFrameworkInfo.plist | 26 + .../example/ios/Flutter/Debug.xcconfig | 1 + .../example/ios/Flutter/Release.xcconfig | 1 + .../ios/Runner.xcodeproj/project.pbxproj | 481 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 87 ++++ .../contents.xcworkspacedata | 3 - .../xcshareddata/IDEWorkspaceChecks.plist | 0 .../xcshareddata/WorkspaceSettings.xcsettings | 8 + .../example/ios/Runner/AppDelegate.swift | 13 + .../AppIcon.appiconset/Contents.json | 122 +++++ .../Icon-App-1024x1024@1x.png | Bin 0 -> 10932 bytes .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 0 -> 564 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 0 -> 1588 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 0 -> 1025 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 0 -> 1716 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 0 -> 1920 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 0 -> 1283 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 0 -> 1895 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 0 -> 2665 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 0 -> 3831 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 0 -> 1888 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 0 -> 3294 bytes .../Icon-App-83.5x83.5@2x.png | Bin 0 -> 3612 bytes .../LaunchImage.imageset/Contents.json | 23 + .../LaunchImage.imageset/LaunchImage.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@2x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/LaunchImage@3x.png | Bin 0 -> 68 bytes .../LaunchImage.imageset/README.md | 5 + .../Runner/Base.lproj/LaunchScreen.storyboard | 37 ++ .../ios/Runner/Base.lproj/Main.storyboard | 26 + .../example/ios/Runner/Info.plist | 49 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + .../example/lib/main.dart | 0 .../example/lib/multi_board_list_example.dart | 2 +- .../lib/single_board_list_example.dart | 2 +- .../example/linux/.gitignore | 0 .../example/linux/CMakeLists.txt | 4 +- .../example/linux/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 11 + .../flutter/generated_plugin_registrant.h | 0 .../linux/flutter/generated_plugins.cmake | 1 - .../example/linux/main.cc | 0 .../example/linux/my_application.cc | 4 +- .../example/linux/my_application.h | 0 .../example/macos/.gitignore | 0 .../macos/Flutter/Flutter-Debug.xcconfig | 1 + .../macos/Flutter/Flutter-Release.xcconfig | 1 + .../Flutter/GeneratedPluginRegistrant.swift | 2 - .../macos/Runner.xcodeproj/project.pbxproj | 66 +-- .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../xcshareddata/xcschemes/Runner.xcscheme | 8 +- .../contents.xcworkspacedata | 7 + .../xcshareddata/IDEWorkspaceChecks.plist | 8 + .../example/macos/Runner/AppDelegate.swift | 0 .../AppIcon.appiconset/Contents.json | 0 .../AppIcon.appiconset/app_icon_1024.png | Bin .../AppIcon.appiconset/app_icon_128.png | Bin .../AppIcon.appiconset/app_icon_16.png | Bin .../AppIcon.appiconset/app_icon_256.png | Bin .../AppIcon.appiconset/app_icon_32.png | Bin .../AppIcon.appiconset/app_icon_512.png | Bin .../AppIcon.appiconset/app_icon_64.png | Bin .../macos/Runner/Base.lproj/MainMenu.xib | 0 .../macos/Runner/Configs/AppInfo.xcconfig | 6 +- .../macos/Runner/Configs/Debug.xcconfig | 0 .../macos/Runner/Configs/Release.xcconfig | 0 .../macos/Runner/Configs/Warnings.xcconfig | 0 .../macos/Runner/DebugProfile.entitlements | 0 .../example/macos/Runner/Info.plist | 0 .../macos/Runner/MainFlutterWindow.swift | 0 .../example/macos/Runner/Release.entitlements | 0 .../example/pubspec.yaml | 27 +- .../example/test/widget_test.dart | 23 +- .../appflowy_board/example/web/favicon.png | Bin 0 -> 917 bytes .../example/web/icons/Icon-192.png | Bin 0 -> 5292 bytes .../example/web/icons/Icon-512.png | Bin 0 -> 8252 bytes .../example/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes .../example/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes .../appflowy_board/example/web/index.html | 58 +++ .../appflowy_board/example/web/manifest.json | 35 ++ .../example/windows/.gitignore | 0 .../example/windows/CMakeLists.txt | 4 +- .../example/windows/flutter/CMakeLists.txt | 0 .../flutter/generated_plugin_registrant.cc | 3 - .../flutter/generated_plugin_registrant.h | 0 .../windows/flutter/generated_plugins.cmake | 1 - .../example/windows/runner/CMakeLists.txt | 0 .../example/windows/runner/Runner.rc | 12 +- .../example/windows/runner/flutter_window.cpp | 0 .../example/windows/runner/flutter_window.h | 0 .../example/windows/runner/main.cpp | 2 +- .../example/windows/runner/resource.h | 0 .../windows/runner/resources/app_icon.ico | Bin .../windows/runner/runner.exe.manifest | 0 .../example/windows/runner/utils.cpp | 0 .../example/windows/runner/utils.h | 0 .../example/windows/runner/win32_window.cpp | 0 .../example/windows/runner/win32_window.h | 0 .../lib/appflowy_board.dart} | 2 +- .../lib/src/rendering/board_overlay.dart | 0 .../lib/src/utils/log.dart | 0 .../lib/src/widgets/board.dart | 0 .../widgets/board_column/board_column.dart | 0 .../board_column/board_column_data.dart | 0 .../lib/src/widgets/board_data.dart | 0 .../lib/src/widgets/flex/drag_state.dart | 0 .../lib/src/widgets/flex/drag_target.dart | 0 .../widgets/flex/drag_target_inteceptor.dart | 0 .../lib/src/widgets/flex/reorder_flex.dart | 0 .../lib/src/widgets/flex/reorder_mixin.dart | 0 .../widgets/phantom/phantom_controller.dart | 0 .../src/widgets/phantom/phantom_state.dart | 0 .../lib/src/widgets/transitions.dart | 0 .../pubspec.yaml | 36 +- .../test/appflowy_board_test.dart | 1 + .../app_flowy/packages/flowy_board/.metadata | 36 -- .../packages/flowy_board/CHANGELOG.md | 3 - .../app_flowy/packages/flowy_board/README.md | 15 - .../flowy_board/example/.vscode/launch.json | 37 -- .../flutter/generated_plugin_registrant.cc | 15 - .../macos/Flutter/Flutter-Debug.xcconfig | 2 - .../macos/Flutter/Flutter-Release.xcconfig | 2 - .../flowy_board/example/macos/Podfile | 40 -- .../flowy_board/example/macos/Podfile.lock | 22 - .../lib/flowy_board_method_channel.dart | 17 - .../lib/flowy_board_platform_interface.dart | 29 -- .../packages/flowy_board/linux/CMakeLists.txt | 47 -- .../flowy_board/linux/flowy_board_plugin.cc | 70 --- .../include/flowy_board/flowy_board_plugin.h | 26 - .../macos/Classes/FlowyBoardPlugin.swift | 19 - .../flowy_board/macos/flowy_board.podspec | 23 - .../packages/flowy_board/windows/.gitignore | 17 - .../flowy_board/windows/CMakeLists.txt | 53 -- .../windows/flowy_board_plugin.cpp | 59 --- .../flowy_board/windows/flowy_board_plugin.h | 32 -- .../windows/flowy_board_plugin_c_api.cpp | 12 - .../flowy_board/flowy_board_plugin_c_api.h | 23 - frontend/app_flowy/pubspec.lock | 4 +- frontend/app_flowy/pubspec.yaml | 3 +- 174 files changed, 1466 insertions(+), 749 deletions(-) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/.gitignore (100%) create mode 100644 frontend/app_flowy/packages/appflowy_board/.metadata create mode 100644 frontend/app_flowy/packages/appflowy_board/CHANGELOG.md rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/LICENSE (100%) create mode 100644 frontend/app_flowy/packages/appflowy_board/README.md rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/analysis_options.yaml (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/.gitignore (98%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/README.md (88%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/analysis_options.yaml (100%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/.gitignore create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/build.gradle create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/debug/AndroidManifest.xml create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/AndroidManifest.xml create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/kotlin/com/appflowy/board/example/MainActivity.kt create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable-v21/launch_background.xml create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable/launch_background.xml create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values-night/styles.xml create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values/styles.xml create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/app/src/profile/AndroidManifest.xml create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/build.gradle create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/gradle.properties create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/gradle/wrapper/gradle-wrapper.properties create mode 100644 frontend/app_flowy/packages/appflowy_board/example/android/settings.gradle create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/.gitignore create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/AppFrameworkInfo.plist create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Debug.xcconfig create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Release.xcconfig create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.pbxproj create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename frontend/app_flowy/packages/{flowy_board/example/macos => appflowy_board/example/ios}/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename frontend/app_flowy/packages/{flowy_board/example/macos => appflowy_board/example/ios}/Runner.xcworkspace/contents.xcworkspacedata (67%) rename frontend/app_flowy/packages/{flowy_board/example/macos => appflowy_board/example/ios}/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist (100%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/AppDelegate.swift create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/LaunchScreen.storyboard create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/Main.storyboard create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Info.plist create mode 100644 frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Runner-Bridging-Header.h rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/lib/main.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/lib/multi_board_list_example.dart (97%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/lib/single_board_list_example.dart (96%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/.gitignore (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/CMakeLists.txt (98%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/flutter/CMakeLists.txt (100%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugin_registrant.cc rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/flutter/generated_plugin_registrant.h (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/flutter/generated_plugins.cmake (98%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/main.cc (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/my_application.cc (96%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/linux/my_application.h (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/.gitignore (100%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Debug.xcconfig create mode 100644 frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Release.xcconfig rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Flutter/GeneratedPluginRegistrant.swift (58%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner.xcodeproj/project.pbxproj (84%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme (93%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata create mode 100644 frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/AppDelegate.swift (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Base.lproj/MainMenu.xib (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Configs/AppInfo.xcconfig (73%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Configs/Debug.xcconfig (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Configs/Release.xcconfig (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Configs/Warnings.xcconfig (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/DebugProfile.entitlements (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Info.plist (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/MainFlutterWindow.swift (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/macos/Runner/Release.entitlements (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/pubspec.yaml (75%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/test/widget_test.dart (53%) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/web/favicon.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-192.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-512.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-maskable-192.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-maskable-512.png create mode 100644 frontend/app_flowy/packages/appflowy_board/example/web/index.html create mode 100644 frontend/app_flowy/packages/appflowy_board/example/web/manifest.json rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/.gitignore (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/CMakeLists.txt (97%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/flutter/CMakeLists.txt (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/flutter/generated_plugin_registrant.cc (50%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/flutter/generated_plugin_registrant.h (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/flutter/generated_plugins.cmake (98%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/CMakeLists.txt (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/Runner.rc (86%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/flutter_window.cpp (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/flutter_window.h (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/main.cpp (94%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/resource.h (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/resources/app_icon.ico (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/runner.exe.manifest (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/utils.cpp (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/utils.h (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/win32_window.cpp (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/example/windows/runner/win32_window.h (100%) rename frontend/app_flowy/packages/{flowy_board/lib/flowy_board.dart => appflowy_board/lib/appflowy_board.dart} (84%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/rendering/board_overlay.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/utils/log.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/board.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/board_column/board_column.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/board_column/board_column_data.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/board_data.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/flex/drag_state.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/flex/drag_target.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/flex/drag_target_inteceptor.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/flex/reorder_flex.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/flex/reorder_mixin.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/phantom/phantom_controller.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/phantom/phantom_state.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/lib/src/widgets/transitions.dart (100%) rename frontend/app_flowy/packages/{flowy_board => appflowy_board}/pubspec.yaml (53%) create mode 100644 frontend/app_flowy/packages/appflowy_board/test/appflowy_board_test.dart delete mode 100644 frontend/app_flowy/packages/flowy_board/.metadata delete mode 100644 frontend/app_flowy/packages/flowy_board/CHANGELOG.md delete mode 100644 frontend/app_flowy/packages/flowy_board/README.md delete mode 100644 frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json delete mode 100644 frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc delete mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig delete mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig delete mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Podfile delete mode 100644 frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock delete mode 100644 frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart delete mode 100644 frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart delete mode 100644 frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt delete mode 100644 frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc delete mode 100644 frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h delete mode 100644 frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift delete mode 100644 frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec delete mode 100644 frontend/app_flowy/packages/flowy_board/windows/.gitignore delete mode 100644 frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt delete mode 100644 frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp delete mode 100644 frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h delete mode 100644 frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp delete mode 100644 frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h diff --git a/.github/workflows/dart_lint.yml b/.github/workflows/dart_lint.yml index eb5703cd72..3ff87dc680 100644 --- a/.github/workflows/dart_lint.yml +++ b/.github/workflows/dart_lint.yml @@ -23,7 +23,7 @@ jobs: uses: actions/checkout@v2 - uses: subosito/flutter-action@v1 with: - flutter-version: '3.0.0' + flutter-version: '3.0.5' channel: "stable" - uses: actions-rs/toolchain@v1 with: diff --git a/frontend/app_flowy/packages/flowy_board/.gitignore b/frontend/app_flowy/packages/appflowy_board/.gitignore similarity index 100% rename from frontend/app_flowy/packages/flowy_board/.gitignore rename to frontend/app_flowy/packages/appflowy_board/.gitignore diff --git a/frontend/app_flowy/packages/appflowy_board/.metadata b/frontend/app_flowy/packages/appflowy_board/.metadata new file mode 100644 index 0000000000..e7011f64f3 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: f1875d570e39de09040c8f79aa13cc56baab8db1 + channel: stable + +project_type: package diff --git a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md new file mode 100644 index 0000000000..119d25f89d --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md @@ -0,0 +1,9 @@ +## 0.0.2 + +* Update documentation + +## 0.0.1 + +* Support drag and drop column +* Support drag and drop column items from one to another + diff --git a/frontend/app_flowy/packages/flowy_board/LICENSE b/frontend/app_flowy/packages/appflowy_board/LICENSE similarity index 100% rename from frontend/app_flowy/packages/flowy_board/LICENSE rename to frontend/app_flowy/packages/appflowy_board/LICENSE diff --git a/frontend/app_flowy/packages/appflowy_board/README.md b/frontend/app_flowy/packages/appflowy_board/README.md new file mode 100644 index 0000000000..922cdd01b2 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/README.md @@ -0,0 +1,64 @@ +# appflowy_board + +The **appflowy_board** is a package that is used in [AppFlowy](https://github.com/AppFlowy-IO/AppFlowy). For the moment, this package is iterated very fast. + + +**appflowy_board** will be a standard git repository when it becomes stable. +## Getting Started + + +```dart +@override + void initState() { + final column1 = BoardColumnData(id: "1", items: [ + TextItem("a"), + TextItem("b"), + TextItem("c"), + TextItem("d"), + ]); + final column2 = BoardColumnData(id: "2", items: [ + TextItem("1"), + TextItem("2"), + TextItem("3"), + TextItem("4"), + TextItem("5"), + ]); + + final column3 = BoardColumnData(id: "3", items: [ + TextItem("A"), + TextItem("B"), + TextItem("C"), + TextItem("D"), + ]); + + boardDataController.addColumn(column1); + boardDataController.addColumn(column2); + boardDataController.addColumn(column3); + + super.initState(); + } + + @override + Widget build(BuildContext context) { + return Board( + dataController: boardDataController, + background: Container(color: Colors.red), + footBuilder: (context, columnData) { + return Container( + color: Colors.purple, + height: 30, + ); + }, + headerBuilder: (context, columnData) { + return Container( + color: Colors.yellow, + height: 30, + ); + }, + cardBuilder: (context, item) { + return _RowWidget(item: item as TextItem, key: ObjectKey(item)); + }, + columnConstraints: const BoxConstraints.tightFor(width: 240), + ); + } +``` \ No newline at end of file diff --git a/frontend/app_flowy/packages/flowy_board/analysis_options.yaml b/frontend/app_flowy/packages/appflowy_board/analysis_options.yaml similarity index 100% rename from frontend/app_flowy/packages/flowy_board/analysis_options.yaml rename to frontend/app_flowy/packages/appflowy_board/analysis_options.yaml diff --git a/frontend/app_flowy/packages/flowy_board/example/.gitignore b/frontend/app_flowy/packages/appflowy_board/example/.gitignore similarity index 98% rename from frontend/app_flowy/packages/flowy_board/example/.gitignore rename to frontend/app_flowy/packages/appflowy_board/example/.gitignore index a8e938c083..34802023f3 100644 --- a/frontend/app_flowy/packages/flowy_board/example/.gitignore +++ b/frontend/app_flowy/packages/appflowy_board/example/.gitignore @@ -45,3 +45,5 @@ app.*.map.json /android/app/debug /android/app/profile /android/app/release + +.metadata \ No newline at end of file diff --git a/frontend/app_flowy/packages/flowy_board/example/README.md b/frontend/app_flowy/packages/appflowy_board/example/README.md similarity index 88% rename from frontend/app_flowy/packages/flowy_board/example/README.md rename to frontend/app_flowy/packages/appflowy_board/example/README.md index 66dda99692..2b3fce4c86 100644 --- a/frontend/app_flowy/packages/flowy_board/example/README.md +++ b/frontend/app_flowy/packages/appflowy_board/example/README.md @@ -1,6 +1,6 @@ -# flowy_board_example +# example -Demonstrates how to use the flowy_board plugin. +A new Flutter project. ## Getting Started diff --git a/frontend/app_flowy/packages/flowy_board/example/analysis_options.yaml b/frontend/app_flowy/packages/appflowy_board/example/analysis_options.yaml similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/analysis_options.yaml rename to frontend/app_flowy/packages/appflowy_board/example/analysis_options.yaml diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/.gitignore b/frontend/app_flowy/packages/appflowy_board/example/android/.gitignore new file mode 100644 index 0000000000..6f568019d3 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/.gitignore @@ -0,0 +1,13 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties +**/*.keystore +**/*.jks diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/build.gradle b/frontend/app_flowy/packages/appflowy_board/example/android/app/build.gradle new file mode 100644 index 0000000000..4466eba652 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/build.gradle @@ -0,0 +1,71 @@ +def localProperties = new Properties() +def localPropertiesFile = rootProject.file('local.properties') +if (localPropertiesFile.exists()) { + localPropertiesFile.withReader('UTF-8') { reader -> + localProperties.load(reader) + } +} + +def flutterRoot = localProperties.getProperty('flutter.sdk') +if (flutterRoot == null) { + throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") +} + +def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +if (flutterVersionCode == null) { + flutterVersionCode = '1' +} + +def flutterVersionName = localProperties.getProperty('flutter.versionName') +if (flutterVersionName == null) { + flutterVersionName = '1.0' +} + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" + +android { + compileSdkVersion flutter.compileSdkVersion + ndkVersion flutter.ndkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + sourceSets { + main.java.srcDirs += 'src/main/kotlin' + } + + defaultConfig { + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "com.appflowy.board.example" + // You can update the following values to match your application needs. + // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-build-configuration. + minSdkVersion flutter.minSdkVersion + targetSdkVersion flutter.targetSdkVersion + versionCode flutterVersionCode.toInteger() + versionName flutterVersionName + } + + buildTypes { + release { + // TODO: Add your own signing config for the release build. + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig signingConfigs.debug + } + } +} + +flutter { + source '../..' +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" +} diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/debug/AndroidManifest.xml b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 0000000000..95bcce0ae3 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/AndroidManifest.xml b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..07ba21884a --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/kotlin/com/appflowy/board/example/MainActivity.kt b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/kotlin/com/appflowy/board/example/MainActivity.kt new file mode 100644 index 0000000000..4cd6f28560 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/kotlin/com/appflowy/board/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.appflowy.board.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable-v21/launch_background.xml b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable-v21/launch_background.xml new file mode 100644 index 0000000000..f74085f3f6 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable-v21/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable/launch_background.xml b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 0000000000..304732f884 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..db77bb4b7b0906d62b1847e87f15cdcacf6a4f29 GIT binary patch literal 544 zcmeAS@N?(olHy`uVBq!ia0vp^9w5xY3?!3`olAj~WQl7;NpOBzNqJ&XDuZK6ep0G} zXKrG8YEWuoN@d~6R2!h8bpbvhu0Wd6uZuB!w&u2PAxD2eNXD>P5D~Wn-+_Wa#27Xc zC?Zj|6r#X(-D3u$NCt}(Ms06KgJ4FxJVv{GM)!I~&n8Bnc94O7-Hd)cjDZswgC;Qs zO=b+9!WcT8F?0rF7!Uys2bs@gozCP?z~o%U|N3vA*22NaGQG zlg@K`O_XuxvZ&Ks^m&R!`&1=spLvfx7oGDKDwpwW`#iqdw@AL`7MR}m`rwr|mZgU`8P7SBkL78fFf!WnuYWm$5Z0 zNXhDbCv&49sM544K|?c)WrFfiZvCi9h0O)B3Pgg&ebxsLQ05GG~ AQ2+n{ literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..17987b79bb8a35cc66c3c1fd44f5a5526c1b78be GIT binary patch literal 442 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA3?vioaBc-sk|nMYCBgY=CFO}lsSJ)O`AMk? zp1FzXsX?iUDV2pMQ*D5Xx&nMcT!A!W`0S9QKQy;}1Cl^CgaH=;G9cpY;r$Q>i*pfB zP2drbID<_#qf;rPZx^FqH)F_D#*k@@q03KywUtLX8Ua?`H+NMzkczFPK3lFz@i_kW%1NOn0|D2I9n9wzH8m|-tHjsw|9>@K=iMBhxvkv6m8Y-l zytQ?X=U+MF$@3 zt`~i=@j|6y)RWMK--}M|=T`o&^Ni>IoWKHEbBXz7?A@mgWoL>!*SXo`SZH-*HSdS+ yn*9;$7;m`l>wYBC5bq;=U}IMqLzqbYCidGC!)_gkIk_C@Uy!y&wkt5C($~2D>~)O*cj@FGjOCM)M>_ixfudOh)?xMu#Fs z#}Y=@YDTwOM)x{K_j*Q;dPdJ?Mz0n|pLRx{4n|)f>SXlmV)XB04CrSJn#dS5nK2lM zrZ9#~WelCp7&e13Y$jvaEXHskn$2V!!DN-nWS__6T*l;H&Fopn?A6HZ-6WRLFP=R` zqG+CE#d4|IbyAI+rJJ`&x9*T`+a=p|0O(+s{UBcyZdkhj=yS1>AirP+0R;mf2uMgM zC}@~JfByORAh4SyRgi&!(cja>F(l*O+nd+@4m$|6K6KDn_&uvCpV23&>G9HJp{xgg zoq1^2_p9@|WEo z*X_Uko@K)qYYv~>43eQGMdbiGbo>E~Q& zrYBH{QP^@Sti!`2)uG{irBBq@y*$B zi#&(U-*=fp74j)RyIw49+0MRPMRU)+a2r*PJ$L5roHt2$UjExCTZSbq%V!HeS7J$N zdG@vOZB4v_lF7Plrx+hxo7(fCV&}fHq)$ literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..d5f1c8d34e7a88e3f88bea192c3a370d44689c3c GIT binary patch literal 1031 zcmeAS@N?(olHy`uVBq!ia0vp^6F``Q8Ax83A=Cw=BuiW)N`mv#O3D+9QW+dm@{>{( zJaZG%Q-e|yQz{EjrrIztFa`(sgt!6~Yi|1%a`XoT0ojZ}lNrNjb9xjc(B0U1_% zz5^97Xt*%oq$rQy4?0GKNfJ44uvxI)gC`h-NZ|&0-7(qS@?b!5r36oQ}zyZrNO3 zMO=Or+<~>+A&uN&E!^Sl+>xE!QC-|oJv`ApDhqC^EWD|@=#J`=d#Xzxs4ah}w&Jnc z$|q_opQ^2TrnVZ0o~wh<3t%W&flvYGe#$xqda2bR_R zvPYgMcHgjZ5nSA^lJr%;<&0do;O^tDDh~=pIxA#coaCY>&N%M2^tq^U%3DB@ynvKo}b?yu-bFc-u0JHzced$sg7S3zqI(2 z#Km{dPr7I=pQ5>FuK#)QwK?Y`E`B?nP+}U)I#c1+FM*1kNvWG|a(TpksZQ3B@sD~b zpQ2)*V*TdwjFOtHvV|;OsiDqHi=6%)o4b!)x$)%9pGTsE z-JL={-Ffv+T87W(Xpooq<`r*VzWQcgBN$$`u}f>-ZQI1BB8ykN*=e4rIsJx9>z}*o zo~|9I;xof literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..4d6372eebdb28e45604e46eeda8dd24651419bc0 GIT binary patch literal 1443 zcmb`G{WsKk6vsdJTdFg%tJav9_E4vzrOaqkWF|A724Nly!y+?N9`YV6wZ}5(X(D_N(?!*n3`|_r0Hc?=PQw&*vnU?QTFY zB_MsH|!j$PP;I}?dppoE_gA(4uc!jV&0!l7_;&p2^pxNo>PEcNJv za5_RT$o2Mf!<+r?&EbHH6nMoTsDOa;mN(wv8RNsHpG)`^ymG-S5By8=l9iVXzN_eG%Xg2@Xeq76tTZ*dGh~Lo9vl;Zfs+W#BydUw zCkZ$o1LqWQO$FC9aKlLl*7x9^0q%0}$OMlp@Kk_jHXOjofdePND+j!A{q!8~Jn+s3 z?~~w@4?egS02}8NuulUA=L~QQfm;MzCGd)XhiftT;+zFO&JVyp2mBww?;QByS_1w! zrQlx%{^cMj0|Bo1FjwY@Q8?Hx0cIPF*@-ZRFpPc#bBw{5@tD(5%sClzIfl8WU~V#u zm5Q;_F!wa$BSpqhN>W@2De?TKWR*!ujY;Yylk_X5#~V!L*Gw~;$%4Q8~Mad z@`-kG?yb$a9cHIApZDVZ^U6Xkp<*4rU82O7%}0jjHlK{id@?-wpN*fCHXyXh(bLt* zPc}H-x0e4E&nQ>y%B-(EL=9}RyC%MyX=upHuFhAk&MLbsF0LP-q`XnH78@fT+pKPW zu72MW`|?8ht^tz$iC}ZwLp4tB;Q49K!QCF3@!iB1qOI=?w z7In!}F~ij(18UYUjnbmC!qKhPo%24?8U1x{7o(+?^Zu0Hx81|FuS?bJ0jgBhEMzf< zCgUq7r2OCB(`XkKcN-TL>u5y#dD6D!)5W?`O5)V^>jb)P)GBdy%t$uUMpf$SNV31$ zb||OojAbvMP?T@$h_ZiFLFVHDmbyMhJF|-_)HX3%m=CDI+ID$0^C>kzxprBW)hw(v zr!Gmda);ICoQyhV_oP5+C%?jcG8v+D@9f?Dk*!BxY}dazmrT@64UrP3hlslANK)bq z$67n83eh}OeW&SV@HG95P|bjfqJ7gw$e+`Hxo!4cx`jdK1bJ>YDSpGKLPZ^1cv$ek zIB?0S<#tX?SJCLWdMd{-ME?$hc7A$zBOdIJ)4!KcAwb=VMov)nK;9z>x~rfT1>dS+ zZ6#`2v@`jgbqq)P22H)Tx2CpmM^o1$B+xT6`(v%5xJ(?j#>Q$+rx_R|7TzDZe{J6q zG1*EcU%tE?!kO%^M;3aM6JN*LAKUVb^xz8-Pxo#jR5(-KBeLJvA@-gxNHx0M-ZJLl z;#JwQoh~9V?`UVo#}{6ka@II>++D@%KqGpMdlQ}?9E*wFcf5(#XQnP$Dk5~%iX^>f z%$y;?M0BLp{O3a(-4A?ewryHrrD%cx#Q^%KY1H zNre$ve+vceSLZcNY4U(RBX&)oZn*Py()h)XkE?PL$!bNb{N5FVI2Y%LKEm%yvpyTP z(1P?z~7YxD~Rf<(a@_y` literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values-night/styles.xml b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values-night/styles.xml new file mode 100644 index 0000000000..06952be745 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values-night/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values/styles.xml b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 0000000000..cb1ef88056 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/app/src/profile/AndroidManifest.xml b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 0000000000..95bcce0ae3 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,8 @@ + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/build.gradle b/frontend/app_flowy/packages/appflowy_board/example/android/build.gradle new file mode 100644 index 0000000000..83ae220041 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/build.gradle @@ -0,0 +1,31 @@ +buildscript { + ext.kotlin_version = '1.6.10' + repositories { + google() + mavenCentral() + } + + dependencies { + classpath 'com.android.tools.build:gradle:7.1.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + } +} + +allprojects { + repositories { + google() + mavenCentral() + } +} + +rootProject.buildDir = '../build' +subprojects { + project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { + project.evaluationDependsOn(':app') +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/gradle.properties b/frontend/app_flowy/packages/appflowy_board/example/android/gradle.properties new file mode 100644 index 0000000000..94adc3a3f9 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.jvmargs=-Xmx1536M +android.useAndroidX=true +android.enableJetifier=true diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/gradle/wrapper/gradle-wrapper.properties b/frontend/app_flowy/packages/appflowy_board/example/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..cc5527d781 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip diff --git a/frontend/app_flowy/packages/appflowy_board/example/android/settings.gradle b/frontend/app_flowy/packages/appflowy_board/example/android/settings.gradle new file mode 100644 index 0000000000..44e62bcf06 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/.gitignore b/frontend/app_flowy/packages/appflowy_board/example/ios/.gitignore new file mode 100644 index 0000000000..7a7f9873ad --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/.gitignore @@ -0,0 +1,34 @@ +**/dgph +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/ephemeral/ +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/AppFrameworkInfo.plist b/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/AppFrameworkInfo.plist new file mode 100644 index 0000000000..8d4492f977 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/AppFrameworkInfo.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + App + CFBundleIdentifier + io.flutter.flutter.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + App + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1.0 + MinimumOSVersion + 9.0 + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Debug.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Debug.xcconfig new file mode 100644 index 0000000000..592ceee85b --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Debug.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Release.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Release.xcconfig new file mode 100644 index 0000000000..592ceee85b --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Flutter/Release.xcconfig @@ -0,0 +1 @@ +#include "Generated.xcconfig" diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.pbxproj b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..74374c8530 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,481 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 50; + objects = { + +/* Begin PBXBuildFile section */ + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; +/* End PBXBuildFile section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 9705A1C41CF9048500538489 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; + 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; + 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; + 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; + 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 97C146EB1CF9000F007C117D /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 9740EEB11CF90186004384FC /* Flutter */ = { + isa = PBXGroup; + children = ( + 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 9740EEB31CF90195004384FC /* Generated.xcconfig */, + ); + name = Flutter; + sourceTree = ""; + }; + 97C146E51CF9000F007C117D = { + isa = PBXGroup; + children = ( + 9740EEB11CF90186004384FC /* Flutter */, + 97C146F01CF9000F007C117D /* Runner */, + 97C146EF1CF9000F007C117D /* Products */, + ); + sourceTree = ""; + }; + 97C146EF1CF9000F007C117D /* Products */ = { + isa = PBXGroup; + children = ( + 97C146EE1CF9000F007C117D /* Runner.app */, + ); + name = Products; + sourceTree = ""; + }; + 97C146F01CF9000F007C117D /* Runner */ = { + isa = PBXGroup; + children = ( + 97C146FA1CF9000F007C117D /* Main.storyboard */, + 97C146FD1CF9000F007C117D /* Assets.xcassets */, + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, + 97C147021CF9000F007C117D /* Info.plist */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, + 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, + 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, + ); + path = Runner; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 97C146ED1CF9000F007C117D /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9740EEB61CF901F6004384FC /* Run Script */, + 97C146EA1CF9000F007C117D /* Sources */, + 97C146EB1CF9000F007C117D /* Frameworks */, + 97C146EC1CF9000F007C117D /* Resources */, + 9705A1C41CF9048500538489 /* Embed Frameworks */, + 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Runner; + productName = Runner; + productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 97C146E61CF9000F007C117D /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 1300; + ORGANIZATIONNAME = ""; + TargetAttributes = { + 97C146ED1CF9000F007C117D = { + CreatedOnToolsVersion = 7.3.1; + LastSwiftMigration = 1100; + }; + }; + }; + buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 97C146E51CF9000F007C117D; + productRefGroup = 97C146EF1CF9000F007C117D /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 97C146ED1CF9000F007C117D /* Runner */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 97C146EC1CF9000F007C117D /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, + 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, + 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, + 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Thin Binary"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + }; + 9740EEB61CF901F6004384FC /* Run Script */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "Run Script"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 97C146EA1CF9000F007C117D /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXVariantGroup section */ + 97C146FA1CF9000F007C117D /* Main.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C146FB1CF9000F007C117D /* Base */, + ); + name = Main.storyboard; + sourceTree = ""; + }; + 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + 97C147001CF9000F007C117D /* Base */, + ); + name = LaunchScreen.storyboard; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.board.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; + 97C147031CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + }; + name = Debug; + }; + 97C147041CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 97C147061CF9000F007C117D /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.board.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Debug; + }; + 97C147071CF9000F007C117D /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + ENABLE_BITCODE = NO; + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.board.example; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; + SWIFT_VERSION = 5.0; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147031CF9000F007C117D /* Debug */, + 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 97C147061CF9000F007C117D /* Debug */, + 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 97C146E61CF9000F007C117D /* Project object */; +} diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..919434a625 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f9b0d7c5ea --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 0000000000..c87d15a335 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 67% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata rename to frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/contents.xcworkspacedata index 21a3cc14c7..1d526a16ed 100644 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/contents.xcworkspacedata @@ -4,7 +4,4 @@ - - diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist rename to frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 0000000000..f9b0d7c5ea --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/AppDelegate.swift b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/AppDelegate.swift new file mode 100644 index 0000000000..70693e4a8c --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 0000000000..d36b1fab2d --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,122 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "20x20", + "idiom" : "iphone", + "filename" : "Icon-App-20x20@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Icon-App-29x29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Icon-App-40x40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Icon-App-60x60@3x.png", + "scale" : "3x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@1x.png", + "scale" : "1x" + }, + { + "size" : "20x20", + "idiom" : "ipad", + "filename" : "Icon-App-20x20@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@1x.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Icon-App-29x29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@1x.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Icon-App-40x40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@1x.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Icon-App-76x76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Icon-App-83.5x83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Icon-App-1024x1024@1x.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..dc9ada4725e9b0ddb1deab583e5b5102493aa332 GIT binary patch literal 10932 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 literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f091b6b0bca859a3f474b03065bef75ba58a9e4c GIT binary patch literal 1588 zcmV-42Fv-0P)C1SqPt}wig>|5Crh^=oyX$BK<}M8eLU3e2hGT;=G|!_SP)7zNI6fqUMB=)y zRAZ>eDe#*r`yDAVgB_R*LB*MAc)8(b{g{9McCXW!lq7r(btRoB9!8B-#AI6JMb~YFBEvdsV)`mEQO^&#eRKx@b&x- z5lZm*!WfD8oCLzfHGz#u7sT0^VLMI1MqGxF^v+`4YYnVYgk*=kU?HsSz{v({E3lb9 z>+xILjBN)t6`=g~IBOelGQ(O990@BfXf(DRI5I$qN$0Gkz-FSc$3a+2fX$AedL4u{ z4V+5Ong(9LiGcIKW?_352sR;LtDPmPJXI{YtT=O8=76o9;*n%_m|xo!i>7$IrZ-{l z-x3`7M}qzHsPV@$v#>H-TpjDh2UE$9g6sysUREDy_R(a)>=eHw-WAyfIN z*qb!_hW>G)Tu8nSw9yn#3wFMiLcfc4pY0ek1}8(NqkBR@t4{~oC>ryc-h_ByH(Cg5 z>ao-}771+xE3um9lWAY1FeQFxowa1(!J(;Jg*wrg!=6FdRX+t_<%z&d&?|Bn){>zm zZQj(aA_HeBY&OC^jj*)N`8fa^ePOU72VpInJoI1?`ty#lvlNzs(&MZX+R%2xS~5Kh zX*|AU4QE#~SgPzOXe9>tRj>hjU@c1k5Y_mW*Jp3fI;)1&g3j|zDgC+}2Q_v%YfDax z!?umcN^n}KYQ|a$Lr+51Nf9dkkYFSjZZjkma$0KOj+;aQ&721~t7QUKx61J3(P4P1 zstI~7-wOACnWP4=8oGOwz%vNDqD8w&Q`qcNGGrbbf&0s9L0De{4{mRS?o0MU+nR_! zrvshUau0G^DeMhM_v{5BuLjb#Hh@r23lDAk8oF(C+P0rsBpv85EP>4CVMx#04MOfG z;P%vktHcXwTj~+IE(~px)3*MY77e}p#|c>TD?sMatC0Tu4iKKJ0(X8jxQY*gYtxsC z(zYC$g|@+I+kY;dg_dE>scBf&bP1Nc@Hz<3R)V`=AGkc;8CXqdi=B4l2k|g;2%#m& z*jfX^%b!A8#bI!j9-0Fi0bOXl(-c^AB9|nQaE`*)Hw+o&jS9@7&Gov#HbD~#d{twV zXd^Tr^mWLfFh$@Dr$e;PBEz4(-2q1FF0}c;~B5sA}+Q>TOoP+t>wf)V9Iy=5ruQa;z)y zI9C9*oUga6=hxw6QasLPnee@3^Rr*M{CdaL5=R41nLs(AHk_=Y+A9$2&H(B7!_pURs&8aNw7?`&Z&xY_Ye z)~D5Bog^td-^QbUtkTirdyK^mTHAOuptDflut!#^lnKqU md>ggs(5nOWAqO?umG&QVYK#ibz}*4>0000U6E9hRK9^#O7(mu>ETqrXGsduA8$)?`v2seloOCza43C{NQ$$gAOH**MCn0Q?+L7dl7qnbRdqZ8LSVp1ItDxhxD?t@5_yHg6A8yI zC*%Wgg22K|8E#!~cTNYR~@Y9KepMPrrB8cABapAFa=`H+UGhkXUZV1GnwR1*lPyZ;*K(i~2gp|@bzp8}og7e*#% zEnr|^CWdVV!-4*Y_7rFvlww2Ze+>j*!Z!pQ?2l->4q#nqRu9`ELo6RMS5=br47g_X zRw}P9a7RRYQ%2Vsd0Me{_(EggTnuN6j=-?uFS6j^u69elMypu?t>op*wBx<=Wx8?( ztpe^(fwM6jJX7M-l*k3kEpWOl_Vk3@(_w4oc}4YF4|Rt=2V^XU?#Yz`8(e?aZ@#li0n*=g^qOcVpd-Wbok=@b#Yw zqn8u9a)z>l(1kEaPYZ6hwubN6i<8QHgsu0oE) ziJ(p;Wxm>sf!K+cw>R-(^Y2_bahB+&KI9y^);#0qt}t-$C|Bo71lHi{_+lg#f%RFy z0um=e3$K3i6K{U_4K!EX?F&rExl^W|G8Z8;`5z-k}OGNZ0#WVb$WCpQu-_YsiqKP?BB# vzVHS-CTUF4Ozn5G+mq_~Qqto~ahA+K`|lyv3(-e}00000NkvXXu0mjfd`9t{ literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..d0ef06e7edb86cdfe0d15b4b0d98334a86163658 GIT binary patch literal 1716 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 literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..c8f9ed8f5cee1c98386d13b17e89f719e83555b2 GIT binary patch literal 1895 zcmV-t2blPYP)FQtfgmafE#=YDCq`qUBt#QpG%*H6QHY765~R=q zZ6iudfM}q!Pz#~9JgOi8QJ|DSu?1-*(kSi1K4#~5?#|rh?sS)(-JQqX*}ciXJ56_H zdw=^s_srbAdqxlvGyrgGet#6T7_|j;95sL%MtM;q86vOxKM$f#puR)Bjv9Zvz9-di zXOTSsZkM83)E9PYBXC<$6(|>lNLVBb&&6y{NByFCp%6+^ALR@NCTse_wqvNmSWI-m z!$%KlHFH2omF!>#%1l3LTZg(s7eof$7*xB)ZQ0h?ejh?Ta9fDv59+u#MokW+1t8Zb zgHv%K(u9G^Lv`lh#f3<6!JVTL3(dCpxHbnbA;kKqQyd1~^Xe0VIaYBSWm6nsr;dFj z4;G-RyL?cYgsN1{L4ZFFNa;8)Rv0fM0C(~Tkit94 zz#~A)59?QjD&pAPSEQ)p8gP|DS{ng)j=2ux)_EzzJ773GmQ_Cic%3JJhC0t2cx>|v zJcVusIB!%F90{+}8hG3QU4KNeKmK%T>mN57NnCZ^56=0?&3@!j>a>B43pi{!u z7JyDj7`6d)qVp^R=%j>UIY6f+3`+qzIc!Y_=+uN^3BYV|o+$vGo-j-Wm<10%A=(Yk^beI{t%ld@yhKjq0iNjqN4XMGgQtbKubPM$JWBz}YA65k%dm*awtC^+f;a-x4+ddbH^7iDWGg&N0n#MW{kA|=8iMUiFYvMoDY@sPC#t$55gn6ykUTPAr`a@!(;np824>2xJthS z*ZdmT`g5-`BuJs`0LVhz+D9NNa3<=6m;cQLaF?tCv8)zcRSh66*Z|vXhG@$I%U~2l z?`Q zykI#*+rQ=z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRe zt3L_uNyQ*cE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=ky zx=~RKa4{iTm{_>_vSCm?$Ej=i6@=m%@VvAITnigVg{&@!7CDgs908761meDK5azA} z4?=NOH|PdvabgJ&fW2{Mo$Q0CcD8Qc84%{JPYt5EiG{MdLIAeX%T=D7NIP4%Hw}p9 zg)==!2Lbp#j{u_}hMiao9=!VSyx0gHbeCS`;q&vzeq|fs`y&^X-lso(Ls@-706qmA z7u*T5PMo_w3{se1t2`zWeO^hOvTsohG_;>J0wVqVe+n)AbQCx)yh9;w+J6?NF5Lmo zecS@ieAKL8%bVd@+-KT{yI|S}O>pYckUFs;ry9Ow$CD@ztz5K-*D$^{i(_1llhSh^ zEkL$}tsQt5>QA^;QgjgIfBDmcOgi5YDyu?t6vSnbp=1+@6D& z5MJ}B8q;bRlVoxasyhcUF1+)o`&3r0colr}QJ3hcSdLu;9;td>kf@Tcn<@9sIx&=m z;AD;SCh95=&p;$r{Xz3iWCO^MX83AGJ(yH&eTXgv|0=34#-&WAmw{)U7OU9!Wz^!7 zZ%jZFi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i h0WYBP*#0Ks^FNSabJA*5${_#%002ovPDHLkV1oKhTl@e3 literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..a6d6b8609df07bf62e5100a53a01510388bd2b22 GIT binary patch literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..75b2d164a5a98e212cca15ea7bf2ab5de5108680 GIT binary patch literal 3831 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 literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..c4df70d39da7941ef3f6dcb7f06a192d8dcb308d GIT binary patch literal 1888 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 iW8Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9da19eacad3b03bb08bbddbbf4ac48dd78b3d838 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx0wlM}@Gt=>Zci7-kcv6Uzs@r-FtIZ-&5|)J Q1PU{Fy85}Sb4q9e0B4a5jsO4v literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 0000000000..89c2725b70 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/LaunchScreen.storyboard new file mode 100644 index 0000000000..f2e259c7c9 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/Main.storyboard b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/Main.storyboard new file mode 100644 index 0000000000..f3c28516fb --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Base.lproj/Main.storyboard @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Info.plist b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Info.plist new file mode 100644 index 0000000000..907f329fe0 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Info.plist @@ -0,0 +1,49 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + example + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleSignature + ???? + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSRequiresIPhoneOS + + UILaunchStoryboardName + LaunchScreen + UIMainStoryboardFile + Main + UISupportedInterfaceOrientations + + UIInterfaceOrientationPortrait + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationPortrait + UIInterfaceOrientationPortraitUpsideDown + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UIViewControllerBasedStatusBarAppearance + + CADisableMinimumFrameDurationOnPhone + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Runner-Bridging-Header.h b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 0000000000..308a2a560b --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/main.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/main.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/lib/main.dart rename to frontend/app_flowy/packages/appflowy_board/example/lib/main.dart diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart similarity index 97% rename from frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart rename to frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart index 95b0a64fc6..8715a4450c 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart @@ -1,4 +1,4 @@ -import 'package:flowy_board/flowy_board.dart'; +import 'package:appflowy_board/appflowy_board.dart'; import 'package:flutter/material.dart'; class MultiBoardListExample extends StatefulWidget { diff --git a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart similarity index 96% rename from frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart rename to frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart index e508896f9f..655f1439c1 100644 --- a/frontend/app_flowy/packages/flowy_board/example/lib/single_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/single_board_list_example.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:flowy_board/flowy_board.dart'; +import 'package:appflowy_board/appflowy_board.dart'; class SingleBoardListExample extends StatefulWidget { const SingleBoardListExample({Key? key}) : super(key: key); diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/.gitignore b/frontend/app_flowy/packages/appflowy_board/example/linux/.gitignore similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/linux/.gitignore rename to frontend/app_flowy/packages/appflowy_board/example/linux/.gitignore diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/CMakeLists.txt b/frontend/app_flowy/packages/appflowy_board/example/linux/CMakeLists.txt similarity index 98% rename from frontend/app_flowy/packages/flowy_board/example/linux/CMakeLists.txt rename to frontend/app_flowy/packages/appflowy_board/example/linux/CMakeLists.txt index f10cda2c6c..697a9f905a 100644 --- a/frontend/app_flowy/packages/flowy_board/example/linux/CMakeLists.txt +++ b/frontend/app_flowy/packages/appflowy_board/example/linux/CMakeLists.txt @@ -4,10 +4,10 @@ project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "flowy_board_example") +set(BINARY_NAME "example") # The unique GTK application identifier for this application. See: # https://wiki.gnome.org/HowDoI/ChooseApplicationID -set(APPLICATION_ID "com.example.flowy_board") +set(APPLICATION_ID "com.appflowy.board.example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/CMakeLists.txt b/frontend/app_flowy/packages/appflowy_board/example/linux/flutter/CMakeLists.txt similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/linux/flutter/CMakeLists.txt rename to frontend/app_flowy/packages/appflowy_board/example/linux/flutter/CMakeLists.txt diff --git a/frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugin_registrant.cc b/frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugin_registrant.cc new file mode 100644 index 0000000000..e71a16d23d --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugin_registrant.cc @@ -0,0 +1,11 @@ +// +// Generated file. Do not edit. +// + +// clang-format off + +#include "generated_plugin_registrant.h" + + +void fl_register_plugins(FlPluginRegistry* registry) { +} diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.h b/frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugin_registrant.h similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.h rename to frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugin_registrant.h diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugins.cmake b/frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugins.cmake similarity index 98% rename from frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugins.cmake rename to frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugins.cmake index dfc1c21713..2e1de87a7e 100644 --- a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugins.cmake +++ b/frontend/app_flowy/packages/appflowy_board/example/linux/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - flowy_board ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/main.cc b/frontend/app_flowy/packages/appflowy_board/example/linux/main.cc similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/linux/main.cc rename to frontend/app_flowy/packages/appflowy_board/example/linux/main.cc diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/my_application.cc b/frontend/app_flowy/packages/appflowy_board/example/linux/my_application.cc similarity index 96% rename from frontend/app_flowy/packages/flowy_board/example/linux/my_application.cc rename to frontend/app_flowy/packages/appflowy_board/example/linux/my_application.cc index 305813bfa8..0ba8f43096 100644 --- a/frontend/app_flowy/packages/flowy_board/example/linux/my_application.cc +++ b/frontend/app_flowy/packages/appflowy_board/example/linux/my_application.cc @@ -40,11 +40,11 @@ static void my_application_activate(GApplication* application) { if (use_header_bar) { GtkHeaderBar* header_bar = GTK_HEADER_BAR(gtk_header_bar_new()); gtk_widget_show(GTK_WIDGET(header_bar)); - gtk_header_bar_set_title(header_bar, "flowy_board_example"); + gtk_header_bar_set_title(header_bar, "example"); gtk_header_bar_set_show_close_button(header_bar, TRUE); gtk_window_set_titlebar(window, GTK_WIDGET(header_bar)); } else { - gtk_window_set_title(window, "flowy_board_example"); + gtk_window_set_title(window, "example"); } gtk_window_set_default_size(window, 1280, 720); diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/my_application.h b/frontend/app_flowy/packages/appflowy_board/example/linux/my_application.h similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/linux/my_application.h rename to frontend/app_flowy/packages/appflowy_board/example/linux/my_application.h diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/.gitignore b/frontend/app_flowy/packages/appflowy_board/example/macos/.gitignore similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/.gitignore rename to frontend/app_flowy/packages/appflowy_board/example/macos/.gitignore diff --git a/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Debug.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 0000000000..c2efd0b608 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Release.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 0000000000..c2efd0b608 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1 @@ +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift b/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift similarity index 58% rename from frontend/app_flowy/packages/flowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift rename to frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift index aaacabcec9..cccf817a52 100644 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,6 @@ import FlutterMacOS import Foundation -import flowy_board func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { - FlowyBoardPlugin.register(with: registry.registrar(forPlugin: "FlowyBoardPlugin")) } diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/project.pbxproj similarity index 84% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/project.pbxproj index d60feedcfe..c84862c675 100644 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/project.pbxproj +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ /* End PBXAggregateTarget section */ /* Begin PBXBuildFile section */ - 2AD48017E3EAE6142B6E265B /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 368626E4047E7783820AEC34 /* Pods_Runner.framework */; }; 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; @@ -53,10 +52,9 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 02BDE4CD9C63CA2562B2FDD1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; - 33CC10ED2044A3C60003C045 /* flowy_board_example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = flowy_board_example.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10ED2044A3C60003C045 /* example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; @@ -68,11 +66,8 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 368626E4047E7783820AEC34 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 59D896B3478D0A2144E570BB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - F368AC3EE3CE4F2FCEC85166 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -80,7 +75,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2AD48017E3EAE6142B6E265B /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -105,14 +99,13 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 33CC10EE2044A3C60003C045 /* Products */, D73912EC22F37F3D000D13A0 /* Frameworks */, - 6BBA375F5E43A645EC061EA0 /* Pods */, ); sourceTree = ""; }; 33CC10EE2044A3C60003C045 /* Products */ = { isa = PBXGroup; children = ( - 33CC10ED2044A3C60003C045 /* flowy_board_example.app */, + 33CC10ED2044A3C60003C045 /* example.app */, ); name = Products; sourceTree = ""; @@ -152,21 +145,9 @@ path = Runner; sourceTree = ""; }; - 6BBA375F5E43A645EC061EA0 /* Pods */ = { - isa = PBXGroup; - children = ( - F368AC3EE3CE4F2FCEC85166 /* Pods-Runner.debug.xcconfig */, - 02BDE4CD9C63CA2562B2FDD1 /* Pods-Runner.release.xcconfig */, - 59D896B3478D0A2144E570BB /* Pods-Runner.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; D73912EC22F37F3D000D13A0 /* Frameworks */ = { isa = PBXGroup; children = ( - 368626E4047E7783820AEC34 /* Pods_Runner.framework */, ); name = Frameworks; sourceTree = ""; @@ -178,13 +159,11 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - DF704441B638BDD90A6EF3BC /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, 33CC110E2044A8840003C045 /* Bundle Framework */, 3399D490228B24CF009A79C7 /* ShellScript */, - B89DDA2E6C13BED8F107400D /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -193,7 +172,7 @@ ); name = Runner; productName = Runner; - productReference = 33CC10ED2044A3C60003C045 /* flowy_board_example.app */; + productReference = 33CC10ED2044A3C60003C045 /* example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -291,45 +270,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - B89DDA2E6C13BED8F107400D /* [CP] Embed Pods Frameworks */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; - }; - DF704441B638BDD90A6EF3BC /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ diff --git a/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 93% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index cd0ca25ddd..fb7259e177 100644 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -15,7 +15,7 @@ @@ -31,7 +31,7 @@ @@ -54,7 +54,7 @@ @@ -71,7 +71,7 @@ diff --git a/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..1d526a16ed --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000000..18d981003d --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/AppDelegate.swift b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/AppDelegate.swift similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/AppDelegate.swift rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/AppDelegate.swift diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Base.lproj/MainMenu.xib b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Base.lproj/MainMenu.xib similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Base.lproj/MainMenu.xib rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Base.lproj/MainMenu.xib diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/AppInfo.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/AppInfo.xcconfig similarity index 73% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/AppInfo.xcconfig rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/AppInfo.xcconfig index 588195548a..e379d4a333 100644 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/AppInfo.xcconfig +++ b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/AppInfo.xcconfig @@ -5,10 +5,10 @@ // 'flutter create' template. // The application's name. By default this is also the title of the Flutter window. -PRODUCT_NAME = flowy_board_example +PRODUCT_NAME = example // The application's bundle identifier -PRODUCT_BUNDLE_IDENTIFIER = com.example.flowyBoardExample +PRODUCT_BUNDLE_IDENTIFIER = com.appflowy.board.example // The copyright displayed in application information -PRODUCT_COPYRIGHT = Copyright © 2022 com.example. All rights reserved. +PRODUCT_COPYRIGHT = Copyright © 2022 com.appflowy.board. All rights reserved. diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Debug.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/Debug.xcconfig similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Debug.xcconfig rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/Debug.xcconfig diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Release.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/Release.xcconfig similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Release.xcconfig rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/Release.xcconfig diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Warnings.xcconfig b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/Warnings.xcconfig similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Configs/Warnings.xcconfig rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Configs/Warnings.xcconfig diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/DebugProfile.entitlements b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/DebugProfile.entitlements similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/DebugProfile.entitlements rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/DebugProfile.entitlements diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Info.plist b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Info.plist similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Info.plist rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Info.plist diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/MainFlutterWindow.swift b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/MainFlutterWindow.swift similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/MainFlutterWindow.swift rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/MainFlutterWindow.swift diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Runner/Release.entitlements b/frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Release.entitlements similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/macos/Runner/Release.entitlements rename to frontend/app_flowy/packages/appflowy_board/example/macos/Runner/Release.entitlements diff --git a/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml b/frontend/app_flowy/packages/appflowy_board/example/pubspec.yaml similarity index 75% rename from frontend/app_flowy/packages/flowy_board/example/pubspec.yaml rename to frontend/app_flowy/packages/appflowy_board/example/pubspec.yaml index 8504fc5208..1a90f3b84a 100644 --- a/frontend/app_flowy/packages/flowy_board/example/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_board/example/pubspec.yaml @@ -1,12 +1,24 @@ -name: flowy_board_example -description: Demonstrates how to use the flowy_board plugin. +name: example +description: A new Flutter project. # The following line prevents the package from being accidentally published to # pub.dev using `flutter pub publish`. This is preferred for private packages. publish_to: 'none' # Remove this line if you wish to publish to pub.dev +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +version: 1.0.0+1 + environment: - sdk: ">=2.17.1 <3.0.0" + sdk: ">=2.17.6 <3.0.0" # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions @@ -17,15 +29,10 @@ environment: dependencies: flutter: sdk: flutter - - flowy_board: - # When depending on this package from a real application you should use: - # flowy_board: ^x.y.z - # See https://dart.dev/tools/pub/dependencies#version-constraints - # The example app is bundled with the plugin so we use a path dependency on - # the parent directory to use the current plugin's version. + appflowy_board: path: ../ + # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 diff --git a/frontend/app_flowy/packages/flowy_board/example/test/widget_test.dart b/frontend/app_flowy/packages/appflowy_board/example/test/widget_test.dart similarity index 53% rename from frontend/app_flowy/packages/flowy_board/example/test/widget_test.dart rename to frontend/app_flowy/packages/appflowy_board/example/test/widget_test.dart index 313f88f30d..092d222f7e 100644 --- a/frontend/app_flowy/packages/flowy_board/example/test/widget_test.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/test/widget_test.dart @@ -8,20 +8,23 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:flowy_board_example/main.dart'; +import 'package:example/main.dart'; void main() { - testWidgets('Verify Platform version', (WidgetTester tester) async { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. await tester.pumpWidget(const MyApp()); - // Verify that platform version is retrieved. - expect( - find.byWidgetPredicate( - (Widget widget) => widget is Text && - widget.data!.startsWith('Running on:'), - ), - findsOneWidget, - ); + // Verify that our counter starts at 0. + expect(find.text('0'), findsOneWidget); + expect(find.text('1'), findsNothing); + + // Tap the '+' icon and trigger a frame. + await tester.tap(find.byIcon(Icons.add)); + await tester.pump(); + + // Verify that our counter has incremented. + expect(find.text('0'), findsNothing); + expect(find.text('1'), findsOneWidget); }); } diff --git a/frontend/app_flowy/packages/appflowy_board/example/web/favicon.png b/frontend/app_flowy/packages/appflowy_board/example/web/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..8aaa46ac1ae21512746f852a42ba87e4165dfdd1 GIT binary patch literal 917 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!61|;P_|4#%`jKx9jP7LeL$-D$|I14-?iy0X7 zltGxWVyS%@P(fs7NJL45ua8x7ey(0(N`6wRUPW#JP&EUCO@$SZnVVXYs8ErclUHn2 zVXFjIVFhG^g!Ppaz)DK8ZIvQ?0~DO|i&7O#^-S~(l1AfjnEK zjFOT9D}DX)@^Za$W4-*MbbUihOG|wNBYh(yU7!lx;>x^|#0uTKVr7USFmqf|i<65o z3raHc^AtelCMM;Vme?vOfh>Xph&xL%(-1c06+^uR^q@XSM&D4+Kp$>4P^%3{)XKjo zGZknv$b36P8?Z_gF{nK@`XI}Z90TzwSQO}0J1!f2c(B=V`5aP@1P1a|PZ!4!3&Gl8 zTYqUsf!gYFyJnXpu0!n&N*SYAX-%d(5gVjrHJWqXQshj@!Zm{!01WsQrH~9=kTxW#6SvuapgMqt>$=j#%eyGrQzr zP{L-3gsMA^$I1&gsBAEL+vxi1*Igl=8#8`5?A-T5=z-sk46WA1IUT)AIZHx1rdUrf zVJrJn<74DDw`j)Ki#gt}mIT-Q`XRa2-jQXQoI%w`nb|XblvzK${ZzlV)m-XcwC(od z71_OEC5Bt9GEXosOXaPTYOia#R4ID2TiU~`zVMl08TV_C%DnU4^+HE>9(CE4D6?Fz oujB08i7adh9xk7*FX66dWH6F5TM;?E2b5PlUHx3vIVCg!0Dx9vYXATM literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-192.png b/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..b749bfef07473333cf1dd31e9eed89862a5d52aa GIT binary patch literal 5292 zcmZ`-2T+sGz6~)*FVZ`aW+(v>MIm&M-g^@e2u-B-DoB?qO+b1Tq<5uCCv>ESfRum& zp%X;f!~1{tzL__3=gjVJ=j=J>+nMj%ncXj1Q(b|Ckbw{Y0FWpt%4y%$uD=Z*c-x~o zE;IoE;xa#7Ll5nj-e4CuXB&G*IM~D21rCP$*xLXAK8rIMCSHuSu%bL&S3)8YI~vyp@KBu9Ph7R_pvKQ@xv>NQ`dZp(u{Z8K3yOB zn7-AR+d2JkW)KiGx0hosml;+eCXp6+w%@STjFY*CJ?udJ64&{BCbuebcuH;}(($@@ znNlgBA@ZXB)mcl9nbX#F!f_5Z=W>0kh|UVWnf!At4V*LQP%*gPdCXd6P@J4Td;!Ur z<2ZLmwr(NG`u#gDEMP19UcSzRTL@HsK+PnIXbVBT@oHm53DZr?~V(0{rsalAfwgo zEh=GviaqkF;}F_5-yA!1u3!gxaR&Mj)hLuj5Q-N-@Lra{%<4ONja8pycD90&>yMB` zchhd>0CsH`^|&TstH-8+R`CfoWqmTTF_0?zDOY`E`b)cVi!$4xA@oO;SyOjJyP^_j zx^@Gdf+w|FW@DMdOi8=4+LJl$#@R&&=UM`)G!y%6ZzQLoSL%*KE8IO0~&5XYR9 z&N)?goEiWA(YoRfT{06&D6Yuu@Qt&XVbuW@COb;>SP9~aRc+z`m`80pB2o%`#{xD@ zI3RAlukL5L>px6b?QW1Ac_0>ew%NM!XB2(H+1Y3AJC?C?O`GGs`331Nd4ZvG~bMo{lh~GeL zSL|tT*fF-HXxXYtfu5z+T5Mx9OdP7J4g%@oeC2FaWO1D{=NvL|DNZ}GO?O3`+H*SI z=grGv=7dL{+oY0eJFGO!Qe(e2F?CHW(i!!XkGo2tUvsQ)I9ev`H&=;`N%Z{L zO?vV%rDv$y(@1Yj@xfr7Kzr<~0{^T8wM80xf7IGQF_S-2c0)0D6b0~yD7BsCy+(zL z#N~%&e4iAwi4F$&dI7x6cE|B{f@lY5epaDh=2-(4N05VO~A zQT3hanGy_&p+7Fb^I#ewGsjyCEUmSCaP6JDB*=_()FgQ(-pZ28-{qx~2foO4%pM9e z*_63RT8XjgiaWY|*xydf;8MKLd{HnfZ2kM%iq}fstImB-K6A79B~YoPVa@tYN@T_$ zea+9)<%?=Fl!kd(Y!G(-o}ko28hg2!MR-o5BEa_72uj7Mrc&{lRh3u2%Y=Xk9^-qa zBPWaD=2qcuJ&@Tf6ue&)4_V*45=zWk@Z}Q?f5)*z)-+E|-yC4fs5CE6L_PH3=zI8p z*Z3!it{1e5_^(sF*v=0{`U9C741&lub89gdhKp|Y8CeC{_{wYK-LSbp{h)b~9^j!s z7e?Y{Z3pZv0J)(VL=g>l;<}xk=T*O5YR|hg0eg4u98f2IrA-MY+StQIuK-(*J6TRR z|IM(%uI~?`wsfyO6Tgmsy1b3a)j6M&-jgUjVg+mP*oTKdHg?5E`!r`7AE_#?Fc)&a z08KCq>Gc=ne{PCbRvs6gVW|tKdcE1#7C4e`M|j$C5EYZ~Y=jUtc zj`+?p4ba3uy7><7wIokM79jPza``{Lx0)zGWg;FW1^NKY+GpEi=rHJ+fVRGfXO zPHV52k?jxei_!YYAw1HIz}y8ZMwdZqU%ESwMn7~t zdI5%B;U7RF=jzRz^NuY9nM)&<%M>x>0(e$GpU9th%rHiZsIT>_qp%V~ILlyt^V`=d z!1+DX@ah?RnB$X!0xpTA0}lN@9V-ePx>wQ?-xrJr^qDlw?#O(RsXeAvM%}rg0NT#t z!CsT;-vB=B87ShG`GwO;OEbeL;a}LIu=&@9cb~Rsx(ZPNQ!NT7H{@j0e(DiLea>QD zPmpe90gEKHEZ8oQ@6%E7k-Ptn#z)b9NbD@_GTxEhbS+}Bb74WUaRy{w;E|MgDAvHw zL)ycgM7mB?XVh^OzbC?LKFMotw3r@i&VdUV%^Efdib)3@soX%vWCbnOyt@Y4swW925@bt45y0HY3YI~BnnzZYrinFy;L?2D3BAL`UQ zEj))+f>H7~g8*VuWQ83EtGcx`hun$QvuurSMg3l4IP8Fe`#C|N6mbYJ=n;+}EQm;< z!!N=5j1aAr_uEnnzrEV%_E|JpTb#1p1*}5!Ce!R@d$EtMR~%9# zd;h8=QGT)KMW2IKu_fA_>p_und#-;Q)p%%l0XZOXQicfX8M~7?8}@U^ihu;mizj)t zgV7wk%n-UOb z#!P5q?Ex+*Kx@*p`o$q8FWL*E^$&1*!gpv?Za$YO~{BHeGY*5%4HXUKa_A~~^d z=E*gf6&+LFF^`j4$T~dR)%{I)T?>@Ma?D!gi9I^HqvjPc3-v~=qpX1Mne@*rzT&Xw zQ9DXsSV@PqpEJO-g4A&L{F&;K6W60D!_vs?Vx!?w27XbEuJJP&);)^+VF1nHqHBWu z^>kI$M9yfOY8~|hZ9WB!q-9u&mKhEcRjlf2nm_@s;0D#c|@ED7NZE% zzR;>P5B{o4fzlfsn3CkBK&`OSb-YNrqx@N#4CK!>bQ(V(D#9|l!e9(%sz~PYk@8zt zPN9oK78&-IL_F zhsk1$6p;GqFbtB^ZHHP+cjMvA0(LqlskbdYE_rda>gvQLTiqOQ1~*7lg%z*&p`Ry& zRcG^DbbPj_jOKHTr8uk^15Boj6>hA2S-QY(W-6!FIq8h$<>MI>PYYRenQDBamO#Fv zAH5&ImqKBDn0v5kb|8i0wFhUBJTpT!rB-`zK)^SNnRmLraZcPYK7b{I@+}wXVdW-{Ps17qdRA3JatEd?rPV z4@}(DAMf5EqXCr4-B+~H1P#;t@O}B)tIJ(W6$LrK&0plTmnPpb1TKn3?f?Kk``?D+ zQ!MFqOX7JbsXfQrz`-M@hq7xlfNz;_B{^wbpG8des56x(Q)H)5eLeDwCrVR}hzr~= zM{yXR6IM?kXxauLza#@#u?Y|o;904HCqF<8yT~~c-xyRc0-vxofnxG^(x%>bj5r}N zyFT+xnn-?B`ohA>{+ZZQem=*Xpqz{=j8i2TAC#x-m;;mo{{sLB_z(UoAqD=A#*juZ zCv=J~i*O8;F}A^Wf#+zx;~3B{57xtoxC&j^ie^?**T`WT2OPRtC`xj~+3Kprn=rVM zVJ|h5ux%S{dO}!mq93}P+h36mZ5aZg1-?vhL$ke1d52qIiXSE(llCr5i=QUS?LIjc zV$4q=-)aaR4wsrQv}^shL5u%6;`uiSEs<1nG^?$kl$^6DL z43CjY`M*p}ew}}3rXc7Xck@k41jx}c;NgEIhKZ*jsBRZUP-x2cm;F1<5$jefl|ppO zmZd%%?gMJ^g9=RZ^#8Mf5aWNVhjAS^|DQO+q$)oeob_&ZLFL(zur$)); zU19yRm)z<4&4-M}7!9+^Wl}Uk?`S$#V2%pQ*SIH5KI-mn%i;Z7-)m$mN9CnI$G7?# zo`zVrUwoSL&_dJ92YhX5TKqaRkfPgC4=Q&=K+;_aDs&OU0&{WFH}kKX6uNQC6%oUH z2DZa1s3%Vtk|bglbxep-w)PbFG!J17`<$g8lVhqD2w;Z0zGsh-r zxZ13G$G<48leNqR!DCVt9)@}(zMI5w6Wo=N zpP1*3DI;~h2WDWgcKn*f!+ORD)f$DZFwgKBafEZmeXQMAsq9sxP9A)7zOYnkHT9JU zRA`umgmP9d6=PHmFIgx=0$(sjb>+0CHG)K@cPG{IxaJ&Ueo8)0RWgV9+gO7+Bl1(F z7!BslJ2MP*PWJ;x)QXbR$6jEr5q3 z(3}F@YO_P1NyTdEXRLU6fp?9V2-S=E+YaeLL{Y)W%6`k7$(EW8EZSA*(+;e5@jgD^I zaJQ2|oCM1n!A&-8`;#RDcZyk*+RPkn_r8?Ak@agHiSp*qFNX)&i21HE?yuZ;-C<3C zwJGd1lx5UzViP7sZJ&|LqH*mryb}y|%AOw+v)yc`qM)03qyyrqhX?ub`Cjwx2PrR! z)_z>5*!*$x1=Qa-0uE7jy0z`>|Ni#X+uV|%_81F7)b+nf%iz=`fF4g5UfHS_?PHbr zB;0$bK@=di?f`dS(j{l3-tSCfp~zUuva+=EWxJcRfp(<$@vd(GigM&~vaYZ0c#BTs z3ijkxMl=vw5AS&DcXQ%eeKt!uKvh2l3W?&3=dBHU=Gz?O!40S&&~ei2vg**c$o;i89~6DVns zG>9a*`k5)NI9|?W!@9>rzJ;9EJ=YlJTx1r1BA?H`LWijk(rTax9(OAu;q4_wTj-yj z1%W4GW&K4T=uEGb+E!>W0SD_C0RR91 literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-512.png b/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..88cfd48dff1169879ba46840804b412fe02fefd6 GIT binary patch literal 8252 zcmd5=2T+s!lYZ%-(h(2@5fr2dC?F^$C=i-}R6$UX8af(!je;W5yC_|HmujSgN*6?W z3knF*TL1$|?oD*=zPbBVex*RUIKsL<(&Rj9%^UD2IK3W?2j>D?eWQgvS-HLymHo9%~|N2Q{~j za?*X-{b9JRowv_*Mh|;*-kPFn>PI;r<#kFaxFqbn?aq|PduQg=2Q;~Qc}#z)_T%x9 zE|0!a70`58wjREmAH38H1)#gof)U3g9FZ^ zF7&-0^Hy{4XHWLoC*hOG(dg~2g6&?-wqcpf{ z&3=o8vw7lMi22jCG9RQbv8H}`+}9^zSk`nlR8?Z&G2dlDy$4#+WOlg;VHqzuE=fM@ z?OI6HEJH4&tA?FVG}9>jAnq_^tlw8NbjNhfqk2rQr?h(F&WiKy03Sn=-;ZJRh~JrD zbt)zLbnabttEZ>zUiu`N*u4sfQaLE8-WDn@tHp50uD(^r-}UsUUu)`!Rl1PozAc!a z?uj|2QDQ%oV-jxUJmJycySBINSKdX{kDYRS=+`HgR2GO19fg&lZKyBFbbXhQV~v~L za^U944F1_GtuFXtvDdDNDvp<`fqy);>Vw=ncy!NB85Tw{&sT5&Ox%-p%8fTS;OzlRBwErvO+ROe?{%q-Zge=%Up|D4L#>4K@Ke=x%?*^_^P*KD zgXueMiS63!sEw@fNLB-i^F|@Oib+S4bcy{eu&e}Xvb^(mA!=U=Xr3||IpV~3K zQWzEsUeX_qBe6fky#M zzOJm5b+l;~>=sdp%i}}0h zO?B?i*W;Ndn02Y0GUUPxERG`3Bjtj!NroLoYtyVdLtl?SE*CYpf4|_${ku2s`*_)k zN=a}V8_2R5QANlxsq!1BkT6$4>9=-Ix4As@FSS;1q^#TXPrBsw>hJ}$jZ{kUHoP+H zvoYiR39gX}2OHIBYCa~6ERRPJ#V}RIIZakUmuIoLF*{sO8rAUEB9|+A#C|@kw5>u0 zBd=F!4I)Be8ycH*)X1-VPiZ+Ts8_GB;YW&ZFFUo|Sw|x~ZajLsp+_3gv((Q#N>?Jz zFBf`~p_#^${zhPIIJY~yo!7$-xi2LK%3&RkFg}Ax)3+dFCjGgKv^1;lUzQlPo^E{K zmCnrwJ)NuSaJEmueEPO@(_6h3f5mFffhkU9r8A8(JC5eOkux{gPmx_$Uv&|hyj)gN zd>JP8l2U&81@1Hc>#*su2xd{)T`Yw< zN$dSLUN}dfx)Fu`NcY}TuZ)SdviT{JHaiYgP4~@`x{&h*Hd>c3K_To9BnQi@;tuoL z%PYQo&{|IsM)_>BrF1oB~+`2_uZQ48z9!)mtUR zdfKE+b*w8cPu;F6RYJiYyV;PRBbThqHBEu_(U{(gGtjM}Zi$pL8Whx}<JwE3RM0F8x7%!!s)UJVq|TVd#hf1zVLya$;mYp(^oZQ2>=ZXU1c$}f zm|7kfk>=4KoQoQ!2&SOW5|JP1)%#55C$M(u4%SP~tHa&M+=;YsW=v(Old9L3(j)`u z2?#fK&1vtS?G6aOt@E`gZ9*qCmyvc>Ma@Q8^I4y~f3gs7*d=ATlP>1S zyF=k&6p2;7dn^8?+!wZO5r~B+;@KXFEn^&C=6ma1J7Au6y29iMIxd7#iW%=iUzq&C=$aPLa^Q zncia$@TIy6UT@69=nbty5epP>*fVW@5qbUcb2~Gg75dNd{COFLdiz3}kODn^U*=@E z0*$7u7Rl2u)=%fk4m8EK1ctR!6%Ve`e!O20L$0LkM#f+)n9h^dn{n`T*^~d+l*Qlx z$;JC0P9+en2Wlxjwq#z^a6pdnD6fJM!GV7_%8%c)kc5LZs_G^qvw)&J#6WSp< zmsd~1-(GrgjC56Pdf6#!dt^y8Rg}!#UXf)W%~PeU+kU`FeSZHk)%sFv++#Dujk-~m zFHvVJC}UBn2jN& zs!@nZ?e(iyZPNo`p1i#~wsv9l@#Z|ag3JR>0#u1iW9M1RK1iF6-RbJ4KYg?B`dET9 zyR~DjZ>%_vWYm*Z9_+^~hJ_|SNTzBKx=U0l9 z9x(J96b{`R)UVQ$I`wTJ@$_}`)_DyUNOso6=WOmQKI1e`oyYy1C&%AQU<0-`(ow)1 zT}gYdwWdm4wW6|K)LcfMe&psE0XGhMy&xS`@vLi|1#Za{D6l@#D!?nW87wcscUZgELT{Cz**^;Zb~7 z(~WFRO`~!WvyZAW-8v!6n&j*PLm9NlN}BuUN}@E^TX*4Or#dMMF?V9KBeLSiLO4?B zcE3WNIa-H{ThrlCoN=XjOGk1dT=xwwrmt<1a)mrRzg{35`@C!T?&_;Q4Ce=5=>z^*zE_c(0*vWo2_#TD<2)pLXV$FlwP}Ik74IdDQU@yhkCr5h zn5aa>B7PWy5NQ!vf7@p_qtC*{dZ8zLS;JetPkHi>IvPjtJ#ThGQD|Lq#@vE2xdl%`x4A8xOln}BiQ92Po zW;0%A?I5CQ_O`@Ad=`2BLPPbBuPUp@Hb%a_OOI}y{Rwa<#h z5^6M}s7VzE)2&I*33pA>e71d78QpF>sNK;?lj^Kl#wU7G++`N_oL4QPd-iPqBhhs| z(uVM}$ItF-onXuuXO}o$t)emBO3Hjfyil@*+GF;9j?`&67GBM;TGkLHi>@)rkS4Nj zAEk;u)`jc4C$qN6WV2dVd#q}2X6nKt&X*}I@jP%Srs%%DS92lpDY^K*Sx4`l;aql$ zt*-V{U&$DM>pdO?%jt$t=vg5|p+Rw?SPaLW zB6nvZ69$ne4Z(s$3=Rf&RX8L9PWMV*S0@R zuIk&ba#s6sxVZ51^4Kon46X^9`?DC9mEhWB3f+o4#2EXFqy0(UTc>GU| zGCJmI|Dn-dX#7|_6(fT)>&YQ0H&&JX3cTvAq(a@ydM4>5Njnuere{J8p;3?1az60* z$1E7Yyxt^ytULeokgDnRVKQw9vzHg1>X@@jM$n$HBlveIrKP5-GJq%iWH#odVwV6cF^kKX(@#%%uQVb>#T6L^mC@)%SMd4DF? zVky!~ge27>cpUP1Vi}Z32lbLV+CQy+T5Wdmva6Fg^lKb!zrg|HPU=5Qu}k;4GVH+x z%;&pN1LOce0w@9i1Mo-Y|7|z}fbch@BPp2{&R-5{GLoeu8@limQmFF zaJRR|^;kW_nw~0V^ zfTnR!Ni*;-%oSHG1yItARs~uxra|O?YJxBzLjpeE-=~TO3Dn`JL5Gz;F~O1u3|FE- zvK2Vve`ylc`a}G`gpHg58Cqc9fMoy1L}7x7T>%~b&irrNMo?np3`q;d3d;zTK>nrK zOjPS{@&74-fA7j)8uT9~*g23uGnxwIVj9HorzUX#s0pcp2?GH6i}~+kv9fWChtPa_ z@T3m+$0pbjdQw7jcnHn;Pi85hk_u2-1^}c)LNvjdam8K-XJ+KgKQ%!?2n_!#{$H|| zLO=%;hRo6EDmnOBKCL9Cg~ETU##@u^W_5joZ%Et%X_n##%JDOcsO=0VL|Lkk!VdRJ z^|~2pB@PUspT?NOeO?=0Vb+fAGc!j%Ufn-cB`s2A~W{Zj{`wqWq_-w0wr@6VrM zbzni@8c>WS!7c&|ZR$cQ;`niRw{4kG#e z70e!uX8VmP23SuJ*)#(&R=;SxGAvq|&>geL&!5Z7@0Z(No*W561n#u$Uc`f9pD70# z=sKOSK|bF~#khTTn)B28h^a1{;>EaRnHj~>i=Fnr3+Fa4 z`^+O5_itS#7kPd20rq66_wH`%?HNzWk@XFK0n;Z@Cx{kx==2L22zWH$Yg?7 zvDj|u{{+NR3JvUH({;b*$b(U5U z7(lF!1bz2%06+|-v(D?2KgwNw7( zJB#Tz+ZRi&U$i?f34m7>uTzO#+E5cbaiQ&L}UxyOQq~afbNB4EI{E04ZWg53w0A{O%qo=lF8d zf~ktGvIgf-a~zQoWf>loF7pOodrd0a2|BzwwPDV}ShauTK8*fmF6NRbO>Iw9zZU}u zw8Ya}?seBnEGQDmH#XpUUkj}N49tP<2jYwTFp!P+&Fd(%Z#yo80|5@zN(D{_pNow*&4%ql zW~&yp@scb-+Qj-EmErY+Tu=dUmf@*BoXY2&oKT8U?8?s1d}4a`Aq>7SV800m$FE~? zjmz(LY+Xx9sDX$;vU`xgw*jLw7dWOnWWCO8o|;}f>cu0Q&`0I{YudMn;P;L3R-uz# zfns_mZED_IakFBPP2r_S8XM$X)@O-xVKi4`7373Jkd5{2$M#%cRhWer3M(vr{S6>h zj{givZJ3(`yFL@``(afn&~iNx@B1|-qfYiZu?-_&Z8+R~v`d6R-}EX9IVXWO-!hL5 z*k6T#^2zAXdardU3Ao~I)4DGdAv2bx{4nOK`20rJo>rmk3S2ZDu}))8Z1m}CKigf0 z3L`3Y`{huj`xj9@`$xTZzZc3je?n^yG<8sw$`Y%}9mUsjUR%T!?k^(q)6FH6Af^b6 zlPg~IEwg0y;`t9y;#D+uz!oE4VP&Je!<#q*F?m5L5?J3i@!0J6q#eu z!RRU`-)HeqGi_UJZ(n~|PSNsv+Wgl{P-TvaUQ9j?ZCtvb^37U$sFpBrkT{7Jpd?HpIvj2!}RIq zH{9~+gErN2+}J`>Jvng2hwM`=PLNkc7pkjblKW|+Fk9rc)G1R>Ww>RC=r-|!m-u7( zc(a$9NG}w#PjWNMS~)o=i~WA&4L(YIW25@AL9+H9!?3Y}sv#MOdY{bb9j>p`{?O(P zIvb`n?_(gP2w3P#&91JX*md+bBEr%xUHMVqfB;(f?OPtMnAZ#rm5q5mh;a2f_si2_ z3oXWB?{NF(JtkAn6F(O{z@b76OIqMC$&oJ_&S|YbFJ*)3qVX_uNf5b8(!vGX19hsG z(OP>RmZp29KH9Ge2kKjKigUmOe^K_!UXP`von)PR8Qz$%=EmOB9xS(ZxE_tnyzo}7 z=6~$~9k0M~v}`w={AeqF?_)9q{m8K#6M{a&(;u;O41j)I$^T?lx5(zlebpY@NT&#N zR+1bB)-1-xj}R8uwqwf=iP1GbxBjneCC%UrSdSxK1vM^i9;bUkS#iRZw2H>rS<2<$ zNT3|sDH>{tXb=zq7XZi*K?#Zsa1h1{h5!Tq_YbKFm_*=A5-<~j63he;4`77!|LBlo zR^~tR3yxcU=gDFbshyF6>o0bdp$qmHS7D}m3;^QZq9kBBU|9$N-~oU?G5;jyFR7>z hN`IR97YZXIo@y!QgFWddJ3|0`sjFx!m))><{BI=FK%f8s literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-maskable-192.png b/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-maskable-512.png b/frontend/app_flowy/packages/appflowy_board/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 zcmeFZ_gj-)&^4Nb2tlbLMU<{!p(#yjqEe+=0IA_oih%ScH9@5#MNp&}Y#;;(h=A0@ zh7{>lT2MkSQ344eAvrhici!td|HJuyvJm#Y_w1Q9Yu3!26dNlO-oxUDK_C#XnW^Co z5C{VN6#{~B0)K2j7}*1Xq(Nqemv23A-6&=ZpEijkVnSwVGqLv40?n0=p;k3-U5e5+ z+z3>aS`u9DS=!wg8ROu?X4TFoW6CFLL&{GzoVT)ldhLekLM|+j3tIxRd|*5=c{=s&*vfPdBr(Fyj(v@%eQj1Soy7m4^@VRl1~@-PV7y+c!xz$8436WBn$t{=}mEdK#k`aystimGgI{(IBx$!pAwFoE9Y`^t^;> zKAD)C(Dl^s%`?q5$P|fZf8Xymrtu^Pv(7D`rn>Z-w$Ahs!z9!94WNVxrJuXfHAaxg zC6s@|Z1$7R$(!#t%Jb{{s6(Y?NoQXDYq)!}X@jKPhe`{9KQ@sAU8y-5`xt?S9$jKH zoi}6m5PcG*^{kjvt+kwPpyQzVg4o)a>;LK`aaN2x4@itBD3Aq?yWTM20VRn1rrd+2 zKO=P0rMjEGq_UqpMa`~7B|p?xAN1SCoCp}QxAv8O`jLJ5CVh@umR%c%i^)6!o+~`F zaalSTQcl5iwOLC&H)efzd{8(88mo`GI(56T<(&p7>Qd^;R1hn1Y~jN~tApaL8>##U zd65bo8)79CplWxr#z4!6HvLz&N7_5AN#x;kLG?zQ(#p|lj<8VUlKY=Aw!ATqeL-VG z42gA!^cMNPj>(`ZMEbCrnkg*QTsn*u(nQPWI9pA{MQ=IsPTzd7q5E#7+z>Ch=fx$~ z;J|?(5jTo5UWGvsJa(Sx0?S#56+8SD!I^tftyeh_{5_31l6&Hywtn`bbqYDqGZXI( zCG7hBgvksX2ak8+)hB4jnxlO@A32C_RM&g&qDSb~3kM&)@A_j1*oTO@nicGUyv+%^ z=vB)4(q!ykzT==Z)3*3{atJ5}2PV*?Uw+HhN&+RvKvZL3p9E?gHjv{6zM!A|z|UHK z-r6jeLxbGn0D@q5aBzlco|nG2tr}N@m;CJX(4#Cn&p&sLKwzLFx1A5izu?X_X4x8r@K*d~7>t1~ zDW1Mv5O&WOxbzFC`DQ6yNJ(^u9vJdj$fl2dq`!Yba_0^vQHXV)vqv1gssZYzBct!j zHr9>ydtM8wIs}HI4=E}qAkv|BPWzh3^_yLH(|kdb?x56^BlDC)diWyPd*|f!`^12_U>TD^^94OCN0lVv~Sgvs94ecpE^}VY$w`qr_>Ue zTfH~;C<3H<0dS5Rkf_f@1x$Gms}gK#&k()IC0zb^QbR!YLoll)c$Agfi6MKI0dP_L z=Uou&u~~^2onea2%XZ@>`0x^L8CK6=I{ge;|HXMj)-@o~h&O{CuuwBX8pVqjJ*o}5 z#8&oF_p=uSo~8vn?R0!AMWvcbZmsrj{ZswRt(aEdbi~;HeVqIe)-6*1L%5u$Gbs}| zjFh?KL&U(rC2izSGtwP5FnsR@6$-1toz?RvLD^k~h9NfZgzHE7m!!7s6(;)RKo2z} zB$Ci@h({l?arO+vF;s35h=|WpefaOtKVx>l399}EsX@Oe3>>4MPy%h&^3N_`UTAHJ zI$u(|TYC~E4)|JwkWW3F!Tib=NzjHs5ii2uj0^m|Qlh-2VnB#+X~RZ|`SA*}}&8j9IDv?F;(Y^1=Z0?wWz;ikB zewU>MAXDi~O7a~?jx1x=&8GcR-fTp>{2Q`7#BE#N6D@FCp`?ht-<1|y(NArxE_WIu zP+GuG=Qq>SHWtS2M>34xwEw^uvo4|9)4s|Ac=ud?nHQ>ax@LvBqusFcjH0}{T3ZPQ zLO1l<@B_d-(IS682}5KA&qT1+{3jxKolW+1zL4inqBS-D>BohA!K5++41tM@ z@xe<-qz27}LnV#5lk&iC40M||JRmZ*A##K3+!j93eouU8@q-`W0r%7N`V$cR&JV;iX(@cS{#*5Q>~4BEDA)EikLSP@>Oo&Bt1Z~&0d5)COI%3$cLB_M?dK# z{yv2OqW!al-#AEs&QFd;WL5zCcp)JmCKJEdNsJlL9K@MnPegK23?G|O%v`@N{rIRa zi^7a}WBCD77@VQ-z_v{ZdRsWYrYgC$<^gRQwMCi6);%R~uIi31OMS}=gUTE(GKmCI z$zM>mytL{uNN+a&S38^ez(UT=iSw=l2f+a4)DyCA1Cs_N-r?Q@$3KTYosY!;pzQ0k zzh1G|kWCJjc(oZVBji@kN%)UBw(s{KaYGy=i{g3{)Z+&H8t2`^IuLLKWT6lL<-C(! zSF9K4xd-|VO;4}$s?Z7J_dYqD#Mt)WCDnsR{Kpjq275uUq6`v0y*!PHyS(}Zmv)_{>Vose9-$h8P0|y;YG)Bo}$(3Z%+Gs0RBmFiW!^5tBmDK-g zfe5%B*27ib+7|A*Fx5e)2%kIxh7xWoc3pZcXS2zik!63lAG1;sC1ja>BqH7D zODdi5lKW$$AFvxgC-l-)!c+9@YMC7a`w?G(P#MeEQ5xID#<}W$3bSmJ`8V*x2^3qz zVe<^^_8GHqYGF$nIQm0Xq2kAgYtm#UC1A(=&85w;rmg#v906 zT;RyMgbMpYOmS&S9c38^40oUp?!}#_84`aEVw;T;r%gTZkWeU;;FwM@0y0adt{-OK z(vGnPSlR=Nv2OUN!2=xazlnHPM9EWxXg2EKf0kI{iQb#FoP>xCB<)QY>OAM$Dcdbm zU6dU|%Mo(~avBYSjRc13@|s>axhrPl@Sr81{RSZUdz4(=|82XEbV*JAX6Lfbgqgz584lYgi0 z2-E{0XCVON$wHfvaLs;=dqhQJ&6aLn$D#0i(FkAVrXG9LGm3pSTf&f~RQb6|1_;W> z?n-;&hrq*~L=(;u#jS`*Yvh@3hU-33y_Kv1nxqrsf>pHVF&|OKkoC)4DWK%I!yq?P z=vXo8*_1iEWo8xCa{HJ4tzxOmqS0&$q+>LroMKI*V-rxhOc%3Y!)Y|N6p4PLE>Yek>Y(^KRECg8<|%g*nQib_Yc#A5q8Io z6Ig&V>k|~>B6KE%h4reAo*DfOH)_01tE0nWOxX0*YTJgyw7moaI^7gW*WBAeiLbD?FV9GSB zPv3`SX*^GRBM;zledO`!EbdBO_J@fEy)B{-XUTVQv}Qf~PSDpK9+@I`7G7|>Dgbbu z_7sX9%spVo$%qwRwgzq7!_N;#Td08m5HV#?^dF-EV1o)Q=Oa+rs2xH#g;ykLbwtCh znUnA^dW!XjspJ;otq$yV@I^s9Up(5k7rqhQd@OLMyyxVLj_+$#Vc*}Usevp^I(^vH zmDgHc0VMme|K&X?9&lkN{yq_(If)O`oUPW8X}1R5pSVBpfJe0t{sPA(F#`eONTh_) zxeLqHMfJX#?P(@6w4CqRE@Eiza; z;^5)Kk=^5)KDvd9Q<`=sJU8rjjxPmtWMTmzcH={o$U)j=QBuHarp?=}c??!`3d=H$nrJMyr3L-& zA#m?t(NqLM?I3mGgWA_C+0}BWy3-Gj7bR+d+U?n*mN$%5P`ugrB{PeV>jDUn;eVc- zzeMB1mI4?fVJatrNyq|+zn=!AiN~<}eoM#4uSx^K?Iw>P2*r=k`$<3kT00BE_1c(02MRz4(Hq`L^M&xt!pV2 zn+#U3@j~PUR>xIy+P>51iPayk-mqIK_5rlQMSe5&tDkKJk_$i(X&;K(11YGpEc-K= zq4Ln%^j>Zi_+Ae9eYEq_<`D+ddb8_aY!N;)(&EHFAk@Ekg&41ABmOXfWTo)Z&KotA zh*jgDGFYQ^y=m)<_LCWB+v48DTJw*5dwMm_YP0*_{@HANValf?kV-Ic3xsC}#x2h8 z`q5}d8IRmqWk%gR)s~M}(Qas5+`np^jW^oEd-pzERRPMXj$kS17g?H#4^trtKtq;C?;c ztd|%|WP2w2Nzg@)^V}!Gv++QF2!@FP9~DFVISRW6S?eP{H;;8EH;{>X_}NGj^0cg@ z!2@A>-CTcoN02^r6@c~^QUa={0xwK0v4i-tQ9wQq^=q*-{;zJ{Qe%7Qd!&X2>rV@4 z&wznCz*63_vw4>ZF8~%QCM?=vfzW0r_4O^>UA@otm_!N%mH)!ERy&b!n3*E*@?9d^ zu}s^By@FAhG(%?xgJMuMzuJw2&@$-oK>n z=UF}rt%vuaP9fzIFCYN-1&b#r^Cl6RDFIWsEsM|ROf`E?O(cy{BPO2Ie~kT+^kI^i zp>Kbc@C?}3vy-$ZFVX#-cx)Xj&G^ibX{pWggtr(%^?HeQL@Z( zM-430g<{>vT*)jK4aY9(a{lSy{8vxLbP~n1MXwM527ne#SHCC^F_2@o`>c>>KCq9c(4c$VSyMl*y3Nq1s+!DF| z^?d9PipQN(mw^j~{wJ^VOXDCaL$UtwwTpyv8IAwGOg<|NSghkAR1GSNLZ1JwdGJYm zP}t<=5=sNNUEjc=g(y)1n5)ynX(_$1-uGuDR*6Y^Wgg(LT)Jp><5X|}bt z_qMa&QP?l_n+iVS>v%s2Li_;AIeC=Ca^v1jX4*gvB$?H?2%ndnqOaK5-J%7a} zIF{qYa&NfVY}(fmS0OmXA70{znljBOiv5Yod!vFU{D~*3B3Ka{P8?^ zfhlF6o7aNT$qi8(w<}OPw5fqA7HUje*r*Oa(YV%*l0|9FP9KW@U&{VSW{&b0?@y)M zs%4k1Ax;TGYuZ9l;vP5@?3oQsp3)rjBeBvQQ>^B;z5pc=(yHhHtq6|0m(h4envn_j787fizY@V`o(!SSyE7vlMT zbo=Z1c=atz*G!kwzGB;*uPL$Ei|EbZLh8o+1BUMOpnU(uX&OG1MV@|!&HOOeU#t^x zr9=w2ow!SsTuJWT7%Wmt14U_M*3XiWBWHxqCVZI0_g0`}*^&yEG9RK9fHK8e+S^m? zfCNn$JTswUVbiC#>|=wS{t>-MI1aYPLtzO5y|LJ9nm>L6*wpr_m!)A2Fb1RceX&*|5|MwrvOk4+!0p99B9AgP*9D{Yt|x=X}O% zgIG$MrTB=n-!q%ROT|SzH#A$Xm;|ym)0>1KR}Yl0hr-KO&qMrV+0Ej3d@?FcgZ+B3 ztEk16g#2)@x=(ko8k7^Tq$*5pfZHC@O@}`SmzT1(V@x&NkZNM2F#Q-Go7-uf_zKC( zB(lHZ=3@dHaCOf6C!6i8rDL%~XM@rVTJbZL09?ht@r^Z_6x}}atLjvH^4Vk#Ibf(^LiBJFqorm?A=lE zzFmwvp4bT@Nv2V>YQT92X;t9<2s|Ru5#w?wCvlhcHLcsq0TaFLKy(?nzezJ>CECqj zggrI~Hd4LudM(m{L@ezfnpELsRFVFw>fx;CqZtie`$BXRn#Ns%AdoE$-Pf~{9A8rV zf7FbgpKmVzmvn-z(g+&+-ID=v`;6=)itq8oM*+Uz**SMm_{%eP_c0{<%1JGiZS19o z@Gj7$Se~0lsu}w!%;L%~mIAO;AY-2i`9A*ZfFs=X!LTd6nWOZ7BZH2M{l2*I>Xu)0 z`<=;ObglnXcVk!T>e$H?El}ra0WmPZ$YAN0#$?|1v26^(quQre8;k20*dpd4N{i=b zuN=y}_ew9SlE~R{2+Rh^7%PA1H5X(p8%0TpJ=cqa$65XL)$#ign-y!qij3;2>j}I; ziO@O|aYfn&up5F`YtjGw68rD3{OSGNYmBnl?zdwY$=RFsegTZ=kkzRQ`r7ZjQP!H( zp4>)&zf<*N!tI00xzm-ME_a{_I!TbDCr;8E;kCH4LlL-tqLxDuBn-+xgPk37S&S2^ z2QZumkIimwz!c@!r0)j3*(jPIs*V!iLTRl0Cpt_UVNUgGZzdvs0(-yUghJfKr7;=h zD~y?OJ-bWJg;VdZ^r@vlDoeGV&8^--!t1AsIMZ5S440HCVr%uk- z2wV>!W1WCvFB~p$P$$_}|H5>uBeAe>`N1FI8AxM|pq%oNs;ED8x+tb44E) zTj{^fbh@eLi%5AqT?;d>Es5D*Fi{Bpk)q$^iF!!U`r2hHAO_?#!aYmf>G+jHsES4W zgpTKY59d?hsb~F0WE&dUp6lPt;Pm zcbTUqRryw^%{ViNW%Z(o8}dd00H(H-MmQmOiTq{}_rnwOr*Ybo7*}3W-qBT!#s0Ie z-s<1rvvJx_W;ViUD`04%1pra*Yw0BcGe)fDKUK8aF#BwBwMPU;9`!6E(~!043?SZx z13K%z@$$#2%2ovVlgFIPp7Q6(vO)ud)=*%ZSucL2Dh~K4B|%q4KnSpj#n@(0B})!9 z8p*hY@5)NDn^&Pmo;|!>erSYg`LkO?0FB@PLqRvc>4IsUM5O&>rRv|IBRxi(RX(gJ ztQ2;??L~&Mv;aVr5Q@(?y^DGo%pO^~zijld41aA0KKsy_6FeHIn?fNHP-z>$OoWer zjZ5hFQTy*-f7KENRiCE$ZOp4|+Wah|2=n@|W=o}bFM}Y@0e62+_|#fND5cwa3;P{^pEzlJbF1Yq^}>=wy8^^^$I2M_MH(4Dw{F6hm+vrWV5!q;oX z;tTNhz5`-V={ew|bD$?qcF^WPR{L(E%~XG8eJx(DoGzt2G{l8r!QPJ>kpHeOvCv#w zr=SSwMDaUX^*~v%6K%O~i)<^6`{go>a3IdfZ8hFmz&;Y@P%ZygShQZ2DSHd`m5AR= zx$wWU06;GYwXOf(%MFyj{8rPFXD};JCe85Bdp4$YJ2$TzZ7Gr#+SwCvBI1o$QP0(c zy`P51FEBV2HTisM3bHqpmECT@H!Y2-bv2*SoSPoO?wLe{M#zDTy@ujAZ!Izzky~3k zRA1RQIIoC*Mej1PH!sUgtkR0VCNMX(_!b65mo66iM*KQ7xT8t2eev$v#&YdUXKwGm z7okYAqYF&bveHeu6M5p9xheRCTiU8PFeb1_Rht0VVSbm%|1cOVobc8mvqcw!RjrMRM#~=7xibH&Fa5Imc|lZ{eC|R__)OrFg4@X_ ze+kk*_sDNG5^ELmHnZ7Ue?)#6!O)#Nv*Dl2mr#2)w{#i-;}0*_h4A%HidnmclH#;Q zmQbq+P4DS%3}PpPm7K_K3d2s#k~x+PlTul7+kIKol0@`YN1NG=+&PYTS->AdzPv!> zQvzT=)9se*Jr1Yq+C{wbK82gAX`NkbXFZ)4==j4t51{|-v!!$H8@WKA={d>CWRW+g z*`L>9rRucS`vbXu0rzA1#AQ(W?6)}1+oJSF=80Kf_2r~Qm-EJ6bbB3k`80rCv(0d` zvCf3;L2ovYG_TES%6vSuoKfIHC6w;V31!oqHM8-I8AFzcd^+_86!EcCOX|Ta9k1!s z_Vh(EGIIsI3fb&dF$9V8v(sTBC%!#<&KIGF;R+;MyC0~}$gC}}= zR`DbUVc&Bx`lYykFZ4{R{xRaUQkWCGCQlEc;!mf=+nOk$RUg*7 z;kP7CVLEc$CA7@6VFpsp3_t~m)W0aPxjsA3e5U%SfY{tp5BV5jH-5n?YX7*+U+Zs%LGR>U- z!x4Y_|4{gx?ZPJobISy991O znrmrC3otC;#4^&Rg_iK}XH(XX+eUHN0@Oe06hJk}F?`$)KmH^eWz@@N%wEc)%>?Ft z#9QAroDeyfztQ5Qe{m*#R#T%-h*&XvSEn@N$hYRTCMXS|EPwzF3IIysD2waj`vQD{ zv_#^Pgr?s~I*NE=acf@dWVRNWTr(GN0wrL)Z2=`Dr>}&ZDNX|+^Anl{Di%v1Id$_p zK5_H5`RDjJx`BW7hc85|> zHMMsWJ4KTMRHGu+vy*kBEMjz*^K8VtU=bXJYdhdZ-?jTXa$&n)C?QQIZ7ln$qbGlr zS*TYE+ppOrI@AoPP=VI-OXm}FzgXRL)OPvR$a_=SsC<3Jb+>5makX|U!}3lx4tX&L z^C<{9TggZNoeX!P1jX_K5HkEVnQ#s2&c#umzV6s2U-Q;({l+j^?hi7JnQ7&&*oOy9 z(|0asVTWUCiCnjcOnB2pN0DpuTglKq;&SFOQ3pUdye*eT<2()7WKbXp1qq9=bhMWlF-7BHT|i3TEIT77AcjD(v=I207wi-=vyiw5mxgPdTVUC z&h^FEUrXwWs9en2C{ywZp;nvS(Mb$8sBEh-*_d-OEm%~p1b2EpcwUdf<~zmJmaSTO zSX&&GGCEz-M^)G$fBvLC2q@wM$;n4jp+mt0MJFLuJ%c`tSp8$xuP|G81GEd2ci$|M z4XmH{5$j?rqDWoL4vs!}W&!?!rtj=6WKJcE>)?NVske(p;|#>vL|M_$as=mi-n-()a*OU3Okmk0wC<9y7t^D(er-&jEEak2!NnDiOQ99Wx8{S8}=Ng!e0tzj*#T)+%7;aM$ z&H}|o|J1p{IK0Q7JggAwipvHvko6>Epmh4RFRUr}$*2K4dz85o7|3#Bec9SQ4Y*;> zXWjT~f+d)dp_J`sV*!w>B%)#GI_;USp7?0810&3S=WntGZ)+tzhZ+!|=XlQ&@G@~3 z-dw@I1>9n1{+!x^Hz|xC+P#Ab`E@=vY?3%Bc!Po~e&&&)Qp85!I|U<-fCXy*wMa&t zgDk!l;gk;$taOCV$&60z+}_$ykz=Ea*)wJQ3-M|p*EK(cvtIre0Pta~(95J7zoxBN zS(yE^3?>88AL0Wfuou$BM{lR1hkrRibz=+I9ccwd`ZC*{NNqL)3pCcw^ygMmrG^Yp zn5f}Xf>%gncC=Yq96;rnfp4FQL#{!Y*->e82rHgY4Zwy{`JH}b9*qr^VA{%~Z}jtp z_t$PlS6}5{NtTqXHN?uI8ut8rOaD#F1C^ls73S=b_yI#iZDOGz3#^L@YheGd>L;<( z)U=iYj;`{>VDNzIxcjbTk-X3keXR8Xbc`A$o5# zKGSk-7YcoBYuAFFSCjGi;7b<;n-*`USs)IX z=0q6WZ=L!)PkYtZE-6)azhXV|+?IVGTOmMCHjhkBjfy@k1>?yFO3u!)@cl{fFAXnRYsWk)kpT?X{_$J=|?g@Q}+kFw|%n!;Zo}|HE@j=SFMvT8v`6Y zNO;tXN^036nOB2%=KzxB?n~NQ1K8IO*UE{;Xy;N^ZNI#P+hRZOaHATz9(=)w=QwV# z`z3+P>9b?l-@$@P3<;w@O1BdKh+H;jo#_%rr!ute{|YX4g5}n?O7Mq^01S5;+lABE+7`&_?mR_z7k|Ja#8h{!~j)| zbBX;*fsbUak_!kXU%HfJ2J+G7;inu#uRjMb|8a){=^))y236LDZ$$q3LRlat1D)%7K0!q5hT5V1j3qHc7MG9 z_)Q=yQ>rs>3%l=vu$#VVd$&IgO}Za#?aN!xY>-<3PhzS&q!N<=1Q7VJBfHjug^4|) z*fW^;%3}P7X#W3d;tUs3;`O&>;NKZBMR8au6>7?QriJ@gBaorz-+`pUWOP73DJL=M z(33uT6Gz@Sv40F6bN|H=lpcO z^AJl}&=TIjdevuDQ!w0K*6oZ2JBOhb31q!XDArFyKpz!I$p4|;c}@^bX{>AXdt7Bm zaLTk?c%h@%xq02reu~;t@$bv`b3i(P=g}~ywgSFpM;}b$zAD+=I!7`V~}ARB(Wx0C(EAq@?GuxOL9X+ffbkn3+Op0*80TqmpAq~EXmv%cq36celXmRz z%0(!oMp&2?`W)ALA&#|fu)MFp{V~~zIIixOxY^YtO5^FSox8v$#d0*{qk0Z)pNTt0QVZ^$`4vImEB>;Lo2!7K05TpY-sl#sWBz_W-aDIV`Ksabi zvpa#93Svo!70W*Ydh)Qzm{0?CU`y;T^ITg-J9nfWeZ-sbw)G@W?$Eomf%Bg2frfh5 zRm1{|E0+(4zXy){$}uC3%Y-mSA2-^I>Tw|gQx|7TDli_hB>``)Q^aZ`LJC2V3U$SABP}T)%}9g2pF9dT}aC~!rFFgkl1J$ z`^z{Arn3On-m%}r}TGF8KQe*OjSJ=T|caa_E;v89A{t@$yT^(G9=N9F?^kT*#s3qhJq!IH5|AhnqFd z0B&^gm3w;YbMNUKU>naBAO@fbz zqw=n!@--}o5;k6DvTW9pw)IJVz;X}ncbPVrmH>4x);8cx;q3UyiML1PWp%bxSiS|^ zC5!kc4qw%NSOGQ*Kcd#&$30=lDvs#*4W4q0u8E02U)7d=!W7+NouEyuF1dyH$D@G& zaFaxo9Ex|ZXA5y{eZT*i*dP~INSMAi@mvEX@q5i<&o&#sM}Df?Og8n8Ku4vOux=T% zeuw~z1hR}ZNwTn8KsQHKLwe2>p^K`YWUJEdVEl|mO21Bov!D0D$qPoOv=vJJ`)|%_ z>l%`eexY7t{BlVKP!`a^U@nM?#9OC*t76My_E_<16vCz1x_#82qj2PkWiMWgF8bM9 z(1t4VdHcJ;B~;Q%x01k_gQ0>u2*OjuEWNOGX#4}+N?Gb5;+NQMqp}Puqw2HnkYuKA zzKFWGHc&K>gwVgI1Sc9OT1s6fq=>$gZU!!xsilA$fF`kLdGoX*^t}ao@+^WBpk>`8 z4v_~gK|c2rCq#DZ+H)$3v~Hoi=)=1D==e3P zpKrRQ+>O^cyTuWJ%2}__0Z9SM_z9rptd*;-9uC1tDw4+A!=+K%8~M&+Zk#13hY$Y$ zo-8$*8dD5@}XDi19RjK6T^J~DIXbF5w&l?JLHMrf0 zLv0{7*G!==o|B%$V!a=EtVHdMwXLtmO~vl}P6;S(R2Q>*kTJK~!}gloxj)m|_LYK{ zl(f1cB=EON&wVFwK?MGn^nWuh@f95SHatPs(jcwSY#Dnl1@_gkOJ5=f`%s$ZHljRH0 z+c%lrb=Gi&N&1>^L_}#m>=U=(oT^vTA&3!xXNyqi$pdW1BDJ#^{h|2tZc{t^vag3& zAD7*8C`chNF|27itjBUo^CCDyEpJLX3&u+(L;YeeMwnXEoyN(ytoEabcl$lSgx~Ltatn}b$@j_yyMrBb03)shJE*$;Mw=;mZd&8e>IzE+4WIoH zCSZE7WthNUL$|Y#m!Hn?x7V1CK}V`KwW2D$-7&ODy5Cj;!_tTOOo1Mm%(RUt)#$@3 zhurA)t<7qik%%1Et+N1?R#hdBB#LdQ7{%-C zn$(`5e0eFh(#c*hvF>WT*07fk$N_631?W>kfjySN8^XC9diiOd#s?4tybICF;wBjp zIPzilX3{j%4u7blhq)tnaOBZ_`h_JqHXuI7SuIlNTgBk9{HIS&3|SEPfrvcE<@}E` zKk$y*nzsqZ{J{uWW9;#n=de&&h>m#A#q)#zRonr(?mDOYU&h&aQWD;?Z(22wY?t$U3qo`?{+amA$^TkxL+Ex2dh`q7iR&TPd0Ymwzo#b? zP$#t=elB5?k$#uE$K>C$YZbYUX_JgnXA`oF_Ifz4H7LEOW~{Gww&3s=wH4+j8*TU| zSX%LtJWqhr-xGNSe{;(16kxnak6RnZ{0qZ^kJI5X*It_YuynSpi(^-}Lolr{)#z_~ zw!(J-8%7Ybo^c3(mED`Xz8xecP35a6M8HarxRn%+NJBE;dw>>Y2T&;jzRd4FSDO3T zt*y+zXCtZQ0bP0yf6HRpD|WmzP;DR^-g^}{z~0x~z4j8m zucTe%k&S9Nt-?Jb^gYW1w6!Y3AUZ0Jcq;pJ)Exz%7k+mUOm6%ApjjSmflfKwBo6`B zhNb@$NHTJ>guaj9S{@DX)!6)b-Shav=DNKWy(V00k(D!v?PAR0f0vDNq*#mYmUp6> z76KxbFDw5U{{qx{BRj(>?|C`82ICKbfLxoldov-M?4Xl+3;I4GzLHyPOzYw7{WQST zPNYcx5onA%MAO9??41Po*1zW(Y%Zzn06-lUp{s<3!_9vv9HBjT02On0Hf$}NP;wF) zP<`2p3}A^~1YbvOh{ePMx$!JGUPX-tbBzp3mDZMY;}h;sQ->!p97GA)9a|tF(Gh{1$xk7 zUw?ELkT({Xw!KIr);kTRb1b|UL`r2_`a+&UFVCdJ)1T#fdh;71EQl9790Br0m_`$x z9|ZANuchFci8GNZ{XbP=+uXSJRe(;V5laQz$u18#?X*9}x7cIEbnr%<=1cX3EIu7$ zhHW6pe5M(&qEtsqRa>?)*{O;OJT+YUhG5{km|YI7I@JL_3Hwao9aXneiSA~a* z|Lp@c-oMNyeAEuUz{F?kuou3x#C*gU?lon!RC1s37gW^0Frc`lqQWH&(J4NoZg3m8 z;Lin#8Q+cFPD7MCzj}#|ws7b@?D9Q4dVjS4dpco=4yX5SSH=A@U@yqPdp@?g?qeia zH=Tt_9)G=6C2QIPsi-QipnK(mc0xXIN;j$WLf@n8eYvMk;*H-Q4tK%(3$CN}NGgO8n}fD~+>?<3UzvsrMf*J~%i;VKQHbF%TPalFi=#sgj)(P#SM^0Q=Tr>4kJVw8X3iWsP|e8tj}NjlMdWp z@2+M4HQu~3!=bZpjh;;DIDk&X}=c8~kn)FWWH z2KL1w^rA5&1@@^X%MjZ7;u(kH=YhH2pJPFQe=hn>tZd5RC5cfGYis8s9PKaxi*}-s6*W zRA^PwR=y^5Z){!(4D9-KC;0~;b*ploznFOaU`bJ_7U?qAi#mTo!&rIECRL$_y@yI27x2?W+zqDBD5~KCVYKFZLK+>ABC(Kj zeAll)KMgIlAG`r^rS{loBrGLtzhHY8$)<_S<(Dpkr(Ym@@vnQ&rS@FC*>2@XCH}M+an74WcRDcoQ+a3@A z9tYhl5$z7bMdTvD2r&jztBuo37?*k~wcU9GK2-)MTFS-lux-mIRYUuGUCI~V$?s#< z?1qAWb(?ZLm(N>%S%y10COdaq_Tm5c^%ooIxpR=`3e4C|@O5wY+eLik&XVi5oT7oe zmxH)Jd*5eo@!7t`x8!K=-+zJ-Sz)B_V$)s1pW~CDU$=q^&ABvf6S|?TOMB-RIm@CoFg>mjIQE)?+A1_3s6zmFU_oW&BqyMz1mY*IcP_2knjq5 zqw~JK(cVsmzc7*EvTT2rvpeqhg)W=%TOZ^>f`rD4|7Z5fq*2D^lpCttIg#ictgqZ$P@ru6P#f$x#KfnfTZj~LG6U_d-kE~`;kU_X)`H5so@?C zWmb!7x|xk@0L~0JFall*@ltyiL^)@3m4MqC7(7H0sH!WidId1#f#6R{Q&A!XzO1IAcIx;$k66dumt6lpUw@nL2MvqJ5^kbOVZ<^2jt5-njy|2@`07}0w z;M%I1$FCoLy`8xp8Tk)bFr;7aJeQ9KK6p=O$U0-&JYYy8woV*>b+FB?xLX`=pirYM z5K$BA(u)+jR{?O2r$c_Qvl?M{=Ar{yQ!UVsVn4k@0!b?_lA;dVz9uaQUgBH8Oz(Sb zrEs;&Ey>_ex8&!N{PmQjp+-Hlh|OA&wvDai#GpU=^-B70V0*LF=^bi+Nhe_o|azZ%~ZZ1$}LTmWt4aoB1 zPgccm$EwYU+jrdBaQFxQfn5gd(gM`Y*Ro1n&Zi?j=(>T3kmf94vdhf?AuS8>$Va#P zGL5F+VHpxdsCUa}+RqavXCobI-@B;WJbMphpK2%6t=XvKWWE|ruvREgM+|V=i6;;O zx$g=7^`$XWn0fu!gF=Xe9cMB8Z_SelD>&o&{1XFS`|nInK3BXlaeD*rc;R-#osyIS zWv&>~^TLIyBB6oDX+#>3<_0+2C4u2zK^wmHXXDD9_)kmLYJ!0SzM|%G9{pi)`X$uf zW}|%%#LgyK7m(4{V&?x_0KEDq56tk|0YNY~B(Sr|>WVz-pO3A##}$JCT}5P7DY+@W z#gJv>pA5>$|E3WO2tV7G^SuymB?tY`ooKcN3!vaQMnBNk-WATF{-$#}FyzgtJ8M^; zUK6KWSG)}6**+rZ&?o@PK3??uN{Q)#+bDP9i1W&j)oaU5d0bIWJ_9T5ac!qc?x66Q z$KUSZ`nYY94qfN_dpTFr8OW~A?}LD;Yty-BA)-be5Z3S#t2Io%q+cAbnGj1t$|qFR z9o?8B7OA^KjCYL=-!p}w(dkC^G6Nd%_I=1))PC0w5}ZZGJxfK)jP4Fwa@b-SYBw?% zdz9B-<`*B2dOn(N;mcTm%Do)rIvfXRNFX&1h`?>Rzuj~Wx)$p13nrDlS8-jwq@e@n zNIj_|8or==8~1h*Ih?w*8K7rYkGlwlTWAwLKc5}~dfz3y`kM&^Q|@C%1VAp_$wnw6zG~W4O+^ z>i?NY?oXf^Puc~+fDM$VgRNBpOZj{2cMP~gCqWAX4 z7>%$ux8@a&_B(pt``KSt;r+sR-$N;jdpY>|pyvPiN)9ohd*>mVST3wMo)){`B(&eX z1?zZJ-4u9NZ|~j1rdZYq4R$?swf}<6(#ex%7r{kh%U@kT)&kWuAszS%oJts=*OcL9 zaZwK<5DZw%1IFHXgFplP6JiL^dk8+SgM$D?8X+gE4172hXh!WeqIO>}$I9?Nry$*S zQ#f)RuH{P7RwA3v9f<-w>{PSzom;>(i&^l{E0(&Xp4A-*q-@{W1oE3K;1zb{&n28dSC2$N+6auXe0}e4b z)KLJ?5c*>@9K#I^)W;uU_Z`enquTUxr>mNq z1{0_puF-M7j${rs!dxxo3EelGodF1TvjV;Zpo;s{5f1pyCuRp=HDZ?s#IA4f?h|-p zGd|Mq^4hDa@Bh!c4ZE?O&x&XZ_ptZGYK4$9F4~{%R!}G1leCBx`dtNUS|K zL-7J5s4W@%mhXg1!}a4PD%!t&Qn%f_oquRajn3@C*)`o&K9o7V6DwzVMEhjVdDJ1fjhr#@=lp#@4EBqi=CCQ>73>R(>QKPNM&_Jpe5G`n4wegeC`FYEPJ{|vwS>$-`fuRSp3927qOv|NC3T3G-0 zA{K`|+tQy1yqE$ShWt8ny&5~)%ITb@^+x$w0)f&om;P8B)@}=Wzy59BwUfZ1vqw87 za2lB8J(&*l#(V}Id8SyQ0C(2amzkz3EqG&Ed0Jq1)$|&>4_|NIe=5|n=3?siFV0fI z{As5DLW^gs|B-b4C;Hd(SM-S~GQhzb>HgF2|2Usww0nL^;x@1eaB)=+Clj+$fF@H( z-fqP??~QMT$KI-#m;QC*&6vkp&8699G3)Bq0*kFZXINw=b9OVaed(3(3kS|IZ)CM? zJdnW&%t8MveBuK21uiYj)_a{Fnw0OErMzMN?d$QoPwkhOwcP&p+t>P)4tHlYw-pPN z^oJ=uc$Sl>pv@fZH~ZqxSvdhF@F1s=oZawpr^-#l{IIOGG=T%QXjtwPhIg-F@k@uIlr?J->Ia zpEUQ*=4g|XYn4Gez&aHr*;t$u3oODPmc2Ku)2Og|xjc%w;q!Zz+zY)*3{7V8bK4;& zYV82FZ+8?v)`J|G1w4I0fWdKg|2b#iaazCv;|?(W-q}$o&Y}Q5d@BRk^jL7#{kbCK zSgkyu;=DV+or2)AxCBgq-nj5=@n^`%T#V+xBGEkW4lCqrE)LMv#f;AvD__cQ@Eg3`~x| zW+h9mofSXCq5|M)9|ez(#X?-sxB%Go8};sJ?2abp(Y!lyi>k)|{M*Z$c{e1-K4ky` MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/frontend/app_flowy/packages/appflowy_board/example/web/index.html b/frontend/app_flowy/packages/appflowy_board/example/web/index.html new file mode 100644 index 0000000000..41b3bc336f --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/web/index.html @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + example + + + + + + + + + + diff --git a/frontend/app_flowy/packages/appflowy_board/example/web/manifest.json b/frontend/app_flowy/packages/appflowy_board/example/web/manifest.json new file mode 100644 index 0000000000..096edf8fe4 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/example/web/manifest.json @@ -0,0 +1,35 @@ +{ + "name": "example", + "short_name": "example", + "start_url": ".", + "display": "standalone", + "background_color": "#0175C2", + "theme_color": "#0175C2", + "description": "A new Flutter project.", + "orientation": "portrait-primary", + "prefer_related_applications": false, + "icons": [ + { + "src": "icons/Icon-192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "icons/Icon-512.png", + "sizes": "512x512", + "type": "image/png" + }, + { + "src": "icons/Icon-maskable-192.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "maskable" + }, + { + "src": "icons/Icon-maskable-512.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" + } + ] +} diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/.gitignore b/frontend/app_flowy/packages/appflowy_board/example/windows/.gitignore similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/.gitignore rename to frontend/app_flowy/packages/appflowy_board/example/windows/.gitignore diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/CMakeLists.txt b/frontend/app_flowy/packages/appflowy_board/example/windows/CMakeLists.txt similarity index 97% rename from frontend/app_flowy/packages/flowy_board/example/windows/CMakeLists.txt rename to frontend/app_flowy/packages/appflowy_board/example/windows/CMakeLists.txt index be7b6a81d7..c0270746b1 100644 --- a/frontend/app_flowy/packages/flowy_board/example/windows/CMakeLists.txt +++ b/frontend/app_flowy/packages/appflowy_board/example/windows/CMakeLists.txt @@ -1,10 +1,10 @@ # Project-level configuration. cmake_minimum_required(VERSION 3.14) -project(flowy_board_example LANGUAGES CXX) +project(example LANGUAGES CXX) # The name of the executable created for the application. Change this to change # the on-disk name of your application. -set(BINARY_NAME "flowy_board_example") +set(BINARY_NAME "example") # Explicitly opt in to modern CMake behaviors to avoid warnings with recent # versions of CMake. diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/CMakeLists.txt b/frontend/app_flowy/packages/appflowy_board/example/windows/flutter/CMakeLists.txt similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/flutter/CMakeLists.txt rename to frontend/app_flowy/packages/appflowy_board/example/windows/flutter/CMakeLists.txt diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.cc b/frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugin_registrant.cc similarity index 50% rename from frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.cc rename to frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugin_registrant.cc index 0f2f5a79bb..8b6d4680af 100644 --- a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.cc +++ b/frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugin_registrant.cc @@ -6,9 +6,6 @@ #include "generated_plugin_registrant.h" -#include void RegisterPlugins(flutter::PluginRegistry* registry) { - FlowyBoardPluginCApiRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlowyBoardPluginCApi")); } diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.h b/frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugin_registrant.h similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugin_registrant.h rename to frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugin_registrant.h diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugins.cmake b/frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugins.cmake similarity index 98% rename from frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugins.cmake rename to frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugins.cmake index f4e524195d..b93c4c30c1 100644 --- a/frontend/app_flowy/packages/flowy_board/example/windows/flutter/generated_plugins.cmake +++ b/frontend/app_flowy/packages/appflowy_board/example/windows/flutter/generated_plugins.cmake @@ -3,7 +3,6 @@ # list(APPEND FLUTTER_PLUGIN_LIST - flowy_board ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/CMakeLists.txt b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/CMakeLists.txt similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/CMakeLists.txt rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/CMakeLists.txt diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/Runner.rc b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/Runner.rc similarity index 86% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/Runner.rc rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/Runner.rc index f8dc624c95..8949260514 100644 --- a/frontend/app_flowy/packages/flowy_board/example/windows/runner/Runner.rc +++ b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/Runner.rc @@ -89,13 +89,13 @@ BEGIN BEGIN BLOCK "040904e4" BEGIN - VALUE "CompanyName", "com.example" "\0" - VALUE "FileDescription", "flowy_board_example" "\0" + VALUE "CompanyName", "com.appflowy.board" "\0" + VALUE "FileDescription", "example" "\0" VALUE "FileVersion", VERSION_AS_STRING "\0" - VALUE "InternalName", "flowy_board_example" "\0" - VALUE "LegalCopyright", "Copyright (C) 2022 com.example. All rights reserved." "\0" - VALUE "OriginalFilename", "flowy_board_example.exe" "\0" - VALUE "ProductName", "flowy_board_example" "\0" + VALUE "InternalName", "example" "\0" + VALUE "LegalCopyright", "Copyright (C) 2022 com.appflowy.board. All rights reserved." "\0" + VALUE "OriginalFilename", "example.exe" "\0" + VALUE "ProductName", "example" "\0" VALUE "ProductVersion", VERSION_AS_STRING "\0" END END diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.cpp b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/flutter_window.cpp similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.cpp rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/flutter_window.cpp diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.h b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/flutter_window.h similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/flutter_window.h rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/flutter_window.h diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/main.cpp b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/main.cpp similarity index 94% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/main.cpp rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/main.cpp index b59eac7d92..bcb57b0e2a 100644 --- a/frontend/app_flowy/packages/flowy_board/example/windows/runner/main.cpp +++ b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/main.cpp @@ -27,7 +27,7 @@ int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, FlutterWindow window(project); Win32Window::Point origin(10, 10); Win32Window::Size size(1280, 720); - if (!window.CreateAndShow(L"flowy_board_example", origin, size)) { + if (!window.CreateAndShow(L"example", origin, size)) { return EXIT_FAILURE; } window.SetQuitOnClose(true); diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/resource.h b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/resource.h similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/resource.h rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/resource.h diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/resources/app_icon.ico b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/resources/app_icon.ico similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/resources/app_icon.ico rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/resources/app_icon.ico diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/runner.exe.manifest b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/runner.exe.manifest similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/runner.exe.manifest rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/runner.exe.manifest diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.cpp b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/utils.cpp similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.cpp rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/utils.cpp diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.h b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/utils.h similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/utils.h rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/utils.h diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.cpp b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/win32_window.cpp similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.cpp rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/win32_window.cpp diff --git a/frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.h b/frontend/app_flowy/packages/appflowy_board/example/windows/runner/win32_window.h similarity index 100% rename from frontend/app_flowy/packages/flowy_board/example/windows/runner/win32_window.h rename to frontend/app_flowy/packages/appflowy_board/example/windows/runner/win32_window.h diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart similarity index 84% rename from frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart rename to frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart index c145be5440..684868a2a3 100644 --- a/frontend/app_flowy/packages/flowy_board/lib/flowy_board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart @@ -1,4 +1,4 @@ -library flowy_board; +library appflowy_board; export 'src/widgets/board_column/board_column_data.dart'; export 'src/widgets/board_data.dart'; diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/rendering/board_overlay.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/rendering/board_overlay.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/rendering/board_overlay.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/utils/log.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/board.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_column/board_column_data.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/board_data.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_state.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_state.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_state.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_flex.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_flex.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_flex.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_mixin.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/flex/reorder_mixin.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_mixin.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_controller.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_controller.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_controller.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_state.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/phantom/phantom_state.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_state.dart diff --git a/frontend/app_flowy/packages/flowy_board/lib/src/widgets/transitions.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/transitions.dart similarity index 100% rename from frontend/app_flowy/packages/flowy_board/lib/src/widgets/transitions.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/transitions.dart diff --git a/frontend/app_flowy/packages/flowy_board/pubspec.yaml b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml similarity index 53% rename from frontend/app_flowy/packages/flowy_board/pubspec.yaml rename to frontend/app_flowy/packages/appflowy_board/pubspec.yaml index f38763fc04..b0955998ea 100644 --- a/frontend/app_flowy/packages/flowy_board/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml @@ -1,16 +1,16 @@ -name: flowy_board -description: A new Flutter plugin project. -version: 0.0.1 -homepage: +name: appflowy_board +description: A new Flutter package project. +version: 0.0.3 +homepage: https://github.com/AppFlowy-IO/AppFlowy +repository: https://github.com/AppFlowy-IO/AppFlowy environment: - sdk: ">=2.17.1 <3.0.0" - flutter: ">=2.5.0" + sdk: ">=2.17.6 <3.0.0" + flutter: ">=1.17.0" dependencies: flutter: sdk: flutter - plugin_platform_interface: ^2.0.2 equatable: ^2.0.3 provider: ^6.0.1 @@ -24,26 +24,8 @@ dev_dependencies: # The following section is specific to Flutter packages. flutter: - # This section identifies this Flutter project as a plugin project. - # The 'pluginClass' specifies the class (in Java, Kotlin, Swift, Objective-C, etc.) - # which should be registered in the plugin registry. This is required for - # using method channels. - # The Android 'package' specifies package in which the registered class is. - # This is required for using method channels on Android. - # The 'ffiPlugin' specifies that native code should be built and bundled. - # This is required for using `dart:ffi`. - # All these are used by the tooling to maintain consistency when - # adding or updating assets for this project. - plugin: - platforms: - linux: - pluginClass: FlowyBoardPlugin - macos: - pluginClass: FlowyBoardPlugin - windows: - pluginClass: FlowyBoardPluginCApi - # To add assets to your plugin package, add an assets section, like this: + # To add assets to your package, add an assets section, like this: # assets: # - images/a_dot_burr.jpeg # - images/a_dot_ham.jpeg @@ -54,7 +36,7 @@ flutter: # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware - # To add custom fonts to your plugin package, add a fonts section here, + # To add custom fonts to your package, add a fonts section here, # in this "flutter" section. Each entry in this list should have a # "family" key with the font family name, and a "fonts" key with a # list giving the asset and other descriptors for the font. For diff --git a/frontend/app_flowy/packages/appflowy_board/test/appflowy_board_test.dart b/frontend/app_flowy/packages/appflowy_board/test/appflowy_board_test.dart new file mode 100644 index 0000000000..ab73b3a234 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/test/appflowy_board_test.dart @@ -0,0 +1 @@ +void main() {} diff --git a/frontend/app_flowy/packages/flowy_board/.metadata b/frontend/app_flowy/packages/flowy_board/.metadata deleted file mode 100644 index 2e1d1339c3..0000000000 --- a/frontend/app_flowy/packages/flowy_board/.metadata +++ /dev/null @@ -1,36 +0,0 @@ -# This file tracks properties of this Flutter project. -# Used by Flutter tool to assess capabilities and perform upgrades etc. -# -# This file should be version controlled. - -version: - revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - channel: stable - -project_type: plugin - -# Tracks metadata for the flutter migrate command -migration: - platforms: - - platform: root - create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - - platform: linux - create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - - platform: macos - create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - - platform: windows - create_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - base_revision: f1875d570e39de09040c8f79aa13cc56baab8db1 - - # User provided section - - # List of Local paths (relative to this file) that should be - # ignored by the migrate tool. - # - # Files that are not part of the templates will be ignored by default. - unmanaged_files: - - 'lib/main.dart' - - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/frontend/app_flowy/packages/flowy_board/CHANGELOG.md b/frontend/app_flowy/packages/flowy_board/CHANGELOG.md deleted file mode 100644 index 41cc7d8192..0000000000 --- a/frontend/app_flowy/packages/flowy_board/CHANGELOG.md +++ /dev/null @@ -1,3 +0,0 @@ -## 0.0.1 - -* TODO: Describe initial release. diff --git a/frontend/app_flowy/packages/flowy_board/README.md b/frontend/app_flowy/packages/flowy_board/README.md deleted file mode 100644 index fab2cf6e57..0000000000 --- a/frontend/app_flowy/packages/flowy_board/README.md +++ /dev/null @@ -1,15 +0,0 @@ -# flowy_board - -A new Flutter plugin project. - -## Getting Started - -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. - -For help getting started with Flutter development, view the -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. - diff --git a/frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json b/frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json deleted file mode 100644 index 3652b92f27..0000000000 --- a/frontend/app_flowy/packages/flowy_board/example/.vscode/launch.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - // Use IntelliSense to learn about possible attributes. - // Hover to view descriptions of existing attributes. - // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 - "version": "0.2.0", - "configurations": [ - { - "name": "example", - "cwd": "example", - "request": "launch", - "env": { - "Dart_LOG": "true" - }, - "type": "dart" - }, - { - "name": "example (profile mode)", - "cwd": "example", - "request": "launch", - "type": "dart", - "env": { - "Dart_LOG": "true" - }, - "flutterMode": "profile" - }, - { - "name": "example (release mode)", - "cwd": "example", - "request": "launch", - "type": "dart", - "env": { - "Dart_LOG": "true" - }, - "flutterMode": "release" - } - ] -} \ No newline at end of file diff --git a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc b/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc deleted file mode 100644 index 8881f303d7..0000000000 --- a/frontend/app_flowy/packages/flowy_board/example/linux/flutter/generated_plugin_registrant.cc +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#include "generated_plugin_registrant.h" - -#include - -void fl_register_plugins(FlPluginRegistry* registry) { - g_autoptr(FlPluginRegistrar) flowy_board_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FlowyBoardPlugin"); - flowy_board_plugin_register_with_registrar(flowy_board_registrar); -} diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig deleted file mode 100644 index 4b81f9b2d2..0000000000 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Debug.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig b/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig deleted file mode 100644 index 5caa9d1579..0000000000 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Flutter/Flutter-Release.xcconfig +++ /dev/null @@ -1,2 +0,0 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" -#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Podfile b/frontend/app_flowy/packages/flowy_board/example/macos/Podfile deleted file mode 100644 index dade8dfad0..0000000000 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Podfile +++ /dev/null @@ -1,40 +0,0 @@ -platform :osx, '10.11' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock b/frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock deleted file mode 100644 index 18dcf94df9..0000000000 --- a/frontend/app_flowy/packages/flowy_board/example/macos/Podfile.lock +++ /dev/null @@ -1,22 +0,0 @@ -PODS: - - flowy_board (0.0.1): - - FlutterMacOS - - FlutterMacOS (1.0.0) - -DEPENDENCIES: - - flowy_board (from `Flutter/ephemeral/.symlinks/plugins/flowy_board/macos`) - - FlutterMacOS (from `Flutter/ephemeral`) - -EXTERNAL SOURCES: - flowy_board: - :path: Flutter/ephemeral/.symlinks/plugins/flowy_board/macos - FlutterMacOS: - :path: Flutter/ephemeral - -SPEC CHECKSUMS: - flowy_board: e93adfa305df65f1ac860f2cf9dc7188f50c9b66 - FlutterMacOS: 57701585bf7de1b3fc2bb61f6378d73bbdea8424 - -PODFILE CHECKSUM: 6eac6b3292e5142cfc23bdeb71848a40ec51c14c - -COCOAPODS: 1.11.3 diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart deleted file mode 100644 index f0acb6faa0..0000000000 --- a/frontend/app_flowy/packages/flowy_board/lib/flowy_board_method_channel.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/foundation.dart'; -import 'package:flutter/services.dart'; - -import 'flowy_board_platform_interface.dart'; - -/// An implementation of [FlowyBoardPlatform] that uses method channels. -class MethodChannelFlowyBoard extends FlowyBoardPlatform { - /// The method channel used to interact with the native platform. - @visibleForTesting - final methodChannel = const MethodChannel('flowy_board'); - - @override - Future getPlatformVersion() async { - final version = await methodChannel.invokeMethod('getPlatformVersion'); - return version; - } -} diff --git a/frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart b/frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart deleted file mode 100644 index 2f8d314b3b..0000000000 --- a/frontend/app_flowy/packages/flowy_board/lib/flowy_board_platform_interface.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:plugin_platform_interface/plugin_platform_interface.dart'; - -import 'flowy_board_method_channel.dart'; - -abstract class FlowyBoardPlatform extends PlatformInterface { - /// Constructs a FlowyBoardPlatform. - FlowyBoardPlatform() : super(token: _token); - - static final Object _token = Object(); - - static FlowyBoardPlatform _instance = MethodChannelFlowyBoard(); - - /// The default instance of [FlowyBoardPlatform] to use. - /// - /// Defaults to [MethodChannelFlowyBoard]. - static FlowyBoardPlatform get instance => _instance; - - /// Platform-specific implementations should set this with their own - /// platform-specific class that extends [FlowyBoardPlatform] when - /// they register themselves. - static set instance(FlowyBoardPlatform instance) { - PlatformInterface.verifyToken(instance, _token); - _instance = instance; - } - - Future getPlatformVersion() { - throw UnimplementedError('platformVersion() has not been implemented.'); - } -} diff --git a/frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt deleted file mode 100644 index ce0512464c..0000000000 --- a/frontend/app_flowy/packages/flowy_board/linux/CMakeLists.txt +++ /dev/null @@ -1,47 +0,0 @@ -# The Flutter tooling requires that developers have CMake 3.10 or later -# installed. You should not increase this version, as doing so will cause -# the plugin to fail to compile for some customers of the plugin. -cmake_minimum_required(VERSION 3.10) - -# Project-level configuration. -set(PROJECT_NAME "flowy_board") -project(${PROJECT_NAME} LANGUAGES CXX) - -# This value is used when generating builds using this plugin, so it must -# not be changed. -set(PLUGIN_NAME "flowy_board_plugin") - -# Define the plugin library target. Its name must not be changed (see comment -# on PLUGIN_NAME above). -# -# Any new source files that you add to the plugin should be added here. -add_library(${PLUGIN_NAME} SHARED - "flowy_board_plugin.cc" -) - -# Apply a standard set of build settings that are configured in the -# application-level CMakeLists.txt. This can be removed for plugins that want -# full control over build settings. -apply_standard_settings(${PLUGIN_NAME}) - -# Symbols are hidden by default to reduce the chance of accidental conflicts -# between plugins. This should not be removed; any symbols that should be -# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. -set_target_properties(${PLUGIN_NAME} PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) - -# Source include directories and library dependencies. Add any plugin-specific -# dependencies here. -target_include_directories(${PLUGIN_NAME} INTERFACE - "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) -target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) - -# List of absolute paths to libraries that should be bundled with the plugin. -# This list could contain prebuilt libraries, or libraries created by an -# external build triggered from this build file. -set(flowy_board_bundled_libraries - "" - PARENT_SCOPE -) diff --git a/frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc b/frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc deleted file mode 100644 index 9e5d65cc4e..0000000000 --- a/frontend/app_flowy/packages/flowy_board/linux/flowy_board_plugin.cc +++ /dev/null @@ -1,70 +0,0 @@ -#include "include/flowy_board/flowy_board_plugin.h" - -#include -#include -#include - -#include - -#define FLOWY_BOARD_PLUGIN(obj) \ - (G_TYPE_CHECK_INSTANCE_CAST((obj), flowy_board_plugin_get_type(), \ - FlowyBoardPlugin)) - -struct _FlowyBoardPlugin { - GObject parent_instance; -}; - -G_DEFINE_TYPE(FlowyBoardPlugin, flowy_board_plugin, g_object_get_type()) - -// Called when a method call is received from Flutter. -static void flowy_board_plugin_handle_method_call( - FlowyBoardPlugin* self, - FlMethodCall* method_call) { - g_autoptr(FlMethodResponse) response = nullptr; - - const gchar* method = fl_method_call_get_name(method_call); - - if (strcmp(method, "getPlatformVersion") == 0) { - struct utsname uname_data = {}; - uname(&uname_data); - g_autofree gchar *version = g_strdup_printf("Linux %s", uname_data.version); - g_autoptr(FlValue) result = fl_value_new_string(version); - response = FL_METHOD_RESPONSE(fl_method_success_response_new(result)); - } else { - response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new()); - } - - fl_method_call_respond(method_call, response, nullptr); -} - -static void flowy_board_plugin_dispose(GObject* object) { - G_OBJECT_CLASS(flowy_board_plugin_parent_class)->dispose(object); -} - -static void flowy_board_plugin_class_init(FlowyBoardPluginClass* klass) { - G_OBJECT_CLASS(klass)->dispose = flowy_board_plugin_dispose; -} - -static void flowy_board_plugin_init(FlowyBoardPlugin* self) {} - -static void method_call_cb(FlMethodChannel* channel, FlMethodCall* method_call, - gpointer user_data) { - FlowyBoardPlugin* plugin = FLOWY_BOARD_PLUGIN(user_data); - flowy_board_plugin_handle_method_call(plugin, method_call); -} - -void flowy_board_plugin_register_with_registrar(FlPluginRegistrar* registrar) { - FlowyBoardPlugin* plugin = FLOWY_BOARD_PLUGIN( - g_object_new(flowy_board_plugin_get_type(), nullptr)); - - g_autoptr(FlStandardMethodCodec) codec = fl_standard_method_codec_new(); - g_autoptr(FlMethodChannel) channel = - fl_method_channel_new(fl_plugin_registrar_get_messenger(registrar), - "flowy_board", - FL_METHOD_CODEC(codec)); - fl_method_channel_set_method_call_handler(channel, method_call_cb, - g_object_ref(plugin), - g_object_unref); - - g_object_unref(plugin); -} diff --git a/frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h b/frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h deleted file mode 100644 index 076b022a5a..0000000000 --- a/frontend/app_flowy/packages/flowy_board/linux/include/flowy_board/flowy_board_plugin.h +++ /dev/null @@ -1,26 +0,0 @@ -#ifndef FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ -#define FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ - -#include - -G_BEGIN_DECLS - -#ifdef FLUTTER_PLUGIN_IMPL -#define FLUTTER_PLUGIN_EXPORT __attribute__((visibility("default"))) -#else -#define FLUTTER_PLUGIN_EXPORT -#endif - -typedef struct _FlowyBoardPlugin FlowyBoardPlugin; -typedef struct { - GObjectClass parent_class; -} FlowyBoardPluginClass; - -FLUTTER_PLUGIN_EXPORT GType flowy_board_plugin_get_type(); - -FLUTTER_PLUGIN_EXPORT void flowy_board_plugin_register_with_registrar( - FlPluginRegistrar* registrar); - -G_END_DECLS - -#endif // FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ diff --git a/frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift b/frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift deleted file mode 100644 index c7f8d0260d..0000000000 --- a/frontend/app_flowy/packages/flowy_board/macos/Classes/FlowyBoardPlugin.swift +++ /dev/null @@ -1,19 +0,0 @@ -import Cocoa -import FlutterMacOS - -public class FlowyBoardPlugin: NSObject, FlutterPlugin { - public static func register(with registrar: FlutterPluginRegistrar) { - let channel = FlutterMethodChannel(name: "flowy_board", binaryMessenger: registrar.messenger) - let instance = FlowyBoardPlugin() - registrar.addMethodCallDelegate(instance, channel: channel) - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "getPlatformVersion": - result("macOS " + ProcessInfo.processInfo.operatingSystemVersionString) - default: - result(FlutterMethodNotImplemented) - } - } -} diff --git a/frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec b/frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec deleted file mode 100644 index 06d567ad65..0000000000 --- a/frontend/app_flowy/packages/flowy_board/macos/flowy_board.podspec +++ /dev/null @@ -1,23 +0,0 @@ -# -# To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html. -# Run `pod lib lint flowy_board.podspec` to validate before publishing. -# -Pod::Spec.new do |s| - s.name = 'flowy_board' - s.version = '0.0.1' - s.summary = 'A new Flutter plugin project.' - s.description = <<-DESC -A new Flutter plugin project. - DESC - s.homepage = 'http://example.com' - s.license = { :file => '../LICENSE' } - s.author = { 'Your Company' => 'email@example.com' } - - s.source = { :path => '.' } - s.source_files = 'Classes/**/*' - s.dependency 'FlutterMacOS' - - s.platform = :osx, '10.11' - s.pod_target_xcconfig = { 'DEFINES_MODULE' => 'YES' } - s.swift_version = '5.0' -end diff --git a/frontend/app_flowy/packages/flowy_board/windows/.gitignore b/frontend/app_flowy/packages/flowy_board/windows/.gitignore deleted file mode 100644 index b3eb2be169..0000000000 --- a/frontend/app_flowy/packages/flowy_board/windows/.gitignore +++ /dev/null @@ -1,17 +0,0 @@ -flutter/ - -# Visual Studio user-specific files. -*.suo -*.user -*.userosscache -*.sln.docstates - -# Visual Studio build-related files. -x64/ -x86/ - -# Visual Studio cache files -# files ending in .cache can be ignored -*.[Cc]ache -# but keep track of directories ending in .cache -!*.[Cc]ache/ diff --git a/frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt b/frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt deleted file mode 100644 index 193c5908ec..0000000000 --- a/frontend/app_flowy/packages/flowy_board/windows/CMakeLists.txt +++ /dev/null @@ -1,53 +0,0 @@ -# The Flutter tooling requires that developers have a version of Visual Studio -# installed that includes CMake 3.14 or later. You should not increase this -# version, as doing so will cause the plugin to fail to compile for some -# customers of the plugin. -cmake_minimum_required(VERSION 3.14) - -# Project-level configuration. -set(PROJECT_NAME "flowy_board") -project(${PROJECT_NAME} LANGUAGES CXX) - -# This value is used when generating builds using this plugin, so it must -# not be changed -set(PLUGIN_NAME "flowy_board_plugin") - -# Any new source files that you add to the plugin should be added here. -list(APPEND PLUGIN_SOURCES - "flowy_board_plugin.cpp" - "flowy_board_plugin.h" -) - -# Define the plugin library target. Its name must not be changed (see comment -# on PLUGIN_NAME above). -add_library(${PLUGIN_NAME} SHARED - "include/flowy_board/flowy_board_plugin_c_api.h" - "flowy_board_plugin_c_api.cpp" - ${PLUGIN_SOURCES} -) - -# Apply a standard set of build settings that are configured in the -# application-level CMakeLists.txt. This can be removed for plugins that want -# full control over build settings. -apply_standard_settings(${PLUGIN_NAME}) - -# Symbols are hidden by default to reduce the chance of accidental conflicts -# between plugins. This should not be removed; any symbols that should be -# exported should be explicitly exported with the FLUTTER_PLUGIN_EXPORT macro. -set_target_properties(${PLUGIN_NAME} PROPERTIES - CXX_VISIBILITY_PRESET hidden) -target_compile_definitions(${PLUGIN_NAME} PRIVATE FLUTTER_PLUGIN_IMPL) - -# Source include directories and library dependencies. Add any plugin-specific -# dependencies here. -target_include_directories(${PLUGIN_NAME} INTERFACE - "${CMAKE_CURRENT_SOURCE_DIR}/include") -target_link_libraries(${PLUGIN_NAME} PRIVATE flutter flutter_wrapper_plugin) - -# List of absolute paths to libraries that should be bundled with the plugin. -# This list could contain prebuilt libraries, or libraries created by an -# external build triggered from this build file. -set(flowy_board_bundled_libraries - "" - PARENT_SCOPE -) diff --git a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp deleted file mode 100644 index f54aaaa86b..0000000000 --- a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.cpp +++ /dev/null @@ -1,59 +0,0 @@ -#include "flowy_board_plugin.h" - -// This must be included before many other Windows headers. -#include - -// For getPlatformVersion; remove unless needed for your plugin implementation. -#include - -#include -#include -#include - -#include -#include - -namespace flowy_board { - -// static -void FlowyBoardPlugin::RegisterWithRegistrar( - flutter::PluginRegistrarWindows *registrar) { - auto channel = - std::make_unique>( - registrar->messenger(), "flowy_board", - &flutter::StandardMethodCodec::GetInstance()); - - auto plugin = std::make_unique(); - - channel->SetMethodCallHandler( - [plugin_pointer = plugin.get()](const auto &call, auto result) { - plugin_pointer->HandleMethodCall(call, std::move(result)); - }); - - registrar->AddPlugin(std::move(plugin)); -} - -FlowyBoardPlugin::FlowyBoardPlugin() {} - -FlowyBoardPlugin::~FlowyBoardPlugin() {} - -void FlowyBoardPlugin::HandleMethodCall( - const flutter::MethodCall &method_call, - std::unique_ptr> result) { - if (method_call.method_name().compare("getPlatformVersion") == 0) { - std::ostringstream version_stream; - version_stream << "Windows "; - if (IsWindows10OrGreater()) { - version_stream << "10+"; - } else if (IsWindows8OrGreater()) { - version_stream << "8"; - } else if (IsWindows7OrGreater()) { - version_stream << "7"; - } - result->Success(flutter::EncodableValue(version_stream.str())); - } else { - result->NotImplemented(); - } -} - -} // namespace flowy_board diff --git a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h deleted file mode 100644 index 27eeafed7b..0000000000 --- a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ -#define FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ - -#include -#include - -#include - -namespace flowy_board { - -class FlowyBoardPlugin : public flutter::Plugin { - public: - static void RegisterWithRegistrar(flutter::PluginRegistrarWindows *registrar); - - FlowyBoardPlugin(); - - virtual ~FlowyBoardPlugin(); - - // Disallow copy and assign. - FlowyBoardPlugin(const FlowyBoardPlugin&) = delete; - FlowyBoardPlugin& operator=(const FlowyBoardPlugin&) = delete; - - private: - // Called when a method is called on this plugin's channel from Dart. - void HandleMethodCall( - const flutter::MethodCall &method_call, - std::unique_ptr> result); -}; - -} // namespace flowy_board - -#endif // FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_H_ diff --git a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp b/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp deleted file mode 100644 index 7e966de43f..0000000000 --- a/frontend/app_flowy/packages/flowy_board/windows/flowy_board_plugin_c_api.cpp +++ /dev/null @@ -1,12 +0,0 @@ -#include "include/flowy_board/flowy_board_plugin_c_api.h" - -#include - -#include "flowy_board_plugin.h" - -void FlowyBoardPluginCApiRegisterWithRegistrar( - FlutterDesktopPluginRegistrarRef registrar) { - flowy_board::FlowyBoardPlugin::RegisterWithRegistrar( - flutter::PluginRegistrarManager::GetInstance() - ->GetRegistrar(registrar)); -} diff --git a/frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h b/frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h deleted file mode 100644 index 9ad37dc483..0000000000 --- a/frontend/app_flowy/packages/flowy_board/windows/include/flowy_board/flowy_board_plugin_c_api.h +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_C_API_H_ -#define FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_C_API_H_ - -#include - -#ifdef FLUTTER_PLUGIN_IMPL -#define FLUTTER_PLUGIN_EXPORT __declspec(dllexport) -#else -#define FLUTTER_PLUGIN_EXPORT __declspec(dllimport) -#endif - -#if defined(__cplusplus) -extern "C" { -#endif - -FLUTTER_PLUGIN_EXPORT void FlowyBoardPluginCApiRegisterWithRegistrar( - FlutterDesktopPluginRegistrarRef registrar); - -#if defined(__cplusplus) -} // extern "C" -#endif - -#endif // FLUTTER_PLUGIN_FLOWY_BOARD_PLUGIN_C_API_H_ diff --git a/frontend/app_flowy/pubspec.lock b/frontend/app_flowy/pubspec.lock index 505280115f..1c8bdf6211 100644 --- a/frontend/app_flowy/pubspec.lock +++ b/frontend/app_flowy/pubspec.lock @@ -7,14 +7,14 @@ packages: name: _fe_analyzer_shared url: "https://pub.dartlang.org" source: hosted - version: "42.0.0" + version: "44.0.0" analyzer: dependency: "direct overridden" description: name: analyzer url: "https://pub.dartlang.org" source: hosted - version: "4.3.0" + version: "4.4.0" animations: dependency: transitive description: diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index 5e112a26c2..60b99061bc 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -77,7 +77,6 @@ dependencies: reorderables: ^0.5.0 linked_scroll_controller: ^0.2.0 - dev_dependencies: flutter_lints: ^1.0.0 flutter_test: @@ -87,7 +86,7 @@ dev_dependencies: bloc_test: ^9.0.2 dependency_overrides: - analyzer: ">=4.2.0 <5.0.0" + analyzer: ">=4.4.0 <5.0.0" # The "flutter_lints" package below contains a set of recommended lints to # encourage good coding practices. The lint set provided by the package is From 971a5e245a87acbf7a27f5eb8aba5e8e6b61dad5 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 8 Aug 2022 10:49:00 +0800 Subject: [PATCH 20/22] chore: rename folder --- .../lib/src/widgets/{flex => reorder_flex}/drag_state.dart | 0 .../lib/src/widgets/{flex => reorder_flex}/drag_target.dart | 0 .../widgets/{flex => reorder_flex}/drag_target_inteceptor.dart | 0 .../lib/src/widgets/{flex => reorder_flex}/reorder_flex.dart | 0 .../lib/src/widgets/{flex => reorder_flex}/reorder_mixin.dart | 0 .../widgets/{phantom => reorder_phantom}/phantom_controller.dart | 0 .../src/widgets/{phantom => reorder_phantom}/phantom_state.dart | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename frontend/app_flowy/packages/appflowy_board/lib/src/widgets/{flex => reorder_flex}/drag_state.dart (100%) rename frontend/app_flowy/packages/appflowy_board/lib/src/widgets/{flex => reorder_flex}/drag_target.dart (100%) rename frontend/app_flowy/packages/appflowy_board/lib/src/widgets/{flex => reorder_flex}/drag_target_inteceptor.dart (100%) rename frontend/app_flowy/packages/appflowy_board/lib/src/widgets/{flex => reorder_flex}/reorder_flex.dart (100%) rename frontend/app_flowy/packages/appflowy_board/lib/src/widgets/{flex => reorder_flex}/reorder_mixin.dart (100%) rename frontend/app_flowy/packages/appflowy_board/lib/src/widgets/{phantom => reorder_phantom}/phantom_controller.dart (100%) rename frontend/app_flowy/packages/appflowy_board/lib/src/widgets/{phantom => reorder_phantom}/phantom_state.dart (100%) diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_state.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_state.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_state.dart diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/drag_target_inteceptor.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_flex.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_mixin.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/flex/reorder_mixin.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_controller.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_controller.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_state.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart similarity index 100% rename from frontend/app_flowy/packages/appflowy_board/lib/src/widgets/phantom/phantom_state.dart rename to frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_state.dart From db5b3e3bd39280e2c7d7ed00e1b0a684a55491f8 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 8 Aug 2022 17:12:34 +0800 Subject: [PATCH 21/22] chore: update ui --- .../packages/appflowy_board/CHANGELOG.md | 5 + .../packages/appflowy_board/README.md | 94 +++++++++------ .../example/gifs/appflowy_board_video_1.gif | Bin 0 -> 70273 bytes .../appflowy_board/example/lib/main.dart | 6 +- .../example/lib/multi_board_list_example.dart | 111 ++++++++++-------- .../appflowy_board/lib/appflowy_board.dart | 1 + .../appflowy_board/lib/src/utils/log.dart | 2 +- .../appflowy_board/lib/src/widgets/board.dart | 86 ++++++++++---- .../widgets/board_column/board_column.dart | 48 ++++++-- .../board_column/board_column_data.dart | 18 ++- .../lib/src/widgets/board_data.dart | 13 +- .../src/widgets/reorder_flex/drag_target.dart | 96 ++++++++++++++- .../reorder_flex/drag_target_inteceptor.dart | 20 ++-- .../widgets/reorder_flex/reorder_flex.dart | 63 +++++----- .../widgets/reorder_flex/reorder_mixin.dart | 54 +++++++++ .../reorder_phantom/phantom_controller.dart | 28 +++-- .../appflowy_styled_widgets.dart | 3 + .../lib/src/widgets/styled_widgets/card.dart | 37 ++++++ .../src/widgets/styled_widgets/footer.dart | 46 ++++++++ .../src/widgets/styled_widgets/header.dart | 74 ++++++++++++ 20 files changed, 615 insertions(+), 190 deletions(-) create mode 100644 frontend/app_flowy/packages/appflowy_board/example/gifs/appflowy_board_video_1.gif create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart create mode 100644 frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart diff --git a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md index 119d25f89d..7cf059202f 100644 --- a/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md +++ b/frontend/app_flowy/packages/appflowy_board/CHANGELOG.md @@ -1,3 +1,8 @@ +# 0.0.3 +* Support customize UI +* Update example +* Add AppFlowy style widget + ## 0.0.2 * Update documentation diff --git a/frontend/app_flowy/packages/appflowy_board/README.md b/frontend/app_flowy/packages/appflowy_board/README.md index 922cdd01b2..893bc3ed64 100644 --- a/frontend/app_flowy/packages/appflowy_board/README.md +++ b/frontend/app_flowy/packages/appflowy_board/README.md @@ -6,30 +6,25 @@ The **appflowy_board** is a package that is used in [AppFlowy](https://github.co **appflowy_board** will be a standard git repository when it becomes stable. ## Getting Started +

+ +

```dart @override void initState() { - final column1 = BoardColumnData(id: "1", items: [ - TextItem("a"), - TextItem("b"), - TextItem("c"), - TextItem("d"), + final column1 = BoardColumnData(id: "To Do", items: [ + TextItem("Card 1"), + TextItem("Card 2"), + TextItem("Card 3"), + TextItem("Card 4"), ]); - final column2 = BoardColumnData(id: "2", items: [ - TextItem("1"), - TextItem("2"), - TextItem("3"), - TextItem("4"), - TextItem("5"), + final column2 = BoardColumnData(id: "In Progress", items: [ + TextItem("Card 5"), + TextItem("Card 6"), ]); - final column3 = BoardColumnData(id: "3", items: [ - TextItem("A"), - TextItem("B"), - TextItem("C"), - TextItem("D"), - ]); + final column3 = BoardColumnData(id: "Done", items: []); boardDataController.addColumn(column1); boardDataController.addColumn(column2); @@ -40,25 +35,52 @@ The **appflowy_board** is a package that is used in [AppFlowy](https://github.co @override Widget build(BuildContext context) { - return Board( - dataController: boardDataController, - background: Container(color: Colors.red), - footBuilder: (context, columnData) { - return Container( - color: Colors.purple, - height: 30, - ); - }, - headerBuilder: (context, columnData) { - return Container( - color: Colors.yellow, - height: 30, - ); - }, - cardBuilder: (context, item) { - return _RowWidget(item: item as TextItem, key: ObjectKey(item)); - }, - columnConstraints: const BoxConstraints.tightFor(width: 240), + final config = BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + ); + return Container( + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Board( + dataController: boardDataController, + footBuilder: (context, columnData) { + return AppFlowyColumnFooter( + icon: const Icon(Icons.add, size: 20), + title: const Text('New'), + height: 50, + margin: config.columnItemPadding, + ); + }, + headerBuilder: (context, columnData) { + return AppFlowyColumnHeader( + icon: const Icon(Icons.lightbulb_circle), + title: Text(columnData.id), + addIcon: const Icon(Icons.add, size: 20), + moreIcon: const Icon(Icons.more_horiz, size: 20), + height: 50, + margin: config.columnItemPadding, + ); + }, + cardBuilder: (context, item) { + final textItem = item as TextItem; + return AppFlowyColumnItemCard( + key: ObjectKey(item), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text(textItem.s), + ), + ), + ); + }, + columnConstraints: const BoxConstraints.tightFor(width: 240), + config: BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + ), + ), + ), ); } ``` \ No newline at end of file diff --git a/frontend/app_flowy/packages/appflowy_board/example/gifs/appflowy_board_video_1.gif b/frontend/app_flowy/packages/appflowy_board/example/gifs/appflowy_board_video_1.gif new file mode 100644 index 0000000000000000000000000000000000000000..bf1345608e2e81cfdb3b8b329fb0024e5a2e2a89 GIT binary patch literal 70273 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 { return MaterialApp( home: Scaffold( appBar: AppBar( - title: const Text('FlowyBoard example'), + title: const Text('AppFlowy Board'), ), body: _examples[_currentIndex], bottomNavigationBar: BottomNavigationBar( @@ -43,10 +43,10 @@ class _MyAppState extends State { items: [ BottomNavigationBarItem( icon: Icon(Icons.grid_on, color: _bottomNavigationColor), - label: "MultiBoardList"), + label: "MultiColumn"), BottomNavigationBarItem( icon: Icon(Icons.grid_on, color: _bottomNavigationColor), - label: "SingleBoardList"), + label: "SingleColumn"), ], onTap: (int index) { setState(() { diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart index 8715a4450c..7fe24362d2 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart @@ -23,26 +23,18 @@ class _MultiBoardListExampleState extends State { @override void initState() { - final column1 = BoardColumnData(id: "1", items: [ - TextItem("a"), - TextItem("b"), - TextItem("c"), - TextItem("d"), + final column1 = BoardColumnData(id: "To Do", items: [ + TextItem("Card 1"), + TextItem("Card 2"), + TextItem("Card 3"), + TextItem("Card 4"), ]); - final column2 = BoardColumnData(id: "2", items: [ - TextItem("1"), - TextItem("2"), - TextItem("3"), - TextItem("4"), - TextItem("5"), + final column2 = BoardColumnData(id: "In Progress", items: [ + TextItem("Card 5"), + TextItem("Card 6"), ]); - final column3 = BoardColumnData(id: "3", items: [ - TextItem("A"), - TextItem("B"), - TextItem("C"), - TextItem("D"), - ]); + final column3 = BoardColumnData(id: "Done", items: []); boardDataController.addColumn(column1); boardDataController.addColumn(column2); @@ -53,40 +45,52 @@ class _MultiBoardListExampleState extends State { @override Widget build(BuildContext context) { - return Board( - dataController: boardDataController, - background: Container(color: Colors.red), - footBuilder: (context, columnData) { - return Container( - color: Colors.purple, - height: 30, - ); - }, - headerBuilder: (context, columnData) { - return Container( - color: Colors.yellow, - height: 30, - ); - }, - cardBuilder: (context, item) { - return _RowWidget(item: item as TextItem, key: ObjectKey(item)); - }, - columnConstraints: const BoxConstraints.tightFor(width: 240), + final config = BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), ); - } -} - -class _RowWidget extends StatelessWidget { - final TextItem item; - const _RowWidget({Key? key, required this.item}) : super(key: key); - - @override - Widget build(BuildContext context) { return Container( - key: ObjectKey(item), - height: 60, - color: Colors.green, - child: Center(child: Text(item.s)), + color: Colors.white, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 30, horizontal: 20), + child: Board( + dataController: boardDataController, + footBuilder: (context, columnData) { + return AppFlowyColumnFooter( + icon: const Icon(Icons.add, size: 20), + title: const Text('New'), + height: 50, + margin: config.columnItemPadding, + ); + }, + headerBuilder: (context, columnData) { + return AppFlowyColumnHeader( + icon: const Icon(Icons.lightbulb_circle), + title: Text(columnData.id), + addIcon: const Icon(Icons.add, size: 20), + moreIcon: const Icon(Icons.more_horiz, size: 20), + height: 50, + margin: config.columnItemPadding, + ); + }, + cardBuilder: (context, item) { + final textItem = item as TextItem; + return AppFlowyColumnItemCard( + key: ObjectKey(item), + child: Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text(textItem.s), + ), + ), + ); + }, + columnConstraints: const BoxConstraints.tightFor(width: 240), + config: BoardConfig( + columnBackgroundColor: HexColor.fromHex('#F7F8FC'), + ), + ), + ), ); } } @@ -99,3 +103,12 @@ class TextItem extends ColumnItem { @override String get id => s; } + +extension HexColor on Color { + static Color fromHex(String hexString) { + final buffer = StringBuffer(); + if (hexString.length == 6 || hexString.length == 7) buffer.write('ff'); + buffer.write(hexString.replaceFirst('#', '')); + return Color(int.parse(buffer.toString(), radix: 16)); + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart index 684868a2a3..fc8f3c662f 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/appflowy_board.dart @@ -2,4 +2,5 @@ library appflowy_board; export 'src/widgets/board_column/board_column_data.dart'; export 'src/widgets/board_data.dart'; +export 'src/widgets/styled_widgets/appflowy_styled_widgets.dart'; export 'src/widgets/board.dart'; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart index 6f923ddf16..b9f766f961 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/utils/log.dart @@ -6,7 +6,7 @@ const DART_LOG = "Dart_LOG"; class Log { // static const enableLog = bool.hasEnvironment(DART_LOG); // static final shared = Log(); - static const enableLog = false; + static const enableLog = true; static void info(String? message) { if (enableLog) { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart index bef98842c0..3cd2a331f1 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board.dart @@ -3,23 +3,29 @@ import 'package:provider/provider.dart'; import 'board_column/board_column.dart'; import 'board_column/board_column_data.dart'; import 'board_data.dart'; -import 'flex/drag_target_inteceptor.dart'; -import 'flex/reorder_flex.dart'; -import 'phantom/phantom_controller.dart'; +import 'reorder_flex/drag_target_inteceptor.dart'; +import 'reorder_flex/reorder_flex.dart'; +import 'reorder_phantom/phantom_controller.dart'; import '../rendering/board_overlay.dart'; +class BoardConfig { + final double cornerRadius; + final EdgeInsets columnPadding; + final EdgeInsets columnItemPadding; + final Color columnBackgroundColor; + + const BoardConfig({ + this.cornerRadius = 6.0, + this.columnPadding = const EdgeInsets.symmetric(horizontal: 8), + this.columnItemPadding = const EdgeInsets.symmetric(horizontal: 10), + this.columnBackgroundColor = Colors.transparent, + }); +} + class Board extends StatelessWidget { /// The direction to use as the main axis. final Axis direction = Axis.vertical; - /// How much space to place between children in a run in the main axis. - /// Defaults to 10.0. - final double spacing; - - /// How much space to place between the runs themselves in the cross axis. - /// Defaults to 0.0. - final double runSpacing; - /// final Widget? background; @@ -40,15 +46,16 @@ class Board extends StatelessWidget { /// final BoardPhantomController phantomController; + final BoardConfig config; + Board({ required this.dataController, required this.cardBuilder, - this.spacing = 10.0, - this.runSpacing = 0.0, this.background, this.footBuilder, this.headerBuilder, this.columnConstraints = const BoxConstraints(maxWidth: 200), + this.config = const BoardConfig(), Key? key, }) : phantomController = BoardPhantomController(delegate: dataController), super(key: key); @@ -60,9 +67,9 @@ class Board extends StatelessWidget { child: Consumer( builder: (context, notifier, child) { return BoardContent( + config: config, dataController: dataController, background: background, - spacing: spacing, delegate: phantomController, columnConstraints: columnConstraints, cardBuilder: cardBuilder, @@ -84,8 +91,8 @@ class BoardContent extends StatefulWidget { final OnDragEnded? onDragEnded; final BoardDataController dataController; final Widget? background; - final double spacing; - final ReorderFlexConfig config; + final BoardConfig config; + final ReorderFlexConfig reorderFlexConfig; final BoxConstraints columnConstraints; /// @@ -101,7 +108,8 @@ class BoardContent extends StatefulWidget { final BoardPhantomController phantomController; - BoardContent({ + const BoardContent({ + required this.config, required this.onReorder, required this.delegate, required this.dataController, @@ -109,14 +117,13 @@ class BoardContent extends StatefulWidget { this.onDragEnded, this.scrollController, this.background, - this.spacing = 10.0, required this.columnConstraints, required this.cardBuilder, this.footBuilder, this.headerBuilder, required this.phantomController, Key? key, - }) : config = ReorderFlexConfig(spacing: spacing), + }) : reorderFlexConfig = const ReorderFlexConfig(), super(key: key); @override @@ -140,7 +147,7 @@ class _BoardContentState extends State { final reorderFlex = ReorderFlex( key: widget.key, - config: widget.config, + config: widget.reorderFlexConfig, scrollController: widget.scrollController, onDragStarted: widget.onDragStarted, onReorder: widget.onReorder, @@ -154,7 +161,15 @@ class _BoardContentState extends State { return Stack( alignment: AlignmentDirectional.topStart, children: [ - if (widget.background != null) widget.background!, + if (widget.background != null) + Container( + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + borderRadius: + BorderRadius.circular(widget.config.cornerRadius), + ), + child: widget.background, + ), reorderFlex, ], ); @@ -173,8 +188,12 @@ class _BoardContentState extends State { } List _buildColumns() { - final List children = widget.dataController.columnDatas.map( - (columnData) { + final List children = + widget.dataController.columnDatas.asMap().entries.map( + (item) { + final columnData = item.value; + final columnIndex = item.key; + final dataSource = _BoardColumnDataSourceImpl( columnId: columnData.id, dataController: widget.dataController, @@ -188,6 +207,8 @@ class _BoardContentState extends State { return ConstrainedBox( constraints: widget.columnConstraints, child: BoardColumnWidget( + margin: _marginFromIndex(columnIndex), + itemMargin: widget.config.columnItemPadding, headerBuilder: widget.headerBuilder, footBuilder: widget.footBuilder, cardBuilder: widget.cardBuilder, @@ -195,7 +216,8 @@ class _BoardContentState extends State { scrollController: ScrollController(), phantomController: widget.phantomController, onReorder: widget.dataController.moveColumnItem, - spacing: 10, + cornerRadius: widget.config.cornerRadius, + backgroundColor: widget.config.columnBackgroundColor, ), ); }, @@ -206,6 +228,22 @@ class _BoardContentState extends State { return children; } + + EdgeInsets _marginFromIndex(int index) { + if (widget.dataController.columnDatas.isEmpty) { + return widget.config.columnPadding; + } + + if (index == 0) { + return EdgeInsets.only(right: widget.config.columnPadding.right); + } + + if (index == widget.dataController.columnDatas.length - 1) { + return EdgeInsets.only(left: widget.config.columnPadding.left); + } + + return widget.config.columnPadding; + } } class _BoardColumnDataSourceImpl extends BoardColumnDataDataSource { diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart index f95f4dae5c..d8981096e3 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column.dart @@ -3,9 +3,9 @@ import 'dart:collection'; import 'package:flutter/material.dart'; import '../../rendering/board_overlay.dart'; import '../../utils/log.dart'; -import '../phantom/phantom_controller.dart'; -import '../flex/reorder_flex.dart'; -import '../flex/drag_target_inteceptor.dart'; +import '../reorder_phantom/phantom_controller.dart'; +import '../reorder_flex/reorder_flex.dart'; +import '../reorder_flex/drag_target_inteceptor.dart'; import 'board_column_data.dart'; typedef OnColumnDragStarted = void Function(int index); @@ -79,7 +79,15 @@ class BoardColumnWidget extends StatefulWidget { final BoardColumnFooterBuilder? footBuilder; - BoardColumnWidget({ + final EdgeInsets margin; + + final EdgeInsets itemMargin; + + final double cornerRadius; + + final Color backgroundColor; + + const BoardColumnWidget({ Key? key, this.headerBuilder, this.footBuilder, @@ -90,8 +98,11 @@ class BoardColumnWidget extends StatefulWidget { this.onDragStarted, this.scrollController, this.onDragEnded, - double? spacing, - }) : config = ReorderFlexConfig(spacing: spacing), + this.margin = EdgeInsets.zero, + this.itemMargin = EdgeInsets.zero, + this.cornerRadius = 0.0, + this.backgroundColor = Colors.transparent, + }) : config = const ReorderFlexConfig(), super(key: key); @override @@ -149,12 +160,25 @@ class _BoardColumnWidgetState extends State { children: children, ); - return Column( - children: [ - if (header != null) header, - Expanded(child: reorderFlex), - if (footer != null) footer, - ], + return Container( + margin: widget.margin, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: widget.backgroundColor, + borderRadius: BorderRadius.circular(widget.cornerRadius), + ), + child: Column( + children: [ + if (header != null) header, + Expanded( + child: Padding( + padding: widget.itemMargin, + child: reorderFlex, + ), + ), + if (footer != null) footer, + ], + ), ); }, opaque: false, diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart index 24d3cc1a96..2ce739220e 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_column/board_column_data.dart @@ -3,7 +3,7 @@ import 'dart:collection'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import '../../utils/log.dart'; -import '../flex/reorder_flex.dart'; +import '../reorder_flex/reorder_flex.dart'; abstract class ColumnItem extends ReoderFlexItem { bool get isPhantom => false; @@ -92,10 +92,16 @@ class BoardColumnDataController extends ChangeNotifier with EquatableMixin { /// Replace the item at index with the [newItem]. void replace(int index, ColumnItem newItem) { - final removedItem = columnData._items.removeAt(index); - columnData._items.insert(index, newItem); - Log.debug( - '[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index'); + if (columnData._items.isEmpty) { + columnData._items.add(newItem); + Log.debug('[$BoardColumnDataController] $columnData add $newItem'); + } else { + final removedItem = columnData._items.removeAt(index); + columnData._items.insert(index, newItem); + Log.debug( + '[$BoardColumnDataController] $columnData replace $removedItem with $newItem at $index'); + } + notifyListeners(); } } @@ -119,6 +125,6 @@ class BoardColumnData extends ReoderFlexItem with EquatableMixin { @override String toString() { - return 'Column$id'; + return 'Column:[$id]'; } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart index fe2fca2c92..06e8ff1a57 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/board_data.dart @@ -4,9 +4,9 @@ import 'package:equatable/equatable.dart'; import '../utils/log.dart'; import 'board_column/board_column_data.dart'; -import 'flex/reorder_flex.dart'; +import 'reorder_flex/reorder_flex.dart'; import 'package:flutter/material.dart'; -import 'phantom/phantom_controller.dart'; +import 'reorder_phantom/phantom_controller.dart'; typedef OnMoveColumn = void Function(int fromIndex, int toIndex); @@ -79,8 +79,11 @@ class BoardDataController extends ChangeNotifier int toColumnIndex, ) { final item = columnController(fromColumnId).removeAt(fromColumnIndex); - assert( - columnController(toColumnId).items[toColumnIndex] is PhantomColumnItem); + + if (columnController(toColumnId).items.length > toColumnIndex) { + assert(columnController(toColumnId).items[toColumnIndex] + is PhantomColumnItem); + } columnController(toColumnId).replace(toColumnIndex, item); @@ -120,7 +123,7 @@ class BoardDataController extends ChangeNotifier columnController.removeAt(index); Log.debug( - '[$BoardDataController] Column$columnId remove phantom, current count: ${columnController.items.length}'); + '[$BoardDataController] Column:[$columnId] remove phantom, current count: ${columnController.items.length}'); } return isExist; } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart index fafdcef774..ea8cc91fab 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target.dart @@ -1,4 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:provider/provider.dart'; import '../transitions.dart'; abstract class DragTargetData { @@ -65,6 +67,8 @@ class ReorderDragTarget extends StatefulWidget { final AnimationController insertAnimationController; final AnimationController deleteAnimationController; + final bool useMoveAnimation; + ReorderDragTarget({ Key? key, required this.child, @@ -74,6 +78,7 @@ class ReorderDragTarget extends StatefulWidget { required this.onWillAccept, required this.insertAnimationController, required this.deleteAnimationController, + required this.useMoveAnimation, this.onAccept, this.onLeave, this.draggableTargetBuilder, @@ -140,7 +145,10 @@ class _ReorderDragTargetState data: widget.dragTargetData, ignoringFeedbackSemantics: false, feedback: feedbackBuilder, - childWhenDragging: IgnorePointerWidget(child: widget.child), + childWhenDragging: IgnorePointerWidget( + useIntrinsicSize: !widget.useMoveAnimation, + child: widget.child, + ), onDragStarted: () { _draggingFeedbackSize = widget._indexGlobalKey.currentContext?.size; widget.onDragStarted( @@ -174,11 +182,13 @@ class _ReorderDragTargetState transform: Matrix4.rotationZ(0), alignment: FractionalOffset.topLeft, child: Material( - elevation: 3.0, color: Colors.transparent, borderRadius: BorderRadius.zero, clipBehavior: Clip.hardEdge, - child: ConstrainedBox(constraints: constraints, child: child), + child: ConstrainedBox( + constraints: constraints, + child: Opacity(opacity: 0.6, child: child), + ), ), ); } @@ -254,10 +264,12 @@ class IgnorePointerWidget extends StatelessWidget { final sizedChild = useIntrinsicSize ? child : SizedBox(width: 0.0, height: 0.0, child: child); + + final opacity = useIntrinsicSize ? 0.3 : 0.0; return IgnorePointer( ignoring: true, child: Opacity( - opacity: 0, + opacity: opacity, child: sizedChild, ), ); @@ -282,6 +294,82 @@ class PhantomWidget extends StatelessWidget { } } +abstract class DragTargetMovePlaceholderDelegate { + void registerPlaceholder( + int dragTargetIndex, + void Function(int currentDragTargetIndex) callback, + ); + + void unregisterPlaceholder(int dragTargetIndex); +} + +class DragTargeMovePlaceholder extends StatefulWidget { + final double height; + final Color color; + final Color highlightColor; + final int dragTargetIndex; + final DragTargetMovePlaceholderDelegate delegate; + + const DragTargeMovePlaceholder({ + required this.delegate, + required this.dragTargetIndex, + this.height = 4, + this.color = Colors.transparent, + this.highlightColor = Colors.lightBlue, + Key? key, + }) : super(key: key); + + @override + State createState() => + _DragTargeMovePlaceholderState(); +} + +class _DragTargeMovePlaceholderState extends State { + ValueNotifier isHighlight = ValueNotifier(false); + + @override + void initState() { + widget.delegate.registerPlaceholder( + widget.dragTargetIndex, + (currentDragTargetIndex) { + if (!mounted) return; + + SchedulerBinding.instance.addPostFrameCallback((Duration duration) { + if (currentDragTargetIndex == -1) { + isHighlight.value = false; + } else { + isHighlight.value = + widget.dragTargetIndex == currentDragTargetIndex; + } + }); + }, + ); + super.initState(); + } + + @override + void dispose() { + isHighlight.dispose(); + widget.delegate.unregisterPlaceholder(widget.dragTargetIndex); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return ChangeNotifierProvider.value( + value: isHighlight, + child: Consumer>( + builder: (context, notifier, child) { + return Container( + height: widget.height, + color: notifier.value ? widget.highlightColor : widget.color, + ); + }, + ), + ); + } +} + abstract class FakeDragTargetEventTrigger { void fakeOnDragEnded(VoidCallback callback); } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart index 86152ed0de..da529819dd 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/drag_target_inteceptor.dart @@ -30,12 +30,14 @@ abstract class DragTargetInterceptor { } abstract class OverlapDragTargetDelegate { - void didReturnOriginalDragTarget(); - void didCrossOtherDragTarget( + void cancel(); + void moveTo( String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex, ); + + bool canMoveTo(String dragTargetId); } /// [OverlappingDragTargetInteceptor] is used to receive the overlapping @@ -68,13 +70,11 @@ class OverlappingDragTargetInteceptor extends DragTargetInterceptor { required String dragTargetId, required int dragTargetIndex}) { if (dragTargetId == dragTargetData.reorderFlexId) { - delegate.didReturnOriginalDragTarget(); + delegate.cancel(); } else { - delegate.didCrossOtherDragTarget( - dragTargetId, - dragTargetData, - dragTargetIndex, - ); + if (delegate.canMoveTo(dragTargetId)) { + delegate.moveTo(dragTargetId, dragTargetData, 0); + } } return true; @@ -128,13 +128,13 @@ class CrossReorderFlexDragTargetInterceptor extends DragTargetInterceptor { @override void onAccept(FlexDragTargetData dragTargetData) { Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on onAccept'); + '[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on onAccept'); } @override void onLeave(FlexDragTargetData dragTargetData) { Log.trace( - '[$CrossReorderFlexDragTargetInterceptor] Column$reorderFlexId on leave'); + '[$CrossReorderFlexDragTargetInterceptor] Column:[$reorderFlexId] on leave'); } @override diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart index 9066c987f0..04de8cae7b 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart @@ -41,16 +41,19 @@ class ReorderFlexConfig { // How long an animation to scroll to an off-screen element final Duration scrollAnimationDuration = const Duration(milliseconds: 250); - final double? spacing; + final bool useMoveAnimation; - const ReorderFlexConfig({this.spacing}); + final bool useMovePlaceholder; + + const ReorderFlexConfig({ + this.useMoveAnimation = true, + }) : useMovePlaceholder = !useMoveAnimation; } class ReorderFlex extends StatefulWidget { final ReorderFlexConfig config; final List children; - final EdgeInsets? padding; /// [direction] How to place the children, default is Axis.vertical final Axis direction; @@ -81,7 +84,6 @@ class ReorderFlex extends StatefulWidget { this.onDragStarted, this.onDragEnded, this.interceptor, - this.padding, this.direction = Axis.vertical, }) : super(key: key); @@ -108,8 +110,11 @@ class ReorderFlexState extends State /// [_animation] controls the dragging animations late DragTargetAnimation _animation; + late ReorderFlexNotifier _notifier; + @override void initState() { + _notifier = ReorderFlexNotifier(); dragState = DraggingState(widget.reorderFlexId); _animation = DragTargetAnimation( @@ -154,13 +159,14 @@ class ReorderFlexState extends State for (int i = 0; i < widget.children.length; i += 1) { Widget child = widget.children[i]; + children.add(_wrap(child, i)); - if (widget.config.spacing != null) { - children.add(SizedBox(width: widget.config.spacing!)); - } - - final wrapChild = _wrap(child, i); - children.add(wrapChild); + // if (widget.config.useMovePlaceholder) { + // children.add(DragTargeMovePlaceholder( + // dragTargetIndex: i, + // delegate: _notifier, + // )); + // } } final child = _wrapContainer(children); @@ -199,7 +205,8 @@ class ReorderFlexState extends State /// [childIndex]: the index of the child in a list Widget _wrap(Widget child, int childIndex) { return Builder(builder: (context) { - final dragTarget = _buildDragTarget(context, child, childIndex); + final ReorderDragTarget dragTarget = + _buildDragTarget(context, child, childIndex); int shiftedIndex = childIndex; if (dragState.isOverlapWithPhantom()) { @@ -207,7 +214,7 @@ class ReorderFlexState extends State } Log.trace( - 'Rebuild: Column${dragState.id} ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); + 'Rebuild: Column:[${dragState.id}] ${dragState.toString()}, childIndex: $childIndex shiftedIndex: $shiftedIndex'); final currentIndex = dragState.currentIndex; final dragPhantomIndex = dragState.phantomIndex; @@ -234,15 +241,18 @@ class ReorderFlexState extends State } /// Determine the size of the drop area to show under the dragging widget. - final feedbackSize = dragState.feedbackSize; + Size? feedbackSize = Size.zero; + if (widget.config.useMoveAnimation) { + feedbackSize = dragState.feedbackSize; + } + Widget appearSpace = _makeAppearSpace(dragSpace, feedbackSize); Widget disappearSpace = _makeDisappearSpace(dragSpace, feedbackSize); /// When start dragging, the dragTarget, [ReorderDragTarget], will /// return a [IgnorePointerWidget] which size is zero. if (dragState.isPhantomAboveDragTarget()) { - //the phantom is moving down, i.e. the tile below the phantom is moving up - Log.trace('index:$childIndex item moving up / phantom moving down'); + _notifier.updateDragTargetIndex(currentIndex); if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { return _buildDraggingContainer(children: [ disappearSpace, @@ -264,8 +274,7 @@ class ReorderFlexState extends State /// if (dragState.isPhantomBelowDragTarget()) { - //the phantom is moving up, i.e. the tile above the phantom is moving down - Log.trace('index:$childIndex item moving down / phantom moving up'); + _notifier.updateDragTargetIndex(currentIndex); if (shiftedIndex == currentIndex && childIndex == dragPhantomIndex) { return _buildDraggingContainer(children: [ appearSpace, @@ -303,10 +312,7 @@ class ReorderFlexState extends State } ReorderDragTarget _buildDragTarget( - BuildContext builderContext, - Widget child, - int dragTargetIndex, - ) { + BuildContext builderContext, Widget child, int dragTargetIndex) { final ReoderFlexItem reorderFlexItem = widget.dataSource.items[dragTargetIndex]; return ReorderDragTarget( @@ -319,14 +325,14 @@ class ReorderFlexState extends State ), onDragStarted: (draggingWidget, draggingIndex, size) { Log.debug( - "[DragTarget] Column${widget.dataSource.identifier} start dragging item at $draggingIndex"); + "[DragTarget] Column:[${widget.dataSource.identifier}] start dragging item at $draggingIndex"); _startDragging(draggingWidget, draggingIndex, size); widget.onDragStarted?.call(draggingIndex); }, onDragEnded: (dragTargetData) { Log.debug( - "[DragTarget]: Column${widget.dataSource.identifier} end dragging"); - + "[DragTarget]: Column:[${widget.dataSource.identifier}] end dragging"); + _notifier.updateDragTargetIndex(-1); setState(() { if (dragTargetData.reorderFlexId == widget.reorderFlexId) { _onReordered( @@ -340,14 +346,11 @@ class ReorderFlexState extends State }); }, onWillAccept: (FlexDragTargetData dragTargetData) { - Log.debug('Insert animation: ${_animation.deleteController.status}'); - if (_animation.deleteController.isAnimating) { return false; } assert(widget.dataSource.items.length > dragTargetIndex); - if (_interceptDragTarget( dragTargetData, (interceptor) => interceptor.onWillAccept( @@ -370,6 +373,7 @@ class ReorderFlexState extends State ); }, onLeave: (dragTargetData) { + _notifier.updateDragTargetIndex(-1); _interceptDragTarget( dragTargetData, (interceptor) => interceptor.onLeave(dragTargetData), @@ -378,6 +382,7 @@ class ReorderFlexState extends State insertAnimationController: _animation.insertController, deleteAnimationController: _animation.deleteController, draggableTargetBuilder: widget.interceptor?.draggableTargetBuilder, + useMoveAnimation: widget.config.useMoveAnimation, child: child, ); } @@ -430,7 +435,7 @@ class ReorderFlexState extends State /// The [willAccept] will be true if the dargTarget is the widget that gets /// dragged and it is dragged on top of the other dragTargets. /// - Log.trace( + Log.debug( '[$ReorderDragTarget] ${widget.dataSource.identifier} on will accept, dragIndex:$dragIndex, dragTargetIndex:$dragTargetIndex, count: ${widget.dataSource.items.length}'); bool willAccept = @@ -442,7 +447,6 @@ class ReorderFlexState extends State } else { dragState.updateNextIndex(dragTargetIndex); } - _requestAnimationToNextIndex(isAcceptingNewTarget: true); }); @@ -467,7 +471,6 @@ class ReorderFlexState extends State } else { return SingleChildScrollView( scrollDirection: widget.direction, - padding: widget.padding, controller: _scrollController, child: child, ); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart index a90ee6a83a..accdaa866b 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_mixin.dart @@ -1,6 +1,7 @@ import 'package:flutter/widgets.dart'; import '../transitions.dart'; +import 'drag_target.dart'; mixin ReorderFlexMinxi { @protected @@ -86,3 +87,56 @@ extension CurveAnimationController on AnimationController { ); } } + +class ReorderFlexNotifier extends DragTargetMovePlaceholderDelegate { + Map dragTargeEventNotifier = {}; + + void updateDragTargetIndex(int index) { + for (var notifier in dragTargeEventNotifier.values) { + notifier.setDragTargetIndex(index); + } + } + + DragTargetEventNotifier _notifierFromIndex(int dragTargetIndex) { + DragTargetEventNotifier? notifier = dragTargeEventNotifier[dragTargetIndex]; + if (notifier == null) { + final newNotifier = DragTargetEventNotifier(); + dragTargeEventNotifier[dragTargetIndex] = newNotifier; + notifier = newNotifier; + } + + return notifier; + } + + void dispose() { + for (var notifier in dragTargeEventNotifier.values) { + notifier.dispose(); + } + } + + @override + void registerPlaceholder( + int dragTargetIndex, + void Function(int dragTargetIndex) callback, + ) { + _notifierFromIndex(dragTargetIndex).addListener(() { + callback.call(_notifierFromIndex(dragTargetIndex).currentDragTargetIndex); + }); + } + + @override + void unregisterPlaceholder(int dragTargetIndex) { + dragTargeEventNotifier.remove(dragTargetIndex); + } +} + +class DragTargetEventNotifier extends ChangeNotifier { + int currentDragTargetIndex = -1; + + void setDragTargetIndex(int index) { + if (currentDragTargetIndex != index) { + currentDragTargetIndex = index; + notifyListeners(); + } + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart index 6b68eefd52..266c83d873 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart @@ -1,9 +1,11 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import '../../utils/log.dart'; import '../board_column/board_column_data.dart'; -import '../flex/drag_state.dart'; -import '../flex/drag_target.dart'; -import '../flex/drag_target_inteceptor.dart'; +import '../reorder_flex/drag_state.dart'; +import '../reorder_flex/drag_target.dart'; +import '../reorder_flex/drag_target_inteceptor.dart'; import 'phantom_state.dart'; abstract class BoardPhantomControllerDelegate { @@ -127,8 +129,8 @@ class BoardPhantomController extends OverlapDragTargetDelegate FlexDragTargetData dragTargetData, int dragTargetIndex, ) { - // Log.debug('[$BoardPhantomController] move Column${dragTargetData.reorderFlexId}:${dragTargetData.draggingIndex} ' - // 'to Column$columnId:$index'); + // Log.debug('[$BoardPhantomController] move Column:[${dragTargetData.reorderFlexId}]:${dragTargetData.draggingIndex} ' + // 'to Column:[$columnId]:$index'); phantomRecord = PhantomRecord( toColumnId: columnId, @@ -177,7 +179,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate } @override - void didReturnOriginalDragTarget() { + void cancel() { if (phantomRecord == null) { return; } @@ -188,7 +190,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate } @override - void didCrossOtherDragTarget( + void moveTo( String reorderFlexId, FlexDragTargetData dragTargetData, int dragTargetIndex, @@ -199,6 +201,12 @@ class BoardPhantomController extends OverlapDragTargetDelegate dragTargetIndex, ); } + + @override + bool canMoveTo(String dragTargetId) { + // TODO: implement shouldReceive + return delegate.controller(dragTargetId)?.columnData.items.length == 0; + } } /// Use [PhantomRecord] to record where to remove the column item and where to @@ -228,7 +236,7 @@ class PhantomRecord { return; } Log.debug( - '[$PhantomRecord] Update Column$fromColumnId remove position to $index'); + '[$PhantomRecord] Update Column:[$fromColumnId] remove position to $index'); fromColumnIndex = index; } @@ -238,13 +246,13 @@ class PhantomRecord { } Log.debug( - '[$PhantomRecord] Column$toColumnId update position $toColumnIndex -> $index'); + '[$PhantomRecord] Column:[$toColumnId] update position $toColumnIndex -> $index'); toColumnIndex = index; } @override String toString() { - return 'Column$fromColumnId:$fromColumnIndex to Column$toColumnId:$toColumnIndex'; + return 'Column:[$fromColumnId]:$fromColumnIndex to Column:[$toColumnId]:$toColumnIndex'; } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart new file mode 100644 index 0000000000..b802d15dae --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/appflowy_styled_widgets.dart @@ -0,0 +1,3 @@ +export 'card.dart'; +export 'footer.dart'; +export 'header.dart'; diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart new file mode 100644 index 0000000000..b2e5085649 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart @@ -0,0 +1,37 @@ +import 'package:flutter/material.dart'; + +class AppFlowyColumnItemCard extends StatefulWidget { + final Widget? child; + final Color backgroundColor; + final double cornerRadius; + final BoxConstraints boxConstraints; + + const AppFlowyColumnItemCard({ + this.child, + this.backgroundColor = Colors.white, + this.cornerRadius = 0.0, + this.boxConstraints = const BoxConstraints.tightFor(height: 60), + Key? key, + }) : super(key: key); + + @override + State createState() => _AppFlowyColumnItemCardState(); +} + +class _AppFlowyColumnItemCardState extends State { + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.all(4.0), + child: Container( + constraints: widget.boxConstraints, + clipBehavior: Clip.hardEdge, + decoration: BoxDecoration( + color: widget.backgroundColor, + borderRadius: BorderRadius.circular(widget.cornerRadius), + ), + child: widget.child, + ), + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart new file mode 100644 index 0000000000..7f5655fe60 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/footer.dart @@ -0,0 +1,46 @@ +import 'package:flutter/material.dart'; + +typedef OnFooterAddButtonClick = void Function(); + +class AppFlowyColumnFooter extends StatefulWidget { + final double height; + final Widget? icon; + final Widget? title; + final EdgeInsets margin; + final OnFooterAddButtonClick? onAddButtonClick; + + const AppFlowyColumnFooter({ + this.icon, + this.title, + this.margin = EdgeInsets.zero, + required this.height, + this.onAddButtonClick, + Key? key, + }) : super(key: key); + + @override + State createState() => _AppFlowyColumnFooterState(); +} + +class _AppFlowyColumnFooterState extends State { + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: widget.onAddButtonClick, + child: SizedBox( + height: widget.height, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (widget.icon != null) widget.icon!, + if (widget.title != null) widget.title!, + ], + ), + ), + ), + ); + } +} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart new file mode 100644 index 0000000000..fdebc7ef21 --- /dev/null +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/header.dart @@ -0,0 +1,74 @@ +import 'package:flutter/material.dart'; + +typedef OnHeaderAddButtonClick = void Function(); +typedef OnHeaderMoreButtonClick = void Function(); + +class AppFlowyColumnHeader extends StatefulWidget { + final double height; + final Widget? icon; + final Widget? title; + final Widget? addIcon; + final Widget? moreIcon; + final EdgeInsets margin; + final OnHeaderAddButtonClick? onAddButtonClick; + final OnHeaderMoreButtonClick? onMoreButtonClick; + + const AppFlowyColumnHeader({ + required this.height, + this.icon, + this.title, + this.addIcon, + this.moreIcon, + this.margin = EdgeInsets.zero, + this.onAddButtonClick, + this.onMoreButtonClick, + Key? key, + }) : super(key: key); + + @override + State createState() => _AppFlowyColumnHeaderState(); +} + +class _AppFlowyColumnHeaderState extends State { + @override + Widget build(BuildContext context) { + List children = []; + + if (widget.icon != null) { + children.add(widget.icon!); + children.add(_hSpace()); + } + + if (widget.title != null) { + children.add(widget.title!); + children.add(_hSpace()); + } + + if (widget.moreIcon != null) { + children.add(const Spacer()); + children.add( + IconButton(onPressed: widget.onMoreButtonClick, icon: widget.moreIcon!), + ); + } + + if (widget.addIcon != null) { + children.add( + IconButton(onPressed: widget.onAddButtonClick, icon: widget.addIcon!), + ); + } + + return SizedBox( + height: widget.height, + child: Padding( + padding: widget.margin, + child: Row( + children: children, + ), + ), + ); + } + + Widget _hSpace() { + return const SizedBox(width: 6); + } +} From 0abd62829ce539fdb58048794ed621dbf3fe7c32 Mon Sep 17 00:00:00 2001 From: appflowy Date: Mon, 8 Aug 2022 19:13:03 +0800 Subject: [PATCH 22/22] chore: update pubspec path --- .../example/lib/multi_board_list_example.dart | 58 +++++++++++++++---- .../example/test/widget_test.dart | 25 +------- .../widgets/reorder_flex/reorder_flex.dart | 4 +- .../reorder_phantom/phantom_controller.dart | 5 +- .../lib/src/widgets/styled_widgets/card.dart | 10 ++-- .../packages/appflowy_board/pubspec.yaml | 2 +- frontend/app_flowy/pubspec.yaml | 2 + 7 files changed, 61 insertions(+), 45 deletions(-) diff --git a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart index 7fe24362d2..3edb32c3bf 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/lib/multi_board_list_example.dart @@ -26,11 +26,11 @@ class _MultiBoardListExampleState extends State { final column1 = BoardColumnData(id: "To Do", items: [ TextItem("Card 1"), TextItem("Card 2"), - TextItem("Card 3"), + RichTextItem(title: "Card 3", subtitle: 'Aug 1, 2020 4:05 PM'), TextItem("Card 4"), ]); final column2 = BoardColumnData(id: "In Progress", items: [ - TextItem("Card 5"), + RichTextItem(title: "Card 5", subtitle: 'Aug 1, 2020 4:05 PM'), TextItem("Card 6"), ]); @@ -73,16 +73,9 @@ class _MultiBoardListExampleState extends State { ); }, cardBuilder: (context, item) { - final textItem = item as TextItem; return AppFlowyColumnItemCard( key: ObjectKey(item), - child: Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 20), - child: Text(textItem.s), - ), - ), + child: _buildCard(item), ); }, columnConstraints: const BoxConstraints.tightFor(width: 240), @@ -93,6 +86,41 @@ class _MultiBoardListExampleState extends State { ), ); } + + Widget _buildCard(ColumnItem item) { + if (item is TextItem) { + return Align( + alignment: Alignment.centerLeft, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 20), + child: Text(item.s), + ), + ); + } + + if (item is RichTextItem) { + return Padding( + padding: const EdgeInsets.all(20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.title, + style: const TextStyle(fontSize: 14), + textAlign: TextAlign.left, + ), + const SizedBox(height: 10), + Text( + item.subtitle, + style: const TextStyle(fontSize: 12, color: Colors.grey), + ) + ], + ), + ); + } + + throw UnimplementedError(); + } } class TextItem extends ColumnItem { @@ -104,6 +132,16 @@ class TextItem extends ColumnItem { String get id => s; } +class RichTextItem extends ColumnItem { + final String title; + final String subtitle; + + RichTextItem({required this.title, required this.subtitle}); + + @override + String get id => title; +} + extension HexColor on Color { static Color fromHex(String hexString) { final buffer = StringBuffer(); diff --git a/frontend/app_flowy/packages/appflowy_board/example/test/widget_test.dart b/frontend/app_flowy/packages/appflowy_board/example/test/widget_test.dart index 092d222f7e..29cad86982 100644 --- a/frontend/app_flowy/packages/appflowy_board/example/test/widget_test.dart +++ b/frontend/app_flowy/packages/appflowy_board/example/test/widget_test.dart @@ -4,27 +4,4 @@ // utility in the flutter_test package. For example, you can send tap and scroll // gestures. You can also use WidgetTester to find child widgets in the widget // tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:example/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} +void main() {} diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart index 04de8cae7b..6354b25c33 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_flex/reorder_flex.dart @@ -500,7 +500,7 @@ class ReorderFlexState extends State case Axis.horizontal: return Row( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: widget.mainAxisAlignment, children: children, ); @@ -508,7 +508,7 @@ class ReorderFlexState extends State default: return Column( mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisAlignment: widget.mainAxisAlignment, children: children, ); diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart index 266c83d873..ccff83b502 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/reorder_phantom/phantom_controller.dart @@ -1,5 +1,3 @@ -import 'dart:async'; - import 'package:flutter/material.dart'; import '../../utils/log.dart'; import '../board_column/board_column_data.dart'; @@ -204,8 +202,7 @@ class BoardPhantomController extends OverlapDragTargetDelegate @override bool canMoveTo(String dragTargetId) { - // TODO: implement shouldReceive - return delegate.controller(dragTargetId)?.columnData.items.length == 0; + return delegate.controller(dragTargetId)?.columnData.items.isEmpty ?? false; } } diff --git a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart index b2e5085649..323964c75f 100644 --- a/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart +++ b/frontend/app_flowy/packages/appflowy_board/lib/src/widgets/styled_widgets/card.dart @@ -4,13 +4,15 @@ class AppFlowyColumnItemCard extends StatefulWidget { final Widget? child; final Color backgroundColor; final double cornerRadius; + final EdgeInsets margin; final BoxConstraints boxConstraints; const AppFlowyColumnItemCard({ this.child, - this.backgroundColor = Colors.white, this.cornerRadius = 0.0, - this.boxConstraints = const BoxConstraints.tightFor(height: 60), + this.margin = const EdgeInsets.all(4), + this.backgroundColor = Colors.white, + this.boxConstraints = const BoxConstraints(minHeight: 40), Key? key, }) : super(key: key); @@ -22,10 +24,10 @@ class _AppFlowyColumnItemCardState extends State { @override Widget build(BuildContext context) { return Padding( - padding: const EdgeInsets.all(4.0), + padding: const EdgeInsets.all(4), child: Container( - constraints: widget.boxConstraints, clipBehavior: Clip.hardEdge, + constraints: widget.boxConstraints, decoration: BoxDecoration( color: widget.backgroundColor, borderRadius: BorderRadius.circular(widget.cornerRadius), diff --git a/frontend/app_flowy/packages/appflowy_board/pubspec.yaml b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml index b0955998ea..92b1782ea0 100644 --- a/frontend/app_flowy/packages/appflowy_board/pubspec.yaml +++ b/frontend/app_flowy/packages/appflowy_board/pubspec.yaml @@ -5,7 +5,7 @@ homepage: https://github.com/AppFlowy-IO/AppFlowy repository: https://github.com/AppFlowy-IO/AppFlowy environment: - sdk: ">=2.17.6 <3.0.0" + sdk: ">=2.17.0 <3.0.0" flutter: ">=1.17.0" dependencies: diff --git a/frontend/app_flowy/pubspec.yaml b/frontend/app_flowy/pubspec.yaml index 60b99061bc..56e96e96dd 100644 --- a/frontend/app_flowy/pubspec.yaml +++ b/frontend/app_flowy/pubspec.yaml @@ -37,6 +37,8 @@ dependencies: path: packages/flowy_infra_ui flowy_infra: path: packages/flowy_infra + appflowy_board: + path: packages/appflowy_board flutter_quill: git: url: https://github.com/appflowy/flutter-quill.git