Merge branch 'main' into main
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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"
|
||||
|
@ -191,8 +191,8 @@
|
||||
"aquaColor": "水色",
|
||||
"blueColor": "青",
|
||||
"deleteTag": "選択候補を削除",
|
||||
"colorPannelTitle": "色",
|
||||
"pannelTitle": "選択候補を検索 または 作成する",
|
||||
"colorPanelTitle": "色",
|
||||
"panelTitle": "選択候補を検索 または 作成する",
|
||||
"searchOption": "選択候補を検索"
|
||||
}
|
||||
},
|
||||
|
@ -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": "Создать"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -206,8 +206,8 @@
|
||||
"aquaColor": "水蓝色",
|
||||
"blueColor": "蓝色",
|
||||
"deleteTag": "删除标签",
|
||||
"colorPannelTitle": "颜色",
|
||||
"pannelTitle": "选择或新建一个标签",
|
||||
"colorPanelTitle": "颜色",
|
||||
"panelTitle": "选择或新建一个标签",
|
||||
"searchOption": "搜索标签"
|
||||
},
|
||||
"menuName": "网格"
|
||||
|
@ -202,8 +202,8 @@
|
||||
"aquaColor": "水藍色",
|
||||
"blueColor": "藍色",
|
||||
"deleteTag": "刪除標籤",
|
||||
"colorPannelTitle": "顏色",
|
||||
"pannelTitle": "搜尋或建立選項",
|
||||
"colorPanelTitle": "顏色",
|
||||
"panelTitle": "搜尋或建立選項",
|
||||
"searchOption": "搜尋選項"
|
||||
},
|
||||
"menuName": "網格"
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
@ -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(
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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.
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
@ -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: () {
|
||||
|
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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(),
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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';
|
||||
|
@ -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 {
|
||||
|
@ -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),
|
||||
);
|
||||
|
@ -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,
|
||||
),
|
||||
|
@ -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';
|
||||
|
||||
|
@ -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"),
|
||||
);
|
||||
|
@ -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),
|
||||
),
|
||||
|
@ -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) {
|
||||
|
@ -99,7 +99,6 @@ class _GridGroupCell extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
onSelected();
|
||||
// FlowyOverlay.of(context).remove(GridGroupList.identifier());
|
||||
},
|
||||
),
|
||||
);
|
||||
|
@ -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,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
@ -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);
|
||||
});
|
||||
},
|
||||
|
@ -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,
|
||||
|
@ -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),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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)
|
||||
|
||||
|
@ -27,7 +27,7 @@ Widget build(BuildContext context) {
|
||||
|
||||
At this point, nothing magic will happen after typing `_xxx_`.
|
||||
|
||||

|
||||

|
||||
|
||||
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) {
|
||||
}
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
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(
|
||||
);
|
||||
```
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||
|
Before Width: | Height: | Size: 998 KiB |
After Width: | Height: | Size: 772 KiB |
After Width: | Height: | Size: 1.7 MiB |
After Width: | Height: | Size: 367 KiB |
After Width: | Height: | Size: 358 KiB |
Before Width: | Height: | Size: 4.2 MiB |
Before Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 84 KiB |
@ -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();
|
||||
});
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
@ -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;
|
||||
};
|
@ -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>
|
||||
|
@ -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';
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -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: () {}),
|
||||
|
@ -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";
|
||||
|
||||
|
@ -0,0 +1,5 @@
|
||||
/// AppFlowyBoard library
|
||||
library appflowy_popover;
|
||||
|
||||
export 'src/mutex.dart';
|
||||
export 'src/popover.dart';
|
@ -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
|
116
frontend/app_flowy/packages/appflowy_popover/lib/src/mask.dart
Normal 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,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
@ -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);
|
||||
|
@ -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(
|
||||
|
@ -29,8 +29,6 @@ dependencies:
|
||||
path: flowy_infra_ui_web
|
||||
appflowy_popover:
|
||||
path: ../appflowy_popover
|
||||
|
||||
# Flowy packages
|
||||
flowy_infra:
|
||||
path: ../flowy_infra
|
||||
|
||||
|
@ -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:
|
||||
|
@ -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",
|
||||
] }
|
||||
|
@ -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"]
|
||||
|
@ -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"]
|
||||
|
@ -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>,
|
||||
|
@ -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"]
|
||||
|