Merge branch 'main' into main

This commit is contained in:
Nathan.fooo 2022-09-21 10:31:38 +08:00 committed by GitHub
commit 1967913db6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
84 changed files with 1668 additions and 1419 deletions

View File

@ -14,7 +14,7 @@ extend = [
on_error_task = "catch"
[tasks.catch]
run_task = {name = ["restore-crate-type"]}
run_task = { name = ["restore-crate-type"] }
[env]
RUST_LOG = "info"
@ -42,8 +42,8 @@ PRODUCT_NAME = "AppFlowy"
CRATE_TYPE = "staticlib"
SDK_EXT = "a"
APP_ENVIRONMENT = "local"
FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
FLUTTER_FLOWY_SDK_PATH = "app_flowy/packages/flowy_sdk"
PROTOBUF_DERIVE_CACHE = "../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
[env.development-mac-arm64]
RUST_LOG = "info"
@ -139,8 +139,7 @@ LINUX_ARCH = "arm64"
APP_ENVIRONMENT = "production"
[tasks.echo_env]
script = [
'''
script = ['''
echo "-------- Env Parameters --------"
echo CRATE_TYPE: ${CRATE_TYPE}
echo BUILD_FLAG: ${BUILD_FLAG}
@ -151,8 +150,7 @@ script = [
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
echo ${platforms}
echo ${BUILD_ARCHS}
'''
]
''']
script_runner = "@shell"
[env.production-ios]
@ -166,6 +164,14 @@ BUILD_FLAG = "debug"
TARGET_OS = "android"
CRATE_TYPE = "cdylib"
FLUTTER_OUTPUT_DIR = "Debug"
FEATURES = "flutter,openssl_vendored"
[env.production-android]
BUILD_FLAG = "release"
TARGET_OS = "android"
CRATE_TYPE = "cdylib"
FLUTTER_OUTPUT_DIR = "Release"
FEATURES = "flutter,openssl_vendored"
[tasks.setup-crate-type]
private = true
@ -192,11 +198,9 @@ script = [
script_runner = "@duckscript"
[tasks.test-build]
condition = { env_set = [ "FLUTTER_FLOWY_SDK_PATH"] }
script = [
"""
condition = { env_set = ["FLUTTER_FLOWY_SDK_PATH"] }
script = ["""
cd ${CARGO_MAKE_WORKSPACE_WORKING_DIRECTORY}/rust-lib/flowy-net
cargo build -vv --features=dart
""",
]
"""]
script_runner = "@shell"

View File

@ -213,8 +213,8 @@
"aquaColor": "Aqua",
"blueColor": "Blue",
"deleteTag": "Delete tag",
"colorPannelTitle": "Colors",
"pannelTitle": "Select an option or create one",
"colorPanelTitle": "Colors",
"panelTitle": "Select an option or create one",
"searchOption": "Search for an option"
},
"menuName": "Grid"

View File

@ -201,8 +201,8 @@
"aquaColor": "Agua",
"blueColor": "Azul",
"deleteTag": "Borrar etiqueta",
"colorPannelTitle": "Colores",
"pannelTitle": "Selecciona una opción o crea una",
"colorPanelTitle": "Colores",
"panelTitle": "Selecciona una opción o crea una",
"searchOption": "Buscar una opción"
},
"menuName": "Grid"
@ -218,4 +218,4 @@
"openSidebar": "Open sidebar",
"closeSidebar": "Close sidebar"
}
}
}

View File

@ -199,8 +199,8 @@
"aquaColor": "Aqua",
"blueColor": "Bleu",
"deleteTag": "Supprimer l'étiquette",
"colorPannelTitle": "Couleurs",
"pannelTitle": "Sélectionnez une option ou créez-en une",
"colorPanelTitle": "Couleurs",
"panelTitle": "Sélectionnez une option ou créez-en une",
"searchOption": "Rechercher une option"
},
"menuName": "Grille"
@ -212,4 +212,4 @@
"timeHintTextInTwentyFourHour": "12:00"
}
}
}
}

View File

@ -202,8 +202,8 @@
"aquaColor": "Air",
"blueColor": "Biru",
"deleteTag": "Hapus tag",
"colorPannelTitle": "Warna",
"pannelTitle": "Pilih opsi atau buat baru",
"colorPanelTitle": "Warna",
"panelTitle": "Pilih opsi atau buat baru",
"searchOption": "Cari opsi"
},
"menuName": "Grid"

View File

@ -191,8 +191,8 @@
"aquaColor": "水色",
"blueColor": "青",
"deleteTag": "選択候補を削除",
"colorPannelTitle": "色",
"pannelTitle": "選択候補を検索 または 作成する",
"colorPanelTitle": "色",
"panelTitle": "選択候補を検索 または 作成する",
"searchOption": "選択候補を検索"
}
},

View File

@ -1,235 +1,233 @@
{
"appName": "AppFlowy",
"defaultUsername": "Я",
"welcomeText": "Добро пожаловать в @:appName",
"githubStarText": "Поставить звезду на GitHub",
"subscribeNewsletterText": "Подписаться на рассылку",
"letsGoButtonText": "Начнём",
"title": "Заголовок",
"signUp": {
"buttonText": "Зарегистрироваться",
"title": "Регистрация в @:appName",
"getStartedText": "Начать",
"emptyPasswordError": "Пароль не может быть пустым",
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
"unmatchedPasswordError": "Пароли не совпадают",
"alreadyHaveAnAccount": "Уже есть аккаунт?",
"emailHint": "Электронная почта",
"passwordHint": "Пароль",
"repeatPasswordHint": "Повторите пароль"
"appName": "AppFlowy",
"defaultUsername": "Я",
"welcomeText": "Добро пожаловать в @:appName",
"githubStarText": "Поставить звезду на GitHub",
"subscribeNewsletterText": "Подписаться на рассылку",
"letsGoButtonText": "Начнём",
"title": "Заголовок",
"signUp": {
"buttonText": "Зарегистрироваться",
"title": "Регистрация в @:appName",
"getStartedText": "Начать",
"emptyPasswordError": "Пароль не может быть пустым",
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
"unmatchedPasswordError": "Пароли не совпадают",
"alreadyHaveAnAccount": "Уже есть аккаунт?",
"emailHint": "Электронная почта",
"passwordHint": "Пароль",
"repeatPasswordHint": "Повторите пароль"
},
"signIn": {
"loginTitle": "Войти в @:appName",
"loginButtonText": "Войти",
"buttonText": "Авторизация",
"forgotPassword": "Забыли пароль?",
"emailHint": "Электронная почта",
"passwordHint": "Пароль",
"dontHaveAnAccount": "Нет аккаунта?",
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
"unmatchedPasswordError": "Пароли не совпадают"
},
"workspace": {
"create": "Создать рабочее пространство",
"hint": "рабочее пространство",
"notFoundError": "Нет такого рабочего пространства"
},
"shareAction": {
"buttonText": "Поделиться",
"workInProgress": "В разработке",
"markdown": "Markdown",
"copyLink": "Скопировать ссылку"
},
"disclosureAction": {
"rename": "Переименовать",
"delete": "Удалить",
"duplicate": "Дублировать"
},
"blankPageTitle": "Пустая страница",
"newPageText": "Новая страница",
"trash": {
"text": "Корзина",
"restoreAll": "Восстановить всё",
"deleteAll": "Очистить",
"pageHeader": {
"fileName": "Имя",
"lastModified": "Последнее изменение",
"created": "Создан"
}
},
"deletePagePrompt": {
"text": "Эта страница в Корзине",
"restore": "Восстановить страницу",
"deletePermanent": "Удалить навсегда"
},
"dialogCreatePageNameHint": "Имя",
"questionBubble": {
"whatsNew": "Что нового?",
"help": "Помощь",
"debug": {
"name": "Отладочная информация",
"success": "Скопировано в буфер обмена!",
"fail": "Не получилось скопировать"
}
},
"menuAppHeader": {
"addPageTooltip": "Быстро добавить новую страницу",
"defaultNewPageName": "Без заголовка",
"renameDialog": "Переименовать"
},
"toolbar": {
"undo": "Отменить",
"redo": "Повторить",
"bold": "Жирный",
"italic": "Курсив",
"underline": "Подчёркнутый",
"strike": "Зачёркнутый",
"numList": "Нумерованный список",
"bulletList": "Маркированный список",
"checkList": "Список To-Do",
"inlineCode": "Код",
"quote": "Цитата",
"header": "Заголовок",
"highlight": "Выделение"
},
"tooltip": {
"darkMode": "Переключиться в тёмную тему",
"openAsPage": "Открыть как страницу",
"addNewRow": "Добавить новую строку",
"openMenu": "Открыть меню"
},
"sideBar": {
"closeSidebar": "Закрыть боковое меню",
"openSidebar": "Открыть боковое меню"
},
"notifications": {
"export": {
"markdown": "Заметка экспортирована в Markdown",
"path": "Документы/flowy"
}
},
"contactsPage": {
"title": "Контакты",
"whatsHappening": "Что происходит на этой неделе?",
"addContact": "Новый контакт",
"editContact": "Редактировать"
},
"button": {
"OK": "OK",
"Cancel": "Отмена",
"signIn": "Войти",
"signOut": "Выйти",
"complete": "Завершить",
"save": "Сохранить"
},
"label": {
"welcome": "Добро пожаловать!",
"firstName": "Имя",
"middleName": "Отчество",
"lastName": "Фамилия",
"stepX": "Этап {X}"
},
"oAuth": {
"err": {
"failedTitle": "Ошибка подключения к аккаунту.",
"failedMsg": "Убедитесь, что вы завершили вход в своём браузере."
},
"signIn": {
"loginTitle": "Войти в @:appName",
"loginButtonText": "Войти",
"buttonText": "Авторизация",
"forgotPassword": "Забыли пароль?",
"emailHint": "Электронная почта",
"passwordHint": "Пароль",
"dontHaveAnAccount": "Нет аккаунта?",
"repeatPasswordEmptyError": "Повтор пароля не может быть пустым",
"unmatchedPasswordError": "Пароли не совпадают"
},
"workspace": {
"create": "Создать рабочее пространство",
"hint": "рабочее пространство",
"notFoundError": "Нет такого рабочего пространства"
},
"shareAction": {
"buttonText": "Поделиться",
"workInProgress": "В разработке",
"markdown": "Markdown",
"copyLink": "Скопировать ссылку"
},
"disclosureAction": {
"rename": "Переименовать",
"delete": "Удалить",
"duplicate": "Дублировать"
},
"blankPageTitle": "Пустая страница",
"newPageText": "Новая страница",
"trash": {
"text": "Корзина",
"restoreAll": "Восстановить всё",
"deleteAll": "Очистить",
"pageHeader": {
"fileName": "Имя",
"lastModified": "Последнее изменение",
"created": "Создан"
}
},
"deletePagePrompt": {
"text": "Эта страница в Корзине",
"restore": "Восстановить страницу",
"deletePermanent": "Удалить навсегда"
},
"dialogCreatePageNameHint": "Имя",
"questionBubble": {
"whatsNew": "Что нового?",
"help": "Помощь",
"debug": {
"name": "Отладочная информация",
"success": "Скопировано в буфер обмена!",
"fail": "Не получилось скопировать"
}
},
"menuAppHeader": {
"addPageTooltip": "Быстро добавить новую страницу",
"defaultNewPageName": "Без заголовка",
"renameDialog": "Переименовать"
},
"toolbar": {
"undo": "Отменить",
"redo": "Повторить",
"bold": "Жирный",
"italic": "Курсив",
"underline": "Подчёркнутый",
"strike": "Зачёркнутый",
"numList": "Нумерованный список",
"bulletList": "Маркированный список",
"checkList": "Список To-Do",
"inlineCode": "Код",
"quote": "Цитата",
"header": "Заголовок",
"highlight": "Выделение"
},
"tooltip": {
"lightMode": "Переключиться в светлую тему",
"darkMode": "Переключиться в тёмную тему",
"openAsPage": "Открыть как страницу",
"addNewRow": "Добавить новую строку",
"openMenu": "Открыть меню"
},
"sideBar": {
"closeSidebar": "Закрыть боковое меню",
"openSidebar": "Открыть боковое меню"
},
"notifications": {
"export": {
"markdown": "Заметка экспортирована в Markdown",
"path": "Документы/flowy"
}
},
"contactsPage": {
"title": "Контакты",
"whatsHappening": "Что происходит на этой неделе?",
"addContact": "Новый контакт",
"editContact": "Редактировать"
},
"button": {
"OK": "OK",
"Cancel": "Отмена",
"signIn": "Войти",
"signOut": "Выйти",
"complete": "Завершить",
"save": "Сохранить"
},
"label": {
"welcome": "Добро пожаловать!",
"firstName": "Имя",
"middleName": "Отчество",
"lastName": "Фамилия",
"stepX": "Этап {X}"
},
"oAuth": {
"err": {
"failedTitle": "Ошибка подключения к аккаунту.",
"failedMsg": "Убедитесь, что вы завершили вход в своём браузере."
},
"google": {
"title": "Вход через Google",
"instruction1": "Чтобы импортировать ваши Google Контакты, вам нужно будет авторизовать приложение через браузер.",
"instruction2": "Скопируйте этот код в буфер обмена (нажав кнопку или выделив текст):",
"instruction3": "Пройдите по ссылке и введите этот код:",
"instruction4": "Нажмите на кнопку ниже, когда завершите вход:"
}
"google": {
"title": "Вход через Google",
"instruction1": "Чтобы импортировать ваши Google Контакты, вам нужно будет авторизовать приложение через браузер.",
"instruction2": "Скопируйте этот код в буфер обмена (нажав кнопку или выделив текст):",
"instruction3": "Пройдите по ссылке и введите этот код:",
"instruction4": "Нажмите на кнопку ниже, когда завершите вход:"
}
},
"settings": {
"title": "Настройки",
"menu": {
"appearance": "Внешний вид",
"language": "Язык",
"user": "Пользователь",
"open": "Открыть настройки"
},
"appearance": {
"lightLabel": "Светлая",
"darkLabel": "Тёмная"
}
},
"grid": {
"settings": {
"title": "Настройки",
"menu": {
"appearance": "Внешний вид",
"language": "Язык",
"user": "Пользователь",
"open": "Открыть настройки"
},
"appearance": {
"lightLabel": "Светлая",
"darkLabel": "Тёмная"
}
"filter": "Фильтр",
"sortBy": "Сортировать",
"Properties": "Свойства",
"group": "Группировать"
},
"grid": {
"settings": {
"filter": "Фильтр",
"sortBy": "Сортировать",
"Properties": "Свойства",
"group": "Группировать"
},
"field": {
"hide": "Скрыть",
"insertLeft": "Вставить слева",
"insertRight": "Вставить справа",
"duplicate": "Дублировать",
"delete": "Удалить",
"textFieldName": "Текст",
"checkboxFieldName": "Чекбокс",
"dateFieldName": "Дата",
"numberFieldName": "Число",
"singleSelectFieldName": "Выбор",
"multiSelectFieldName": "Выбор нескольких",
"urlFieldName": "URL",
"numberFormat": " Формат числа",
"dateFormat": " Формат даты",
"includeTime": " Время",
"dateFormatFriendly": "День Месяц, Год",
"dateFormatISO": "Год-Месяц-День",
"dateFormatLocal": "Год/Месяц/День",
"dateFormatUS": "Год/Месяц/День",
"timeFormat": " Форматировать время",
"invalidTimeFormat": "Неверный формат",
"timeFormatTwelveHour": "12 часов",
"timeFormatTwentyFourHour": "24 часа",
"addSelectOption": "Добавить вариант",
"optionTitle": "Варианты",
"addOption": "Добавить",
"editProperty": "Редактировать свойство",
"newColumn": "Добавить колонку",
"deleteFieldPromptMessage": "Вы уверены? Свойство будет удалено"
},
"row": {
"duplicate": "Дублировать",
"delete": "Удалить",
"textPlaceholder": "Пусто",
"copyProperty": "Свойство скопировано",
"count": "Количество"
},
"selectOption": {
"create": "Создать",
"purpleColor": "Фиолетовый",
"pinkColor": "Розовый",
"lightPinkColor": "Светло-розовый",
"orangeColor": "Оранжевый",
"yellowColor": "Желтый",
"limeColor": "Ярко-зелёный",
"greenColor": "Зелёный",
"aquaColor": "Бирюзовый",
"blueColor": "Синий",
"deleteTag": "Удалить вариант",
"colorPannelTitle": "Цвета",
"pannelTitle": "Выберите или создайте вариант",
"searchOption": "Поиск"
},
"menuName": "Сетка"
"field": {
"hide": "Скрыть",
"insertLeft": "Вставить слева",
"insertRight": "Вставить справа",
"duplicate": "Дублировать",
"delete": "Удалить",
"textFieldName": "Текст",
"checkboxFieldName": "Чекбокс",
"dateFieldName": "Дата",
"numberFieldName": "Число",
"singleSelectFieldName": "Выбор",
"multiSelectFieldName": "Выбор нескольких",
"urlFieldName": "URL",
"numberFormat": " Формат числа",
"dateFormat": " Формат даты",
"includeTime": " Время",
"dateFormatFriendly": "День Месяц, Год",
"dateFormatISO": "Год-Месяц-День",
"dateFormatLocal": "Год/Месяц/День",
"dateFormatUS": "Год/Месяц/День",
"timeFormat": " Форматировать время",
"invalidTimeFormat": "Неверный формат",
"timeFormatTwelveHour": "12 часов",
"timeFormatTwentyFourHour": "24 часа",
"addSelectOption": "Добавить вариант",
"optionTitle": "Варианты",
"addOption": "Добавить",
"editProperty": "Редактировать свойство",
"newColumn": "Добавить колонку",
"deleteFieldPromptMessage": "Вы уверены? Свойство будет удалено"
},
"document": {
"menuName": "Документ",
"date": {
"timeHintTextInTwelveHour": "12:00 AM",
"timeHintTextInTwentyFourHour": "12:00"
}
"row": {
"duplicate": "Дублировать",
"delete": "Удалить",
"textPlaceholder": "Пусто",
"copyProperty": "Свойство скопировано",
"count": "Количество"
},
"board": {
"column": {
"create_new_card": "Создать"
}
"selectOption": {
"create": "Создать",
"purpleColor": "Фиолетовый",
"pinkColor": "Розовый",
"lightPinkColor": "Светло-розовый",
"orangeColor": "Оранжевый",
"yellowColor": "Желтый",
"limeColor": "Ярко-зелёный",
"greenColor": "Зелёный",
"aquaColor": "Бирюзовый",
"blueColor": "Синий",
"deleteTag": "Удалить вариант",
"colorPanelTitle": "Цвета",
"panelTitle": "Выберите или создайте вариант",
"searchOption": "Поиск"
},
"menuName": "Сетка"
},
"document": {
"menuName": "Документ",
"date": {
"timeHintTextInTwelveHour": "12:00 AM",
"timeHintTextInTwentyFourHour": "12:00"
}
},
"board": {
"column": {
"create_new_card": "Создать"
}
}
}

View File

@ -206,8 +206,8 @@
"aquaColor": "水蓝色",
"blueColor": "蓝色",
"deleteTag": "删除标签",
"colorPannelTitle": "颜色",
"pannelTitle": "选择或新建一个标签",
"colorPanelTitle": "颜色",
"panelTitle": "选择或新建一个标签",
"searchOption": "搜索标签"
},
"menuName": "网格"

View File

@ -202,8 +202,8 @@
"aquaColor": "水藍色",
"blueColor": "藍色",
"deleteTag": "刪除標籤",
"colorPannelTitle": "顏色",
"pannelTitle": "搜尋或建立選項",
"colorPanelTitle": "顏色",
"panelTitle": "搜尋或建立選項",
"searchOption": "搜尋選項"
},
"menuName": "網格"

View File

@ -23,7 +23,7 @@ class EditableRowNotifier {
EditableRowNotifier({required bool isEditing})
: isEditing = ValueNotifier(isEditing);
void insertCell(
void bindCell(
GridCellIdentifier cellIdentifier,
EditableCellNotifier notifier,
) {
@ -59,7 +59,7 @@ class EditableRowNotifier {
_cells.values.first.isCellEditing.value = false;
}
void clear() {
void unbind() {
for (final notifier in _cells.values) {
notifier.dispose();
}

View File

@ -3,7 +3,6 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'define.dart';
class BoardNumberCell extends StatefulWidget {

View File

@ -2,6 +2,8 @@ import 'package:app_flowy/plugins/board/application/card/board_select_option_cel
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/extension.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/cell/select_option_cell/select_option_editor.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
@ -26,9 +28,11 @@ class BoardSelectOptionCell extends StatefulWidget with EditableCell {
class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
late BoardSelectOptionCellBloc _cellBloc;
late PopoverController _popover;
@override
void initState() {
_popover = PopoverController();
final cellController =
widget.cellControllerBuilder.build() as GridSelectOptionCellController;
_cellBloc = BoardSelectOptionCellBloc(cellController: cellController)
@ -41,43 +45,60 @@ class _BoardSelectOptionCellState extends State<BoardSelectOptionCell> {
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<BoardSelectOptionCellBloc, BoardSelectOptionCellState>(
buildWhen: (previous, current) {
return previous.selectedOptions != current.selectedOptions;
},
builder: (context, state) {
if (state.selectedOptions
.where((element) => element.id == widget.groupId)
.isNotEmpty ||
state.selectedOptions.isEmpty) {
return const SizedBox();
} else {
final children = state.selectedOptions
.map(
(option) => SelectOptionTag.fromOption(
context: context,
option: option,
onSelected: () {
SelectOptionCellEditor.show(
context: context,
cellController: widget.cellControllerBuilder.build()
as GridSelectOptionCellController,
);
},
),
)
.toList();
buildWhen: (previous, current) {
return previous.selectedOptions != current.selectedOptions;
}, builder: (context, state) {
// Returns SizedBox if the content of the cell is empty
if (_isEmpty(state)) return const SizedBox();
return IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: SizedBox.expand(
child: Wrap(spacing: 4, runSpacing: 2, children: children),
),
),
final children = state.selectedOptions.map(
(option) {
final tag = SelectOptionTag.fromOption(
context: context,
option: option,
onSelected: () => _popover.show(),
);
}
},
),
return _wrapPopover(tag);
},
).toList();
return IntrinsicHeight(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 6),
child: SizedBox.expand(
child: Wrap(spacing: 4, runSpacing: 2, children: children),
),
),
);
}),
);
}
bool _isEmpty(BoardSelectOptionCellState state) {
// The cell should hide if the option id is equal to the groupId.
final isInGroup = state.selectedOptions
.where((element) => element.id == widget.groupId)
.isNotEmpty;
return isInGroup || state.selectedOptions.isEmpty;
}
Widget _wrapPopover(Widget child) {
final constraints = BoxConstraints.loose(Size(
SelectOptionCellEditor.editorPanelWidth,
300,
));
return AppFlowyPopover(
controller: _popover,
constraints: constraints,
direction: PopoverDirection.bottomWithLeftAligned,
popupBuilder: (BuildContext context) {
return SelectOptionCellEditor(
cellController: widget.cellControllerBuilder.build()
as GridSelectOptionCellController,
);
},
onClose: () {},
child: child,
);
}

View File

@ -1,16 +1,17 @@
import 'package:app_flowy/plugins/board/application/card/card_bloc.dart';
import 'package:app_flowy/plugins/board/application/card/card_data_controller.dart';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/row/row_action_sheet.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'board_cell.dart';
import 'card_cell_builder.dart';
import 'card_container.dart';
import 'container/accessory.dart';
import 'container/card_container.dart';
class BoardCard extends StatefulWidget {
final String gridId;
@ -39,6 +40,8 @@ class BoardCard extends StatefulWidget {
class _BoardCardState extends State<BoardCard> {
late BoardCardBloc _cardBloc;
late EditableRowNotifier rowNotifier;
late PopoverController popoverController;
AccessoryType? accessoryType;
@override
void initState() {
@ -55,6 +58,7 @@ class _BoardCardState extends State<BoardCard> {
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
});
popoverController = PopoverController();
super.initState();
}
@ -64,31 +68,42 @@ class _BoardCardState extends State<BoardCard> {
value: _cardBloc,
child: BlocBuilder<BoardCardBloc, BoardCardState>(
buildWhen: (previous, current) {
// Rebuild when:
// 1.If the length of the cells is not the same
// 2.isEditing changed
if (previous.cells.length != current.cells.length ||
previous.isEditing != current.isEditing) {
return true;
}
// 3.Compare the content of the cells. The cells consists of
// list of [BoardCellEquatable] that extends the [Equatable].
return !listEquals(previous.cells, current.cells);
},
builder: (context, state) {
return BoardCardContainer(
buildAccessoryWhen: () => state.isEditing == false,
accessoryBuilder: (context) {
return [
_CardEditOption(
startEditing: () => rowNotifier.becomeFirstResponder(),
),
const _CardMoreOption(),
];
},
onTap: (context) {
widget.openCard(context);
},
child: Column(
mainAxisSize: MainAxisSize.min,
children: _makeCells(
context,
state.cells.map((cell) => cell.identifier).toList(),
return AppFlowyPopover(
controller: popoverController,
constraints: BoxConstraints.loose(const Size(140, 200)),
direction: PopoverDirection.rightWithCenterAligned,
popupBuilder: (popoverContext) => _handlePopoverBuilder(
context,
popoverContext,
),
child: BoardCardContainer(
buildAccessoryWhen: () => state.isEditing == false,
accessoryBuilder: (context) {
return [
_CardEditOption(rowNotifier: rowNotifier),
_CardMoreOption(),
];
},
openAccessory: _handleOpenAccessory,
openCard: (context) => widget.openCard(context),
child: _CellColumn(
groupId: widget.groupId,
rowNotifier: rowNotifier,
cellBuilder: widget.cellBuilder,
cells: state.cells,
),
),
);
@ -97,42 +112,28 @@ class _BoardCardState extends State<BoardCard> {
);
}
List<Widget> _makeCells(
void _handleOpenAccessory(AccessoryType newAccessoryType) {
accessoryType = newAccessoryType;
switch (newAccessoryType) {
case AccessoryType.edit:
break;
case AccessoryType.more:
popoverController.show();
break;
}
}
Widget _handlePopoverBuilder(
BuildContext context,
List<GridCellIdentifier> cells,
BuildContext popoverContext,
) {
final List<Widget> children = [];
rowNotifier.clear();
cells.asMap().forEach(
(int index, GridCellIdentifier cellId) {
EditableCellNotifier cellNotifier;
if (index == 0) {
// Only use the first cell to receive user's input when click the edit
// button
cellNotifier = EditableCellNotifier(
isEditing: rowNotifier.isEditing.value,
);
rowNotifier.insertCell(cellId, cellNotifier);
} else {
cellNotifier = EditableCellNotifier();
}
Widget child = widget.cellBuilder.buildCell(
widget.groupId,
cellId,
cellNotifier,
);
child = Padding(
key: cellId.key(),
padding: const EdgeInsets.only(left: 4, right: 4),
child: child,
);
children.add(child);
},
);
return children;
switch (accessoryType!) {
case AccessoryType.edit:
throw UnimplementedError();
case AccessoryType.more:
return GridRowActionSheet(
rowData: context.read<BoardCardBloc>().rowInfo());
}
}
@override
@ -143,8 +144,65 @@ class _BoardCardState extends State<BoardCard> {
}
}
class _CellColumn extends StatelessWidget {
final String groupId;
final BoardCellBuilder cellBuilder;
final EditableRowNotifier rowNotifier;
final List<BoardCellEquatable> cells;
const _CellColumn({
required this.groupId,
required this.rowNotifier,
required this.cellBuilder,
required this.cells,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: _makeCells(context, cells),
);
}
List<Widget> _makeCells(
BuildContext context,
List<BoardCellEquatable> cells,
) {
final List<Widget> children = [];
// Remove all the cell listeners.
rowNotifier.unbind();
cells.asMap().forEach(
(int index, BoardCellEquatable cell) {
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
final cellNotifier = EditableCellNotifier(isEditing: isEditing);
if (index == 0) {
// Only use the first cell to receive user's input when click the edit
// button
rowNotifier.bindCell(cell.identifier, cellNotifier);
}
final child = Padding(
key: cell.identifier.key(),
padding: const EdgeInsets.only(left: 4, right: 4),
child: cellBuilder.buildCell(
groupId,
cell.identifier,
cellNotifier,
),
);
children.add(child);
},
);
return children;
}
}
class _CardMoreOption extends StatelessWidget with CardAccessory {
const _CardMoreOption({Key? key}) : super(key: key);
_CardMoreOption({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -156,17 +214,13 @@ class _CardMoreOption extends StatelessWidget with CardAccessory {
}
@override
void onTap(BuildContext context) {
GridRowActionSheet(
rowData: context.read<BoardCardBloc>().rowInfo(),
).show(context, direction: AnchorDirection.bottomWithCenterAligned);
}
AccessoryType get type => AccessoryType.more;
}
class _CardEditOption extends StatelessWidget with CardAccessory {
final VoidCallback startEditing;
final EditableRowNotifier rowNotifier;
const _CardEditOption({
required this.startEditing,
required this.rowNotifier,
Key? key,
}) : super(key: key);
@ -182,7 +236,8 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
}
@override
void onTap(BuildContext context) {
startEditing();
}
void onTap(BuildContext context) => rowNotifier.becomeFirstResponder();
@override
AccessoryType get type => AccessoryType.edit;
}

View File

@ -0,0 +1,74 @@
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum AccessoryType {
edit,
more,
}
abstract class CardAccessory implements Widget {
AccessoryType get type;
void onTap(BuildContext context) {}
}
typedef CardAccessoryBuilder = List<CardAccessory> Function(
BuildContext buildContext,
);
class CardAccessoryContainer extends StatelessWidget {
final void Function(AccessoryType) onTapAccessory;
final List<CardAccessory> accessories;
const CardAccessoryContainer({
required this.accessories,
required this.onTapAccessory,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
final children = accessories.map((accessory) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
accessory.onTap(context);
onTapAccessory(accessory.type);
},
child: _wrapHover(theme, accessory),
);
}).toList();
return _wrapDecoration(context, Row(children: children));
}
FlowyHover _wrapHover(AppTheme theme, CardAccessory accessory) {
return FlowyHover(
style: HoverStyle(
hoverColor: theme.hover,
backgroundColor: theme.surface,
borderRadius: BorderRadius.zero,
),
builder: (_, onHover) => SizedBox(
width: 24,
height: 24,
child: accessory,
),
);
}
Widget _wrapDecoration(BuildContext context, Widget child) {
final theme = context.read<AppTheme>();
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
final decoration = BoxDecoration(
color: Colors.transparent,
border: Border.fromBorderSide(borderSide),
borderRadius: const BorderRadius.all(Radius.circular(4)),
);
return Container(
clipBehavior: Clip.hardEdge,
decoration: decoration,
child: child,
);
}
}

View File

@ -1,17 +1,19 @@
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart';
import 'accessory.dart';
class BoardCardContainer extends StatelessWidget {
final Widget child;
final CardAccessoryBuilder? accessoryBuilder;
final bool Function()? buildAccessoryWhen;
final void Function(BuildContext) onTap;
final void Function(BuildContext) openCard;
final void Function(AccessoryType) openAccessory;
const BoardCardContainer({
required this.child,
required this.onTap,
required this.openCard,
required this.openAccessory,
this.accessoryBuilder,
this.buildAccessoryWhen,
Key? key,
@ -34,13 +36,14 @@ class BoardCardContainer extends StatelessWidget {
if (accessories.isNotEmpty) {
container = _CardEnterRegion(
accessories: accessories,
onTapAccessory: openAccessory,
child: container,
);
}
}
return GestureDetector(
onTap: () => onTap(context),
onTap: () => openCard(context),
child: Padding(
padding: const EdgeInsets.all(8),
child: ConstrainedBox(
@ -55,75 +58,16 @@ class BoardCardContainer extends StatelessWidget {
}
}
abstract class CardAccessory implements Widget {
void onTap(BuildContext context);
}
typedef CardAccessoryBuilder = List<CardAccessory> Function(
BuildContext buildContext,
);
class CardAccessoryContainer extends StatelessWidget {
final List<CardAccessory> accessories;
const CardAccessoryContainer({required this.accessories, Key? key})
: super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
final children = accessories.map((accessory) {
final hover = FlowyHover(
style: HoverStyle(
hoverColor: theme.hover,
backgroundColor: theme.surface,
borderRadius: BorderRadius.zero,
),
builder: (_, onHover) => SizedBox(
width: 24,
height: 24,
child: accessory,
),
);
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => accessory.onTap(context),
child: hover,
);
}).toList();
return Container(
clipBehavior: Clip.hardEdge,
decoration: _makeBoxDecoration(context),
child: Row(children: children),
);
}
}
BoxDecoration _makeBoxDecoration(BuildContext context) {
final theme = context.read<AppTheme>();
final borderSide = BorderSide(color: theme.shader6, width: 1.0);
return BoxDecoration(
color: Colors.transparent,
border: Border.fromBorderSide(borderSide),
// boxShadow: const [
// BoxShadow(
// color: Colors.transparent,
// spreadRadius: 0,
// blurRadius: 5,
// offset: Offset.zero,
// )
// ],
borderRadius: const BorderRadius.all(Radius.circular(4)),
);
}
class _CardEnterRegion extends StatelessWidget {
final Widget child;
final List<CardAccessory> accessories;
const _CardEnterRegion(
{required this.child, required this.accessories, Key? key})
: super(key: key);
final void Function(AccessoryType) onTapAccessory;
const _CardEnterRegion({
required this.child,
required this.accessories,
required this.onTapAccessory,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -132,9 +76,12 @@ class _CardEnterRegion extends StatelessWidget {
builder: (context, onEnter, _) {
List<Widget> children = [child];
if (onEnter) {
children.add(CardAccessoryContainer(
accessories: accessories,
).positioned(right: 0));
children.add(
CardAccessoryContainer(
accessories: accessories,
onTapAccessory: onTapAccessory,
).positioned(right: 0),
);
}
return MouseRegion(

View File

@ -4,7 +4,7 @@ import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/presentation/layout/sizes.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_group.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/toolbar/grid_property.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -50,7 +50,6 @@ class BoardSettingList extends StatelessWidget {
previous.selectedAction != current.selectedAction,
listener: (context, state) {
state.selectedAction.foldLeft(null, (_, action) {
// FlowyOverlay.of(context).remove(identifier());
onAction(action, settingContext);
});
},

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -62,10 +62,10 @@ class _SettingButtonState extends State<_SettingButton> {
@override
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
return AppFlowyStylePopover(
return AppFlowyPopover(
controller: popoverController,
constraints: BoxConstraints.loose(const Size(260, 400)),
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
child: FlowyIconButton(
hoverColor: theme.hover,
width: 22,

View File

@ -33,8 +33,10 @@ class FieldEditorBloc extends Bloc<FieldEditorEvent, FieldEditorState> {
await dataController.loadTypeOptionData();
},
updateName: (name) {
dataController.fieldName = name;
emit(state.copyWith(name: name));
if (state.name != name) {
dataController.fieldName = name;
emit(state.copyWith(name: name));
}
},
didReceiveFieldChanged: (FieldPB field) {
emit(state.copyWith(

View File

@ -4,7 +4,7 @@ import 'package:flutter/widgets.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import '../cell_builder.dart';
import 'date_editor.dart';
@ -58,12 +58,12 @@ class _DateCellState extends GridCellState<GridDateCell> {
Widget build(BuildContext context) {
final alignment = widget.cellStyle != null
? widget.cellStyle!.alignment
: Alignment.center;
: Alignment.centerLeft;
return BlocProvider.value(
value: _cellBloc,
child: BlocBuilder<DateCellBloc, DateCellState>(
builder: (context, state) {
return AppFlowyStylePopover(
return AppFlowyPopover(
controller: _popover,
offset: const Offset(0, 20),
direction: PopoverDirection.bottomWithLeftAligned,
@ -77,7 +77,10 @@ class _DateCellState extends GridCellState<GridDateCell> {
cursor: SystemMouseCursors.click,
child: Align(
alignment: alignment,
child: FlowyText.medium(state.dateStr, fontSize: 12),
child: FlowyText.medium(
state.dateStr,
fontSize: 12,
),
),
),
),

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/cell/date_cal_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -299,9 +299,8 @@ class _DateTypeOptionButton extends StatelessWidget {
return BlocSelector<DateCalBloc, DateCalState, DateTypeOptionPB>(
selector: (state) => state.dateTypeOptionPB,
builder: (context, dateTypeOptionPB) {
return AppFlowyStylePopover(
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
return AppFlowyPopover(
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(140, 100)),
child: FlowyButton(
@ -340,36 +339,30 @@ class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
@override
Widget build(BuildContext context) {
List<Widget> children = [
Popover(
AppFlowyPopover(
mutex: _popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: DateFormatList(
selectedFormat: widget.dateTypeOptionPB.dateFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setDateFormat(format)),
),
return DateFormatList(
selectedFormat: widget.dateTypeOptionPB.dateFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setDateFormat(format)),
);
},
child: const DateFormatButton(),
),
Popover(
AppFlowyPopover(
mutex: _popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
popupBuilder: (BuildContext context) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(460, 440)),
child: TimeFormatList(
selectedFormat: widget.dateTypeOptionPB.timeFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setTimeFormat(format)),
),
return TimeFormatList(
selectedFormat: widget.dateTypeOptionPB.timeFormat,
onSelected: (format) =>
widget.onEvent(DateCalEvent.setTimeFormat(format)),
);
},
child: TimeFormatButton(timeFormat: widget.dateTypeOptionPB.timeFormat),

View File

@ -1,6 +1,6 @@
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -164,67 +164,65 @@ class _SelectOptionWrapState extends State<SelectOptionWrap> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
final Widget child;
if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
child = Align(
alignment: Alignment.centerLeft,
child: FlowyText.medium(
widget.cellStyle!.placeholder,
fontSize: 14,
color: theme.shader3,
),
);
} else {
child = Align(
alignment: Alignment.centerLeft,
child: Wrap(
spacing: 4,
runSpacing: 2,
children: widget.selectOptions
.map((option) => SelectOptionTag.fromOption(
context: context,
option: option,
))
.toList(),
),
);
}
Widget child = _buildOptions(theme, context);
return Stack(
alignment: AlignmentDirectional.center,
fit: StackFit.expand,
children: [
AppFlowyStylePopover(
controller: _popover,
constraints: BoxConstraints.loose(
Size(SelectOptionCellEditor.editorPanelWidth, 300)),
offset: const Offset(0, 20),
direction: PopoverDirection.bottomWithLeftAligned,
// triggerActions: PopoverTriggerActionFlags.c,
popupBuilder: (BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
widget.onFocus?.call(true);
});
return SizedBox(
width: SelectOptionCellEditor.editorPanelWidth,
child: SelectOptionCellEditor(
cellController: widget.cellControllerBuilder.build()
as GridSelectOptionCellController,
onDismissed: () {
widget.onFocus?.call(false);
},
),
);
},
onClose: () {
widget.onFocus?.call(false);
},
child: child,
),
InkWell(onTap: () {
_popover.show();
}),
_wrapPopover(child),
InkWell(onTap: () => _popover.show()),
],
);
}
Widget _wrapPopover(Widget child) {
final constraints = BoxConstraints.loose(Size(
SelectOptionCellEditor.editorPanelWidth,
300,
));
return AppFlowyPopover(
controller: _popover,
constraints: constraints,
direction: PopoverDirection.bottomWithLeftAligned,
popupBuilder: (BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
widget.onFocus?.call(true);
});
return SelectOptionCellEditor(
cellController: widget.cellControllerBuilder.build()
as GridSelectOptionCellController,
);
},
onClose: () => widget.onFocus?.call(false),
child: child,
);
}
Widget _buildOptions(AppTheme theme, BuildContext context) {
final Widget child;
if (widget.selectOptions.isEmpty && widget.cellStyle != null) {
child = FlowyText.medium(
widget.cellStyle!.placeholder,
fontSize: 14,
color: theme.shader3,
);
} else {
final children = widget.selectOptions.map(
(option) {
return SelectOptionTag.fromOption(
context: context,
option: option,
);
},
).toList();
child = Wrap(
spacing: 4,
runSpacing: 2,
children: children,
);
}
return Align(alignment: Alignment.centerLeft, child: child);
}
}

View File

@ -1,7 +1,7 @@
import 'dart:collection';
import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_service.dart';
import 'package:app_flowy/plugins/grid/application/cell/select_option_editor_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -25,83 +25,61 @@ import 'text_field.dart';
const double _editorPanelWidth = 300;
class SelectOptionCellEditor extends StatelessWidget with FlowyOverlayDelegate {
class SelectOptionCellEditor extends StatefulWidget {
final GridSelectOptionCellController cellController;
final VoidCallback? onDismissed;
static double editorPanelWidth = 300;
const SelectOptionCellEditor({
required this.cellController,
this.onDismissed,
Key? key,
}) : super(key: key);
const SelectOptionCellEditor({required this.cellController, Key? key})
: super(key: key);
@override
State<SelectOptionCellEditor> createState() => _SelectOptionCellEditorState();
}
class _SelectOptionCellEditorState extends State<SelectOptionCellEditor> {
late PopoverMutex popoverMutex;
@override
void initState() {
popoverMutex = PopoverMutex();
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SelectOptionCellEditorBloc(
cellController: cellController,
cellController: widget.cellController,
)..add(const SelectOptionEditorEvent.initial()),
child: BlocBuilder<SelectOptionCellEditorBloc, SelectOptionEditorState>(
builder: (context, state) {
return CustomScrollView(
shrinkWrap: true,
slivers: [
SliverToBoxAdapter(child: _TextField()),
SliverToBoxAdapter(
child: _TextField(popoverMutex: popoverMutex),
),
const SliverToBoxAdapter(child: VSpace(6)),
const SliverToBoxAdapter(child: TypeOptionSeparator()),
const SliverToBoxAdapter(child: VSpace(6)),
const SliverToBoxAdapter(child: _Title()),
const SliverToBoxAdapter(child: _OptionList()),
SliverToBoxAdapter(
child: _OptionList(popoverMutex: popoverMutex),
),
],
);
},
),
);
}
static void show({
required BuildContext context,
required GridSelectOptionCellController cellController,
VoidCallback? onDismissed,
}) {
SelectOptionCellEditor.remove(context);
final editor = SelectOptionCellEditor(
cellController: cellController,
onDismissed: onDismissed,
);
//
FlowyOverlay.of(context).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(_editorPanelWidth, 300)),
child: SizedBox(width: _editorPanelWidth, child: editor),
),
identifier: SelectOptionCellEditor.identifier(),
anchorContext: context,
anchorDirection: AnchorDirection.bottomWithCenterAligned,
delegate: editor,
);
}
static void remove(BuildContext context) {
FlowyOverlay.of(context).remove(identifier());
}
static String identifier() {
return (SelectOptionCellEditor).toString();
}
@override
bool asBarrier() => true;
@override
void didRemove() => onDismissed?.call();
}
class _OptionList extends StatelessWidget {
const _OptionList({Key? key}) : super(key: key);
final PopoverMutex popoverMutex;
const _OptionList({
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -110,7 +88,10 @@ class _OptionList extends StatelessWidget {
List<Widget> cells = [];
cells.addAll(state.options.map((option) {
return _SelectOptionCell(
option, state.selectedOptions.contains(option));
option: option,
isSelected: state.selectedOptions.contains(option),
popoverMutex: popoverMutex,
);
}).toList());
state.createOption.fold(
@ -143,9 +124,13 @@ class _OptionList extends StatelessWidget {
}
class _TextField extends StatelessWidget {
final PopoverMutex popoverMutex;
final TextfieldTagsController _tagController = TextfieldTagsController();
_TextField({Key? key}) : super(key: key);
_TextField({
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -163,8 +148,7 @@ class _TextField extends StatelessWidget {
selectedOptionMap: optionMap,
distanceToText: _editorPanelWidth * 0.7,
tagController: _tagController,
onClick: () => FlowyOverlay.of(context)
.remove(SelectOptionTypeOptionEditor.identifier),
onClick: () => popoverMutex.close(),
newText: (text) {
context
.read<SelectOptionCellEditorBloc>()
@ -193,7 +177,7 @@ class _Title extends StatelessWidget {
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 6),
child: FlowyText.medium(
LocaleKeys.grid_selectOption_pannelTitle.tr(),
LocaleKeys.grid_selectOption_panelTitle.tr(),
fontSize: 12,
color: theme.shader3,
),
@ -231,9 +215,14 @@ class _CreateOptionCell extends StatelessWidget {
class _SelectOptionCell extends StatefulWidget {
final SelectOptionPB option;
final PopoverMutex popoverMutex;
final bool isSelected;
const _SelectOptionCell(this.option, this.isSelected, {Key? key})
: super(key: key);
const _SelectOptionCell({
required this.option,
required this.isSelected,
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
State<_SelectOptionCell> createState() => _SelectOptionCellState();
@ -251,10 +240,12 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return AppFlowyStylePopover(
return AppFlowyPopover(
controller: _popoverController,
offset: const Offset(20, 0),
asBarrier: true,
constraints: BoxConstraints.loose(const Size(200, 300)),
mutex: widget.popoverMutex,
child: SizedBox(
height: GridSize.typeOptionItemHeight,
child: Row(
@ -299,8 +290,9 @@ class _SelectOptionCellState extends State<_SelectOptionCell> {
.read<SelectOptionCellEditorBloc>()
.add(SelectOptionEditorEvent.updateOption(updatedOption));
},
key: ValueKey(widget.option
.id), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
key: ValueKey(
widget.option.id,
), // Use ValueKey to refresh the UI, otherwise, it will remain the old value.
);
},
);

View File

@ -11,20 +11,17 @@ import 'package:textfield_tags/textfield_tags.dart';
import 'extension.dart';
class SelectOptionTextField extends StatelessWidget {
final FocusNode _focusNode;
final TextEditingController _controller;
class SelectOptionTextField extends StatefulWidget {
final TextfieldTagsController tagController;
final List<SelectOptionPB> options;
final LinkedHashMap<String, SelectOptionPB> selectedOptionMap;
final double distanceToText;
final Function(String) onNewTag;
final Function(String) newText;
final VoidCallback? onClick;
SelectOptionTextField({
const SelectOptionTextField({
required this.options,
required this.selectedOptionMap,
required this.distanceToText,
@ -35,33 +32,55 @@ class SelectOptionTextField extends StatelessWidget {
TextEditingController? textController,
FocusNode? focusNode,
Key? key,
}) : _controller = textController ?? TextEditingController(),
_focusNode = focusNode ?? FocusNode(),
super(key: key);
}) : super(key: key);
@override
State<SelectOptionTextField> createState() => _SelectOptionTextFieldState();
}
class _SelectOptionTextFieldState extends State<SelectOptionTextField> {
late FocusNode focusNode;
late TextEditingController controller;
@override
void initState() {
focusNode = FocusNode();
controller = TextEditingController();
WidgetsBinding.instance.addPostFrameCallback((_) {
focusNode.requestFocus();
});
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return TextFieldTags(
textEditingController: _controller,
textfieldTagsController: tagController,
initialTags: selectedOptionMap.keys.toList(),
focusNode: _focusNode,
textEditingController: controller,
textfieldTagsController: widget.tagController,
initialTags: widget.selectedOptionMap.keys.toList(),
focusNode: focusNode,
textSeparators: const [','],
inputfieldBuilder: (BuildContext context, editController, focusNode,
error, onChanged, onSubmitted) {
inputfieldBuilder: (
BuildContext context,
editController,
focusNode,
error,
onChanged,
onSubmitted,
) {
return ((context, sc, tags, onTagDelegate) {
return TextField(
autofocus: true,
controller: editController,
focusNode: focusNode,
onTap: onClick,
onTap: widget.onClick,
onChanged: (text) {
if (onChanged != null) {
onChanged(text);
}
newText(text);
widget.newText(text);
},
onSubmitted: (text) {
if (onSubmitted != null) {
@ -69,7 +88,7 @@ class SelectOptionTextField extends StatelessWidget {
}
if (text.isNotEmpty) {
onNewTag(text);
widget.onNewTag(text);
focusNode.requestFocus();
}
},
@ -83,7 +102,8 @@ class SelectOptionTextField extends StatelessWidget {
isDense: true,
prefixIcon: _renderTags(context, sc),
hintText: LocaleKeys.grid_selectOption_searchOption.tr(),
prefixIconConstraints: BoxConstraints(maxWidth: distanceToText),
prefixIconConstraints:
BoxConstraints(maxWidth: widget.distanceToText),
focusedBorder: OutlineInputBorder(
borderSide: BorderSide(color: theme.main1, width: 1.0),
borderRadius: Corners.s10Border,
@ -96,11 +116,11 @@ class SelectOptionTextField extends StatelessWidget {
}
Widget? _renderTags(BuildContext context, ScrollController sc) {
if (selectedOptionMap.isEmpty) {
if (widget.selectedOptionMap.isEmpty) {
return null;
}
final children = selectedOptionMap.values
final children = widget.selectedOptionMap.values
.map((option) =>
SelectOptionTag.fromOption(context: context, option: option))
.toList();

View File

@ -2,7 +2,7 @@ import 'dart:async';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:app_flowy/plugins/grid/application/cell/url_cell_bloc.dart';
import 'package:app_flowy/workspace/presentation/home/toast.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -130,7 +130,7 @@ class _GridURLCellState extends GridCellState<GridURLCell> {
),
);
return AppFlowyStylePopover(
return AppFlowyPopover(
controller: _popoverController,
constraints: BoxConstraints.loose(const Size(300, 160)),
direction: PopoverDirection.bottomWithLeftAligned,
@ -216,11 +216,11 @@ class _EditURLAccessoryState extends State<_EditURLAccessory>
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return AppFlowyStylePopover(
return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(300, 160)),
controller: _popoverController,
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(0, 20),
child: svgWidget("editor/edit", color: theme.iconColor),
popupBuilder: (BuildContext popoverContext) {

View File

@ -1,8 +1,9 @@
import 'package:app_flowy/plugins/grid/application/field/field_cell_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/field_service.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/hover.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@ -29,9 +30,10 @@ class GridFieldCell extends StatelessWidget {
},
child: BlocBuilder<FieldCellBloc, FieldCellState>(
builder: (context, state) {
final button = Popover(
final button = AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(240, 840)),
direction: PopoverDirection.bottomWithLeftAligned,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(0, 10),
popupBuilder: (BuildContext context) {
return GridFieldCellActionSheet(
@ -135,9 +137,11 @@ class _DragToExpandLine extends StatelessWidget {
class FieldCellButton extends StatelessWidget {
final VoidCallback onTap;
final FieldPB field;
final int? maxLines;
const FieldCellButton({
required this.field,
required this.onTap,
this.maxLines = 1,
Key? key,
}) : super(key: key);
@ -148,7 +152,11 @@ class FieldCellButton extends StatelessWidget {
hoverColor: theme.shader6,
onTap: onTap,
leftIcon: svgWidget(field.fieldType.iconName(), color: theme.iconColor),
text: FlowyText.medium(field.name, fontSize: 12),
text: FlowyText.medium(
field.name,
fontSize: 12,
maxLines: maxLines,
),
margin: GridSize.cellContentInsets,
);
}

View File

@ -3,9 +3,9 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
@ -32,38 +32,32 @@ class _GridFieldCellActionSheetState extends State<GridFieldCellActionSheet> {
Widget build(BuildContext context) {
if (_showFieldEditor) {
final field = widget.cellContext.field;
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FieldEditor(
return FieldEditor(
gridId: widget.cellContext.gridId,
fieldName: field.name,
typeOptionLoader: FieldTypeOptionLoader(
gridId: widget.cellContext.gridId,
fieldName: field.name,
typeOptionLoader: FieldTypeOptionLoader(
gridId: widget.cellContext.gridId,
field: field,
),
field: field,
),
);
}
return BlocProvider(
create: (context) =>
getIt<FieldActionSheetBloc>(param1: widget.cellContext),
child: OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: SingleChildScrollView(
child: Column(
children: [
_EditFieldButton(
cellContext: widget.cellContext,
onTap: () {
setState(() {
_showFieldEditor = true;
});
},
),
const VSpace(6),
_FieldOperationList(widget.cellContext, () {}),
],
),
child: SingleChildScrollView(
child: Column(
children: [
_EditFieldButton(
cellContext: widget.cellContext,
onTap: () {
setState(() {
_showFieldEditor = true;
});
},
),
const VSpace(6),
_FieldOperationList(widget.cellContext, () {}),
],
),
),
);
@ -159,8 +153,11 @@ class FieldActionCell extends StatelessWidget {
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return FlowyButton(
text: FlowyText.medium(action.title(),
fontSize: 12, color: enable ? null : theme.shader4),
text: FlowyText.medium(
action.title(),
fontSize: 12,
color: enable ? null : theme.shader4,
),
hoverColor: theme.hover,
onTap: () {
if (enable) {
@ -168,8 +165,10 @@ class FieldActionCell extends StatelessWidget {
onTap();
}
},
leftIcon: svgWidget(action.iconName(),
color: enable ? theme.iconColor : theme.disableIconColor),
leftIcon: svgWidget(
action.iconName(),
color: enable ? theme.iconColor : theme.disableIconColor,
),
);
}
}
@ -216,6 +215,7 @@ extension _FieldActionExtension on FieldAction {
.add(const FieldActionSheetEvent.duplicateField());
break;
case FieldAction.delete:
PopoverContainer.of(context).close();
NavigatorAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {

View File

@ -1,18 +1,17 @@
import 'package:app_flowy/plugins/grid/application/field/field_editor_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:dartz/dartz.dart' show none;
import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'field_name_input.dart';
import 'field_type_option_editor.dart';
class FieldEditor extends StatefulWidget {
@ -44,6 +43,12 @@ class _FieldEditorState extends State<FieldEditor> {
super.initState();
}
@override
void dispose() {
popoverMutex.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return BlocProvider(
@ -53,32 +58,42 @@ class _FieldEditorState extends State<FieldEditor> {
isGroupField: widget.isGroupField,
loader: widget.typeOptionLoader,
)..add(const FieldEditorEvent.initial()),
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
child: ListView(
shrinkWrap: true,
children: [
FlowyText.medium(
LocaleKeys.grid_field_editProperty.tr(),
fontSize: 12,
),
const VSpace(10),
_FieldNameTextField(popoverMutex: popoverMutex),
const VSpace(10),
..._addDeleteFieldButton(),
_FieldTypeOptionCell(popoverMutex: popoverMutex),
],
),
);
}
List<Widget> _addDeleteFieldButton() {
if (widget.onDeleted == null) {
return [];
}
return [
BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) {
return ListView(
shrinkWrap: true,
children: [
FlowyText.medium(LocaleKeys.grid_field_editProperty.tr(),
fontSize: 12),
const VSpace(10),
const _FieldNameCell(),
const VSpace(10),
_DeleteFieldButton(
popoverMutex: popoverMutex,
onDeleted: () {
state.field.fold(
() => Log.error('Can not delete the field'),
(field) => widget.onDeleted?.call(field.id),
);
},
),
const VSpace(10),
_FieldTypeOptionCell(popoverMutex: popoverMutex),
],
return _DeleteFieldButton(
popoverMutex: popoverMutex,
onDeleted: () {
state.field.fold(
() => Log.error('Can not delete the field'),
(field) => widget.onDeleted?.call(field.id),
);
},
);
},
),
);
];
}
}
@ -111,25 +126,90 @@ class _FieldTypeOptionCell extends StatelessWidget {
}
}
class _FieldNameCell extends StatelessWidget {
const _FieldNameCell({Key? key}) : super(key: key);
class _FieldNameTextField extends StatefulWidget {
final PopoverMutex popoverMutex;
const _FieldNameTextField({
required this.popoverMutex,
Key? key,
}) : super(key: key);
@override
State<_FieldNameTextField> createState() => _FieldNameTextFieldState();
}
class _FieldNameTextFieldState extends State<_FieldNameTextField> {
FocusNode focusNode = FocusNode();
VoidCallback? _popoverCallback;
late TextEditingController controller;
@override
void initState() {
controller = TextEditingController();
focusNode.addListener(() {
if (focusNode.hasFocus) {
widget.popoverMutex.close();
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return BlocBuilder<FieldEditorBloc, FieldEditorState>(
builder: (context, state) {
return FieldNameTextField(
name: state.name,
errorText: context.read<FieldEditorBloc>().state.errorText,
onNameChanged: (newName) {
context
.read<FieldEditorBloc>()
.add(FieldEditorEvent.updateName(newName));
final theme = context.watch<AppTheme>();
return MultiBlocListener(
listeners: [
BlocListener<FieldEditorBloc, FieldEditorState>(
listenWhen: (p, c) => p.field == none(),
listener: (context, state) {
focusNode.requestFocus();
},
);
},
),
BlocListener<FieldEditorBloc, FieldEditorState>(
listenWhen: (p, c) => controller.text != c.name,
listener: (context, state) {
controller.text = state.name;
},
),
],
child: BlocBuilder<FieldEditorBloc, FieldEditorState>(
buildWhen: (previous, current) =>
previous.errorText != current.errorText,
builder: (context, state) {
listenOnPopoverChanged(context);
return RoundedInputField(
height: 36,
focusNode: focusNode,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
controller: controller,
normalBorderColor: theme.shader4,
errorBorderColor: theme.red,
focusBorderColor: theme.main1,
cursorColor: theme.main1,
errorText: context.read<FieldEditorBloc>().state.errorText,
onChanged: (newName) {
context
.read<FieldEditorBloc>()
.add(FieldEditorEvent.updateName(newName));
},
);
},
),
);
}
void listenOnPopoverChanged(BuildContext context) {
if (_popoverCallback != null) {
widget.popoverMutex.removePopoverListener(_popoverCallback!);
}
_popoverCallback = widget.popoverMutex.listenOnPopoverChanged(() {
if (focusNode.hasFocus) {
final node = FocusScope.of(context);
node.unfocus();
}
});
}
}
class _DeleteFieldButton extends StatelessWidget {
@ -155,31 +235,11 @@ class _DeleteFieldButton extends StatelessWidget {
fontSize: 12,
color: enable ? null : theme.shader4,
),
onTap: () => onDeleted?.call(),
);
if (enable) button = _wrapPopover(button);
// if (enable) button = button;
return button;
},
);
}
Widget _wrapPopover(Widget widget) {
return AppFlowyStylePopover(
triggerActions: PopoverTriggerActionFlags.click,
constraints: BoxConstraints.loose(const Size(400, 240)),
mutex: popoverMutex,
direction: PopoverDirection.center,
popupBuilder: (popupContext) {
return PopoverAlertView(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
cancel: () => popoverMutex.state?.close(),
confirm: () {
onDeleted?.call();
popoverMutex.state?.close();
},
popoverMutex: popoverMutex,
);
},
child: widget,
);
}
}

View File

@ -1,56 +0,0 @@
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class FieldNameTextField extends StatefulWidget {
final void Function(String) onNameChanged;
final String name;
final String errorText;
const FieldNameTextField({
required this.name,
required this.errorText,
required this.onNameChanged,
Key? key,
}) : super(key: key);
@override
State<FieldNameTextField> createState() => _FieldNameTextFieldState();
}
class _FieldNameTextFieldState extends State<FieldNameTextField> {
late String name;
TextEditingController controller = TextEditingController();
@override
void initState() {
controller.text = widget.name;
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return RoundedInputField(
height: 36,
autoFocus: true,
style: const TextStyle(fontSize: 13, fontWeight: FontWeight.w500),
controller: controller,
normalBorderColor: theme.shader4,
errorBorderColor: theme.red,
focusBorderColor: theme.main1,
cursorColor: theme.main1,
errorText: widget.errorText,
onChanged: widget.onNameChanged,
);
}
@override
void didUpdateWidget(covariant FieldNameTextField oldWidget) {
controller.text = widget.name;
controller.selection = TextSelection.fromPosition(
TextPosition(offset: controller.text.length));
super.didUpdateWidget(oldWidget);
}
}

View File

@ -1,4 +1,4 @@
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -47,10 +47,6 @@ class FieldTypeList extends StatelessWidget with FlowyOverlayDelegate {
),
);
}
static String identifier() {
return (FieldTypeList).toString();
}
}
class FieldTypeCell extends StatelessWidget {

View File

@ -1,6 +1,6 @@
import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:dartz/dartz.dart' show Either;
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -64,9 +64,10 @@ class FieldTypeOptionEditor extends StatelessWidget {
final theme = context.watch<AppTheme>();
return SizedBox(
height: GridSize.typeOptionItemHeight,
child: AppFlowyStylePopover(
constraints: BoxConstraints.loose(const Size(460, 440)),
triggerActions: PopoverTriggerActionFlags.click,
child: AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(460, 540)),
asBarrier: true,
triggerActions: PopoverTriggerFlags.click | PopoverTriggerFlags.hover,
mutex: popoverMutex,
offset: const Offset(20, 0),
popupBuilder: (context) {

View File

@ -4,7 +4,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/type_option
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -176,10 +176,11 @@ class CreateFieldButton extends StatelessWidget {
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return AppFlowyStylePopover(
triggerActions: PopoverTriggerActionFlags.click,
return AppFlowyPopover(
triggerActions: PopoverTriggerFlags.click,
direction: PopoverDirection.bottomWithRightAligned,
constraints: BoxConstraints.loose(const Size(240, 200)),
asBarrier: true,
constraints: BoxConstraints.loose(const Size(240, 600)),
child: FlowyButton(
text: FlowyText.medium(
LocaleKeys.grid_field_newColumn.tr(),

View File

@ -3,7 +3,7 @@ import 'dart:typed_data';
import 'package:app_flowy/plugins/grid/application/field/field_controller.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_data_controller.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/multi_select_type_option.pb.dart';
@ -50,7 +50,9 @@ Widget? makeTypeOptionWidget({
required PopoverMutex popoverMutex,
}) {
final builder = makeTypeOptionWidgetBuilder(
dataController: dataController, popoverMutex: popoverMutex);
dataController: dataController,
popoverMutex: popoverMutex,
);
return builder.build(context);
}

View File

@ -11,7 +11,7 @@ import 'package:flowy_infra_ui/widget/spacing.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_type_option_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import '../../../layout/sizes.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';
@ -62,10 +62,10 @@ class DateTypeOptionWidget extends TypeOptionWidget {
}
Widget _renderDateFormatButton(BuildContext context, DateFormat dataFormat) {
return AppFlowyStylePopover(
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(460, 440)),
popupBuilder: (popoverContext) {
@ -75,7 +75,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectDateFormat(format));
PopoverContainer.of(popoverContext).closeAll();
PopoverContainer.of(popoverContext).close();
},
);
},
@ -84,10 +84,10 @@ class DateTypeOptionWidget extends TypeOptionWidget {
}
Widget _renderTimeFormatButton(BuildContext context, TimeFormat timeFormat) {
return AppFlowyStylePopover(
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions:
PopoverTriggerActionFlags.hover | PopoverTriggerActionFlags.click,
asBarrier: true,
triggerActions: PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(460, 440)),
popupBuilder: (BuildContext popoverContext) {
@ -97,7 +97,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
context
.read<DateTypeOptionBloc>()
.add(DateTypeOptionEvent.didSelectTimeFormat(format));
PopoverContainer.of(popoverContext).closeAll();
PopoverContainer.of(popoverContext).close();
},
);
},
@ -201,12 +201,10 @@ class DateFormatList extends StatelessWidget {
Widget build(BuildContext context) {
final cells = DateFormat.values.map((format) {
return DateFormatCell(
dateFormat: format,
onSelected: (format) {
onSelected(format);
FlowyOverlay.of(context).remove(DateFormatList.identifier());
},
isSelected: selectedFormat == format);
dateFormat: format,
onSelected: onSelected,
isSelected: selectedFormat == format,
);
}).toList();
return SizedBox(
@ -224,10 +222,6 @@ class DateFormatList extends StatelessWidget {
),
);
}
static String identifier() {
return (DateFormatList).toString();
}
}
class DateFormatCell extends StatelessWidget {
@ -291,12 +285,10 @@ class TimeFormatList extends StatelessWidget {
Widget build(BuildContext context) {
final cells = TimeFormat.values.map((format) {
return TimeFormatCell(
isSelected: format == selectedFormat,
timeFormat: format,
onSelected: (format) {
onSelected(format);
FlowyOverlay.of(context).remove(TimeFormatList.identifier());
});
isSelected: format == selectedFormat,
timeFormat: format,
onSelected: onSelected,
);
}).toList();
return SizedBox(
@ -314,10 +306,6 @@ class TimeFormatList extends StatelessWidget {
),
);
}
static String identifier() {
return (TimeFormatList).toString();
}
}
class TimeFormatCell extends StatelessWidget {

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/multi_select_type_option.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import '../field_type_option_editor.dart';
import 'builder.dart';

View File

@ -1,7 +1,7 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/number_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/number_format_bloc.dart';
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -55,10 +55,10 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
listener: (context, state) =>
typeOptionContext.typeOption = state.typeOption,
builder: (context, state) {
return AppFlowyStylePopover(
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
triggerActions:
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(460, 440)),
child: FlowyButton(
@ -82,7 +82,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
context
.read<NumberTypeOptionBloc>()
.add(NumberTypeOptionEvent.didSelectFormat(format));
PopoverContainer.of(popoverContext).closeAll();
PopoverContainer.of(popoverContext).close();
},
selectedFormat: state.typeOption.format,
);
@ -123,8 +123,6 @@ class NumberFormatList extends StatelessWidget {
format: format,
onSelected: (format) {
onSelected(format);
FlowyOverlay.of(context)
.remove(NumberFormatList.identifier());
});
}).toList();
@ -147,10 +145,6 @@ class NumberFormatList extends StatelessWidget {
),
);
}
static String identifier() {
return (NumberFormatList).toString();
}
}
class NumberFormatCell extends StatelessWidget {

View File

@ -1,5 +1,5 @@
import 'package:app_flowy/plugins/grid/application/field/type_option/select_option_type_option_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -180,10 +180,11 @@ class _OptionCellState extends State<_OptionCell> {
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return AppFlowyStylePopover(
return AppFlowyPopover(
controller: _popoverController,
mutex: widget.popoverMutex,
offset: const Offset(20, 0),
asBarrier: true,
constraints: BoxConstraints.loose(const Size(460, 440)),
child: SizedBox(
height: GridSize.typeOptionItemHeight,
@ -207,13 +208,13 @@ class _OptionCellState extends State<_OptionCell> {
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.deleteOption(widget.option));
PopoverContainer.of(popoverContext).closeAll();
PopoverContainer.of(popoverContext).close();
},
onUpdated: (updatedOption) {
context
.read<SelectOptionTypeOptionBloc>()
.add(SelectOptionTypeOptionEvent.updateOption(updatedOption));
PopoverContainer.of(popoverContext).closeAll();
PopoverContainer.of(popoverContext).close();
},
key: ValueKey(widget.option.id),
);

View File

@ -139,7 +139,7 @@ class SelectOptionColorList extends StatelessWidget {
child: SizedBox(
height: GridSize.typeOptionItemHeight,
child: FlowyText.medium(
LocaleKeys.grid_selectOption_colorPannelTitle.tr(),
LocaleKeys.grid_selectOption_colorPanelTitle.tr(),
fontSize: 12,
textAlign: TextAlign.left,
),

View File

@ -2,7 +2,7 @@ import 'package:app_flowy/plugins/grid/application/field/type_option/single_sele
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:flutter/material.dart';
import '../field_type_option_editor.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'builder.dart';
import 'select_option.dart';

View File

@ -1,8 +1,10 @@
import 'package:app_flowy/plugins/grid/application/prelude.dart';
import 'package:app_flowy/plugins/grid/application/row/row_cache.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
@ -56,20 +58,21 @@ class _GridRowWidgetState extends State<GridRowWidget> {
child: BlocBuilder<RowBloc, RowState>(
buildWhen: (p, c) => p.rowInfo.rowPB.height != c.rowInfo.rowPB.height,
builder: (context, state) {
final children = [
const _RowLeading(),
Expanded(
child: RowContent(
builder: widget.cellBuilder,
onExpand: () => widget.openDetailPage(
context,
widget.cellBuilder,
),
final content = Expanded(
child: RowContent(
builder: widget.cellBuilder,
onExpand: () => widget.openDetailPage(
context,
widget.cellBuilder,
),
),
);
return Row(children: [
const _RowLeading(),
content,
const _RowTrailing(),
];
return Row(children: children);
]);
},
),
),
@ -83,26 +86,51 @@ class _GridRowWidgetState extends State<GridRowWidget> {
}
}
class _RowLeading extends StatelessWidget {
class _RowLeading extends StatefulWidget {
const _RowLeading({Key? key}) : super(key: key);
@override
State<_RowLeading> createState() => _RowLeadingState();
}
class _RowLeadingState extends State<_RowLeading> {
late PopoverController popoverController;
@override
void initState() {
popoverController = PopoverController();
super.initState();
}
@override
Widget build(BuildContext context) {
return Consumer<RegionStateNotifier>(
builder: (context, state, _) {
return SizedBox(
width: GridSize.leadingHeaderPadding,
child: state.onEnter ? _activeWidget() : null);
return AppFlowyPopover(
controller: popoverController,
constraints: BoxConstraints.loose(const Size(140, 200)),
direction: PopoverDirection.rightWithCenterAligned,
popupBuilder: (BuildContext popoverContext) {
return GridRowActionSheet(
rowData: context.read<RowBloc>().state.rowInfo);
},
child: Consumer<RegionStateNotifier>(
builder: (context, state, _) {
return SizedBox(
width: GridSize.leadingHeaderPadding,
child: state.onEnter ? _activeWidget() : null,
);
},
),
);
}
Widget _activeWidget() {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
_InsertRowButton(),
_DeleteRowButton(),
children: [
const _InsertButton(),
_MenuButton(openMenu: () {
popoverController.show();
}),
],
);
}
@ -117,8 +145,8 @@ class _RowTrailing extends StatelessWidget {
}
}
class _InsertRowButton extends StatelessWidget {
const _InsertRowButton({Key? key}) : super(key: key);
class _InsertButton extends StatelessWidget {
const _InsertButton({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -128,17 +156,29 @@ class _InsertRowButton extends StatelessWidget {
hoverColor: theme.hover,
width: 20,
height: 30,
onPressed: () => context.read<RowBloc>().add(
const RowEvent.createRow(),
),
onPressed: () => context.read<RowBloc>().add(const RowEvent.createRow()),
iconPadding: const EdgeInsets.all(3),
icon: svgWidget("home/add"),
);
}
}
class _DeleteRowButton extends StatelessWidget {
const _DeleteRowButton({Key? key}) : super(key: key);
class _MenuButton extends StatefulWidget {
final VoidCallback openMenu;
const _MenuButton({
required this.openMenu,
Key? key,
}) : super(key: key);
@override
State<_MenuButton> createState() => _MenuButtonState();
}
class _MenuButtonState extends State<_MenuButton> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
@ -148,9 +188,7 @@ class _DeleteRowButton extends StatelessWidget {
hoverColor: theme.hover,
width: 20,
height: 30,
onPressed: () => GridRowActionSheet(
rowData: context.read<RowBloc>().state.rowInfo,
).show(context),
onPressed: () => widget.openMenu(),
iconPadding: const EdgeInsets.all(3),
icon: svgWidget("editor/details"),
);

View File

@ -3,7 +3,6 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@ -27,10 +26,7 @@ class GridRowActionSheet extends StatelessWidget {
final cells = _RowAction.values
.where((value) => value.enable())
.map(
(action) => _RowActionCell(
action: action,
onDismissed: () => remove(context),
),
(action) => _RowActionCell(action: action),
)
.toList();
@ -52,37 +48,11 @@ class GridRowActionSheet extends StatelessWidget {
),
);
}
void show(
BuildContext overlayContext, {
AnchorDirection direction = AnchorDirection.leftWithCenterAligned,
}) {
FlowyOverlay.of(overlayContext).insertWithAnchor(
widget: OverlayContainer(
constraints: BoxConstraints.loose(const Size(140, 200)),
child: this,
),
identifier: GridRowActionSheet.identifier(),
anchorContext: overlayContext,
anchorDirection: direction,
);
}
void remove(BuildContext overlayContext) {
FlowyOverlay.of(overlayContext).remove(GridRowActionSheet.identifier());
}
static String identifier() {
return (GridRowActionSheet).toString();
}
}
class _RowActionCell extends StatelessWidget {
final _RowAction action;
final VoidCallback onDismissed;
const _RowActionCell(
{required this.action, required this.onDismissed, Key? key})
: super(key: key);
const _RowActionCell({required this.action, Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
@ -101,7 +71,6 @@ class _RowActionCell extends StatelessWidget {
if (action.enable()) {
action.performAction(context);
}
onDismissed();
},
leftIcon: svgWidget(action.iconName(), color: theme.iconColor),
),

View File

@ -2,6 +2,7 @@ import 'package:app_flowy/plugins/grid/application/cell/cell_service/cell_servic
import 'package:app_flowy/plugins/grid/application/field/type_option/type_option_context.dart';
import 'package:app_flowy/plugins/grid/application/row/row_data_controller.dart';
import 'package:app_flowy/plugins/grid/application/row/row_detail_bloc.dart';
import 'package:app_flowy/workspace/presentation/widgets/dialogs.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -15,7 +16,7 @@ import 'package:app_flowy/generated/locale_keys.g.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import '../../layout/sizes.dart';
import '../cell/cell_accessory.dart';
@ -112,67 +113,61 @@ class _PropertyList extends StatelessWidget {
builder: (context, state) {
return Column(
children: [
Expanded(
child: ScrollbarListStack(
axis: Axis.vertical,
controller: _scrollController,
barSize: GridSize.scrollBarSize,
autoHideScrollbar: false,
child: ListView.separated(
controller: _scrollController,
itemCount: state.gridCells.length,
itemBuilder: (BuildContext context, int index) {
return _RowDetailCell(
cellId: state.gridCells[index],
cellBuilder: cellBuilder,
);
},
separatorBuilder: (BuildContext context, int index) {
return const VSpace(2);
},
),
),
),
Expanded(child: _wrapScrollbar(buildList(state))),
const VSpace(10),
_CreateFieldButton(
viewId: viewId,
onClosed: () {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
});
},
onOpened: (controller) {
return FieldEditor(
gridId: viewId,
typeOptionLoader: NewFieldTypeOptionLoader(gridId: viewId),
onDeleted: (fieldId) {
controller.close();
context
.read<RowDetailBloc>()
.add(RowDetailEvent.deleteField(fieldId));
},
);
},
onClosed: _handleDidCreateField,
),
],
);
},
);
}
Widget buildList(RowDetailState state) {
return ListView.separated(
controller: _scrollController,
itemCount: state.gridCells.length,
itemBuilder: (BuildContext context, int index) {
return _RowDetailCell(
cellId: state.gridCells[index],
cellBuilder: cellBuilder,
);
},
separatorBuilder: (BuildContext context, int index) {
return const VSpace(2);
},
);
}
Widget _wrapScrollbar(Widget child) {
return ScrollbarListStack(
axis: Axis.vertical,
controller: _scrollController,
barSize: GridSize.scrollBarSize,
autoHideScrollbar: false,
child: child,
);
}
void _handleDidCreateField() {
WidgetsBinding.instance.addPostFrameCallback((_) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: const Duration(milliseconds: 250),
curve: Curves.ease,
);
});
}
}
class _CreateFieldButton extends StatefulWidget {
final String viewId;
final Widget Function(PopoverController) onOpened;
final VoidCallback onClosed;
const _CreateFieldButton({
required this.viewId,
required this.onOpened,
required this.onClosed,
Key? key,
}) : super(key: key);
@ -194,10 +189,10 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> {
Widget build(BuildContext context) {
final theme = context.read<AppTheme>();
return AppFlowyStylePopover(
return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(240, 200)),
controller: popoverController,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
direction: PopoverDirection.topWithLeftAligned,
onClose: widget.onClosed,
child: Container(
@ -213,8 +208,24 @@ class _CreateFieldButtonState extends State<_CreateFieldButton> {
leftIcon: svgWidget("home/add"),
),
),
popupBuilder: (BuildContext context) =>
widget.onOpened(popoverController),
popupBuilder: (BuildContext popOverContext) {
return FieldEditor(
gridId: widget.viewId,
typeOptionLoader: NewFieldTypeOptionLoader(gridId: widget.viewId),
onDeleted: (fieldId) {
popoverController.close();
NavigatorAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {
context
.read<RowDetailBloc>()
.add(RowDetailEvent.deleteField(fieldId));
},
).show(context);
},
);
},
);
}
@ -260,41 +271,25 @@ class _RowDetailCellState extends State<_RowDetailCell> {
),
);
return ConstrainedBox(
constraints: const BoxConstraints(minHeight: 40),
child: IntrinsicHeight(
return IntrinsicHeight(
child: ConstrainedBox(
constraints: const BoxConstraints(minHeight: 40),
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 150,
child: Popover(
controller: popover,
offset: const Offset(20, 0),
popupBuilder: (popoverContext) {
return OverlayContainer(
constraints: BoxConstraints.loose(const Size(240, 200)),
child: FieldEditor(
gridId: widget.cellId.gridId,
fieldName: widget.cellId.fieldContext.field.name,
isGroupField: widget.cellId.fieldContext.isGroupField,
typeOptionLoader: FieldTypeOptionLoader(
gridId: widget.cellId.gridId,
field: widget.cellId.fieldContext.field,
),
onDeleted: (fieldId) {
popover.close();
context
.read<RowDetailBloc>()
.add(RowDetailEvent.deleteField(fieldId));
},
),
);
},
AppFlowyPopover(
controller: popover,
constraints: BoxConstraints.loose(const Size(240, 600)),
popupBuilder: (popoverContext) => buildFieldEditor(),
child: SizedBox(
width: 150,
child: FieldCellButton(
maxLines: null,
field: widget.cellId.fieldContext.field,
onTap: () => popover.show(),
onTap: () {
popover.show();
},
),
),
),
@ -305,6 +300,30 @@ class _RowDetailCellState extends State<_RowDetailCell> {
),
);
}
Widget buildFieldEditor() {
return FieldEditor(
gridId: widget.cellId.gridId,
fieldName: widget.cellId.fieldContext.field.name,
isGroupField: widget.cellId.fieldContext.isGroupField,
typeOptionLoader: FieldTypeOptionLoader(
gridId: widget.cellId.gridId,
field: widget.cellId.fieldContext.field,
),
onDeleted: (fieldId) {
popover.close();
NavigatorAlertDialog(
title: LocaleKeys.grid_field_deleteFieldPromptMessage.tr(),
confirm: () {
context
.read<RowDetailBloc>()
.add(RowDetailEvent.deleteField(fieldId));
},
).show(context);
},
);
}
}
GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {

View File

@ -99,7 +99,6 @@ class _GridGroupCell extends StatelessWidget {
),
);
onSelected();
// FlowyOverlay.of(context).remove(GridGroupList.identifier());
},
),
);

View File

@ -3,7 +3,7 @@ import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_editor.
import 'package:app_flowy/startup/startup.dart';
import 'package:app_flowy/plugins/grid/application/setting/property_bloc.dart';
import 'package:app_flowy/plugins/grid/presentation/widgets/header/field_type_extension.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
@ -116,11 +116,11 @@ class _GridPropertyCell extends StatelessWidget {
}
Widget _editFieldButton(AppTheme theme, BuildContext context) {
return AppFlowyStylePopover(
return AppFlowyPopover(
mutex: popoverMutex,
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(20, 0),
constraints: BoxConstraints.loose(const Size(240, 200)),
constraints: BoxConstraints.loose(const Size(240, 400)),
child: FlowyButton(
text: FlowyText.medium(fieldContext.name, fontSize: 12),
hoverColor: theme.hover,
@ -131,8 +131,10 @@ class _GridPropertyCell extends StatelessWidget {
return FieldEditor(
gridId: gridId,
fieldName: fieldContext.name,
typeOptionLoader:
FieldTypeOptionLoader(gridId: gridId, field: fieldContext.field),
typeOptionLoader: FieldTypeOptionLoader(
gridId: gridId,
field: fieldContext.field,
),
);
},
);

View File

@ -2,7 +2,6 @@ import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/style_widget/button.dart';
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
import 'package:flowy_infra_ui/style_widget/text.dart';
@ -40,7 +39,6 @@ class GridSettingList extends StatelessWidget {
previous.selectedAction != current.selectedAction,
listener: (context, state) {
state.selectedAction.foldLeft(null, (_, action) {
FlowyOverlay.of(context).remove(identifier());
onAction(action, settingContext);
});
},

View File

@ -1,4 +1,4 @@
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:app_flowy/plugins/grid/application/setting/setting_bloc.dart';
import 'package:flowy_infra/image.dart';
import 'package:flowy_infra/theme.dart';
@ -53,9 +53,9 @@ class _SettingButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return AppFlowyStylePopover(
return AppFlowyPopover(
constraints: BoxConstraints.loose(const Size(260, 400)),
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: const Offset(0, 10),
child: FlowyIconButton(
width: 22,

View File

@ -140,23 +140,23 @@ class _TrashPageState extends State<TrashPage> {
return SizedBox(
height: 36,
child: Row(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
FlowyText.semibold(LocaleKeys.trash_text.tr()),
const Spacer(),
SizedBox.fromSize(
size: const Size(102, 30),
IntrinsicWidth(
child: FlowyButton(
text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr(),
fontSize: 12),
leftIcon: svgWidget('editor/restore', color: theme.iconColor),
hoverColor: theme.hover,
onTap: () =>
context.read<TrashBloc>().add(const TrashEvent.restoreAll()),
onTap: () => context.read<TrashBloc>().add(
const TrashEvent.restoreAll(),
),
),
),
const HSpace(6),
SizedBox.fromSize(
size: const Size(102, 30),
IntrinsicWidth(
child: FlowyButton(
text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr(),
fontSize: 12),

View File

@ -16,7 +16,8 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
AppearanceSettingModel(this.setting)
: _theme = AppTheme.fromName(name: setting.theme),
_locale = Locale(setting.locale.languageCode, setting.locale.countryCode);
_locale =
Locale(setting.locale.languageCode, setting.locale.countryCode);
AppTheme get theme => _theme;
Locale get locale => _locale;
@ -34,7 +35,8 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
}
void swapTheme() {
final themeType = (_theme.ty == ThemeType.light ? ThemeType.dark : ThemeType.light);
final themeType =
(_theme.ty == ThemeType.light ? ThemeType.dark : ThemeType.light);
if (_theme.ty != themeType) {
_theme = AppTheme.fromType(themeType);
@ -45,14 +47,15 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
}
void setLocale(BuildContext context, Locale newLocale) {
if (_locale != newLocale) {
if (!context.supportedLocales.contains(newLocale)) {
Log.warn("Unsupported locale: $newLocale");
newLocale = const Locale('en');
Log.debug("Fallback to locale: $newLocale");
}
if (!context.supportedLocales.contains(newLocale)) {
Log.warn("Unsupported locale: $newLocale");
newLocale = const Locale('en');
Log.debug("Fallback to locale: $newLocale");
}
context.setLocale(newLocale);
context.setLocale(newLocale);
if (_locale != newLocale) {
_locale = newLocale;
setting.locale.languageCode = _locale.languageCode;
setting.locale.countryCode = _locale.countryCode ?? "";
@ -67,6 +70,10 @@ class AppearanceSettingModel extends ChangeNotifier with EquatableMixin {
save();
setLocale(context, context.deviceLocale);
return;
}
// when opening app the first time
setLocale(context, _locale);
}
}

View File

@ -1,4 +1,3 @@
import 'package:appflowy_popover/popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flowy_infra/text_style.dart';
import 'package:flowy_infra/theme.dart';
@ -87,44 +86,6 @@ class _CreateTextFieldDialog extends State<NavigatorTextFieldDialog> {
}
}
class PopoverAlertView extends StatelessWidget {
final PopoverMutex popoverMutex;
final String title;
final void Function()? cancel;
final void Function()? confirm;
const PopoverAlertView({
required this.popoverMutex,
required this.title,
this.confirm,
this.cancel,
Key? key,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return StyledDialog(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
...[
FlowyText.medium(title, color: theme.shader4),
],
if (confirm != null) ...[
const VSpace(20),
OkCancelButton(
onOkPressed: confirm,
onCancelPressed: cancel,
)
]
],
),
);
}
}
class NavigatorAlertDialog extends StatefulWidget {
final String title;
final void Function()? cancel;

View File

@ -1,63 +0,0 @@
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
import 'package:flowy_infra_ui/widget/rounded_input_field.dart';
import 'package:flutter/material.dart';
import 'package:window_size/window_size.dart';
class FlowyPoppuWindow extends StatelessWidget {
final Widget child;
const FlowyPoppuWindow({Key? key, required this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
type: MaterialType.transparency,
child: child,
);
}
static Future<void> show(
BuildContext context, {
required Widget child,
required Size size,
}) async {
final window = await getWindowInfo();
// ignore: use_build_context_synchronously
FlowyOverlay.of(context).insertWithRect(
widget: FlowyPoppuWindow(child: child),
identifier: 'FlowyPoppuWindow',
anchorPosition: Offset(-size.width / 2.0, -size.height / 2.0),
anchorSize: window.frame.size,
anchorDirection: AnchorDirection.center,
style: FlowyOverlayStyle(blur: false),
);
}
}
class PopupTextField extends StatelessWidget {
final void Function(String) textDidChange;
const PopupTextField({
Key? key,
required this.textDidChange,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return RoundedInputField(
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
hintText: '',
normalBorderColor: const Color(0xffbdbdbd),
onChanged: textDidChange,
);
}
static void show(
{required BuildContext context,
required Size size,
required void Function(String) textDidChange}) {
FlowyPoppuWindow.show(
context,
size: size,
child: PopupTextField(textDidChange: textDidChange),
);
}
}

View File

@ -416,7 +416,6 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@ -550,7 +549,6 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
@ -575,7 +573,6 @@
isa = XCBuildConfiguration;
baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */;
buildSettings = {
ARCHS = arm64;
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;

View File

@ -27,7 +27,7 @@ and the Flutter guide for
</p>
<div align="center">
<img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy-editor-example.gif?raw=true" width = "700" style = "padding: 100"/>
<img src="https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/documentation/images/appflowy_editor_example.mp4?raw=true" width = "700" style = "padding: 100"/>
</div>
## Key Features
@ -58,8 +58,6 @@ final editorStyle = EditorStyle.defaultStyle();
final editorState = EditorState.empty(); // an empty state
final editor = AppFlowyEditor(
editorState: editorState,
shortcutEvents: const [],
customBuilders: const {},
editorStyle: editorStyle,
);
```
@ -72,8 +70,6 @@ final editorStyle = EditorStyle.defaultStyle();
final editorState = EditorState(StateTree.fromJson(data));
final editor = AppFlowyEditor(
editorState: editorState,
shortcutEvents: const [],
customBuilders: const {},
editorStyle: editorStyle,
);
```
@ -113,7 +109,7 @@ Please refer to our documentation on customizing AppFlowy for a detailed discuss
Below are some examples of shortcut event customizations:
* [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/update_text_style_by_command_x_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
* [BIUS](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/format_style_handler.dart) demonstrates how to make text bold/italic/underline/strikethrough through shortcut keys
* [Paste HTML](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers/copy_paste_handler.dart) gives you an idea on how to handle pasted styles through shortcut keys
* Need more examples? Check out [Internal key event handlers](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/lib/src/service/internal_key_event_handlers)

View File

@ -27,7 +27,7 @@ Widget build(BuildContext context) {
At this point, nothing magic will happen after typing `_xxx_`.
![Before](./images/customizing_a_shortcut_event_before.gif)
![Before](./images/customize_a_shortcut_event_before.gif)
To implement our shortcut event we will create a `ShortcutEvent` instance to handle an underscore input.
@ -39,11 +39,10 @@ We need to define `key` and `command` in a ShortCutEvent object to customize hot
```dart
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
key: 'Underscore to italic',
command: 'underscore',
command: 'shift+underscore',
handler: _underscoreToItalicHandler,
);
@ -82,9 +81,12 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
final textNode = textNodes.first;
final text = textNode.toRawString();
// Determine if an 'underscore' already exists in the text node
final previousUnderscore = text.indexOf('_');
if (previousUnderscore == -1) {
// Determine if an 'underscore' already exists in the text node and only once.
final firstUnderscore = text.indexOf('_');
final lastUnderscore = text.lastIndexOf('_');
if (firstUnderscore == -1 ||
firstUnderscore != lastUnderscore ||
firstUnderscore == selection.start.offset - 1) {
return KeyEventResult.ignored;
}
@ -92,15 +94,20 @@ ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
// update the style of the text surrounded by the two underscores to 'italic',
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, previousUnderscore, 1)
..deleteText(textNode, firstUnderscore, 1)
..formatText(
textNode,
previousUnderscore,
selection.end.offset - previousUnderscore - 1,
{'italic': true},
firstUnderscore,
selection.end.offset - firstUnderscore - 1,
{
BuiltInAttributeKey.italic: true,
},
)
..afterSelection = Selection.collapsed(
Position(path: textNode.path, offset: selection.end.offset - 1),
Position(
path: textNode.path,
offset: selection.end.offset - 1,
),
)
..commit();
@ -121,7 +128,7 @@ Widget build(BuildContext context) {
editorStyle: EditorStyle.defaultStyle(),
customBuilders: const {},
shortcutEvents: [
_underscoreToItalicHandler,
underscoreToItalic,
],
),
),
@ -129,9 +136,9 @@ Widget build(BuildContext context) {
}
```
![After](./images/customizing_a_shortcut_event_after.gif)
![After](./images/customize_a_shortcut_event_after.gif)
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic_key_event_handler.dart) file of this example.
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/underscore_to_italic.dart) file of this example.
## Customizing a Component
@ -297,7 +304,7 @@ return AppFlowyEditor(
);
```
![Whew!](./images/customizing_a_component.gif)
![Whew!](./images/customize_a_component.gif)
Check out the [complete code](https://github.com/AppFlowy-IO/AppFlowy/blob/main/frontend/app_flowy/packages/appflowy_editor/example/lib/plugin/network_image_node_widget.dart) file of this example.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 998 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 772 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 358 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 MiB

View File

@ -1,7 +1,7 @@
import 'dart:convert';
import 'dart:io';
import 'package:example/plugin/underscore_to_italic_key_event_handler.dart';
import 'package:example/plugin/underscore_to_italic.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@ -29,7 +29,7 @@ class MyApp extends StatelessWidget {
GlobalWidgetsLocalizations.delegate,
AppFlowyEditorLocalizations.delegate,
],
supportedLocales: AppFlowyEditorLocalizations.delegate.supportedLocales,
supportedLocales: const [Locale('en', 'US')],
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
@ -113,7 +113,7 @@ class _MyHomePageState extends State<MyHomePage> {
editorState: _editorState!,
editorStyle: _editorStyle,
shortcutEvents: [
underscoreToItalicEvent,
underscoreToItalic,
],
),
);
@ -186,6 +186,7 @@ class _MyHomePageState extends State<MyHomePage> {
final path = directory.path;
final file = File('$path/editor.json');
setState(() {
_editorState = null;
_jsonString = file.readAsString();
});
}

View File

@ -0,0 +1,53 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
ShortcutEvent underscoreToItalic = ShortcutEvent(
key: 'Underscore to italic',
command: 'shift+underscore',
handler: _underscoreToItalicHandler,
);
ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
// Obtain the selection and selected nodes of the current document through the 'selectionService'
// to determine whether the selection is collapsed and whether the selected node is a text node.
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toRawString();
// Determine if an 'underscore' already exists in the text node and only once.
final firstUnderscore = text.indexOf('_');
final lastUnderscore = text.lastIndexOf('_');
if (firstUnderscore == -1 ||
firstUnderscore != lastUnderscore ||
firstUnderscore == selection.start.offset - 1) {
return KeyEventResult.ignored;
}
// Delete the previous 'underscore',
// update the style of the text surrounded by the two underscores to 'italic',
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, firstUnderscore, 1)
..formatText(
textNode,
firstUnderscore,
selection.end.offset - firstUnderscore - 1,
{
BuiltInAttributeKey.italic: true,
},
)
..afterSelection = Selection.collapsed(
Position(
path: textNode.path,
offset: selection.end.offset - 1,
),
)
..commit();
return KeyEventResult.handled;
};

View File

@ -1,45 +0,0 @@
import 'package:appflowy_editor/appflowy_editor.dart';
import 'package:flutter/material.dart';
ShortcutEvent underscoreToItalicEvent = ShortcutEvent(
key: 'Underscore to italic',
command: 'underscore',
handler: _underscoreToItalicHandler,
);
ShortcutEventHandler _underscoreToItalicHandler = (editorState, event) {
// Obtaining the selection and selected nodes of the current document through `selectionService`,
// and determine whether it is a single selection and whether the selected node is a text node.
final selectionService = editorState.service.selectionService;
final selection = selectionService.currentSelection.value;
final textNodes = selectionService.currentSelectedNodes.whereType<TextNode>();
if (selection == null || !selection.isSingle || textNodes.length != 1) {
return KeyEventResult.ignored;
}
final textNode = textNodes.first;
final text = textNode.toRawString();
// Determine if `underscore` already exists in the text node
final previousUnderscore = text.indexOf('_');
if (previousUnderscore == -1) {
return KeyEventResult.ignored;
}
// Delete the previous `underscore`,
// update the style of the text surrounded by two underscores to `italic`,
// and update the cursor position.
TransactionBuilder(editorState)
..deleteText(textNode, previousUnderscore, 1)
..formatText(
textNode,
previousUnderscore,
selection.end.offset - previousUnderscore - 1,
{'italic': true},
)
..afterSelection = Selection.collapsed(
Position(path: textNode.path, offset: selection.end.offset - 1),
)
..commit();
return KeyEventResult.handled;
};

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="21225" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14490.70"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="21225"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
@ -13,7 +13,7 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="Runner" customModuleProvider="target">
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="example" customModuleProvider="target">
<connections>
<outlet property="applicationMenu" destination="uQy-DD-JDr" id="XBo-yE-nKs"/>
<outlet property="mainFlutterWindow" destination="QvC-M9-y7g" id="gIp-Ho-8D9"/>
@ -330,14 +330,15 @@
</items>
<point key="canvasLocation" x="142" y="-258"/>
</menu>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="Runner" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<window title="APP_NAME" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" animationBehavior="default" titlebarAppearsTransparent="YES" titleVisibility="hidden" id="QvC-M9-y7g" customClass="MainFlutterWindow" customModule="example" customModuleProvider="target">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES" fullSizeContentView="YES"/>
<rect key="contentRect" x="335" y="390" width="800" height="600"/>
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
<rect key="screenRect" x="0.0" y="0.0" width="1920" height="1055"/>
<view key="contentView" wantsLayer="YES" id="EiT-Mj-1SZ">
<rect key="frame" x="0.0" y="0.0" width="800" height="600"/>
<autoresizingMask key="autoresizingMask"/>
</view>
<point key="canvasLocation" x="126" y="-658"/>
</window>
</objects>
</document>

View File

@ -10,6 +10,7 @@ export 'src/document/selection.dart';
export 'src/document/state_tree.dart';
export 'src/document/text_delta.dart';
export 'src/document/attributes.dart';
export 'src/document/built_in_attribute_keys.dart';
export 'src/editor_state.dart';
export 'src/operation/operation.dart';
export 'src/operation/transaction.dart';

View File

@ -44,10 +44,17 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
}
final textNode = textNodes.first;
final text = textNode.toRawString();
final text = textNode.toRawString().substring(0, selection.end.offset);
final numberMatch = _numberRegex.firstMatch(text);
if (numberMatch != null) {
if ((_checkboxListSymbols + _unCheckboxListSymbols).contains(text)) {
return _toCheckboxList(editorState, textNode);
} else if (_bulletedListSymbols.contains(text)) {
return _toBulletedList(editorState, textNode);
} else if (_countOfSign(text, selection) != 0) {
return _toHeadingStyle(editorState, textNode, selection);
} else if (numberMatch != null) {
final matchText = numberMatch.group(0);
final numText = numberMatch.group(1);
if (matchText != null && numText != null) {
@ -55,14 +62,6 @@ ShortcutEventHandler whiteSpaceHandler = (editorState, event) {
}
}
if ((_checkboxListSymbols + _unCheckboxListSymbols).any(text.startsWith)) {
return _toCheckboxList(editorState, textNode);
} else if (_bulletedListSymbols.any(text.startsWith)) {
return _toBulletedList(editorState, textNode);
} else if (_countOfSign(text, selection) != 0) {
return _toHeadingStyle(editorState, textNode, selection);
}
return KeyEventResult.ignored;
};
@ -196,7 +195,7 @@ KeyEventResult _toHeadingStyle(
int _countOfSign(String text, Selection selection) {
for (var i = 6; i >= 0; i--) {
if (text.substring(0, selection.end.offset).startsWith('#' * i)) {
if (text.substring(0, selection.end.offset).contains('#' * i)) {
return i;
}
}

View File

@ -174,5 +174,45 @@ void main() async {
expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
}
});
testWidgets('Presses whitespace key in edge cases', (tester) async {
const text = '';
final editor = tester.editor..insertTextNode(text);
await editor.startTesting();
final textNode = editor.nodeAtPath([0]) as TextNode;
await editor.updateSelection(
Selection.single(path: [0], startOffset: 0),
);
await editor.insertText(textNode, '*', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.bulletedList);
await editor.insertText(textNode, '[]', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
expect(textNode.attributes.check, false);
await editor.insertText(textNode, '1.', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.numberList);
await editor.insertText(textNode, '#', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.heading);
await editor.insertText(textNode, '[x]', 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
expect(textNode.attributes.check, true);
const insertedText = '[]AppFlowy';
await editor.insertText(textNode, insertedText, 0);
await editor.pressLogicKey(LogicalKeyboardKey.space);
expect(textNode.subtype, BuiltInAttributeKey.checkbox);
expect(textNode.attributes.check, true);
expect(textNode.toRawString(), insertedText);
});
});
}

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
class PopoverMenu extends StatefulWidget {
const PopoverMenu({Key? key}) : super(key: key);
@ -41,8 +41,8 @@ class _PopoverMenuState extends State<PopoverMenu> {
decoration: null)),
),
Popover(
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
triggerActions:
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
mutex: popOverMutex,
offset: const Offset(10, 0),
popupBuilder: (BuildContext context) {
@ -54,8 +54,8 @@ class _PopoverMenuState extends State<PopoverMenu> {
),
),
Popover(
triggerActions: PopoverTriggerActionFlags.hover |
PopoverTriggerActionFlags.click,
triggerActions:
PopoverTriggerFlags.hover | PopoverTriggerFlags.click,
mutex: popOverMutex,
offset: const Offset(10, 0),
popupBuilder: (BuildContext context) {
@ -86,7 +86,7 @@ class ExampleButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Popover(
triggerActions: PopoverTriggerActionFlags.click,
triggerActions: PopoverTriggerFlags.click,
offset: offset,
direction: direction ?? PopoverDirection.rightWithTopAligned,
child: TextButton(child: Text(label), onPressed: () {}),

View File

@ -1,4 +1,4 @@
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart';
import "./example_button.dart";

View File

@ -0,0 +1,5 @@
/// AppFlowyBoard library
library appflowy_popover;
export 'src/mutex.dart';
export 'src/popover.dart';

View File

@ -323,9 +323,21 @@ class PopoverTargetRenderBox extends RenderProxyBox {
@override
void detach() {
super.detach();
link.leaderOffset = null;
link.leaderSize = null;
super.detach();
}
@override
void attach(covariant PipelineOwner owner) {
super.attach(owner);
if (hasSize) {
// The leaderSize was set after [performLayout], but was
// set to null when [detach] get called.
//
// set the leaderSize when attach get called
link.leaderSize = size;
}
}
@override

View File

@ -0,0 +1,116 @@
import 'dart:collection';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
typedef EntryMap = LinkedHashMap<PopoverState, OverlayEntryContext>;
class RootOverlayEntry {
final EntryMap _entries = EntryMap();
RootOverlayEntry();
void addEntry(
BuildContext context,
PopoverState newState,
OverlayEntry entry,
bool asBarrier,
) {
_entries[newState] = OverlayEntryContext(entry, newState, asBarrier);
Overlay.of(context)?.insert(entry);
}
bool contains(PopoverState oldState) {
return _entries.containsKey(oldState);
}
void removeEntry(PopoverState oldState) {
if (_entries.isEmpty) return;
final removedEntry = _entries.remove(oldState);
removedEntry?.overlayEntry.remove();
}
bool get isEmpty => _entries.isEmpty;
bool get isNotEmpty => _entries.isNotEmpty;
bool hasEntry() {
return _entries.isNotEmpty;
}
PopoverState? popEntry() {
if (_entries.isEmpty) return null;
final lastEntry = _entries.values.last;
_entries.remove(lastEntry.popoverState);
lastEntry.overlayEntry.remove();
lastEntry.popoverState.widget.onClose?.call();
if (lastEntry.asBarrier) {
return lastEntry.popoverState;
} else {
return popEntry();
}
}
}
class OverlayEntryContext {
final bool asBarrier;
final PopoverState popoverState;
final OverlayEntry overlayEntry;
OverlayEntryContext(
this.overlayEntry,
this.popoverState,
this.asBarrier,
);
}
class PopoverMask extends StatefulWidget {
final void Function() onTap;
final void Function()? onExit;
final Decoration? decoration;
const PopoverMask(
{Key? key, required this.onTap, this.onExit, this.decoration})
: super(key: key);
@override
State<StatefulWidget> createState() => _PopoverMaskState();
}
class _PopoverMaskState extends State<PopoverMask> {
@override
void initState() {
HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
super.initState();
}
bool _handleGlobalKeyEvent(KeyEvent event) {
if (event.logicalKey == LogicalKeyboardKey.escape) {
if (widget.onExit != null) {
widget.onExit!();
}
return true;
} else {
return false;
}
}
@override
void deactivate() {
HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
super.deactivate();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
child: Container(
decoration: widget.decoration,
),
);
}
}

View File

@ -0,0 +1,51 @@
import 'package:flutter/material.dart';
import 'popover.dart';
/// If multiple popovers are exclusive,
/// pass the same mutex to them.
class PopoverMutex {
final _PopoverStateNotifier _stateNotifier = _PopoverStateNotifier();
PopoverMutex();
void removePopoverListener(VoidCallback listener) {
_stateNotifier.removeListener(listener);
}
VoidCallback listenOnPopoverChanged(VoidCallback callback) {
listenerCallback() {
callback();
}
_stateNotifier.addListener(listenerCallback);
return listenerCallback;
}
void close() => _stateNotifier.state?.close();
PopoverState? get state => _stateNotifier.state;
set state(PopoverState? newState) => _stateNotifier.state = newState;
void removeState() {
_stateNotifier.state = null;
}
void dispose() {
_stateNotifier.dispose();
}
}
class _PopoverStateNotifier extends ChangeNotifier {
PopoverState? _state;
PopoverState? get state => _state;
set state(PopoverState? newState) {
if (_state != null && _state != newState) {
_state?.close();
}
_state = newState;
notifyListeners();
}
}

View File

@ -1,27 +1,22 @@
import 'package:appflowy_popover/layout.dart';
import 'package:flutter/gestures.dart';
import 'dart:async';
import 'package:appflowy_popover/src/layout.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
/// If multiple popovers are exclusive,
/// pass the same mutex to them.
class PopoverMutex {
PopoverState? state;
}
import 'mask.dart';
import 'mutex.dart';
class PopoverController {
PopoverState? state;
PopoverState? _state;
close() {
state?.close();
_state?.close();
}
show() {
state?.showOverlay();
_state?.showOverlay();
}
}
class PopoverTriggerActionFlags {
class PopoverTriggerFlags {
static int click = 0x01;
static int hover = 0x02;
}
@ -72,6 +67,8 @@ class Popover extends StatefulWidget {
final void Function()? onClose;
final bool asBarrier;
/// The content area of the popover.
final Widget child;
@ -81,11 +78,14 @@ class Popover extends StatefulWidget {
required this.popupBuilder,
this.controller,
this.offset,
this.maskDecoration,
this.maskDecoration = const BoxDecoration(
color: Color.fromARGB(0, 244, 67, 54),
),
this.triggerActions = 0,
this.direction = PopoverDirection.rightWithTopAligned,
this.mutex,
this.onClose,
this.asBarrier = false,
}) : super(key: key);
@override
@ -93,172 +93,106 @@ class Popover extends StatefulWidget {
}
class PopoverState extends State<Popover> {
static final RootOverlayEntry _rootEntry = RootOverlayEntry();
final PopoverLink popoverLink = PopoverLink();
OverlayEntry? _overlayEntry;
bool hasMask = true;
static PopoverState? _popoverWithMask;
Timer? _debounceEnterRegionAction;
@override
void initState() {
widget.controller?.state = this;
widget.controller?._state = this;
super.initState();
}
showOverlay() {
void showOverlay() {
close();
if (widget.mutex != null) {
if (widget.mutex!.state != null && widget.mutex!.state != this) {
widget.mutex!.state!.close();
}
widget.mutex!.state = this;
widget.mutex?.state = this;
}
if (_popoverWithMask == null) {
_popoverWithMask = this;
} else {
hasMask = false;
}
final shouldAddMask = _rootEntry.isEmpty;
final newEntry = OverlayEntry(builder: (context) {
final children = <Widget>[];
if (hasMask) {
children.add(_PopoverMask(
decoration: widget.maskDecoration,
onTap: () => close(),
onExit: () => close(),
));
if (shouldAddMask) {
children.add(
PopoverMask(
decoration: widget.maskDecoration,
onTap: () => _removeRootOverlay(),
onExit: () => _removeRootOverlay(),
),
);
}
children.add(PopoverContainer(
direction: widget.direction,
popoverLink: popoverLink,
offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder,
onClose: () => close(),
onCloseAll: () => closeAll(),
));
children.add(
PopoverContainer(
direction: widget.direction,
popoverLink: popoverLink,
offset: widget.offset ?? Offset.zero,
popupBuilder: widget.popupBuilder,
onClose: () => close(),
onCloseAll: () => _removeRootOverlay(),
),
);
return Stack(children: children);
});
_overlayEntry = newEntry;
Overlay.of(context)?.insert(newEntry);
_rootEntry.addEntry(context, this, newEntry, widget.asBarrier);
}
close() {
if (_overlayEntry != null) {
_overlayEntry!.remove();
_overlayEntry = null;
if (_popoverWithMask == this) {
_popoverWithMask = null;
}
if (widget.onClose != null) {
widget.onClose!();
}
void close() {
if (_rootEntry.contains(this)) {
_rootEntry.removeEntry(this);
widget.onClose?.call();
}
}
void _removeRootOverlay() {
_rootEntry.popEntry();
if (widget.mutex?.state == this) {
widget.mutex!.state = null;
widget.mutex?.removeState();
}
}
closeAll() {
_popoverWithMask?.close();
}
@override
void deactivate() {
close();
super.deactivate();
}
_handleTargetPointerDown(PointerDownEvent event) {
if (widget.triggerActions & PopoverTriggerActionFlags.click != 0) {
showOverlay();
}
}
_handleTargetPointerEnter(PointerEnterEvent event) {
if (widget.triggerActions & PopoverTriggerActionFlags.hover != 0) {
showOverlay();
}
}
_buildContent(BuildContext context) {
if (widget.triggerActions == 0) {
return widget.child;
}
return MouseRegion(
onEnter: _handleTargetPointerEnter,
child: Listener(
onPointerDown: _handleTargetPointerDown,
child: widget.child,
),
);
}
@override
Widget build(BuildContext context) {
return PopoverTarget(
link: popoverLink,
child: _buildContent(context),
child: _buildChild(context),
);
}
}
class _PopoverMask extends StatefulWidget {
final void Function() onTap;
final void Function()? onExit;
final Decoration? decoration;
const _PopoverMask(
{Key? key, required this.onTap, this.onExit, this.decoration})
: super(key: key);
@override
State<StatefulWidget> createState() => _PopoverMaskState();
}
class _PopoverMaskState extends State<_PopoverMask> {
@override
void initState() {
HardwareKeyboard.instance.addHandler(_handleGlobalKeyEvent);
super.initState();
}
bool _handleGlobalKeyEvent(KeyEvent event) {
if (event.logicalKey == LogicalKeyboardKey.escape) {
if (widget.onExit != null) {
widget.onExit!();
}
return true;
Widget _buildChild(BuildContext context) {
if (widget.triggerActions == 0) {
return widget.child;
}
return false;
}
@override
void deactivate() {
HardwareKeyboard.instance.removeHandler(_handleGlobalKeyEvent);
super.deactivate();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: widget.onTap,
child: Container(
// decoration: widget.decoration,
decoration: widget.decoration ??
const BoxDecoration(
color: Color.fromARGB(0, 244, 67, 54),
),
return MouseRegion(
onEnter: (event) {
_debounceEnterRegionAction =
Timer(const Duration(milliseconds: 200), () {
if (widget.triggerActions & PopoverTriggerFlags.hover != 0) {
showOverlay();
}
});
},
onExit: (event) {
_debounceEnterRegionAction?.cancel();
_debounceEnterRegionAction = null;
},
child: Listener(
child: widget.child,
onPointerDown: (PointerDownEvent event) {
if (widget.triggerActions & PopoverTriggerFlags.click != 0) {
showOverlay();
}
},
),
);
}

View File

@ -1,8 +1,8 @@
import 'package:flowy_infra_ui/flowy_infra_ui_web.dart';
import 'package:appflowy_popover/popover.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:flutter/material.dart';
class AppFlowyStylePopover extends StatelessWidget {
class AppFlowyPopover extends StatelessWidget {
final Widget child;
final PopoverController? controller;
final Widget Function(BuildContext context) popupBuilder;
@ -12,8 +12,9 @@ class AppFlowyStylePopover extends StatelessWidget {
final void Function()? onClose;
final PopoverMutex? mutex;
final Offset? offset;
final bool asBarrier;
const AppFlowyStylePopover({
const AppFlowyPopover({
Key? key,
required this.child,
required this.popupBuilder,
@ -24,6 +25,7 @@ class AppFlowyStylePopover extends StatelessWidget {
this.triggerActions = 0,
this.offset,
this.controller,
this.asBarrier = false,
}) : super(key: key);
@override
@ -33,6 +35,7 @@ class AppFlowyStylePopover extends StatelessWidget {
onClose: onClose,
direction: direction,
mutex: mutex,
asBarrier: asBarrier,
triggerActions: triggerActions,
popupBuilder: (context) {
final child = popupBuilder(context);

View File

@ -8,6 +8,7 @@ class FlowyText extends StatelessWidget {
final double fontSize;
final FontWeight fontWeight;
final TextAlign? textAlign;
final int? maxLines;
final Color? color;
const FlowyText(
@ -18,21 +19,40 @@ class FlowyText extends StatelessWidget {
this.fontWeight = FontWeight.w400,
this.textAlign,
this.color,
this.maxLines = 1,
}) : super(key: key);
const FlowyText.semibold(this.title,
{Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
: fontWeight = FontWeight.w600,
const FlowyText.semibold(
this.title, {
Key? key,
this.fontSize = 16,
this.overflow,
this.color,
this.textAlign,
this.maxLines = 1,
}) : fontWeight = FontWeight.w600,
super(key: key);
const FlowyText.medium(this.title,
{Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
: fontWeight = FontWeight.w500,
const FlowyText.medium(
this.title, {
Key? key,
this.fontSize = 16,
this.overflow,
this.color,
this.textAlign,
this.maxLines = 1,
}) : fontWeight = FontWeight.w500,
super(key: key);
const FlowyText.regular(this.title,
{Key? key, this.fontSize = 16, this.overflow, this.color, this.textAlign})
: fontWeight = FontWeight.w400,
const FlowyText.regular(
this.title, {
Key? key,
this.fontSize = 16,
this.overflow,
this.color,
this.textAlign,
this.maxLines = 1,
}) : fontWeight = FontWeight.w400,
super(key: key);
@override
@ -40,6 +60,7 @@ class FlowyText extends StatelessWidget {
final theme = context.watch<AppTheme>();
return Text(
title,
maxLines: maxLines,
textAlign: textAlign,
overflow: overflow ?? TextOverflow.clip,
style: TextStyle(

View File

@ -29,8 +29,6 @@ dependencies:
path: flowy_infra_ui_web
appflowy_popover:
path: ../appflowy_popover
# Flowy packages
flowy_infra:
path: ../flowy_infra

View File

@ -35,7 +35,7 @@ packages:
path: "packages/appflowy_editor"
relative: true
source: path
version: "0.0.4"
version: "0.0.5"
appflowy_popover:
dependency: "direct main"
description:
@ -43,6 +43,13 @@ packages:
relative: true
source: path
version: "0.0.1"
archive:
dependency: transitive
description:
name: archive
url: "https://pub.dartlang.org"
source: hosted
version: "3.3.1"
args:
dependency: transitive
description:
@ -660,6 +667,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.17.0"
intl_utils:
dependency: transitive
description:
name: intl_utils
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.0"
io:
dependency: transitive
description:

View File

@ -12,30 +12,32 @@ crate-type = ["staticlib"]
[dependencies]
allo-isolate = {version = "^0.1", features = ["catch-unwind",]}
byteorder = {version = "1.3.4"}
ffi-support = {version = "0.4.2"}
protobuf = {version = "2.20.0"}
allo-isolate = { version = "^0.1", features = ["catch-unwind"] }
byteorder = { version = "1.3.4" }
ffi-support = { version = "0.4.2" }
protobuf = { version = "2.20.0" }
tokio = { version = "1", features = ["rt", "rt-multi-thread"] }
log = "0.4.14"
serde = { version = "1.0", features = ["derive"] }
serde_json = {version = "1.0"}
serde_json = { version = "1.0" }
bytes = { version = "1.0" }
once_cell = "1"
crossbeam-utils = "0.8.7"
lib-dispatch = {path = "../lib-dispatch" }
flowy-sdk = {path = "../flowy-sdk"}
dart-notify = {path = "../dart-notify" }
flowy-derive = {path = "../../../shared-lib/flowy-derive" }
lib-dispatch = { path = "../lib-dispatch" }
flowy-sdk = { path = "../flowy-sdk" }
dart-notify = { path = "../dart-notify" }
flowy-derive = { path = "../../../shared-lib/flowy-derive" }
[features]
default = ["flowy-sdk/dart", "dart-notify/dart", "flutter"]
flutter = []
http_sync = ["flowy-sdk/http_sync", "flowy-sdk/use_bunyan"]
#use_serde = ["bincode"]
#use_protobuf= ["protobuf"]
openssl_vendored = ["flowy-sdk/openssl_vendored"]
[build-dependencies]
lib-infra = { path = "../../../shared-lib/lib-infra", features = ["protobuf_file_gen", "dart"] }
lib-infra = { path = "../../../shared-lib/lib-infra", features = [
"protobuf_file_gen",
"dart",
] }

View File

@ -1,14 +1,17 @@
[package]
name = "flowy-database"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
diesel = {version = "1.4.8", features = ["sqlite"]}
diesel_derives = {version = "1.4.1", features = ["sqlite"]}
diesel_migrations = {version = "1.4.0", features = ["sqlite"]}
lib-sqlite = { path = "../lib-sqlite" }
log = "0.4"
lazy_static = "1.4.0"
[package]
name = "flowy-database"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
diesel = { version = "1.4.8", features = ["sqlite"] }
diesel_derives = { version = "1.4.1", features = ["sqlite"] }
diesel_migrations = { version = "1.4.0", features = ["sqlite"] }
lib-sqlite = { path = "../lib-sqlite" }
log = "0.4"
lazy_static = "1.4.0"
[features]
openssl_vendored = ["lib-sqlite/openssl_vendored"]

View File

@ -1,44 +1,52 @@
[package]
name = "flowy-sdk"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lib-dispatch = { path = "../lib-dispatch" }
lib-log = { path = "../lib-log" }
flowy-user = { path = "../flowy-user" }
flowy-net = { path = "../flowy-net" }
flowy-folder = { path = "../flowy-folder", default-features = false }
flowy-grid = { path = "../flowy-grid", default-features = false }
flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" }
flowy-database = { path = "../flowy-database" }
flowy-text-block = { path = "../flowy-text-block", default-features = false }
flowy-revision = { path = "../flowy-revision" }
tracing = { version = "0.1" }
log = "0.4.14"
futures-core = { version = "0.3", default-features = false }
color-eyre = { version = "0.5", default-features = false }
bytes = "1.0"
tokio = { version = "1", features = ["rt"] }
parking_lot = "0.11"
flowy-sync = { path = "../../../shared-lib/flowy-sync" }
lib-ws = { path = "../../../shared-lib/lib-ws" }
lib-infra = { path = "../../../shared-lib/lib-infra" }
[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
bincode = { version = "1.3"}
protobuf = {version = "2.24.1"}
claim = "0.5.0"
tokio = { version = "1", features = ["full"]}
futures-util = "0.3.15"
[features]
http_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
native_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
use_bunyan = ["lib-log/use_bunyan"]
dart = ["flowy-user/dart", "flowy-net/dart", "flowy-folder/dart", "flowy-sync/dart", "flowy-grid/dart", "flowy-text-block/dart"]
[package]
name = "flowy-sdk"
version = "0.1.0"
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
lib-dispatch = { path = "../lib-dispatch" }
lib-log = { path = "../lib-log" }
flowy-user = { path = "../flowy-user" }
flowy-net = { path = "../flowy-net" }
flowy-folder = { path = "../flowy-folder", default-features = false }
flowy-grid = { path = "../flowy-grid", default-features = false }
flowy-grid-data-model = { path = "../../../shared-lib/flowy-grid-data-model" }
flowy-database = { path = "../flowy-database" }
flowy-text-block = { path = "../flowy-text-block", default-features = false }
flowy-revision = { path = "../flowy-revision" }
tracing = { version = "0.1" }
log = "0.4.14"
futures-core = { version = "0.3", default-features = false }
color-eyre = { version = "0.5", default-features = false }
bytes = "1.0"
tokio = { version = "1", features = ["rt"] }
parking_lot = "0.11"
flowy-sync = { path = "../../../shared-lib/flowy-sync" }
lib-ws = { path = "../../../shared-lib/lib-ws" }
lib-infra = { path = "../../../shared-lib/lib-infra" }
[dev-dependencies]
serde = { version = "1.0", features = ["derive"] }
bincode = { version = "1.3" }
protobuf = { version = "2.24.1" }
claim = "0.5.0"
tokio = { version = "1", features = ["full"] }
futures-util = "0.3.15"
[features]
http_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
native_sync = ["flowy-folder/cloud_sync", "flowy-text-block/cloud_sync"]
use_bunyan = ["lib-log/use_bunyan"]
dart = [
"flowy-user/dart",
"flowy-net/dart",
"flowy-folder/dart",
"flowy-sync/dart",
"flowy-grid/dart",
"flowy-text-block/dart",
]
openssl_vendored = ["flowy-database/openssl_vendored"]

View File

@ -24,6 +24,7 @@ use tokio::sync::{mpsc, oneshot};
pub struct TextBlockEditor {
pub doc_id: String,
#[allow(dead_code)]
rev_manager: Arc<RevisionManager>,
#[cfg(feature = "sync")]
ws_manager: Arc<flowy_revision::RevisionWebSocketManager>,

View File

@ -7,14 +7,15 @@ edition = "2018"
[dependencies]
r2d2 = "0.8.9"
libsqlite3-sys = {version = ">=0.8.0, <0.24.0", features = ["bundled"]}
diesel = {version = "1.4.8", features = ["sqlite"]}
diesel_derives = {version = "1.4.1", features = ["sqlite"]}
diesel_migrations = {version = "1.4.0", features = ["sqlite"]}
libsqlite3-sys = { version = ">=0.8.0, <0.24.0", features = ["bundled"] }
diesel = { version = "1.4.8", features = ["sqlite"] }
diesel_derives = { version = "1.4.1", features = ["sqlite"] }
diesel_migrations = { version = "1.4.0", features = ["sqlite"] }
lazy_static = "1.4.0"
scheduled-thread-pool = "0.2.5"
error-chain = "=0.12.0"
log = "0.4.11"
openssl = { version = "0.10.38", features = ["vendored"] }
#[features]
#windows = ["libsqlite3-sys/bundled-windows"]
openssl = { version = "0.10.38", optional = true }
[features]
openssl_vendored = ["openssl/vendored"]