mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge remote-tracking branch 'upstream/main' into export_notify
This commit is contained in:
commit
86150ec6a1
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -18,7 +18,7 @@ jobs:
|
|||||||
- os: ubuntu-latest
|
- os: ubuntu-latest
|
||||||
flutter_profile: development-linux-x86
|
flutter_profile: development-linux-x86
|
||||||
- os: macos-latest
|
- os: macos-latest
|
||||||
flutter_profile: development-mac
|
flutter_profile: development-mac-x86_64
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -113,7 +113,7 @@ jobs:
|
|||||||
working-directory: frontend
|
working-directory: frontend
|
||||||
run: |
|
run: |
|
||||||
flutter config --enable-macos-desktop
|
flutter config --enable-macos-desktop
|
||||||
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86 appflowy
|
cargo make --env APP_VERSION=${{ github.ref_name }} --profile production-mac-x86_64 appflowy
|
||||||
|
|
||||||
- name: Archive macOS app
|
- name: Archive macOS app
|
||||||
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
working-directory: ${{ env.MACOS_APP_RELEASE_PATH }}
|
||||||
|
35
CHANGELOG.md
35
CHANGELOG.md
@ -1,5 +1,40 @@
|
|||||||
# Release Notes
|
# Release Notes
|
||||||
|
|
||||||
|
## Version 0.0.4 - 2022-06-06
|
||||||
|
- Drag to adjust the width of a column
|
||||||
|
- Upgrade to Flutter 3.0
|
||||||
|
- Native support for M1 chip
|
||||||
|
- Date supports time formats
|
||||||
|
- New property: URL
|
||||||
|
- Keyboard shortcuts support for Grid: press Enter to leave the edit mode; control c/v to copy-paste cell values
|
||||||
|
|
||||||
|
### Bug Fixes
|
||||||
|
- Fixed some bugs
|
||||||
|
|
||||||
|
|
||||||
|
## Version 0.0.4 - beta.3 - 2022-05-02
|
||||||
|
- Drag to reorder app/ view/ field
|
||||||
|
- Row record open as a page
|
||||||
|
- Auto resize the height of the row in the grid
|
||||||
|
- Support more number formats
|
||||||
|
- Search column options, supporting Single select, Multi-select, and number format
|
||||||
|
|
||||||
|
![May-03-2022 10-03-00](https://user-images.githubusercontent.com/86001920/166394640-a8f1f3bc-5f20-4033-93e9-16bc308d7005.gif)
|
||||||
|
|
||||||
|
|
||||||
|
### Bug Fixes & Improvements
|
||||||
|
- Improved row/cell data cache
|
||||||
|
- Fixed some bugs
|
||||||
|
|
||||||
|
|
||||||
|
## Version 0.0.4 - beta.2 - 2022-04-11
|
||||||
|
|
||||||
|
- Support properties: Text, Number, Date, Checkbox, Select, Multi-select
|
||||||
|
- Insert / delete rows
|
||||||
|
- Add / delete / hide columns
|
||||||
|
- Edit property
|
||||||
|
![](https://user-images.githubusercontent.com/12026239/162753644-bf2f4e7a-2367-4d48-87e6-35e244e83a5b.png)
|
||||||
|
|
||||||
## Version 0.0.4 - beta.1 - 2022-04-08
|
## Version 0.0.4 - beta.1 - 2022-04-08
|
||||||
v0.0.4 - beta.1 is pre-release
|
v0.0.4 - beta.1 is pre-release
|
||||||
|
|
||||||
|
@ -45,7 +45,15 @@ APP_ENVIRONMENT = "local"
|
|||||||
FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
|
FLUTTER_FLOWY_SDK_PATH="app_flowy/packages/flowy_sdk"
|
||||||
PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
PROTOBUF_DERIVE_CACHE="../shared-lib/flowy-derive/src/derive_cache/derive_cache.rs"
|
||||||
|
|
||||||
[env.development-mac]
|
[env.development-mac-arm64]
|
||||||
|
RUST_LOG = "info"
|
||||||
|
TARGET_OS = "macos"
|
||||||
|
RUST_COMPILE_TARGET = "aarch64-apple-darwin"
|
||||||
|
BUILD_FLAG = "debug"
|
||||||
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
|
PRODUCT_EXT = "app"
|
||||||
|
|
||||||
|
[env.development-mac-x86_64]
|
||||||
RUST_LOG = "info"
|
RUST_LOG = "info"
|
||||||
TARGET_OS = "macos"
|
TARGET_OS = "macos"
|
||||||
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||||
@ -53,21 +61,23 @@ BUILD_FLAG = "debug"
|
|||||||
FLUTTER_OUTPUT_DIR = "Debug"
|
FLUTTER_OUTPUT_DIR = "Debug"
|
||||||
PRODUCT_EXT = "app"
|
PRODUCT_EXT = "app"
|
||||||
|
|
||||||
[env.production-mac-aarch64]
|
[env.production-mac-arm64]
|
||||||
BUILD_FLAG = "release"
|
BUILD_FLAG = "release"
|
||||||
TARGET_OS = "macos"
|
TARGET_OS = "macos"
|
||||||
RUST_COMPILE_TARGET = "aarch64-apple-darwin"
|
RUST_COMPILE_TARGET = "aarch64-apple-darwin"
|
||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
PRODUCT_EXT = "app"
|
PRODUCT_EXT = "app"
|
||||||
APP_ENVIRONMENT = "production"
|
APP_ENVIRONMENT = "production"
|
||||||
|
BUILD_ARCHS = "arm64"
|
||||||
|
|
||||||
[env.production-mac-x86]
|
[env.production-mac-x86_64]
|
||||||
BUILD_FLAG = "release"
|
BUILD_FLAG = "release"
|
||||||
TARGET_OS = "macos"
|
TARGET_OS = "macos"
|
||||||
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
RUST_COMPILE_TARGET = "x86_64-apple-darwin"
|
||||||
FLUTTER_OUTPUT_DIR = "Release"
|
FLUTTER_OUTPUT_DIR = "Release"
|
||||||
PRODUCT_EXT = "app"
|
PRODUCT_EXT = "app"
|
||||||
APP_ENVIRONMENT = "production"
|
APP_ENVIRONMENT = "production"
|
||||||
|
BUILD_ARCHS = "x86_64"
|
||||||
|
|
||||||
[env.development-windows-x86]
|
[env.development-windows-x86]
|
||||||
TARGET_OS = "windows"
|
TARGET_OS = "windows"
|
||||||
@ -138,6 +148,7 @@ script = [
|
|||||||
echo PRODUCT_EXT: ${PRODUCT_EXT}
|
echo PRODUCT_EXT: ${PRODUCT_EXT}
|
||||||
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
|
echo APP_ENVIRONMENT: ${APP_ENVIRONMENT}
|
||||||
echo ${platforms}
|
echo ${platforms}
|
||||||
|
echo ${BUILD_ARCHS}
|
||||||
'''
|
'''
|
||||||
]
|
]
|
||||||
script_runner = "@shell"
|
script_runner = "@shell"
|
||||||
|
@ -186,7 +186,8 @@
|
|||||||
"row": {
|
"row": {
|
||||||
"duplicate": "Duplicate",
|
"duplicate": "Duplicate",
|
||||||
"delete": "Delete",
|
"delete": "Delete",
|
||||||
"textPlaceholder": "Empty"
|
"textPlaceholder": "Empty",
|
||||||
|
"copyProperty": "Copied property to clipboard"
|
||||||
},
|
},
|
||||||
"selectOption": {
|
"selectOption": {
|
||||||
"create": "Create",
|
"create": "Create",
|
||||||
@ -203,6 +204,10 @@
|
|||||||
"colorPannelTitle": "Colors",
|
"colorPannelTitle": "Colors",
|
||||||
"pannelTitle": "Select an option or create one",
|
"pannelTitle": "Select an option or create one",
|
||||||
"searchOption": "Search for an option"
|
"searchOption": "Search for an option"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,11 @@
|
|||||||
"letsGoButtonText": "Vamos lá",
|
"letsGoButtonText": "Vamos lá",
|
||||||
"title": "Título",
|
"title": "Título",
|
||||||
"signUp": {
|
"signUp": {
|
||||||
"buttonText": "Inscreve-se",
|
"buttonText": "Se inscreva",
|
||||||
"title": "Inscrever-se @:appName",
|
"title": "Se inscreva no @:appName",
|
||||||
"getStartedText": "Começar",
|
"getStartedText": "Começar",
|
||||||
"emptyPasswordError": "Senha não pode ser em branco.",
|
"emptyPasswordError": "Senha não pode estar em branco.",
|
||||||
"repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.",
|
"repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
|
||||||
"unmatchedPasswordError": "As senhas não conferem.",
|
"unmatchedPasswordError": "As senhas não conferem.",
|
||||||
"alreadyHaveAnAccount": "Já possui uma conta?",
|
"alreadyHaveAnAccount": "Já possui uma conta?",
|
||||||
"emailHint": "Email",
|
"emailHint": "Email",
|
||||||
@ -19,14 +19,14 @@
|
|||||||
"repeatPasswordHint": "Confirme a senha"
|
"repeatPasswordHint": "Confirme a senha"
|
||||||
},
|
},
|
||||||
"signIn": {
|
"signIn": {
|
||||||
"loginTitle": "Login to @:appName",
|
"loginTitle": "Entre no @:appName",
|
||||||
"loginButtonText": "Login",
|
"loginButtonText": "Login",
|
||||||
"buttonText": "Entre",
|
"buttonText": "Entre",
|
||||||
"forgotPassword": "Esqueceu a senha?",
|
"forgotPassword": "Esqueceu a senha?",
|
||||||
"emailHint": "Email",
|
"emailHint": "Email",
|
||||||
"passwordHint": "Senha",
|
"passwordHint": "Senha",
|
||||||
"dontHaveAnAccount": "Não possui uma conta?",
|
"dontHaveAnAccount": "Não possui uma conta?",
|
||||||
"repeatPasswordEmptyError": "Confirmar a senha não pode ser em branco.",
|
"repeatPasswordEmptyError": "Confirmar a senha não pode estar em branco.",
|
||||||
"unmatchedPasswordError": "As senhas não conferem."
|
"unmatchedPasswordError": "As senhas não conferem."
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
@ -67,7 +67,7 @@
|
|||||||
"whatsNew": "O que há de novo?",
|
"whatsNew": "O que há de novo?",
|
||||||
"help": "Ajuda & Suporte",
|
"help": "Ajuda & Suporte",
|
||||||
"debug": {
|
"debug": {
|
||||||
"name": "Informação de debug",
|
"name": "Informação de depuração",
|
||||||
"success": "Copiar informação de debug para o clipboard!",
|
"success": "Copiar informação de debug para o clipboard!",
|
||||||
"fail": "Falha em copiar a informação de debug para o clipboard"
|
"fail": "Falha em copiar a informação de debug para o clipboard"
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@
|
|||||||
},
|
},
|
||||||
"button": {
|
"button": {
|
||||||
"OK": "OK",
|
"OK": "OK",
|
||||||
"Cancel": "Canelar",
|
"Cancel": "Cancelar",
|
||||||
"signIn": "Entrar",
|
"signIn": "Entrar",
|
||||||
"signOut": "Sair",
|
"signOut": "Sair",
|
||||||
"complete": "Completar",
|
"complete": "Completar",
|
||||||
@ -143,4 +143,5 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
146
frontend/app_flowy/assets/translations/pt-PT.json
Normal file
146
frontend/app_flowy/assets/translations/pt-PT.json
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
{
|
||||||
|
"appName": "AppFlowy",
|
||||||
|
"defaultUsername": "Me",
|
||||||
|
"welcomeText": "Bem vindo ao @:appName",
|
||||||
|
"githubStarText": "Star on GitHub",
|
||||||
|
"subscribeNewsletterText": "Inscreve-te ao Newsletter",
|
||||||
|
"letsGoButtonText": "Bora",
|
||||||
|
"title": "Título",
|
||||||
|
"signUp": {
|
||||||
|
"buttonText": "Inscreve-te",
|
||||||
|
"title": "Inscreve-te ao @:appName",
|
||||||
|
"getStartedText": "Começar",
|
||||||
|
"emptyPasswordError": "A palavra-passe não pode estar em branco.",
|
||||||
|
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
|
||||||
|
"unmatchedPasswordError": "As palavras-passes não coincidem.",
|
||||||
|
"alreadyHaveAnAccount": "Já possuis uma conta?",
|
||||||
|
"emailHint": "Email",
|
||||||
|
"passwordHint": "Password",
|
||||||
|
"repeatPasswordHint": "Confirma a tua password"
|
||||||
|
},
|
||||||
|
"signIn": {
|
||||||
|
"loginTitle": "Entre no @:appName",
|
||||||
|
"loginButtonText": "Login",
|
||||||
|
"buttonText": "Entre",
|
||||||
|
"forgotPassword": "Esqueceste-te da tua palavra-passe?",
|
||||||
|
"emailHint": "Email",
|
||||||
|
"passwordHint": "Palavra-passe",
|
||||||
|
"dontHaveAnAccount": "Não possuis uma conta?",
|
||||||
|
"repeatPasswordEmptyError": "Confirmar a palavra-passe não pode estar em branco.",
|
||||||
|
"unmatchedPasswordError": "As palavras-passes não conferem."
|
||||||
|
},
|
||||||
|
"workspace": {
|
||||||
|
"create": "Cria um ambiente de trabalho",
|
||||||
|
"hint": "ambiente de trabalho",
|
||||||
|
"notFoundError": "Ambiente de trabalho não encontrada"
|
||||||
|
},
|
||||||
|
"shareAction": {
|
||||||
|
"buttonText": "Partilhar",
|
||||||
|
"workInProgress": "Em breve",
|
||||||
|
"markdown": "Markdown",
|
||||||
|
"copyLink": "Copiar o link"
|
||||||
|
},
|
||||||
|
"disclosureAction": {
|
||||||
|
"rename": "Renomear",
|
||||||
|
"delete": "Apagar",
|
||||||
|
"duplicate": "Duplicar"
|
||||||
|
},
|
||||||
|
"blankPageTitle": "Página em branco",
|
||||||
|
"newPageText": "Nova página",
|
||||||
|
"trash": {
|
||||||
|
"text": "Lixo",
|
||||||
|
"restoreAll": "Restaurar todos",
|
||||||
|
"deleteAll": "Apagar todos",
|
||||||
|
"pageHeader": {
|
||||||
|
"fileName": "Nome do ficheiro",
|
||||||
|
"lastModified": "Última modificação",
|
||||||
|
"created": "Criado"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"deletePagePrompt": {
|
||||||
|
"text": "Esta página está no lixo",
|
||||||
|
"restore": "Restaurar a página",
|
||||||
|
"deletePermanent": "Apagar permanentemente"
|
||||||
|
},
|
||||||
|
"dialogCreatePageNameHint": "Nome da página",
|
||||||
|
"questionBubble": {
|
||||||
|
"whatsNew": "O que há de novo?",
|
||||||
|
"help": "Ajuda & Suporte",
|
||||||
|
"debug": {
|
||||||
|
"name": "Informação de depuração",
|
||||||
|
"success": "Copiar informação de depuração para o clipboard!",
|
||||||
|
"fail": "Falha em copiar a informação de depuração para o clipboard"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"menuAppHeader": {
|
||||||
|
"addPageTooltip": "Adiciona uma nova página.",
|
||||||
|
"defaultNewPageName": "Sem título",
|
||||||
|
"renameDialog": "Renomear"
|
||||||
|
},
|
||||||
|
"toolbar": {
|
||||||
|
"undo": "Desfazer",
|
||||||
|
"redo": "Refazer",
|
||||||
|
"bold": "Negrito",
|
||||||
|
"italic": "Itálico",
|
||||||
|
"underline": "Sublinhado",
|
||||||
|
"strike": "Riscado",
|
||||||
|
"numList": "Lista numerada",
|
||||||
|
"bulletList": "Lista com marcadores",
|
||||||
|
"checkList": "Lista de verificação",
|
||||||
|
"inlineCode": "Embutir código",
|
||||||
|
"quote": "Citação em bloco",
|
||||||
|
"header": "Cabeçalho",
|
||||||
|
"highlight": "Realçar"
|
||||||
|
},
|
||||||
|
"tooltip": {
|
||||||
|
"lightMode": "Mudar para o modo Claro.",
|
||||||
|
"darkMode": "Mudar para o modo Escuro."
|
||||||
|
},
|
||||||
|
"contactsPage": {
|
||||||
|
"title": "Conctatos",
|
||||||
|
"whatsHappening": "O que está a acontecer nesta semana?",
|
||||||
|
"addContact": "Adicionar um conctato",
|
||||||
|
"editContact": "Editar um conctato"
|
||||||
|
},
|
||||||
|
"button": {
|
||||||
|
"OK": "OK",
|
||||||
|
"Cancel": "Cancelar",
|
||||||
|
"signIn": "Entrar",
|
||||||
|
"signOut": "Sair",
|
||||||
|
"complete": "Completar",
|
||||||
|
"save": "Guardar"
|
||||||
|
},
|
||||||
|
"label": {
|
||||||
|
"welcome": "Bem vindo!",
|
||||||
|
"firstName": "Nome",
|
||||||
|
"middleName": "Nome do Meio",
|
||||||
|
"lastName": "Apelido",
|
||||||
|
"stepX": "Passo {X}"
|
||||||
|
},
|
||||||
|
"oAuth": {
|
||||||
|
"err": {
|
||||||
|
"failedTitle": "Erro ao conectar à sua conta.",
|
||||||
|
"failedMsg": "Verifica se concluiste o processo de login no teu navegador."
|
||||||
|
},
|
||||||
|
"google": {
|
||||||
|
"title": "GOOGLE SIGN-IN",
|
||||||
|
"instruction1": "Para importar os teus Conctatos do Google, tens de autorizar esta aplicação usando o teu navegador web.",
|
||||||
|
"instruction2": "Copia este código para a tua área de transferências clicando no ícone ou selecionando o texto:",
|
||||||
|
"instruction3": "Navega até o link a seguir no seu navegador e digite o código acima:",
|
||||||
|
"instruction4": "Clica no botão abaixo ao concluir a inscrição:"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"settings": {
|
||||||
|
"title": "Definições",
|
||||||
|
"menu": {
|
||||||
|
"appearance": "Aparência",
|
||||||
|
"language": "Idioma",
|
||||||
|
"open": "Abrir as Definições"
|
||||||
|
},
|
||||||
|
"appearance": {
|
||||||
|
"lightLabel": "Modo Claro",
|
||||||
|
"darkLabel": "Modo Escuro"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -141,6 +141,68 @@
|
|||||||
"lightLabel": "Светлая тема",
|
"lightLabel": "Светлая тема",
|
||||||
"darkLabel": "Тёмная тема"
|
"darkLabel": "Тёмная тема"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"grid": {
|
||||||
|
"settings": {
|
||||||
|
"filter": "Фильтр",
|
||||||
|
"sortBy": "Сортировать",
|
||||||
|
"Properties": "Свойства"
|
||||||
|
},
|
||||||
|
"field": {
|
||||||
|
"hide": "Скрыть",
|
||||||
|
"insertLeft": "Вставить слева",
|
||||||
|
"insertRight": "Вставить справа",
|
||||||
|
"duplicate": "Дублировать",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"textFieldName": "Текст",
|
||||||
|
"checkboxFieldName": "Checkbox",
|
||||||
|
"dateFieldName": "Дата",
|
||||||
|
"numberFieldName": "Число",
|
||||||
|
"singleSelectFieldName": "Выбор",
|
||||||
|
"multiSelectFieldName": "Выбор многих",
|
||||||
|
"urlFieldName": "URL",
|
||||||
|
"numberFormat": " Формат числа",
|
||||||
|
"dateFormat": " Формат даты",
|
||||||
|
"includeTime": " Время",
|
||||||
|
"dateFormatFriendly": "День Месяц, Год",
|
||||||
|
"dateFormatISO": "Год-Месяц-День",
|
||||||
|
"dateFormatLocal": "Год/Месяц/День",
|
||||||
|
"dateFormatUS": "Год/Месяц/День",
|
||||||
|
"timeFormat": " Форматировать время",
|
||||||
|
"invalidTimeFormat": "Неверный формат",
|
||||||
|
"timeFormatTwelveHour": "12 часов",
|
||||||
|
"timeFormatTwentyFourHour": "24 часа",
|
||||||
|
"addSelectOption": "Добавить вариант",
|
||||||
|
"optionTitle": "Варианты",
|
||||||
|
"addOption": "Добавить",
|
||||||
|
"editProperty": "Редактировать свойство"
|
||||||
|
},
|
||||||
|
"row": {
|
||||||
|
"duplicate": "Дублировать",
|
||||||
|
"delete": "Удалить",
|
||||||
|
"textPlaceholder": "Пусто",
|
||||||
|
"copyProperty": "Свойство скопировано"
|
||||||
|
},
|
||||||
|
"selectOption": {
|
||||||
|
"create": "Создать",
|
||||||
|
"purpleColor": "Фиолетовый",
|
||||||
|
"pinkColor": "Розовый",
|
||||||
|
"lightPinkColor": "Светло-розовый",
|
||||||
|
"orangeColor": "Оранжевый",
|
||||||
|
"yellowColor": "Желтый",
|
||||||
|
"limeColor": "Ярко-зелёный",
|
||||||
|
"greenColor": "Зелёный",
|
||||||
|
"aquaColor": "Морской волны",
|
||||||
|
"blueColor": "Синий",
|
||||||
|
"deleteTag": "Удалить вариант",
|
||||||
|
"colorPannelTitle": "Цвета",
|
||||||
|
"pannelTitle": "Выберите или создайте вариант",
|
||||||
|
"searchOption": "Поиск"
|
||||||
|
},
|
||||||
|
"date": {
|
||||||
|
"timeHintTextInTwelveHour": "12:00 AM",
|
||||||
|
"timeHintTextInTwentyFourHour": "12:00"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -16,6 +16,7 @@ import 'package:app_flowy/workspace/presentation/home/menu/menu.dart';
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/app.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-user-data-model/user_profile.pb.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
import 'package:get_it/get_it.dart';
|
import 'package:get_it/get_it.dart';
|
||||||
|
|
||||||
class DependencyResolver {
|
class DependencyResolver {
|
||||||
@ -46,6 +47,8 @@ void _resolveUserDeps(GetIt getIt) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _resolveHomeDeps(GetIt getIt) {
|
void _resolveHomeDeps(GetIt getIt) {
|
||||||
|
getIt.registerSingleton(FToast());
|
||||||
|
|
||||||
getIt.registerSingleton(MenuSharedState());
|
getIt.registerSingleton(MenuSharedState());
|
||||||
|
|
||||||
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
getIt.registerFactoryParam<UserListener, UserProfile, void>(
|
||||||
|
@ -67,40 +67,42 @@ class ApplicationWidget extends StatelessWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => ChangeNotifierProvider.value(
|
Widget build(BuildContext context) {
|
||||||
value: settingModel,
|
return ChangeNotifierProvider.value(
|
||||||
builder: (context, _) {
|
value: settingModel,
|
||||||
const ratio = 1.73;
|
builder: (context, _) {
|
||||||
const minWidth = 600.0;
|
const ratio = 1.73;
|
||||||
setWindowMinSize(const Size(minWidth, minWidth / ratio));
|
const minWidth = 600.0;
|
||||||
settingModel.readLocaleWhenAppLaunch(context);
|
setWindowMinSize(const Size(minWidth, minWidth / ratio));
|
||||||
AppTheme theme = context.select<AppearanceSettingModel, AppTheme>(
|
settingModel.readLocaleWhenAppLaunch(context);
|
||||||
(value) => value.theme,
|
AppTheme theme = context.select<AppearanceSettingModel, AppTheme>(
|
||||||
);
|
(value) => value.theme,
|
||||||
Locale locale = context.select<AppearanceSettingModel, Locale>(
|
);
|
||||||
(value) => value.locale,
|
Locale locale = context.select<AppearanceSettingModel, Locale>(
|
||||||
);
|
(value) => value.locale,
|
||||||
|
);
|
||||||
|
|
||||||
return MultiProvider(
|
return MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
Provider.value(value: theme),
|
Provider.value(value: theme),
|
||||||
Provider.value(value: locale),
|
Provider.value(value: locale),
|
||||||
],
|
],
|
||||||
builder: (context, _) {
|
builder: (context, _) {
|
||||||
return MaterialApp(
|
return MaterialApp(
|
||||||
builder: overlayManagerBuilder(),
|
builder: overlayManagerBuilder(),
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
theme: theme.themeData,
|
theme: theme.themeData,
|
||||||
localizationsDelegates: context.localizationDelegates,
|
localizationsDelegates: context.localizationDelegates,
|
||||||
supportedLocales: context.supportedLocales,
|
supportedLocales: context.supportedLocales,
|
||||||
locale: locale,
|
locale: locale,
|
||||||
navigatorKey: AppGlobals.rootNavKey,
|
navigatorKey: AppGlobals.rootNavKey,
|
||||||
home: child,
|
home: child,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AppGlobals {
|
class AppGlobals {
|
||||||
|
@ -12,14 +12,14 @@ class DocumentService {
|
|||||||
await FolderEventSetLatestView(ViewId(value: docId)).send();
|
await FolderEventSetLatestView(ViewId(value: docId)).send();
|
||||||
|
|
||||||
final payload = TextBlockId(value: docId);
|
final payload = TextBlockId(value: docId);
|
||||||
return BlockEventGetBlockData(payload).send();
|
return TextBlockEventGetBlockData(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<TextBlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
Future<Either<TextBlockDelta, FlowyError>> composeDelta({required String docId, required String data}) {
|
||||||
final payload = TextBlockDelta.create()
|
final payload = TextBlockDelta.create()
|
||||||
..blockId = docId
|
..blockId = docId
|
||||||
..deltaStr = data;
|
..deltaStr = data;
|
||||||
return BlockEventApplyDelta(payload).send();
|
return TextBlockEventApplyDelta(payload).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
Future<Either<Unit, FlowyError>> closeDocument({required String docId}) {
|
||||||
|
@ -10,7 +10,7 @@ class ShareService {
|
|||||||
..viewId = docId
|
..viewId = docId
|
||||||
..exportType = type;
|
..exportType = type;
|
||||||
|
|
||||||
return BlockEventExportDocument(request).send();
|
return TextBlockEventExportDocument(request).send();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Either<ExportData, FlowyError>> exportText(String docId) {
|
Future<Either<ExportData, FlowyError>> exportText(String docId) {
|
||||||
|
@ -2,7 +2,7 @@ part of 'cell_service.dart';
|
|||||||
|
|
||||||
typedef GridCellContext = _GridCellContext<String, String>;
|
typedef GridCellContext = _GridCellContext<String, String>;
|
||||||
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
|
typedef GridSelectOptionCellContext = _GridCellContext<SelectOptionCellData, String>;
|
||||||
typedef GridDateCellContext = _GridCellContext<DateCellData, DateCalData>;
|
typedef GridDateCellContext = _GridCellContext<DateCellData, CalendarData>;
|
||||||
typedef GridURLCellContext = _GridCellContext<URLCellData, String>;
|
typedef GridURLCellContext = _GridCellContext<URLCellData, String>;
|
||||||
|
|
||||||
class GridCellContextBuilder {
|
class GridCellContextBuilder {
|
||||||
@ -31,6 +31,7 @@ class GridCellContextBuilder {
|
|||||||
final cellDataLoader = GridCellDataLoader(
|
final cellDataLoader = GridCellDataLoader(
|
||||||
gridCell: _gridCell,
|
gridCell: _gridCell,
|
||||||
parser: DateCellDataParser(),
|
parser: DateCellDataParser(),
|
||||||
|
config: const GridCellDataConfig(reloadOnFieldChanged: true),
|
||||||
);
|
);
|
||||||
|
|
||||||
return GridDateCellContext(
|
return GridDateCellContext(
|
||||||
@ -105,7 +106,7 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
final FieldService _fieldService;
|
final FieldService _fieldService;
|
||||||
|
|
||||||
late final CellListener _cellListener;
|
late final CellListener _cellListener;
|
||||||
late final ValueNotifier<T?> _cellDataNotifier;
|
late final ValueNotifier<T?>? _cellDataNotifier;
|
||||||
bool isListening = false;
|
bool isListening = false;
|
||||||
VoidCallback? _onFieldChangedFn;
|
VoidCallback? _onFieldChangedFn;
|
||||||
Timer? _loadDataOperation;
|
Timer? _loadDataOperation;
|
||||||
@ -163,19 +164,19 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onCellChangedFn() {
|
onCellChangedFn() {
|
||||||
onCellChanged(_cellDataNotifier.value);
|
onCellChanged(_cellDataNotifier?.value);
|
||||||
|
|
||||||
if (cellDataLoader.config.reloadOnCellChanged) {
|
if (cellDataLoader.config.reloadOnCellChanged) {
|
||||||
_loadData();
|
_loadData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_cellDataNotifier.addListener(onCellChangedFn);
|
_cellDataNotifier?.addListener(onCellChangedFn);
|
||||||
return onCellChangedFn;
|
return onCellChangedFn;
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeListener(VoidCallback fn) {
|
void removeListener(VoidCallback fn) {
|
||||||
_cellDataNotifier.removeListener(fn);
|
_cellDataNotifier?.removeListener(fn);
|
||||||
}
|
}
|
||||||
|
|
||||||
T? getCellData({bool loadIfNoCache = true}) {
|
T? getCellData({bool loadIfNoCache = true}) {
|
||||||
@ -211,13 +212,14 @@ class _GridCellContext<T, D> extends Equatable {
|
|||||||
_loadDataOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
_loadDataOperation = Timer(const Duration(milliseconds: 10), () {
|
||||||
cellDataLoader.loadData().then((data) {
|
cellDataLoader.loadData().then((data) {
|
||||||
_cellDataNotifier.value = data;
|
_cellDataNotifier?.value = data;
|
||||||
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
cellCache.insert(GridCellCacheData(key: _cacheKey, object: data));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() {
|
void dispose() {
|
||||||
|
_cellListener.stop();
|
||||||
_loadDataOperation?.cancel();
|
_loadDataOperation?.cancel();
|
||||||
_saveDataOperation?.cancel();
|
_saveDataOperation?.cancel();
|
||||||
|
|
||||||
|
@ -58,11 +58,7 @@ class GridCellDataLoader<T> extends IGridCellDataLoader<T> {
|
|||||||
return fut.then(
|
return fut.then(
|
||||||
(result) => result.fold((Cell cell) {
|
(result) => result.fold((Cell cell) {
|
||||||
try {
|
try {
|
||||||
if (cell.data.isEmpty) {
|
return parser.parserData(cell.data);
|
||||||
return null;
|
|
||||||
} else {
|
|
||||||
return parser.parserData(cell.data);
|
|
||||||
}
|
|
||||||
} catch (e, s) {
|
} catch (e, s) {
|
||||||
Log.error('$parser parser cellData failed, $e');
|
Log.error('$parser parser cellData failed, $e');
|
||||||
Log.error('Stack trace \n $s');
|
Log.error('Stack trace \n $s');
|
||||||
@ -102,13 +98,17 @@ class SelectOptionCellDataLoader extends IGridCellDataLoader<SelectOptionCellDat
|
|||||||
class StringCellDataParser implements ICellDataParser<String> {
|
class StringCellDataParser implements ICellDataParser<String> {
|
||||||
@override
|
@override
|
||||||
String? parserData(List<int> data) {
|
String? parserData(List<int> data) {
|
||||||
return utf8.decode(data);
|
final s = utf8.decode(data);
|
||||||
|
return s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class DateCellDataParser implements ICellDataParser<DateCellData> {
|
class DateCellDataParser implements ICellDataParser<DateCellData> {
|
||||||
@override
|
@override
|
||||||
DateCellData? parserData(List<int> data) {
|
DateCellData? parserData(List<int> data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return DateCellData.fromBuffer(data);
|
return DateCellData.fromBuffer(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -116,6 +116,9 @@ class DateCellDataParser implements ICellDataParser<DateCellData> {
|
|||||||
class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> {
|
class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData> {
|
||||||
@override
|
@override
|
||||||
SelectOptionCellData? parserData(List<int> data) {
|
SelectOptionCellData? parserData(List<int> data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return SelectOptionCellData.fromBuffer(data);
|
return SelectOptionCellData.fromBuffer(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,6 +126,9 @@ class SelectOptionCellDataParser implements ICellDataParser<SelectOptionCellData
|
|||||||
class URLCellDataParser implements ICellDataParser<URLCellData> {
|
class URLCellDataParser implements ICellDataParser<URLCellData> {
|
||||||
@override
|
@override
|
||||||
URLCellData? parserData(List<int> data) {
|
URLCellData? parserData(List<int> data) {
|
||||||
|
if (data.isEmpty) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return URLCellData.fromBuffer(data);
|
return URLCellData.fromBuffer(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -31,18 +31,18 @@ class CellDataPersistence implements _GridCellDataPersistence<String> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class DateCalData with _$DateCalData {
|
class CalendarData with _$CalendarData {
|
||||||
const factory DateCalData({required DateTime date, String? time}) = _DateCellPersistenceData;
|
const factory CalendarData({required DateTime date, String? time}) = _CalendarData;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DateCellDataPersistence implements _GridCellDataPersistence<DateCalData> {
|
class DateCellDataPersistence implements _GridCellDataPersistence<CalendarData> {
|
||||||
final GridCell gridCell;
|
final GridCell gridCell;
|
||||||
DateCellDataPersistence({
|
DateCellDataPersistence({
|
||||||
required this.gridCell,
|
required this.gridCell,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Option<FlowyError>> save(DateCalData data) {
|
Future<Option<FlowyError>> save(CalendarData data) {
|
||||||
var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell);
|
var payload = DateChangesetPayload.create()..cellIdentifier = _cellIdentifier(gridCell);
|
||||||
|
|
||||||
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
|
final date = (data.date.millisecondsSinceEpoch ~/ 1000).toString();
|
||||||
|
@ -38,9 +38,9 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
emit(state.copyWith(focusedDay: focusedDay));
|
emit(state.copyWith(focusedDay: focusedDay));
|
||||||
},
|
},
|
||||||
didReceiveCellUpdate: (DateCellData? cellData) {
|
didReceiveCellUpdate: (DateCellData? cellData) {
|
||||||
final dateData = dateDataFromCellData(cellData);
|
final calData = calDataFromCellData(cellData);
|
||||||
final time = dateData.foldRight("", (dateData, previous) => dateData.time);
|
final time = calData.foldRight("", (dateData, previous) => dateData.time);
|
||||||
emit(state.copyWith(dateData: dateData, time: time));
|
emit(state.copyWith(calData: calData, time: time));
|
||||||
},
|
},
|
||||||
setIncludeTime: (includeTime) async {
|
setIncludeTime: (includeTime) async {
|
||||||
await _updateTypeOption(emit, includeTime: includeTime);
|
await _updateTypeOption(emit, includeTime: includeTime);
|
||||||
@ -52,7 +52,12 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
await _updateTypeOption(emit, timeFormat: timeFormat);
|
await _updateTypeOption(emit, timeFormat: timeFormat);
|
||||||
},
|
},
|
||||||
setTime: (time) async {
|
setTime: (time) async {
|
||||||
await _updateDateData(emit, time: time);
|
if (state.calData.isSome()) {
|
||||||
|
await _updateDateData(emit, time: time);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
didUpdateCalData: (Option<CalendarData> data, Option<String> timeFormatError) {
|
||||||
|
emit(state.copyWith(calData: data, timeFormatError: timeFormatError));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -60,8 +65,8 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateDateData(Emitter<DateCalState> emit, {DateTime? date, String? time}) {
|
Future<void> _updateDateData(Emitter<DateCalState> emit, {DateTime? date, String? time}) {
|
||||||
final DateCalData newDateData = state.dateData.fold(
|
final CalendarData newDateData = state.calData.fold(
|
||||||
() => DateCalData(date: date ?? DateTime.now(), time: time),
|
() => CalendarData(date: date ?? DateTime.now(), time: time),
|
||||||
(dateData) {
|
(dateData) {
|
||||||
var newDateData = dateData;
|
var newDateData = dateData;
|
||||||
if (date != null && !isSameDay(newDateData.date, date)) {
|
if (date != null && !isSameDay(newDateData.date, date)) {
|
||||||
@ -78,24 +83,22 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
return _saveDateData(emit, newDateData);
|
return _saveDateData(emit, newDateData);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _saveDateData(Emitter<DateCalState> emit, DateCalData newDateData) async {
|
Future<void> _saveDateData(Emitter<DateCalState> emit, CalendarData newCalData) async {
|
||||||
if (state.dateData == Some(newDateData)) {
|
if (state.calData == Some(newCalData)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cellContext.saveCellData(newDateData, resultCallback: (result) {
|
updateCalData(Option<CalendarData> calData, Option<String> timeFormatError) {
|
||||||
|
if (!isClosed) add(DateCalEvent.didUpdateCalData(calData, timeFormatError));
|
||||||
|
}
|
||||||
|
|
||||||
|
cellContext.saveCellData(newCalData, resultCallback: (result) {
|
||||||
result.fold(
|
result.fold(
|
||||||
() => emit(state.copyWith(
|
() => updateCalData(Some(newCalData), none()),
|
||||||
dateData: Some(newDateData),
|
|
||||||
timeFormatError: none(),
|
|
||||||
)),
|
|
||||||
(err) {
|
(err) {
|
||||||
switch (ErrorCode.valueOf(err.code)!) {
|
switch (ErrorCode.valueOf(err.code)!) {
|
||||||
case ErrorCode.InvalidDateTimeFormat:
|
case ErrorCode.InvalidDateTimeFormat:
|
||||||
emit(state.copyWith(
|
updateCalData(none(), Some(timeFormatPrompt(err)));
|
||||||
dateData: Some(newDateData),
|
|
||||||
timeFormatError: Some(timeFormatPrompt(err)),
|
|
||||||
));
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Log.error(err);
|
Log.error(err);
|
||||||
@ -168,7 +171,7 @@ class DateCalBloc extends Bloc<DateCalEvent, DateCalState> {
|
|||||||
);
|
);
|
||||||
|
|
||||||
result.fold(
|
result.fold(
|
||||||
(l) => emit(state.copyWith(dateTypeOption: newDateTypeOption)),
|
(l) => emit(state.copyWith(dateTypeOption: newDateTypeOption, timeHintText: _timeHintText(newDateTypeOption))),
|
||||||
(err) => Log.error(err),
|
(err) => Log.error(err),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -185,6 +188,8 @@ class DateCalEvent with _$DateCalEvent {
|
|||||||
const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
|
const factory DateCalEvent.setIncludeTime(bool includeTime) = _IncludeTime;
|
||||||
const factory DateCalEvent.setTime(String time) = _Time;
|
const factory DateCalEvent.setTime(String time) = _Time;
|
||||||
const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate;
|
const factory DateCalEvent.didReceiveCellUpdate(DateCellData? data) = _DidReceiveCellUpdate;
|
||||||
|
const factory DateCalEvent.didUpdateCalData(Option<CalendarData> data, Option<String> timeFormatError) =
|
||||||
|
_DidUpdateCalData;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -194,36 +199,48 @@ class DateCalState with _$DateCalState {
|
|||||||
required CalendarFormat format,
|
required CalendarFormat format,
|
||||||
required DateTime focusedDay,
|
required DateTime focusedDay,
|
||||||
required Option<String> timeFormatError,
|
required Option<String> timeFormatError,
|
||||||
required Option<DateCalData> dateData,
|
required Option<CalendarData> calData,
|
||||||
required String? time,
|
required String? time,
|
||||||
|
required String timeHintText,
|
||||||
}) = _DateCalState;
|
}) = _DateCalState;
|
||||||
|
|
||||||
factory DateCalState.initial(
|
factory DateCalState.initial(
|
||||||
DateTypeOption dateTypeOption,
|
DateTypeOption dateTypeOption,
|
||||||
DateCellData? cellData,
|
DateCellData? cellData,
|
||||||
) {
|
) {
|
||||||
Option<DateCalData> dateData = dateDataFromCellData(cellData);
|
Option<CalendarData> calData = calDataFromCellData(cellData);
|
||||||
final time = dateData.foldRight("", (dateData, previous) => dateData.time);
|
final time = calData.foldRight("", (dateData, previous) => dateData.time);
|
||||||
return DateCalState(
|
return DateCalState(
|
||||||
dateTypeOption: dateTypeOption,
|
dateTypeOption: dateTypeOption,
|
||||||
format: CalendarFormat.month,
|
format: CalendarFormat.month,
|
||||||
focusedDay: DateTime.now(),
|
focusedDay: DateTime.now(),
|
||||||
time: time,
|
time: time,
|
||||||
dateData: dateData,
|
calData: calData,
|
||||||
timeFormatError: none(),
|
timeFormatError: none(),
|
||||||
|
timeHintText: _timeHintText(dateTypeOption),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Option<DateCalData> dateDataFromCellData(DateCellData? cellData) {
|
String _timeHintText(DateTypeOption typeOption) {
|
||||||
|
switch (typeOption.timeFormat) {
|
||||||
|
case TimeFormat.TwelveHour:
|
||||||
|
return LocaleKeys.grid_date_timeHintTextInTwelveHour.tr();
|
||||||
|
case TimeFormat.TwentyFourHour:
|
||||||
|
return LocaleKeys.grid_date_timeHintTextInTwentyFourHour.tr();
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
Option<CalendarData> calDataFromCellData(DateCellData? cellData) {
|
||||||
String? time = timeFromCellData(cellData);
|
String? time = timeFromCellData(cellData);
|
||||||
Option<DateCalData> dateData = none();
|
Option<CalendarData> calData = none();
|
||||||
if (cellData != null) {
|
if (cellData != null) {
|
||||||
final timestamp = cellData.timestamp * 1000;
|
final timestamp = cellData.timestamp * 1000;
|
||||||
final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
|
final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
|
||||||
dateData = Some(DateCalData(date: date, time: time));
|
calData = Some(CalendarData(date: date, time: time));
|
||||||
}
|
}
|
||||||
return dateData;
|
return calData;
|
||||||
}
|
}
|
||||||
|
|
||||||
$fixnum.Int64 timestampFromDateTime(DateTime dateTime) {
|
$fixnum.Int64 timestampFromDateTime(DateTime dateTime) {
|
||||||
|
@ -4,7 +4,6 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'cell_service/cell_service.dart';
|
import 'cell_service/cell_service.dart';
|
||||||
import 'package:dartz/dartz.dart';
|
|
||||||
part 'date_cell_bloc.freezed.dart';
|
part 'date_cell_bloc.freezed.dart';
|
||||||
|
|
||||||
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
||||||
@ -17,11 +16,7 @@ class DateCellBloc extends Bloc<DateCellEvent, DateCellState> {
|
|||||||
event.when(
|
event.when(
|
||||||
initial: () => _startListening(),
|
initial: () => _startListening(),
|
||||||
didReceiveCellUpdate: (DateCellData? cellData) {
|
didReceiveCellUpdate: (DateCellData? cellData) {
|
||||||
if (cellData != null) {
|
emit(state.copyWith(data: cellData, dateStr: _dateStrFromCellData(cellData)));
|
||||||
emit(state.copyWith(data: Some(cellData)));
|
|
||||||
} else {
|
|
||||||
emit(state.copyWith(data: none()));
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)),
|
didReceiveFieldUpdate: (Field value) => emit(state.copyWith(field: value)),
|
||||||
);
|
);
|
||||||
@ -60,21 +55,26 @@ class DateCellEvent with _$DateCellEvent {
|
|||||||
@freezed
|
@freezed
|
||||||
class DateCellState with _$DateCellState {
|
class DateCellState with _$DateCellState {
|
||||||
const factory DateCellState({
|
const factory DateCellState({
|
||||||
required Option<DateCellData> data,
|
required DateCellData? data,
|
||||||
|
required String dateStr,
|
||||||
required Field field,
|
required Field field,
|
||||||
}) = _DateCellState;
|
}) = _DateCellState;
|
||||||
|
|
||||||
factory DateCellState.initial(GridDateCellContext context) {
|
factory DateCellState.initial(GridDateCellContext context) {
|
||||||
final cellData = context.getCellData();
|
final cellData = context.getCellData();
|
||||||
Option<DateCellData> data = none();
|
|
||||||
|
|
||||||
if (cellData != null) {
|
|
||||||
data = Some(cellData);
|
|
||||||
}
|
|
||||||
|
|
||||||
return DateCellState(
|
return DateCellState(
|
||||||
field: context.field,
|
field: context.field,
|
||||||
data: data,
|
data: cellData,
|
||||||
|
dateStr: _dateStrFromCellData(cellData),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _dateStrFromCellData(DateCellData? cellData) {
|
||||||
|
String dateStr = "";
|
||||||
|
if (cellData != null) {
|
||||||
|
dateStr = cellData.date + " " + cellData.time;
|
||||||
|
}
|
||||||
|
return dateStr;
|
||||||
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:dartz/dartz.dart';
|
||||||
import 'cell_service/cell_service.dart';
|
import 'cell_service/cell_service.dart';
|
||||||
|
|
||||||
part 'number_cell_bloc.freezed.dart';
|
part 'number_cell_bloc.freezed.dart';
|
||||||
@ -14,25 +16,28 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
|||||||
}) : super(NumberCellState.initial(cellContext)) {
|
}) : super(NumberCellState.initial(cellContext)) {
|
||||||
on<NumberCellEvent>(
|
on<NumberCellEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
event.when(
|
||||||
initial: (_Initial value) async {
|
initial: () {
|
||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
didReceiveCellUpdate: (_DidReceiveCellUpdate value) {
|
didReceiveCellUpdate: (content) {
|
||||||
emit(state.copyWith(content: value.cellContent ?? ""));
|
emit(state.copyWith(content: content));
|
||||||
},
|
},
|
||||||
updateCell: (_UpdateCell value) async {
|
updateCell: (text) {
|
||||||
await _updateCellValue(value, emit);
|
cellContext.saveCellData(text, resultCallback: (result) {
|
||||||
|
result.fold(
|
||||||
|
() => null,
|
||||||
|
(err) {
|
||||||
|
if (!isClosed) add(NumberCellEvent.didReceiveCellUpdate(right(err)));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _updateCellValue(_UpdateCell value, Emitter<NumberCellState> emit) async {
|
|
||||||
cellContext.saveCellData(value.text);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
if (_onCellChangedFn != null) {
|
if (_onCellChangedFn != null) {
|
||||||
@ -47,7 +52,7 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
|||||||
_onCellChangedFn = cellContext.startListening(
|
_onCellChangedFn = cellContext.startListening(
|
||||||
onCellChanged: ((cellContent) {
|
onCellChanged: ((cellContent) {
|
||||||
if (!isClosed) {
|
if (!isClosed) {
|
||||||
add(NumberCellEvent.didReceiveCellUpdate(cellContent));
|
add(NumberCellEvent.didReceiveCellUpdate(left(cellContent ?? "")));
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -58,17 +63,19 @@ class NumberCellBloc extends Bloc<NumberCellEvent, NumberCellState> {
|
|||||||
class NumberCellEvent with _$NumberCellEvent {
|
class NumberCellEvent with _$NumberCellEvent {
|
||||||
const factory NumberCellEvent.initial() = _Initial;
|
const factory NumberCellEvent.initial() = _Initial;
|
||||||
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
const factory NumberCellEvent.updateCell(String text) = _UpdateCell;
|
||||||
const factory NumberCellEvent.didReceiveCellUpdate(String? cellContent) = _DidReceiveCellUpdate;
|
const factory NumberCellEvent.didReceiveCellUpdate(Either<String, FlowyError> cellContent) = _DidReceiveCellUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
class NumberCellState with _$NumberCellState {
|
class NumberCellState with _$NumberCellState {
|
||||||
const factory NumberCellState({
|
const factory NumberCellState({
|
||||||
required String content,
|
required Either<String, FlowyError> content,
|
||||||
}) = _NumberCellState;
|
}) = _NumberCellState;
|
||||||
|
|
||||||
factory NumberCellState.initial(GridCellContext context) {
|
factory NumberCellState.initial(GridCellContext context) {
|
||||||
final cellContent = context.getCellData() ?? "";
|
final cellContent = context.getCellData() ?? "";
|
||||||
return NumberCellState(content: cellContent);
|
return NumberCellState(
|
||||||
|
content: left(cellContent),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,9 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
|||||||
url: cellData?.url ?? "",
|
url: cellData?.url ?? "",
|
||||||
));
|
));
|
||||||
},
|
},
|
||||||
|
updateURL: (String url) {
|
||||||
|
cellContext.saveCellData(url, deduplicate: true);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -53,6 +56,7 @@ class URLCellBloc extends Bloc<URLCellEvent, URLCellState> {
|
|||||||
@freezed
|
@freezed
|
||||||
class URLCellEvent with _$URLCellEvent {
|
class URLCellEvent with _$URLCellEvent {
|
||||||
const factory URLCellEvent.initial() = _InitialCell;
|
const factory URLCellEvent.initial() = _InitialCell;
|
||||||
|
const factory URLCellEvent.updateURL(String url) = _UpdateURL;
|
||||||
const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate;
|
const factory URLCellEvent.didReceiveCellUpdate(URLCellData? cell) = _DidReceiveCellUpdate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,13 +24,15 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
|
|||||||
_startListening();
|
_startListening();
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (field) {
|
didReceiveFieldUpdate: (field) {
|
||||||
emit(state.copyWith(field: field));
|
emit(state.copyWith(field: cellContext.field));
|
||||||
},
|
},
|
||||||
updateWidth: (offset) {
|
startUpdateWidth: (offset) {
|
||||||
final defaultWidth = state.field.width.toDouble();
|
final width = state.width + offset;
|
||||||
final width = defaultWidth + offset;
|
emit(state.copyWith(width: width));
|
||||||
if (width > defaultWidth && width < 300) {
|
},
|
||||||
_fieldService.updateField(width: width);
|
endUpdateWidth: () {
|
||||||
|
if (state.width != state.field.width.toDouble()) {
|
||||||
|
_fieldService.updateField(width: state.width);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -61,7 +63,8 @@ class FieldCellBloc extends Bloc<FieldCellEvent, FieldCellState> {
|
|||||||
class FieldCellEvent with _$FieldCellEvent {
|
class FieldCellEvent with _$FieldCellEvent {
|
||||||
const factory FieldCellEvent.initial() = _InitialCell;
|
const factory FieldCellEvent.initial() = _InitialCell;
|
||||||
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
const factory FieldCellEvent.didReceiveFieldUpdate(Field field) = _DidReceiveFieldUpdate;
|
||||||
const factory FieldCellEvent.updateWidth(double offset) = _UpdateWidth;
|
const factory FieldCellEvent.startUpdateWidth(double offset) = _StartUpdateWidth;
|
||||||
|
const factory FieldCellEvent.endUpdateWidth() = _EndUpdateWidth;
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
@ -69,10 +72,12 @@ class FieldCellState with _$FieldCellState {
|
|||||||
const factory FieldCellState({
|
const factory FieldCellState({
|
||||||
required String gridId,
|
required String gridId,
|
||||||
required Field field,
|
required Field field,
|
||||||
|
required double width,
|
||||||
}) = _FieldCellState;
|
}) = _FieldCellState;
|
||||||
|
|
||||||
factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState(
|
factory FieldCellState.initial(GridFieldCellContext cellContext) => FieldCellState(
|
||||||
gridId: cellContext.gridId,
|
gridId: cellContext.gridId,
|
||||||
field: cellContext.field,
|
field: cellContext.field,
|
||||||
|
width: cellContext.field.width.toDouble(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
import 'package:app_flowy/workspace/application/grid/field/type_option/type_option_service.dart';
|
||||||
|
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'package:dartz/dartz.dart';
|
import 'package:dartz/dartz.dart';
|
||||||
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-folder-data-model/view.pb.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/protobuf.dart';
|
||||||
@ -8,6 +9,7 @@ import 'package:freezed_annotation/freezed_annotation.dart';
|
|||||||
import 'cell/cell_service/cell_service.dart';
|
import 'cell/cell_service/cell_service.dart';
|
||||||
import 'grid_service.dart';
|
import 'grid_service.dart';
|
||||||
import 'row/row_service.dart';
|
import 'row/row_service.dart';
|
||||||
|
import 'dart:collection';
|
||||||
|
|
||||||
part 'grid_bloc.freezed.dart';
|
part 'grid_bloc.freezed.dart';
|
||||||
|
|
||||||
@ -33,19 +35,19 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
|
|
||||||
on<GridEvent>(
|
on<GridEvent>(
|
||||||
(event, emit) async {
|
(event, emit) async {
|
||||||
await event.map(
|
await event.when(
|
||||||
initial: (InitialGrid value) async {
|
initial: () async {
|
||||||
_startListening();
|
_startListening();
|
||||||
await _loadGrid(emit);
|
await _loadGrid(emit);
|
||||||
},
|
},
|
||||||
createRow: (_CreateRow value) {
|
createRow: () {
|
||||||
_gridService.createRow();
|
_gridService.createRow();
|
||||||
},
|
},
|
||||||
didReceiveRowUpdate: (_DidReceiveRowUpdate value) {
|
didReceiveRowUpdate: (rows, listState) {
|
||||||
emit(state.copyWith(rows: value.rows, listState: value.listState));
|
emit(state.copyWith(rows: rows, listState: listState));
|
||||||
},
|
},
|
||||||
didReceiveFieldUpdate: (_DidReceiveFieldUpdate value) {
|
didReceiveFieldUpdate: (fields) {
|
||||||
emit(state.copyWith(rows: rowCache.clonedRows, fields: value.fields));
|
emit(state.copyWith(rows: rowCache.clonedRows, fields: GridFieldEquatable(fields)));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -93,7 +95,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
|||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
grid: Some(grid),
|
grid: Some(grid),
|
||||||
fields: fieldCache.fields,
|
fields: GridFieldEquatable(fieldCache.fields),
|
||||||
rows: rowCache.clonedRows,
|
rows: rowCache.clonedRows,
|
||||||
loadingState: GridLoadingState.finish(left(unit)),
|
loadingState: GridLoadingState.finish(left(unit)),
|
||||||
));
|
));
|
||||||
@ -117,14 +119,14 @@ class GridState with _$GridState {
|
|||||||
const factory GridState({
|
const factory GridState({
|
||||||
required String gridId,
|
required String gridId,
|
||||||
required Option<Grid> grid,
|
required Option<Grid> grid,
|
||||||
required List<Field> fields,
|
required GridFieldEquatable fields,
|
||||||
required List<GridRow> rows,
|
required List<GridRow> rows,
|
||||||
required GridLoadingState loadingState,
|
required GridLoadingState loadingState,
|
||||||
required GridRowChangeReason listState,
|
required GridRowChangeReason listState,
|
||||||
}) = _GridState;
|
}) = _GridState;
|
||||||
|
|
||||||
factory GridState.initial(String gridId) => GridState(
|
factory GridState.initial(String gridId) => GridState(
|
||||||
fields: [],
|
fields: const GridFieldEquatable([]),
|
||||||
rows: [],
|
rows: [],
|
||||||
grid: none(),
|
grid: none(),
|
||||||
gridId: gridId,
|
gridId: gridId,
|
||||||
@ -138,3 +140,19 @@ class GridLoadingState with _$GridLoadingState {
|
|||||||
const factory GridLoadingState.loading() = _Loading;
|
const factory GridLoadingState.loading() = _Loading;
|
||||||
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
|
const factory GridLoadingState.finish(Either<Unit, FlowyError> successOrFail) = _Finish;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GridFieldEquatable extends Equatable {
|
||||||
|
final List<Field> _fields;
|
||||||
|
|
||||||
|
const GridFieldEquatable(List<Field> fields) : _fields = fields;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<Object?> get props {
|
||||||
|
return [
|
||||||
|
_fields.length,
|
||||||
|
_fields.map((field) => field.width).reduce((value, element) => value + element),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
UnmodifiableListView<Field> get value => UnmodifiableListView(_fields);
|
||||||
|
}
|
||||||
|
@ -30,7 +30,7 @@ class RowBloc extends Bloc<RowEvent, RowState> {
|
|||||||
_rowService.createRow();
|
_rowService.createRow();
|
||||||
},
|
},
|
||||||
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
didReceiveCellDatas: (_DidReceiveCellDatas value) async {
|
||||||
final fields = value.gridCellMap.values.map((e) => CellSnapshot(e.field)).toList();
|
final fields = value.gridCellMap.values.map((e) => GridCellEquatable(e.field)).toList();
|
||||||
final snapshots = UnmodifiableListView(fields);
|
final snapshots = UnmodifiableListView(fields);
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
gridCellMap: value.gridCellMap,
|
gridCellMap: value.gridCellMap,
|
||||||
@ -74,26 +74,27 @@ class RowState with _$RowState {
|
|||||||
const factory RowState({
|
const factory RowState({
|
||||||
required GridRow rowData,
|
required GridRow rowData,
|
||||||
required GridCellMap gridCellMap,
|
required GridCellMap gridCellMap,
|
||||||
required UnmodifiableListView<CellSnapshot> snapshots,
|
required UnmodifiableListView<GridCellEquatable> snapshots,
|
||||||
GridRowChangeReason? changeReason,
|
GridRowChangeReason? changeReason,
|
||||||
}) = _RowState;
|
}) = _RowState;
|
||||||
|
|
||||||
factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState(
|
factory RowState.initial(GridRow rowData, GridCellMap cellDataMap) => RowState(
|
||||||
rowData: rowData,
|
rowData: rowData,
|
||||||
gridCellMap: cellDataMap,
|
gridCellMap: cellDataMap,
|
||||||
snapshots: UnmodifiableListView(cellDataMap.values.map((e) => CellSnapshot(e.field)).toList()),
|
snapshots: UnmodifiableListView(cellDataMap.values.map((e) => GridCellEquatable(e.field)).toList()),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellSnapshot extends Equatable {
|
class GridCellEquatable extends Equatable {
|
||||||
final Field _field;
|
final Field _field;
|
||||||
|
|
||||||
const CellSnapshot(Field field) : _field = field;
|
const GridCellEquatable(Field field) : _field = field;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [
|
List<Object?> get props => [
|
||||||
_field.id,
|
_field.id,
|
||||||
_field.fieldType,
|
_field.fieldType,
|
||||||
_field.visibility,
|
_field.visibility,
|
||||||
|
_field.width,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,6 @@ import 'home_stack.dart';
|
|||||||
import 'menu/menu.dart';
|
import 'menu/menu.dart';
|
||||||
|
|
||||||
class HomeScreen extends StatefulWidget {
|
class HomeScreen extends StatefulWidget {
|
||||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
|
||||||
final UserProfile user;
|
final UserProfile user;
|
||||||
final CurrentWorkspaceSetting workspaceSetting;
|
final CurrentWorkspaceSetting workspaceSetting;
|
||||||
const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key);
|
const HomeScreen(this.user, this.workspaceSetting, {Key? key}) : super(key: key);
|
||||||
@ -52,7 +51,6 @@ class _HomeScreenState extends State<HomeScreen> {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
key: HomeScreen.scaffoldKey,
|
|
||||||
body: BlocListener<HomeBloc, HomeState>(
|
body: BlocListener<HomeBloc, HomeState>(
|
||||||
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
|
listenWhen: (p, c) => p.unauthorized != c.unauthorized,
|
||||||
listener: (context, state) {
|
listener: (context, state) {
|
||||||
|
@ -2,15 +2,13 @@ import 'dart:io' show Platform;
|
|||||||
|
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
import 'package:app_flowy/workspace/application/home/home_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_screen.dart';
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
import 'package:flowy_sdk/log.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:time/time.dart';
|
import 'package:time/time.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
|
|
||||||
import 'package:app_flowy/plugin/plugin.dart';
|
import 'package:app_flowy/plugin/plugin.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/blank/blank.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
import 'package:app_flowy/workspace/presentation/home/home_sizes.dart';
|
||||||
@ -22,11 +20,7 @@ import 'package:flowy_infra/notifier.dart';
|
|||||||
|
|
||||||
typedef NavigationCallback = void Function(String id);
|
typedef NavigationCallback = void Function(String id);
|
||||||
|
|
||||||
late FToast fToast;
|
|
||||||
|
|
||||||
class HomeStack extends StatelessWidget {
|
class HomeStack extends StatelessWidget {
|
||||||
static GlobalKey<ScaffoldState> scaffoldKey = GlobalKey();
|
|
||||||
// final Size size;
|
|
||||||
const HomeStack({Key? key}) : super(key: key);
|
const HomeStack({Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -74,8 +68,7 @@ class _FadingIndexedStackState extends State<FadingIndexedStack> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
fToast = FToast();
|
initToastWithContext(context);
|
||||||
fToast.init(HomeScreen.scaffoldKey.currentState!.context);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -28,7 +28,7 @@ class AddButton extends StatelessWidget {
|
|||||||
onSelected: onSelected,
|
onSelected: onSelected,
|
||||||
).show(context);
|
).show(context);
|
||||||
},
|
},
|
||||||
icon: svgWidget("home/add").padding(horizontal: 3, vertical: 3),
|
icon: svgWidget("home/add", color: theme.iconColor).padding(horizontal: 3, vertical: 3),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -46,8 +46,8 @@ class ActionList {
|
|||||||
return CreateItem(
|
return CreateItem(
|
||||||
pluginBuilder: pluginBuilder,
|
pluginBuilder: pluginBuilder,
|
||||||
onSelected: (builder) {
|
onSelected: (builder) {
|
||||||
FlowyOverlay.of(buildContext).remove(_identifier);
|
|
||||||
onSelected(builder);
|
onSelected(builder);
|
||||||
|
FlowyOverlay.of(buildContext).remove(_identifier);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:fluttertoast/fluttertoast.dart';
|
||||||
|
|
||||||
|
class FlowyMessageToast extends StatelessWidget {
|
||||||
|
final String message;
|
||||||
|
const FlowyMessageToast({required this.message, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
|
||||||
|
child: FlowyText.medium(message, color: Colors.white),
|
||||||
|
),
|
||||||
|
decoration: const BoxDecoration(
|
||||||
|
borderRadius: BorderRadius.all(Radius.circular(4)),
|
||||||
|
color: Colors.black,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void initToastWithContext(BuildContext context) {
|
||||||
|
getIt<FToast>().init(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
void showMessageToast(String message) {
|
||||||
|
final child = FlowyMessageToast(message: message);
|
||||||
|
|
||||||
|
getIt<FToast>().showToast(
|
||||||
|
child: child,
|
||||||
|
gravity: ToastGravity.BOTTOM,
|
||||||
|
toastDuration: const Duration(seconds: 3),
|
||||||
|
);
|
||||||
|
}
|
@ -29,9 +29,9 @@ class ToolbarIconButton extends StatelessWidget {
|
|||||||
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
iconPadding: const EdgeInsets.symmetric(horizontal: 4, vertical: 4),
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
width: width,
|
width: width,
|
||||||
icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName),
|
icon: isToggled == true ? svgWidget(iconName, color: Colors.white) : svgWidget(iconName, color: theme.iconColor),
|
||||||
fillColor: isToggled == true ? theme.main1 : theme.shader6,
|
fillColor: isToggled == true ? theme.main1 : theme.shader6,
|
||||||
hoverColor: isToggled == true ? theme.main1 : theme.shader5,
|
hoverColor: isToggled == true ? theme.main1 : theme.hover,
|
||||||
tooltipText: tooltipText,
|
tooltipText: tooltipText,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import 'layout/sizes.dart';
|
|||||||
import 'widgets/row/grid_row.dart';
|
import 'widgets/row/grid_row.dart';
|
||||||
import 'widgets/footer/grid_footer.dart';
|
import 'widgets/footer/grid_footer.dart';
|
||||||
import 'widgets/header/grid_header.dart';
|
import 'widgets/header/grid_header.dart';
|
||||||
|
import 'widgets/shortcuts.dart';
|
||||||
import 'widgets/toolbar/grid_toolbar.dart';
|
import 'widgets/toolbar/grid_toolbar.dart';
|
||||||
|
|
||||||
class GridPage extends StatefulWidget {
|
class GridPage extends StatefulWidget {
|
||||||
@ -40,7 +41,7 @@ class _GridPageState extends State<GridPage> {
|
|||||||
return state.loadingState.map(
|
return state.loadingState.map(
|
||||||
loading: (_) => const Center(child: CircularProgressIndicator.adaptive()),
|
loading: (_) => const Center(child: CircularProgressIndicator.adaptive()),
|
||||||
finish: (result) => result.successOrFail.fold(
|
finish: (result) => result.successOrFail.fold(
|
||||||
(_) => const FlowyGrid(),
|
(_) => const GridShortcuts(child: FlowyGrid()),
|
||||||
(err) => FlowyErrorPage(err.toString()),
|
(err) => FlowyErrorPage(err.toString()),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -91,9 +92,9 @@ class _FlowyGridState extends State<FlowyGrid> {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocBuilder<GridBloc, GridState>(
|
return BlocBuilder<GridBloc, GridState>(
|
||||||
buildWhen: (previous, current) => previous.fields.length != current.fields.length,
|
buildWhen: (previous, current) => previous.fields != current.fields,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final contentWidth = GridLayout.headerWidth(state.fields);
|
final contentWidth = GridLayout.headerWidth(state.fields.value);
|
||||||
final child = _wrapScrollView(
|
final child = _wrapScrollView(
|
||||||
contentWidth,
|
contentWidth,
|
||||||
[
|
[
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
import 'package:flowy_infra/image.dart';
|
||||||
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:flowy_infra/size.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
class GridCellAccessoryBuildContext {
|
||||||
|
final BuildContext anchorContext;
|
||||||
|
final bool isCellEditing;
|
||||||
|
|
||||||
|
GridCellAccessoryBuildContext({
|
||||||
|
required this.anchorContext,
|
||||||
|
required this.isCellEditing,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GridCellAccessory implements Widget {
|
||||||
|
void onTap();
|
||||||
|
|
||||||
|
// The accessory will be hidden if enable() return false;
|
||||||
|
bool enable() => true;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PrimaryCellAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
|
final VoidCallback onTapCallback;
|
||||||
|
final bool isCellEditing;
|
||||||
|
const PrimaryCellAccessory({
|
||||||
|
required this.onTapCallback,
|
||||||
|
required this.isCellEditing,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isCellEditing) {
|
||||||
|
return const SizedBox();
|
||||||
|
} else {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return svgWidget("grid/expander", color: theme.main1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTap() => onTapCallback();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool enable() => !isCellEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef AccessoryBuilder = List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext);
|
||||||
|
|
||||||
|
abstract class CellAccessory extends Widget {
|
||||||
|
const CellAccessory({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
// The hover will show if the isHover's value is true
|
||||||
|
ValueNotifier<bool>? get onAccessoryHover;
|
||||||
|
|
||||||
|
AccessoryBuilder? get accessoryBuilder;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccessoryHover extends StatefulWidget {
|
||||||
|
final CellAccessory child;
|
||||||
|
final EdgeInsets contentPadding;
|
||||||
|
const AccessoryHover({
|
||||||
|
required this.child,
|
||||||
|
this.contentPadding = EdgeInsets.zero,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AccessoryHover> createState() => _AccessoryHoverState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AccessoryHoverState extends State<AccessoryHover> {
|
||||||
|
late AccessoryHoverState _hoverState;
|
||||||
|
VoidCallback? _listenerFn;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
_hoverState = AccessoryHoverState();
|
||||||
|
_listenerFn = () => _hoverState.onHover = widget.child.onAccessoryHover?.value ?? false;
|
||||||
|
widget.child.onAccessoryHover?.addListener(_listenerFn!);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_hoverState.dispose();
|
||||||
|
|
||||||
|
if (_listenerFn != null) {
|
||||||
|
widget.child.onAccessoryHover?.removeListener(_listenerFn!);
|
||||||
|
_listenerFn = null;
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
List<Widget> children = [
|
||||||
|
const _Background(),
|
||||||
|
Padding(padding: widget.contentPadding, child: widget.child),
|
||||||
|
];
|
||||||
|
|
||||||
|
final accessoryBuilder = widget.child.accessoryBuilder;
|
||||||
|
if (accessoryBuilder != null) {
|
||||||
|
final accessories = accessoryBuilder((GridCellAccessoryBuildContext(
|
||||||
|
anchorContext: context,
|
||||||
|
isCellEditing: false,
|
||||||
|
)));
|
||||||
|
children.add(
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
child: CellAccessoryContainer(accessories: accessories),
|
||||||
|
).positioned(right: 0),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ChangeNotifierProvider.value(
|
||||||
|
value: _hoverState,
|
||||||
|
child: MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
opaque: false,
|
||||||
|
onEnter: (p) => setState(() => _hoverState.onHover = true),
|
||||||
|
onExit: (p) => setState(() => _hoverState.onHover = false),
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.loose,
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class AccessoryHoverState extends ChangeNotifier {
|
||||||
|
bool _onHover = false;
|
||||||
|
|
||||||
|
set onHover(bool value) {
|
||||||
|
if (_onHover != value) {
|
||||||
|
_onHover = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get onHover => _onHover;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _Background extends StatelessWidget {
|
||||||
|
const _Background({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return Consumer<AccessoryHoverState>(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
if (state.onHover) {
|
||||||
|
return FlowyHoverContainer(
|
||||||
|
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellAccessoryContainer extends StatelessWidget {
|
||||||
|
final List<GridCellAccessory> accessories;
|
||||||
|
const CellAccessoryContainer({required this.accessories, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
final children = accessories.where((accessory) => accessory.enable()).map((accessory) {
|
||||||
|
final hover = FlowyHover(
|
||||||
|
style: HoverStyle(hoverColor: theme.bg3, backgroundColor: theme.surface),
|
||||||
|
builder: (_, onHover) => Container(
|
||||||
|
width: 26,
|
||||||
|
height: 26,
|
||||||
|
padding: const EdgeInsets.all(3),
|
||||||
|
child: accessory,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return GestureDetector(
|
||||||
|
child: hover,
|
||||||
|
behavior: HitTestBehavior.opaque,
|
||||||
|
onTap: () => accessory.onTap(),
|
||||||
|
);
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
return Wrap(children: children, spacing: 6);
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,10 @@
|
|||||||
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/cell_service/cell_service.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show FieldType;
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
|
|
||||||
import 'package:flowy_infra/theme.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'cell_accessory.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'cell_shortcuts.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
|
||||||
import 'checkbox_cell.dart';
|
import 'checkbox_cell.dart';
|
||||||
import 'date_cell/date_cell.dart';
|
import 'date_cell/date_cell.dart';
|
||||||
import 'number_cell.dart';
|
import 'number_cell.dart';
|
||||||
@ -48,24 +45,132 @@ class BlankCell extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class GridCellWidget implements FlowyHoverWidget {
|
abstract class CellEditable {
|
||||||
@override
|
GridCellFocusListener get beginFocus;
|
||||||
final ValueNotifier<bool> onFocus = ValueNotifier<bool>(false);
|
|
||||||
|
|
||||||
final GridCellRequestFocusNotifier requestFocus = GridCellRequestFocusNotifier();
|
ValueNotifier<bool> get onCellFocus;
|
||||||
|
|
||||||
|
ValueNotifier<bool> get onCellEditing;
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridCellRequestFocusNotifier extends ChangeNotifier {
|
abstract class GridCellWidget extends StatefulWidget implements CellAccessory, CellEditable, CellShortcuts {
|
||||||
VoidCallback? _listener;
|
GridCellWidget({Key? key}) : super(key: key) {
|
||||||
|
onCellEditing.addListener(() {
|
||||||
|
onCellFocus.value = onCellEditing.value;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void addListener(VoidCallback listener) {
|
final ValueNotifier<bool> onCellFocus = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
// When the cell is focused, we assume that the accessory alse be hovered.
|
||||||
|
@override
|
||||||
|
ValueNotifier<bool> get onAccessoryHover => onCellFocus;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final ValueNotifier<bool> onCellEditing = ValueNotifier<bool>(false);
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext)? get accessoryBuilder => null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
final GridCellFocusListener beginFocus = GridCellFocusListener();
|
||||||
|
|
||||||
|
@override
|
||||||
|
final Map<CellKeyboardKey, CellKeyboardAction> shortcutHandlers = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GridCellState<T extends GridCellWidget> extends State<T> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.beginFocus.setListener(() => requestBeginFocus());
|
||||||
|
widget.shortcutHandlers[CellKeyboardKey.onCopy] = () => onCopy();
|
||||||
|
widget.shortcutHandlers[CellKeyboardKey.onInsert] = () {
|
||||||
|
Clipboard.getData("text/plain").then((data) {
|
||||||
|
final s = data?.text;
|
||||||
|
if (s is String) {
|
||||||
|
onInsert(s);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant T oldWidget) {
|
||||||
|
if (oldWidget != this) {
|
||||||
|
widget.beginFocus.setListener(() => requestBeginFocus());
|
||||||
|
}
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.beginFocus.removeAllListener();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void requestBeginFocus();
|
||||||
|
|
||||||
|
String? onCopy() => null;
|
||||||
|
|
||||||
|
void onInsert(String value) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class GridFocusNodeCellState<T extends GridCellWidget> extends GridCellState<T> {
|
||||||
|
SingleListenrFocusNode focusNode = SingleListenrFocusNode();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.shortcutHandlers[CellKeyboardKey.onEnter] = () => focusNode.unfocus();
|
||||||
|
_listenOnFocusNodeChanged();
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant T oldWidget) {
|
||||||
|
if (oldWidget != this) {
|
||||||
|
_listenOnFocusNodeChanged();
|
||||||
|
}
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
widget.shortcutHandlers.clear();
|
||||||
|
focusNode.removeAllListener();
|
||||||
|
focusNode.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void requestBeginFocus() {
|
||||||
|
if (focusNode.hasFocus == false && focusNode.canRequestFocus) {
|
||||||
|
FocusScope.of(context).requestFocus(focusNode);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void _listenOnFocusNodeChanged() {
|
||||||
|
widget.onCellEditing.value = focusNode.hasFocus;
|
||||||
|
focusNode.setListener(() {
|
||||||
|
widget.onCellEditing.value = focusNode.hasFocus;
|
||||||
|
focusChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> focusChanged() async {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellFocusListener extends ChangeNotifier {
|
||||||
|
VoidCallback? _listener;
|
||||||
|
|
||||||
|
void setListener(VoidCallback listener) {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
|
|
||||||
_listener = listener;
|
_listener = listener;
|
||||||
super.addListener(listener);
|
addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeAllListener() {
|
void removeAllListener() {
|
||||||
@ -81,10 +186,10 @@ class GridCellRequestFocusNotifier extends ChangeNotifier {
|
|||||||
|
|
||||||
abstract class GridCellStyle {}
|
abstract class GridCellStyle {}
|
||||||
|
|
||||||
class CellSingleFocusNode extends FocusNode {
|
class SingleListenrFocusNode extends FocusNode {
|
||||||
VoidCallback? _listener;
|
VoidCallback? _listener;
|
||||||
|
|
||||||
void setSingleListener(VoidCallback listener) {
|
void setListener(VoidCallback listener) {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
@ -93,120 +198,9 @@ class CellSingleFocusNode extends FocusNode {
|
|||||||
super.addListener(listener);
|
super.addListener(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
void removeSingleListener() {
|
void removeAllListener() {
|
||||||
if (_listener != null) {
|
if (_listener != null) {
|
||||||
removeListener(_listener!);
|
removeListener(_listener!);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellStateNotifier extends ChangeNotifier {
|
|
||||||
bool _isFocus = false;
|
|
||||||
bool _onEnter = false;
|
|
||||||
|
|
||||||
set isFocus(bool value) {
|
|
||||||
if (_isFocus != value) {
|
|
||||||
_isFocus = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
set onEnter(bool value) {
|
|
||||||
if (_onEnter != value) {
|
|
||||||
_onEnter = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get isFocus => _isFocus;
|
|
||||||
|
|
||||||
bool get onEnter => _onEnter;
|
|
||||||
}
|
|
||||||
|
|
||||||
class CellContainer extends StatelessWidget {
|
|
||||||
final GridCellWidget child;
|
|
||||||
final Widget? expander;
|
|
||||||
final double width;
|
|
||||||
final RegionStateNotifier rowStateNotifier;
|
|
||||||
const CellContainer({
|
|
||||||
Key? key,
|
|
||||||
required this.child,
|
|
||||||
required this.width,
|
|
||||||
required this.rowStateNotifier,
|
|
||||||
this.expander,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ChangeNotifierProxyProvider<RegionStateNotifier, CellStateNotifier>(
|
|
||||||
create: (_) => CellStateNotifier(),
|
|
||||||
update: (_, row, cell) => cell!..onEnter = row.onEnter,
|
|
||||||
child: Selector<CellStateNotifier, bool>(
|
|
||||||
selector: (context, notifier) => notifier.isFocus,
|
|
||||||
builder: (context, isFocus, _) {
|
|
||||||
Widget container = Center(child: child);
|
|
||||||
child.onFocus.addListener(() {
|
|
||||||
Provider.of<CellStateNotifier>(context, listen: false).isFocus = child.onFocus.value;
|
|
||||||
});
|
|
||||||
|
|
||||||
if (expander != null) {
|
|
||||||
container = CellEnterRegion(child: container, expander: expander!);
|
|
||||||
}
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
behavior: HitTestBehavior.translucent,
|
|
||||||
onTap: () => child.requestFocus.notify(),
|
|
||||||
child: Container(
|
|
||||||
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
|
||||||
decoration: _makeBoxDecoration(context, isFocus),
|
|
||||||
padding: GridSize.cellContentInsets,
|
|
||||||
child: container,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
if (isFocus) {
|
|
||||||
final borderSide = BorderSide(color: theme.main1, width: 1.0);
|
|
||||||
return BoxDecoration(border: Border.fromBorderSide(borderSide));
|
|
||||||
} else {
|
|
||||||
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
|
||||||
return BoxDecoration(border: Border(right: borderSide, bottom: borderSide));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CellEnterRegion extends StatelessWidget {
|
|
||||||
final Widget child;
|
|
||||||
final Widget expander;
|
|
||||||
const CellEnterRegion({required this.child, required this.expander, Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Selector<CellStateNotifier, bool>(
|
|
||||||
selector: (context, notifier) => notifier.onEnter,
|
|
||||||
builder: (context, onEnter, _) {
|
|
||||||
List<Widget> children = [child];
|
|
||||||
if (onEnter) {
|
|
||||||
children.add(expander.positioned(right: 0));
|
|
||||||
}
|
|
||||||
|
|
||||||
return MouseRegion(
|
|
||||||
cursor: SystemMouseCursors.click,
|
|
||||||
onEnter: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = true,
|
|
||||||
onExit: (p) => Provider.of<CellStateNotifier>(context, listen: false).onEnter = false,
|
|
||||||
child: Stack(
|
|
||||||
alignment: AlignmentDirectional.center,
|
|
||||||
fit: StackFit.expand,
|
|
||||||
// alignment: AlignmentDirectional.centerEnd,
|
|
||||||
children: children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/row/grid_row.dart';
|
||||||
|
import 'package:flowy_infra/theme.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
|
|
||||||
|
import 'cell_accessory.dart';
|
||||||
|
import 'cell_builder.dart';
|
||||||
|
import 'cell_shortcuts.dart';
|
||||||
|
|
||||||
|
class CellContainer extends StatelessWidget {
|
||||||
|
final GridCellWidget child;
|
||||||
|
final AccessoryBuilder? accessoryBuilder;
|
||||||
|
final double width;
|
||||||
|
final RegionStateNotifier rowStateNotifier;
|
||||||
|
const CellContainer({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
required this.width,
|
||||||
|
required this.rowStateNotifier,
|
||||||
|
this.accessoryBuilder,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return ChangeNotifierProxyProvider<RegionStateNotifier, CellContainerNotifier>(
|
||||||
|
create: (_) => CellContainerNotifier(child),
|
||||||
|
update: (_, rowStateNotifier, cellStateNotifier) => cellStateNotifier!..onEnter = rowStateNotifier.onEnter,
|
||||||
|
child: Selector<CellContainerNotifier, bool>(
|
||||||
|
selector: (context, notifier) => notifier.isFocus,
|
||||||
|
builder: (context, isFocus, _) {
|
||||||
|
Widget container = Center(child: GridCellShortcuts(child: child));
|
||||||
|
|
||||||
|
if (accessoryBuilder != null) {
|
||||||
|
final accessories = accessoryBuilder!(GridCellAccessoryBuildContext(
|
||||||
|
anchorContext: context,
|
||||||
|
isCellEditing: isFocus,
|
||||||
|
));
|
||||||
|
|
||||||
|
if (accessories.isNotEmpty) {
|
||||||
|
container = CellEnterRegion(child: container, accessories: accessories);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () => child.beginFocus.notify(),
|
||||||
|
child: Container(
|
||||||
|
constraints: BoxConstraints(maxWidth: width, minHeight: 46),
|
||||||
|
decoration: _makeBoxDecoration(context, isFocus),
|
||||||
|
padding: GridSize.cellContentInsets,
|
||||||
|
child: container,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
BoxDecoration _makeBoxDecoration(BuildContext context, bool isFocus) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
if (isFocus) {
|
||||||
|
final borderSide = BorderSide(color: theme.main1, width: 1.0);
|
||||||
|
return BoxDecoration(border: Border.fromBorderSide(borderSide));
|
||||||
|
} else {
|
||||||
|
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
||||||
|
return BoxDecoration(border: Border(right: borderSide, bottom: borderSide));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellEnterRegion extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
final List<GridCellAccessory> accessories;
|
||||||
|
const CellEnterRegion({required this.child, required this.accessories, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Selector<CellContainerNotifier, bool>(
|
||||||
|
selector: (context, notifier) => notifier.onEnter,
|
||||||
|
builder: (context, onEnter, _) {
|
||||||
|
List<Widget> children = [child];
|
||||||
|
if (onEnter) {
|
||||||
|
children.add(CellAccessoryContainer(accessories: accessories).positioned(right: 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return MouseRegion(
|
||||||
|
cursor: SystemMouseCursors.click,
|
||||||
|
onEnter: (p) => Provider.of<CellContainerNotifier>(context, listen: false).onEnter = true,
|
||||||
|
onExit: (p) => Provider.of<CellContainerNotifier>(context, listen: false).onEnter = false,
|
||||||
|
child: Stack(
|
||||||
|
alignment: AlignmentDirectional.center,
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CellContainerNotifier extends ChangeNotifier {
|
||||||
|
final CellEditable cellEditable;
|
||||||
|
bool mouted = false;
|
||||||
|
VoidCallback? _onCellFocusListener;
|
||||||
|
bool _isFocus = false;
|
||||||
|
bool _onEnter = false;
|
||||||
|
|
||||||
|
CellContainerNotifier(this.cellEditable) {
|
||||||
|
_onCellFocusListener = () => isFocus = cellEditable.onCellFocus.value;
|
||||||
|
cellEditable.onCellFocus.addListener(_onCellFocusListener!);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
if (_onCellFocusListener != null) {
|
||||||
|
cellEditable.onCellFocus.removeListener(_onCellFocusListener!);
|
||||||
|
}
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
set isFocus(bool value) {
|
||||||
|
if (_isFocus != value) {
|
||||||
|
_isFocus = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
set onEnter(bool value) {
|
||||||
|
if (_onEnter != value) {
|
||||||
|
_onEnter = value;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool get isFocus => _isFocus;
|
||||||
|
|
||||||
|
bool get onEnter => _onEnter;
|
||||||
|
}
|
@ -0,0 +1,96 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
typedef CellKeyboardAction = dynamic Function();
|
||||||
|
|
||||||
|
enum CellKeyboardKey {
|
||||||
|
onEnter,
|
||||||
|
onCopy,
|
||||||
|
onInsert,
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class CellShortcuts extends Widget {
|
||||||
|
const CellShortcuts({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
Map<CellKeyboardKey, CellKeyboardAction> get shortcutHandlers;
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellShortcuts extends StatelessWidget {
|
||||||
|
final CellShortcuts child;
|
||||||
|
const GridCellShortcuts({required this.child, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Shortcuts(
|
||||||
|
shortcuts: {
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.enter): const GridCellEnterIdent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyC): const GridCellCopyIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.keyV): const GridCellInsertIntent(),
|
||||||
|
},
|
||||||
|
child: Actions(
|
||||||
|
actions: {
|
||||||
|
GridCellEnterIdent: GridCellEnterAction(child: child),
|
||||||
|
GridCellCopyIntent: GridCellCopyAction(child: child),
|
||||||
|
GridCellInsertIntent: GridCellInsertAction(child: child),
|
||||||
|
},
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellEnterIdent extends Intent {
|
||||||
|
const GridCellEnterIdent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellEnterAction extends Action<GridCellEnterIdent> {
|
||||||
|
final CellShortcuts child;
|
||||||
|
GridCellEnterAction({required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(covariant GridCellEnterIdent intent) {
|
||||||
|
final callback = child.shortcutHandlers[CellKeyboardKey.onEnter];
|
||||||
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellCopyIntent extends Intent {
|
||||||
|
const GridCellCopyIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellCopyAction extends Action<GridCellCopyIntent> {
|
||||||
|
final CellShortcuts child;
|
||||||
|
GridCellCopyAction({required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(covariant GridCellCopyIntent intent) {
|
||||||
|
final callback = child.shortcutHandlers[CellKeyboardKey.onCopy];
|
||||||
|
if (callback == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final s = callback();
|
||||||
|
if (s is String) {
|
||||||
|
Clipboard.setData(ClipboardData(text: s));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellInsertIntent extends Intent {
|
||||||
|
const GridCellInsertIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridCellInsertAction extends Action<GridCellInsertIntent> {
|
||||||
|
final CellShortcuts child;
|
||||||
|
GridCellInsertAction({required this.child});
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(covariant GridCellInsertIntent intent) {
|
||||||
|
final callback = child.shortcutHandlers[CellKeyboardKey.onInsert];
|
||||||
|
if (callback != null) {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,7 +6,7 @@ import 'package:flutter/widgets.dart';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'cell_builder.dart';
|
import 'cell_builder.dart';
|
||||||
|
|
||||||
class CheckboxCell extends StatefulWidget with GridCellWidget {
|
class CheckboxCell extends GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
CheckboxCell({
|
CheckboxCell({
|
||||||
required this.cellContextBuilder,
|
required this.cellContextBuilder,
|
||||||
@ -14,17 +14,16 @@ class CheckboxCell extends StatefulWidget with GridCellWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<CheckboxCell> createState() => _CheckboxCellState();
|
GridCellState<CheckboxCell> createState() => _CheckboxCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CheckboxCellState extends State<CheckboxCell> {
|
class _CheckboxCellState extends GridCellState<CheckboxCell> {
|
||||||
late CheckboxCellBloc _cellBloc;
|
late CheckboxCellBloc _cellBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContextBuilder.build();
|
final cellContext = widget.cellContextBuilder.build();
|
||||||
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)..add(const CheckboxCellEvent.initial());
|
_cellBloc = getIt<CheckboxCellBloc>(param1: cellContext)..add(const CheckboxCellEvent.initial());
|
||||||
_listenCellRequestFocus();
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,22 +48,23 @@ class _CheckboxCellState extends State<CheckboxCell> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
void didUpdateWidget(covariant CheckboxCell oldWidget) {
|
|
||||||
_listenCellRequestFocus();
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenCellRequestFocus() {
|
@override
|
||||||
widget.requestFocus.addListener(() {
|
void requestBeginFocus() {
|
||||||
_cellBloc.add(const CheckboxCellEvent.select());
|
_cellBloc.add(const CheckboxCellEvent.select());
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() {
|
||||||
|
if (_cellBloc.state.isSelected) {
|
||||||
|
return "Yes";
|
||||||
|
} else {
|
||||||
|
return "No";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ abstract class GridCellDelegate {
|
|||||||
GridCellDelegate get delegate;
|
GridCellDelegate get delegate;
|
||||||
}
|
}
|
||||||
|
|
||||||
class DateCell extends StatefulWidget with GridCellWidget {
|
class DateCell extends GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
late final DateCellStyle? cellStyle;
|
late final DateCellStyle? cellStyle;
|
||||||
|
|
||||||
@ -35,10 +35,10 @@ class DateCell extends StatefulWidget with GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<DateCell> createState() => _DateCellState();
|
GridCellState<DateCell> createState() => _DateCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DateCellState extends State<DateCell> {
|
class _DateCellState extends GridCellState<DateCell> {
|
||||||
late DateCellBloc _cellBloc;
|
late DateCellBloc _cellBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -64,7 +64,7 @@ class _DateCellState extends State<DateCell> {
|
|||||||
cursor: SystemMouseCursors.click,
|
cursor: SystemMouseCursors.click,
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: alignment,
|
alignment: alignment,
|
||||||
child: FlowyText.medium(state.data.foldRight("", (data, _) => data.date), fontSize: 12),
|
child: FlowyText.medium(state.dateStr, fontSize: 12),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -76,8 +76,8 @@ class _DateCellState extends State<DateCell> {
|
|||||||
|
|
||||||
void _showCalendar(BuildContext context) {
|
void _showCalendar(BuildContext context) {
|
||||||
final bloc = context.read<DateCellBloc>();
|
final bloc = context.read<DateCellBloc>();
|
||||||
widget.onFocus.value = true;
|
widget.onCellEditing.value = true;
|
||||||
final calendar = DateCellEditor(onDismissed: () => widget.onFocus.value = false);
|
final calendar = DateCellEditor(onDismissed: () => widget.onCellEditing.value = false);
|
||||||
calendar.show(
|
calendar.show(
|
||||||
context,
|
context,
|
||||||
cellContext: bloc.cellContext.clone(),
|
cellContext: bloc.cellContext.clone(),
|
||||||
@ -89,4 +89,10 @@ class _DateCellState extends State<DateCell> {
|
|||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void requestBeginFocus() {}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() => _cellBloc.state.dateStr;
|
||||||
}
|
}
|
||||||
|
@ -160,18 +160,21 @@ class _CellCalendarWidget extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
selectedDayPredicate: (day) {
|
selectedDayPredicate: (day) {
|
||||||
return state.dateData.fold(
|
return state.calData.fold(
|
||||||
() => false,
|
() => false,
|
||||||
(dateData) => isSameDay(dateData.date, day),
|
(dateData) => isSameDay(dateData.date, day),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onDaySelected: (selectedDay, focusedDay) {
|
onDaySelected: (selectedDay, focusedDay) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
context.read<DateCalBloc>().add(DateCalEvent.selectDay(selectedDay));
|
context.read<DateCalBloc>().add(DateCalEvent.selectDay(selectedDay));
|
||||||
},
|
},
|
||||||
onFormatChanged: (format) {
|
onFormatChanged: (format) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
context.read<DateCalBloc>().add(DateCalEvent.setCalFormat(format));
|
context.read<DateCalBloc>().add(DateCalEvent.setCalFormat(format));
|
||||||
},
|
},
|
||||||
onPageChanged: (focusedDay) {
|
onPageChanged: (focusedDay) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
context.read<DateCalBloc>().add(DateCalEvent.setFocusedDay(focusedDay));
|
context.read<DateCalBloc>().add(DateCalEvent.setFocusedDay(focusedDay));
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -234,6 +237,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
|||||||
if (widget.bloc.state.dateTypeOption.includeTime) {
|
if (widget.bloc.state.dateTypeOption.includeTime) {
|
||||||
_focusNode.addListener(() {
|
_focusNode.addListener(() {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
|
_CalDateTimeSetting.hide(context);
|
||||||
widget.bloc.add(DateCalEvent.setTime(_controller.text));
|
widget.bloc.add(DateCalEvent.setTime(_controller.text));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -257,6 +261,7 @@ class _TimeTextFieldState extends State<_TimeTextField> {
|
|||||||
child: RoundedInputField(
|
child: RoundedInputField(
|
||||||
height: 40,
|
height: 40,
|
||||||
focusNode: _focusNode,
|
focusNode: _focusNode,
|
||||||
|
hintText: state.timeHintText,
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
normalBorderColor: theme.shader4,
|
normalBorderColor: theme.shader4,
|
||||||
@ -326,6 +331,7 @@ class _CalDateTimeSetting extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void show(BuildContext context) {
|
void show(BuildContext context) {
|
||||||
|
hide(context);
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: this,
|
child: this,
|
||||||
@ -337,6 +343,10 @@ class _CalDateTimeSetting extends StatefulWidget {
|
|||||||
anchorOffset: const Offset(20, 0),
|
anchorOffset: const Offset(20, 0),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void hide(BuildContext context) {
|
||||||
|
FlowyOverlay.of(context).remove(identifier());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
class _CalDateTimeSettingState extends State<_CalDateTimeSetting> {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:app_flowy/startup/startup.dart';
|
import 'package:app_flowy/startup/startup.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
@ -7,7 +6,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
|
|
||||||
import 'cell_builder.dart';
|
import 'cell_builder.dart';
|
||||||
|
|
||||||
class NumberCell extends StatefulWidget with GridCellWidget {
|
class NumberCell extends GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
|
|
||||||
NumberCell({
|
NumberCell({
|
||||||
@ -16,101 +15,79 @@ class NumberCell extends StatefulWidget with GridCellWidget {
|
|||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<NumberCell> createState() => _NumberCellState();
|
GridFocusNodeCellState<NumberCell> createState() => _NumberCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NumberCellState extends State<NumberCell> {
|
class _NumberCellState extends GridFocusNodeCellState<NumberCell> {
|
||||||
late NumberCellBloc _cellBloc;
|
late NumberCellBloc _cellBloc;
|
||||||
late TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
late CellSingleFocusNode _focusNode;
|
|
||||||
Timer? _delayOperation;
|
Timer? _delayOperation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final cellContext = widget.cellContextBuilder.build();
|
final cellContext = widget.cellContextBuilder.build();
|
||||||
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
|
_cellBloc = getIt<NumberCellBloc>(param1: cellContext)..add(const NumberCellEvent.initial());
|
||||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
_controller = TextEditingController(text: contentFromState(_cellBloc.state));
|
||||||
_focusNode = CellSingleFocusNode();
|
|
||||||
_listenFocusNode();
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
_listenCellRequestFocus(context);
|
|
||||||
return BlocProvider.value(
|
return BlocProvider.value(
|
||||||
value: _cellBloc,
|
value: _cellBloc,
|
||||||
child: BlocConsumer<NumberCellBloc, NumberCellState>(
|
child: MultiBlocListener(
|
||||||
listener: (context, state) {
|
listeners: [
|
||||||
if (_controller.text != state.content) {
|
BlocListener<NumberCellBloc, NumberCellState>(
|
||||||
_controller.text = state.content;
|
listenWhen: (p, c) => p.content != c.content,
|
||||||
}
|
listener: (context, state) => _controller.text = contentFromState(state),
|
||||||
},
|
),
|
||||||
builder: (context, state) {
|
],
|
||||||
return TextField(
|
child: TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: focusNode,
|
||||||
onEditingComplete: () => _focusNode.unfocus(),
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
decoration: const InputDecoration(
|
decoration: const InputDecoration(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
border: InputBorder.none,
|
border: InputBorder.none,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
),
|
),
|
||||||
);
|
),
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
_focusNode.removeSingleListener();
|
|
||||||
_focusNode.dispose();
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant NumberCell oldWidget) {
|
|
||||||
if (oldWidget != widget) {
|
|
||||||
_listenFocusNode();
|
|
||||||
}
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> focusChanged() async {
|
Future<void> focusChanged() async {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_delayOperation = Timer(const Duration(milliseconds: 300), () {
|
_delayOperation = Timer(const Duration(milliseconds: 300), () {
|
||||||
if (_cellBloc.isClosed == false && _controller.text != _cellBloc.state.content) {
|
if (_cellBloc.isClosed == false && _controller.text != contentFromState(_cellBloc.state)) {
|
||||||
final number = num.tryParse(_controller.text);
|
_cellBloc.add(NumberCellEvent.updateCell(_controller.text));
|
||||||
if (number != null) {
|
|
||||||
_cellBloc.add(NumberCellEvent.updateCell(_controller.text));
|
|
||||||
} else {
|
|
||||||
_controller.text = "";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenFocusNode() {
|
String contentFromState(NumberCellState state) {
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
return state.content.fold((l) => l, (r) => "");
|
||||||
_focusNode.setSingleListener(() {
|
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
|
||||||
focusChanged();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenCellRequestFocus(BuildContext context) {
|
@override
|
||||||
widget.requestFocus.addListener(() {
|
String? onCopy() {
|
||||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
return _cellBloc.state.content.fold((content) => content, (r) => null);
|
||||||
FocusScope.of(context).requestFocus(_focusNode);
|
}
|
||||||
}
|
|
||||||
});
|
@override
|
||||||
|
void onInsert(String value) {
|
||||||
|
_cellBloc.add(NumberCellEvent.updateCell(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,9 +64,11 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
final String name;
|
final String name;
|
||||||
final Color color;
|
final Color color;
|
||||||
final bool isSelected;
|
final bool isSelected;
|
||||||
|
final VoidCallback? onSelected;
|
||||||
const SelectOptionTag({
|
const SelectOptionTag({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.color,
|
required this.color,
|
||||||
|
this.onSelected,
|
||||||
this.isSelected = false,
|
this.isSelected = false,
|
||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
@ -74,12 +76,14 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
factory SelectOptionTag.fromSelectOption({
|
factory SelectOptionTag.fromSelectOption({
|
||||||
required BuildContext context,
|
required BuildContext context,
|
||||||
required SelectOption option,
|
required SelectOption option,
|
||||||
|
VoidCallback? onSelected,
|
||||||
bool isSelected = false,
|
bool isSelected = false,
|
||||||
}) {
|
}) {
|
||||||
return SelectOptionTag(
|
return SelectOptionTag(
|
||||||
name: option.name,
|
name: option.name,
|
||||||
color: option.color.make(context),
|
color: option.color.make(context),
|
||||||
isSelected: isSelected,
|
isSelected: isSelected,
|
||||||
|
onSelected: onSelected,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,19 +96,12 @@ class SelectOptionTag extends StatelessWidget {
|
|||||||
backgroundColor: color,
|
backgroundColor: color,
|
||||||
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
labelPadding: const EdgeInsets.symmetric(horizontal: 6),
|
||||||
selected: true,
|
selected: true,
|
||||||
onSelected: (_) {},
|
onSelected: (_) {
|
||||||
|
if (onSelected != null) {
|
||||||
|
onSelected!();
|
||||||
|
}
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
// return Container(
|
|
||||||
// decoration: BoxDecoration(
|
|
||||||
// color: option.color.make(context),
|
|
||||||
// shape: BoxShape.rectangle,
|
|
||||||
// borderRadius: BorderRadius.circular(8.0),
|
|
||||||
// ),
|
|
||||||
// child: Center(child: FlowyText.medium(option.name, fontSize: 12)),
|
|
||||||
// margin: const EdgeInsets.symmetric(horizontal: 3.0),
|
|
||||||
// padding: const EdgeInsets.symmetric(horizontal: 6.0),
|
|
||||||
// );
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -136,7 +133,11 @@ class SelectOptionTagCell extends StatelessWidget {
|
|||||||
Flexible(
|
Flexible(
|
||||||
fit: FlexFit.loose,
|
fit: FlexFit.loose,
|
||||||
flex: 2,
|
flex: 2,
|
||||||
child: SelectOptionTag.fromSelectOption(context: context, option: option),
|
child: SelectOptionTag.fromSelectOption(
|
||||||
|
context: context,
|
||||||
|
option: option,
|
||||||
|
onSelected: () => onSelected(option),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
...children,
|
...children,
|
||||||
|
@ -20,7 +20,7 @@ class SelectOptionCellStyle extends GridCellStyle {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class SingleSelectCell extends StatefulWidget with GridCellWidget {
|
class SingleSelectCell extends GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
late final SelectOptionCellStyle? cellStyle;
|
late final SelectOptionCellStyle? cellStyle;
|
||||||
|
|
||||||
@ -59,7 +59,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
|||||||
return _SelectOptionCell(
|
return _SelectOptionCell(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onFocus.value = value,
|
onFocus: (value) => widget.onCellEditing.value = value,
|
||||||
cellContextBuilder: widget.cellContextBuilder);
|
cellContextBuilder: widget.cellContextBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@ -74,7 +74,7 @@ class _SingleSelectCellState extends State<SingleSelectCell> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//----------------------------------------------------------------
|
//----------------------------------------------------------------
|
||||||
class MultiSelectCell extends StatefulWidget with GridCellWidget {
|
class MultiSelectCell extends GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
late final SelectOptionCellStyle? cellStyle;
|
late final SelectOptionCellStyle? cellStyle;
|
||||||
|
|
||||||
@ -113,7 +113,7 @@ class _MultiSelectCellState extends State<MultiSelectCell> {
|
|||||||
return _SelectOptionCell(
|
return _SelectOptionCell(
|
||||||
selectOptions: state.selectedOptions,
|
selectOptions: state.selectedOptions,
|
||||||
cellStyle: widget.cellStyle,
|
cellStyle: widget.cellStyle,
|
||||||
onFocus: (value) => widget.onFocus.value = value,
|
onFocus: (value) => widget.onCellEditing.value = value,
|
||||||
cellContextBuilder: widget.cellContextBuilder);
|
cellContextBuilder: widget.cellContextBuilder);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -156,6 +156,7 @@ class _TextField extends StatelessWidget {
|
|||||||
selectedOptionMap: optionMap,
|
selectedOptionMap: optionMap,
|
||||||
distanceToText: _editorPannelWidth * 0.7,
|
distanceToText: _editorPannelWidth * 0.7,
|
||||||
tagController: _tagController,
|
tagController: _tagController,
|
||||||
|
onClick: () => FlowyOverlay.of(context).remove(SelectOptionTypeOptionEditor.identifier),
|
||||||
newText: (text) {
|
newText: (text) {
|
||||||
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.filterOption(text));
|
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.filterOption(text));
|
||||||
},
|
},
|
||||||
@ -207,6 +208,7 @@ class _CreateOptionCell extends StatelessWidget {
|
|||||||
SelectOptionTag(
|
SelectOptionTag(
|
||||||
name: name,
|
name: name,
|
||||||
color: theme.shader6,
|
color: theme.shader6,
|
||||||
|
onSelected: () => context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.newOption(name)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
@ -233,7 +235,11 @@ class _SelectOptionCell extends StatelessWidget {
|
|||||||
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
|
context.read<SelectOptionCellEditorBloc>().add(SelectOptionEditorEvent.selectOption(option.id));
|
||||||
},
|
},
|
||||||
children: [
|
children: [
|
||||||
if (isSelected) svgWidget("grid/checkmark"),
|
if (isSelected)
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(right: 6),
|
||||||
|
child: svgWidget("grid/checkmark"),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -22,6 +22,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
|
|
||||||
final Function(String) onNewTag;
|
final Function(String) onNewTag;
|
||||||
final Function(String) newText;
|
final Function(String) newText;
|
||||||
|
final VoidCallback? onClick;
|
||||||
|
|
||||||
SelectOptionTextField({
|
SelectOptionTextField({
|
||||||
required this.options,
|
required this.options,
|
||||||
@ -30,6 +31,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
required this.tagController,
|
required this.tagController,
|
||||||
required this.onNewTag,
|
required this.onNewTag,
|
||||||
required this.newText,
|
required this.newText,
|
||||||
|
this.onClick,
|
||||||
TextEditingController? controller,
|
TextEditingController? controller,
|
||||||
FocusNode? focusNode,
|
FocusNode? focusNode,
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -53,6 +55,7 @@ class SelectOptionTextField extends StatelessWidget {
|
|||||||
autofocus: true,
|
autofocus: true,
|
||||||
controller: editController,
|
controller: editController,
|
||||||
focusNode: focusNode,
|
focusNode: focusNode,
|
||||||
|
onTap: onClick,
|
||||||
onChanged: (text) {
|
onChanged: (text) {
|
||||||
if (onChanged != null) {
|
if (onChanged != null) {
|
||||||
onChanged(text);
|
onChanged(text);
|
||||||
|
@ -13,7 +13,7 @@ class GridTextCellStyle extends GridCellStyle {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridTextCell extends StatefulWidget with GridCellWidget {
|
class GridTextCell extends GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
late final GridTextCellStyle? cellStyle;
|
late final GridTextCellStyle? cellStyle;
|
||||||
GridTextCell({
|
GridTextCell({
|
||||||
@ -29,13 +29,12 @@ class GridTextCell extends StatefulWidget with GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GridTextCell> createState() => _GridTextCellState();
|
GridFocusNodeCellState<GridTextCell> createState() => _GridTextCellState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridTextCellState extends State<GridTextCell> {
|
class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
|
||||||
late TextCellBloc _cellBloc;
|
late TextCellBloc _cellBloc;
|
||||||
late TextEditingController _controller;
|
late TextEditingController _controller;
|
||||||
late CellSingleFocusNode _focusNode;
|
|
||||||
Timer? _delayOperation;
|
Timer? _delayOperation;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -44,10 +43,6 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
_cellBloc = getIt<TextCellBloc>(param1: cellContext);
|
||||||
_cellBloc.add(const TextCellEvent.initial());
|
_cellBloc.add(const TextCellEvent.initial());
|
||||||
_controller = TextEditingController(text: _cellBloc.state.content);
|
_controller = TextEditingController(text: _cellBloc.state.content);
|
||||||
_focusNode = CellSingleFocusNode();
|
|
||||||
|
|
||||||
_listenFocusNode();
|
|
||||||
_listenRequestFocus(context);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,9 +58,9 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
},
|
},
|
||||||
child: TextField(
|
child: TextField(
|
||||||
controller: _controller,
|
controller: _controller,
|
||||||
focusNode: _focusNode,
|
focusNode: focusNode,
|
||||||
onChanged: (value) => focusChanged(),
|
onChanged: (value) => focusChanged(),
|
||||||
onEditingComplete: () => _focusNode.unfocus(),
|
onEditingComplete: () => focusNode.unfocus(),
|
||||||
maxLines: null,
|
maxLines: null,
|
||||||
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
style: const TextStyle(fontSize: 14, fontWeight: FontWeight.w500),
|
||||||
decoration: InputDecoration(
|
decoration: InputDecoration(
|
||||||
@ -81,39 +76,12 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
_focusNode.removeSingleListener();
|
|
||||||
_focusNode.dispose();
|
|
||||||
|
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void didUpdateWidget(covariant GridTextCell oldWidget) {
|
|
||||||
if (oldWidget != widget) {
|
|
||||||
_listenFocusNode();
|
|
||||||
}
|
|
||||||
super.didUpdateWidget(oldWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenFocusNode() {
|
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
|
||||||
_focusNode.setSingleListener(() {
|
|
||||||
widget.onFocus.value = _focusNode.hasFocus;
|
|
||||||
focusChanged();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void _listenRequestFocus(BuildContext context) {
|
|
||||||
widget.requestFocus.addListener(() {
|
|
||||||
if (_focusNode.hasFocus == false && _focusNode.canRequestFocus) {
|
|
||||||
FocusScope.of(context).requestFocus(_focusNode);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> focusChanged() async {
|
Future<void> focusChanged() async {
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
_delayOperation?.cancel();
|
_delayOperation?.cancel();
|
||||||
@ -124,4 +92,12 @@ class _GridTextCellState extends State<GridTextCell> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() => _cellBloc.state.content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInsert(String value) {
|
||||||
|
_cellBloc.add(TextCellEvent.updateText(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,10 @@ import 'dart:async';
|
|||||||
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
|
||||||
class URLCellEditor extends StatefulWidget {
|
class URLCellEditor extends StatefulWidget with FlowyOverlayDelegate {
|
||||||
final GridURLCellContext cellContext;
|
final GridURLCellContext cellContext;
|
||||||
const URLCellEditor({required this.cellContext, Key? key}) : super(key: key);
|
final VoidCallback completed;
|
||||||
|
const URLCellEditor({required this.cellContext, required this.completed, Key? key}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<URLCellEditor> createState() => _URLCellEditorState();
|
State<URLCellEditor> createState() => _URLCellEditorState();
|
||||||
@ -16,27 +17,43 @@ class URLCellEditor extends StatefulWidget {
|
|||||||
static void show(
|
static void show(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
GridURLCellContext cellContext,
|
GridURLCellContext cellContext,
|
||||||
|
VoidCallback completed,
|
||||||
) {
|
) {
|
||||||
FlowyOverlay.of(context).remove(identifier());
|
FlowyOverlay.of(context).remove(identifier());
|
||||||
final editor = URLCellEditor(
|
final editor = URLCellEditor(
|
||||||
cellContext: cellContext,
|
cellContext: cellContext,
|
||||||
|
completed: completed,
|
||||||
);
|
);
|
||||||
|
|
||||||
//
|
//
|
||||||
FlowyOverlay.of(context).insertWithAnchor(
|
FlowyOverlay.of(context).insertWithAnchor(
|
||||||
widget: OverlayContainer(
|
widget: OverlayContainer(
|
||||||
child: SizedBox(width: 200, child: editor),
|
child: SizedBox(
|
||||||
|
width: 200,
|
||||||
|
child: Padding(padding: const EdgeInsets.all(6), child: editor),
|
||||||
|
),
|
||||||
constraints: BoxConstraints.loose(const Size(300, 160)),
|
constraints: BoxConstraints.loose(const Size(300, 160)),
|
||||||
),
|
),
|
||||||
identifier: URLCellEditor.identifier(),
|
identifier: URLCellEditor.identifier(),
|
||||||
anchorContext: context,
|
anchorContext: context,
|
||||||
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
anchorDirection: AnchorDirection.bottomWithCenterAligned,
|
||||||
|
delegate: editor,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static String identifier() {
|
static String identifier() {
|
||||||
return (URLCellEditor).toString();
|
return (URLCellEditor).toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool asBarrier() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didRemove() {
|
||||||
|
completed();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _URLCellEditorState extends State<URLCellEditor> {
|
class _URLCellEditorState extends State<URLCellEditor> {
|
||||||
|
@ -1,10 +1,13 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/cell/url_cell_bloc.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
@ -14,12 +17,20 @@ import 'cell_editor.dart';
|
|||||||
class GridURLCellStyle extends GridCellStyle {
|
class GridURLCellStyle extends GridCellStyle {
|
||||||
String? placeholder;
|
String? placeholder;
|
||||||
|
|
||||||
|
List<GridURLCellAccessoryType> accessoryTypes;
|
||||||
|
|
||||||
GridURLCellStyle({
|
GridURLCellStyle({
|
||||||
this.placeholder,
|
this.placeholder,
|
||||||
|
this.accessoryTypes = const [],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class GridURLCell extends StatefulWidget with GridCellWidget {
|
enum GridURLCellAccessoryType {
|
||||||
|
edit,
|
||||||
|
copyURL,
|
||||||
|
}
|
||||||
|
|
||||||
|
class GridURLCell extends GridCellWidget {
|
||||||
final GridCellContextBuilder cellContextBuilder;
|
final GridCellContextBuilder cellContextBuilder;
|
||||||
late final GridURLCellStyle? cellStyle;
|
late final GridURLCellStyle? cellStyle;
|
||||||
GridURLCell({
|
GridURLCell({
|
||||||
@ -35,10 +46,39 @@ class GridURLCell extends StatefulWidget with GridCellWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<GridURLCell> createState() => _GridURLCellState();
|
GridCellState<GridURLCell> createState() => _GridURLCellState();
|
||||||
|
|
||||||
|
GridCellAccessory accessoryFromType(GridURLCellAccessoryType ty, GridCellAccessoryBuildContext buildContext) {
|
||||||
|
switch (ty) {
|
||||||
|
case GridURLCellAccessoryType.edit:
|
||||||
|
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
return _EditURLAccessory(cellContext: cellContext, anchorContext: buildContext.anchorContext);
|
||||||
|
|
||||||
|
case GridURLCellAccessoryType.copyURL:
|
||||||
|
final cellContext = cellContextBuilder.build() as GridURLCellContext;
|
||||||
|
return _CopyURLAccessory(cellContext: cellContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<GridCellAccessory> Function(GridCellAccessoryBuildContext buildContext) get accessoryBuilder => (buildContext) {
|
||||||
|
final List<GridCellAccessory> accessories = [];
|
||||||
|
if (cellStyle != null) {
|
||||||
|
accessories.addAll(cellStyle!.accessoryTypes.map((ty) {
|
||||||
|
return accessoryFromType(ty, buildContext);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the accessories is empty then the default accessory will be GridURLCellAccessoryType.edit
|
||||||
|
if (accessories.isEmpty) {
|
||||||
|
accessories.add(accessoryFromType(GridURLCellAccessoryType.edit, buildContext));
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessories;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GridURLCellState extends State<GridURLCell> {
|
class _GridURLCellState extends GridCellState<GridURLCell> {
|
||||||
late URLCellBloc _cellBloc;
|
late URLCellBloc _cellBloc;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -46,7 +86,6 @@ class _GridURLCellState extends State<GridURLCell> {
|
|||||||
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
||||||
_cellBloc = URLCellBloc(cellContext: cellContext);
|
_cellBloc = URLCellBloc(cellContext: cellContext);
|
||||||
_cellBloc.add(const URLCellEvent.initial());
|
_cellBloc.add(const URLCellEvent.initial());
|
||||||
_listenRequestFocus(context);
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,14 +105,17 @@ class _GridURLCellState extends State<GridURLCell> {
|
|||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
decoration: TextDecoration.underline,
|
decoration: TextDecoration.underline,
|
||||||
),
|
),
|
||||||
recognizer: _tapGesture(context),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return CellEnterRegion(
|
return SizedBox.expand(
|
||||||
|
child: GestureDetector(
|
||||||
child: Align(alignment: Alignment.centerLeft, child: richText),
|
child: Align(alignment: Alignment.centerLeft, child: richText),
|
||||||
expander: _EditCellIndicator(onTap: () {}),
|
onTap: () async {
|
||||||
);
|
final url = context.read<URLCellBloc>().state.url;
|
||||||
|
await _openUrlOrEdit(url);
|
||||||
|
},
|
||||||
|
));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -81,51 +123,72 @@ class _GridURLCellState extends State<GridURLCell> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<void> dispose() async {
|
Future<void> dispose() async {
|
||||||
widget.requestFocus.removeAllListener();
|
|
||||||
_cellBloc.close();
|
_cellBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
TapGestureRecognizer _tapGesture(BuildContext context) {
|
|
||||||
final gesture = TapGestureRecognizer();
|
|
||||||
gesture.onTap = () async {
|
|
||||||
final url = context.read<URLCellBloc>().state.url;
|
|
||||||
await _openUrlOrEdit(url);
|
|
||||||
};
|
|
||||||
return gesture;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _openUrlOrEdit(String url) async {
|
Future<void> _openUrlOrEdit(String url) async {
|
||||||
final uri = Uri.parse(url);
|
final uri = Uri.parse(url);
|
||||||
if (url.isNotEmpty && await canLaunchUrl(uri)) {
|
if (url.isNotEmpty && await canLaunchUrl(uri)) {
|
||||||
await launchUrl(uri);
|
await launchUrl(uri);
|
||||||
} else {
|
} else {
|
||||||
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
final cellContext = widget.cellContextBuilder.build() as GridURLCellContext;
|
||||||
URLCellEditor.show(context, cellContext);
|
widget.onCellEditing.value = true;
|
||||||
|
URLCellEditor.show(context, cellContext, () {
|
||||||
|
widget.onCellEditing.value = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _listenRequestFocus(BuildContext context) {
|
@override
|
||||||
widget.requestFocus.addListener(() {
|
void requestBeginFocus() {
|
||||||
_openUrlOrEdit(_cellBloc.state.url);
|
_openUrlOrEdit(_cellBloc.state.url);
|
||||||
});
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? onCopy() => _cellBloc.state.content;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInsert(String value) {
|
||||||
|
_cellBloc.add(URLCellEvent.updateURL(value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _EditCellIndicator extends StatelessWidget {
|
class _EditURLAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
final VoidCallback onTap;
|
final GridURLCellContext cellContext;
|
||||||
const _EditCellIndicator({required this.onTap, Key? key}) : super(key: key);
|
final BuildContext anchorContext;
|
||||||
|
const _EditURLAccessory({
|
||||||
|
required this.cellContext,
|
||||||
|
required this.anchorContext,
|
||||||
|
Key? key,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
return FlowyIconButton(
|
return svgWidget("editor/edit", color: theme.iconColor);
|
||||||
width: 26,
|
}
|
||||||
onPressed: onTap,
|
|
||||||
hoverColor: theme.hover,
|
@override
|
||||||
radius: BorderRadius.circular(4),
|
void onTap() {
|
||||||
iconPadding: const EdgeInsets.all(5),
|
URLCellEditor.show(anchorContext, cellContext, () {});
|
||||||
icon: svgWidget("editor/edit", color: theme.iconColor),
|
}
|
||||||
);
|
}
|
||||||
|
|
||||||
|
class _CopyURLAccessory extends StatelessWidget with GridCellAccessory {
|
||||||
|
final GridURLCellContext cellContext;
|
||||||
|
const _CopyURLAccessory({required this.cellContext, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = context.watch<AppTheme>();
|
||||||
|
return svgWidget("editor/copy", color: theme.iconColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onTap() {
|
||||||
|
final content = cellContext.getCellData(loadIfNoCache: false)?.content ?? "";
|
||||||
|
Clipboard.setData(ClipboardData(text: content));
|
||||||
|
showMessageToast(LocaleKeys.grid_row_copyProperty.tr());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import 'package:flowy_infra/theme.dart';
|
|||||||
import 'package:flowy_infra_ui/style_widget/button.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/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_sdk/log.dart';
|
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
import 'package:flowy_sdk/protobuf/flowy-grid-data-model/grid.pb.dart' show Field;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
@ -24,6 +23,7 @@ class GridFieldCell extends StatelessWidget {
|
|||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
|
create: (context) => FieldCellBloc(cellContext: cellContext)..add(const FieldCellEvent.initial()),
|
||||||
child: BlocBuilder<FieldCellBloc, FieldCellState>(
|
child: BlocBuilder<FieldCellBloc, FieldCellState>(
|
||||||
|
// buildWhen: (p, c) => p.field != c.field,
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final button = FieldCellButton(
|
final button = FieldCellButton(
|
||||||
field: state.field,
|
field: state.field,
|
||||||
@ -38,7 +38,7 @@ class GridFieldCell extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
|
|
||||||
return _GridHeaderCellContainer(
|
return _GridHeaderCellContainer(
|
||||||
width: state.field.width.toDouble(),
|
width: state.width,
|
||||||
child: Stack(
|
child: Stack(
|
||||||
alignment: Alignment.centerRight,
|
alignment: Alignment.centerRight,
|
||||||
fit: StackFit.expand,
|
fit: StackFit.expand,
|
||||||
@ -60,13 +60,14 @@ class GridFieldCell extends StatelessWidget {
|
|||||||
|
|
||||||
void _showFieldEditor(BuildContext context) {
|
void _showFieldEditor(BuildContext context) {
|
||||||
final state = context.read<FieldCellBloc>().state;
|
final state = context.read<FieldCellBloc>().state;
|
||||||
|
final field = state.field;
|
||||||
|
|
||||||
FieldEditor(
|
FieldEditor(
|
||||||
gridId: state.gridId,
|
gridId: state.gridId,
|
||||||
fieldName: state.field.name,
|
fieldName: field.name,
|
||||||
contextLoader: FieldContextLoader(
|
contextLoader: FieldContextLoader(
|
||||||
gridId: state.gridId,
|
gridId: state.gridId,
|
||||||
field: state.field,
|
field: field,
|
||||||
),
|
),
|
||||||
).show(context);
|
).show(context);
|
||||||
}
|
}
|
||||||
@ -84,7 +85,7 @@ class _GridHeaderCellContainer extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
final borderSide = BorderSide(color: theme.shader4, width: 0.4);
|
final borderSide = BorderSide(color: theme.shader5, width: 1.0);
|
||||||
final decoration = BoxDecoration(
|
final decoration = BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
top: borderSide,
|
top: borderSide,
|
||||||
@ -113,21 +114,19 @@ class _DragToExpandLine extends StatelessWidget {
|
|||||||
onTap: () {},
|
onTap: () {},
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
behavior: HitTestBehavior.opaque,
|
behavior: HitTestBehavior.opaque,
|
||||||
onHorizontalDragCancel: () {},
|
|
||||||
onHorizontalDragUpdate: (value) {
|
onHorizontalDragUpdate: (value) {
|
||||||
// context.read<FieldCellBloc>().add(FieldCellEvent.updateWidth(value.delta.dx));
|
context.read<FieldCellBloc>().add(FieldCellEvent.startUpdateWidth(value.delta.dx));
|
||||||
Log.info(value);
|
|
||||||
},
|
},
|
||||||
onHorizontalDragEnd: (end) {
|
onHorizontalDragEnd: (end) {
|
||||||
Log.info(end);
|
context.read<FieldCellBloc>().add(const FieldCellEvent.endUpdateWidth());
|
||||||
},
|
},
|
||||||
child: FlowyHover(
|
child: FlowyHover(
|
||||||
style: HoverStyle(
|
style: HoverStyle(
|
||||||
hoverColor: theme.main1,
|
hoverColor: theme.main1,
|
||||||
borderRadius: BorderRadius.zero,
|
borderRadius: BorderRadius.zero,
|
||||||
contentMargin: const EdgeInsets.only(left: 5),
|
contentMargin: const EdgeInsets.only(left: 6),
|
||||||
),
|
),
|
||||||
child: const SizedBox(width: 2),
|
child: const SizedBox(width: 4),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -9,7 +9,7 @@ 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/button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
import 'package:flowy_sdk/protobuf/flowy-grid/number_type_option.pb.dart';
|
import 'package:flowy_sdk/protobuf/flowy-grid/format.pbenum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart' hide NumberFormat;
|
import 'package:easy_localization/easy_localization.dart' hide NumberFormat;
|
||||||
|
@ -25,6 +25,8 @@ class SelectOptionTypeOptionEditor extends StatelessWidget {
|
|||||||
Key? key,
|
Key? key,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
|
static String get identifier => (SelectOptionTypeOptionEditor).toString();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_cotainer.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
@ -170,16 +172,29 @@ class _RowCells extends StatelessWidget {
|
|||||||
List<Widget> _makeCells(BuildContext context, GridCellMap gridCellMap) {
|
List<Widget> _makeCells(BuildContext context, GridCellMap gridCellMap) {
|
||||||
return gridCellMap.values.map(
|
return gridCellMap.values.map(
|
||||||
(gridCell) {
|
(gridCell) {
|
||||||
Widget? expander;
|
final GridCellWidget child = buildGridCellWidget(gridCell, cellCache);
|
||||||
if (gridCell.field.isPrimary) {
|
|
||||||
expander = _CellExpander(onExpand: onExpand);
|
accessoryBuilder(GridCellAccessoryBuildContext buildContext) {
|
||||||
|
final builder = child.accessoryBuilder;
|
||||||
|
List<GridCellAccessory> accessories = [];
|
||||||
|
if (gridCell.field.isPrimary) {
|
||||||
|
accessories.add(PrimaryCellAccessory(
|
||||||
|
onTapCallback: onExpand,
|
||||||
|
isCellEditing: buildContext.isCellEditing,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (builder != null) {
|
||||||
|
accessories.addAll(builder(buildContext));
|
||||||
|
}
|
||||||
|
return accessories;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CellContainer(
|
return CellContainer(
|
||||||
width: gridCell.field.width.toDouble(),
|
width: gridCell.field.width.toDouble(),
|
||||||
child: buildGridCellWidget(gridCell, cellCache),
|
child: child,
|
||||||
rowStateNotifier: Provider.of<RegionStateNotifier>(context, listen: false),
|
rowStateNotifier: Provider.of<RegionStateNotifier>(context, listen: false),
|
||||||
expander: expander,
|
accessoryBuilder: accessoryBuilder,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).toList();
|
).toList();
|
||||||
@ -199,26 +214,6 @@ class RegionStateNotifier extends ChangeNotifier {
|
|||||||
bool get onEnter => _onEnter;
|
bool get onEnter => _onEnter;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _CellExpander extends StatelessWidget {
|
|
||||||
final VoidCallback onExpand;
|
|
||||||
const _CellExpander({required this.onExpand, Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
return FittedBox(
|
|
||||||
fit: BoxFit.contain,
|
|
||||||
child: FlowyIconButton(
|
|
||||||
width: 26,
|
|
||||||
onPressed: onExpand,
|
|
||||||
iconPadding: const EdgeInsets.all(5),
|
|
||||||
radius: BorderRadius.circular(4),
|
|
||||||
icon: svgWidget("grid/expander", color: theme.main1),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _RowEnterRegion extends StatefulWidget {
|
class _RowEnterRegion extends StatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
const _RowEnterRegion({required this.child, Key? key}) : super(key: key);
|
const _RowEnterRegion({required this.child, Key? key}) : super(key: key);
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
import 'package:app_flowy/startup/startup.dart';
|
|
||||||
import 'package:app_flowy/workspace/application/grid/prelude.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
|
|
||||||
class NumberCell extends StatefulWidget {
|
|
||||||
final GridCell cellData;
|
|
||||||
|
|
||||||
const NumberCell({
|
|
||||||
required this.cellData,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<NumberCell> createState() => _NumberCellState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _NumberCellState extends State<NumberCell> {
|
|
||||||
late NumberCellBloc _cellBloc;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_cellBloc = getIt<NumberCellBloc>(param1: widget.cellData);
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return BlocProvider.value(
|
|
||||||
value: _cellBloc,
|
|
||||||
child: BlocBuilder<NumberCellBloc, NumberCellState>(
|
|
||||||
builder: (context, state) {
|
|
||||||
return Container();
|
|
||||||
},
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> dispose() async {
|
|
||||||
_cellBloc.close();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
@ -24,6 +24,7 @@ class GridRowActionSheet extends StatelessWidget {
|
|||||||
child: BlocBuilder<RowActionSheetBloc, RowActionSheetState>(
|
child: BlocBuilder<RowActionSheetBloc, RowActionSheetState>(
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final cells = _RowAction.values
|
final cells = _RowAction.values
|
||||||
|
.where((value) => value.enable())
|
||||||
.map(
|
.map(
|
||||||
(action) => _RowActionCell(
|
(action) => _RowActionCell(
|
||||||
action: action,
|
action: action,
|
||||||
|
@ -3,6 +3,7 @@ import 'package:app_flowy/workspace/application/grid/field/field_service.dart';
|
|||||||
import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
|
import 'package:app_flowy/workspace/application/grid/row/row_detail_bloc.dart';
|
||||||
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
import 'package:app_flowy/workspace/application/grid/row/row_service.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/layout/sizes.dart';
|
||||||
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/cell_accessory.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/prelude.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/cell/url_cell/url_cell.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
|
import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header/field_cell.dart';
|
||||||
@ -10,7 +11,6 @@ import 'package:app_flowy/workspace/presentation/plugins/grid/src/widgets/header
|
|||||||
import 'package:flowy_infra/image.dart';
|
import 'package:flowy_infra/image.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
|
||||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
import 'package:flowy_infra_ui/style_widget/scrolling/styled_scroll_bar.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
@ -149,12 +149,18 @@ class _RowDetailCell extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
|
final style = _customCellStyle(theme, gridCell.field.fieldType);
|
||||||
|
final cell = buildGridCellWidget(gridCell, cellCache, style: style);
|
||||||
|
|
||||||
final cell = buildGridCellWidget(
|
final gesture = GestureDetector(
|
||||||
gridCell,
|
behavior: HitTestBehavior.translucent,
|
||||||
cellCache,
|
onTap: () => cell.beginFocus.notify(),
|
||||||
style: _buildCellStyle(theme, gridCell.field.fieldType),
|
child: AccessoryHover(
|
||||||
|
child: cell,
|
||||||
|
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return ConstrainedBox(
|
return ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minHeight: 40),
|
constraints: const BoxConstraints(minHeight: 40),
|
||||||
child: IntrinsicHeight(
|
child: IntrinsicHeight(
|
||||||
@ -167,12 +173,7 @@ class _RowDetailCell extends StatelessWidget {
|
|||||||
child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
|
child: FieldCellButton(field: gridCell.field, onTap: () => _showFieldEditor(context)),
|
||||||
),
|
),
|
||||||
const HSpace(10),
|
const HSpace(10),
|
||||||
Expanded(
|
Expanded(child: gesture),
|
||||||
child: FlowyHover2(
|
|
||||||
child: cell,
|
|
||||||
contentPadding: const EdgeInsets.symmetric(horizontal: 10, vertical: 12),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -191,7 +192,7 @@ class _RowDetailCell extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
GridCellStyle? _customCellStyle(AppTheme theme, FieldType fieldType) {
|
||||||
switch (fieldType) {
|
switch (fieldType) {
|
||||||
case FieldType.Checkbox:
|
case FieldType.Checkbox:
|
||||||
return null;
|
return null;
|
||||||
@ -217,7 +218,11 @@ GridCellStyle? _buildCellStyle(AppTheme theme, FieldType fieldType) {
|
|||||||
case FieldType.URL:
|
case FieldType.URL:
|
||||||
return GridURLCellStyle(
|
return GridURLCellStyle(
|
||||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||||
|
accessoryTypes: [
|
||||||
|
GridURLCellAccessoryType.edit,
|
||||||
|
GridURLCellAccessoryType.copyURL,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
throw UnimplementedError;
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
|
class GridShortcuts extends StatelessWidget {
|
||||||
|
final Widget child;
|
||||||
|
const GridShortcuts({required this.child, Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Shortcuts(
|
||||||
|
shortcuts: bindKeys([]),
|
||||||
|
child: Actions(
|
||||||
|
dispatcher: LoggingActionDispatcher(),
|
||||||
|
actions: const {},
|
||||||
|
child: child,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<ShortcutActivator, Intent> bindKeys(List<LogicalKeyboardKey> keys) {
|
||||||
|
return {for (var key in keys) LogicalKeySet(key): KeyboardKeyIdent(key)};
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Type, Action<Intent>> bindActions() {
|
||||||
|
return {
|
||||||
|
KeyboardKeyIdent: KeyboardBindingAction(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyboardKeyIdent extends Intent {
|
||||||
|
final KeyboardKey key;
|
||||||
|
|
||||||
|
const KeyboardKeyIdent(this.key);
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeyboardBindingAction extends Action<KeyboardKeyIdent> {
|
||||||
|
KeyboardBindingAction();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(covariant KeyboardKeyIdent intent) {
|
||||||
|
// print(intent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LoggingActionDispatcher extends ActionDispatcher {
|
||||||
|
@override
|
||||||
|
Object? invokeAction(
|
||||||
|
covariant Action<Intent> action,
|
||||||
|
covariant Intent intent, [
|
||||||
|
BuildContext? context,
|
||||||
|
]) {
|
||||||
|
// print('Action invoked: $action($intent) from $context');
|
||||||
|
super.invokeAction(action, intent, context);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
@ -85,7 +85,7 @@ class GridSettingList extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _renderList() {
|
Widget _renderList() {
|
||||||
final cells = GridSettingAction.values.map((action) {
|
final cells = GridSettingAction.values.where((value) => value.enable()).map((action) {
|
||||||
return _SettingItem(action: action);
|
return _SettingItem(action: action);
|
||||||
}).toList();
|
}).toList();
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ class _EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
|
|||||||
bool _isToggled = false;
|
bool _isToggled = false;
|
||||||
// Style get _selectionStyle => widget.controller.getSelectionStyle();
|
// Style get _selectionStyle => widget.controller.getSelectionStyle();
|
||||||
final GlobalKey emojiButtonKey = GlobalKey();
|
final GlobalKey emojiButtonKey = GlobalKey();
|
||||||
OverlayEntry _entry = OverlayEntry(builder: (context) => Container());
|
OverlayEntry? _entry;
|
||||||
// final FocusNode _keyFocusNode = FocusNode();
|
// final FocusNode _keyFocusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -52,6 +52,12 @@ class _EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_entry?.remove();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
// @override
|
// @override
|
||||||
// void didUpdateWidget(covariant FlowyEmojiStyleButton oldWidget) {
|
// void didUpdateWidget(covariant FlowyEmojiStyleButton oldWidget) {
|
||||||
// super.didUpdateWidget(oldWidget);
|
// super.didUpdateWidget(oldWidget);
|
||||||
@ -77,8 +83,9 @@ class _EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
|
|||||||
// }
|
// }
|
||||||
|
|
||||||
void _toggleAttribute() {
|
void _toggleAttribute() {
|
||||||
if (_entry.mounted) {
|
if (_entry?.mounted ?? false) {
|
||||||
_entry.remove();
|
_entry?.remove();
|
||||||
|
_entry = null;
|
||||||
setState(() => _isToggled = false);
|
setState(() => _isToggled = false);
|
||||||
} else {
|
} else {
|
||||||
RenderBox box = emojiButtonKey.currentContext?.findRenderObject() as RenderBox;
|
RenderBox box = emojiButtonKey.currentContext?.findRenderObject() as RenderBox;
|
||||||
@ -93,7 +100,7 @@ class _EmojiStyleButtonState extends State<FlowyEmojiStyleButton> {
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
Overlay.of(context)!.insert(_entry);
|
Overlay.of(context)!.insert(_entry!);
|
||||||
setState(() => _isToggled = true);
|
setState(() => _isToggled = true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
import 'package:app_flowy/startup/tasks/rust_sdk.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/home/home_stack.dart';
|
import 'package:app_flowy/workspace/presentation/home/toast.dart';
|
||||||
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
import 'package:app_flowy/workspace/presentation/widgets/pop_up_action.dart';
|
||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:flowy_infra/theme.dart';
|
import 'package:flowy_infra/theme.dart';
|
||||||
@ -16,7 +16,6 @@ import 'package:package_info_plus/package_info_plus.dart';
|
|||||||
import 'package:url_launcher/url_launcher.dart';
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
import 'package:app_flowy/generated/locale_keys.g.dart';
|
import 'package:app_flowy/generated/locale_keys.g.dart';
|
||||||
import 'package:device_info_plus/device_info_plus.dart';
|
import 'package:device_info_plus/device_info_plus.dart';
|
||||||
import 'package:fluttertoast/fluttertoast.dart';
|
|
||||||
|
|
||||||
class QuestionBubble extends StatelessWidget {
|
class QuestionBubble extends StatelessWidget {
|
||||||
const QuestionBubble({Key? key}) : super(key: key);
|
const QuestionBubble({Key? key}) : super(key: key);
|
||||||
@ -46,7 +45,7 @@ class QuestionBubble extends StatelessWidget {
|
|||||||
_launchURL("https://discord.gg/9Q2xaN37tV");
|
_launchURL("https://discord.gg/9Q2xaN37tV");
|
||||||
break;
|
break;
|
||||||
case BubbleAction.debug:
|
case BubbleAction.debug:
|
||||||
const _DebugToast().show();
|
_DebugToast().show();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -71,55 +70,14 @@ class QuestionBubble extends StatelessWidget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _DebugToast extends StatelessWidget {
|
class _DebugToast {
|
||||||
const _DebugToast({Key? key}) : super(key: key);
|
void show() async {
|
||||||
|
var debugInfo = "";
|
||||||
|
debugInfo += await _getDeviceInfo();
|
||||||
|
debugInfo += await _getDocumentPath();
|
||||||
|
Clipboard.setData(ClipboardData(text: debugInfo));
|
||||||
|
|
||||||
@override
|
showMessageToast(LocaleKeys.questionBubble_debug_success.tr());
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return FutureBuilder(
|
|
||||||
future: Future(() async {
|
|
||||||
var debugInfo = "";
|
|
||||||
debugInfo += await _getDeviceInfo();
|
|
||||||
debugInfo += await _getDocumentPath();
|
|
||||||
|
|
||||||
Clipboard.setData(ClipboardData(text: debugInfo));
|
|
||||||
}),
|
|
||||||
builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return _done(context, Text("Error: ${snapshot.error}"));
|
|
||||||
} else {
|
|
||||||
return _done(context, null);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return const CircularProgressIndicator();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _done(BuildContext context, Widget? error) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 12.0),
|
|
||||||
decoration: BoxDecoration(borderRadius: BorderRadius.circular(25.0), color: theme.main1),
|
|
||||||
child: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.check),
|
|
||||||
const SizedBox(width: 12.0),
|
|
||||||
(error == null) ? Text(LocaleKeys.questionBubble_debug_success.tr()) : error
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void show() {
|
|
||||||
fToast.showToast(
|
|
||||||
child: this,
|
|
||||||
gravity: ToastGravity.BOTTOM,
|
|
||||||
toastDuration: const Duration(seconds: 3),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> _getDeviceInfo() async {
|
Future<String> _getDeviceInfo() async {
|
||||||
|
@ -26,6 +26,23 @@ require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelpe
|
|||||||
|
|
||||||
flutter_macos_podfile_setup
|
flutter_macos_podfile_setup
|
||||||
|
|
||||||
|
def build_specify_archs_only
|
||||||
|
if ENV.has_key?('BUILD_ARCHS')
|
||||||
|
xcodeproj_path = File.dirname(__FILE__) + '/Runner.xcodeproj'
|
||||||
|
project = Xcodeproj::Project.open(xcodeproj_path)
|
||||||
|
project.targets.each do |target|
|
||||||
|
if target.name == 'Runner'
|
||||||
|
target.build_configurations.each do |config|
|
||||||
|
config.build_settings['ARCHS'] = ENV['BUILD_ARCHS']
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
project.save()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
build_specify_archs_only()
|
||||||
|
|
||||||
target 'Runner' do
|
target 'Runner' do
|
||||||
use_frameworks!
|
use_frameworks!
|
||||||
use_modular_headers!
|
use_modular_headers!
|
||||||
|
@ -421,7 +421,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
EXCLUDED_ARCHS = arm64;
|
EXCLUDED_ARCHS = "";
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -553,7 +553,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
EXCLUDED_ARCHS = arm64;
|
EXCLUDED_ARCHS = "";
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
@ -577,7 +577,7 @@
|
|||||||
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements;
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
COMBINE_HIDPI_IMAGES = YES;
|
COMBINE_HIDPI_IMAGES = YES;
|
||||||
EXCLUDED_ARCHS = arm64;
|
EXCLUDED_ARCHS = "";
|
||||||
INFOPLIST_FILE = Runner/Info.plist;
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
LD_RUNPATH_SEARCH_PATHS = (
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
|
@ -71,7 +71,7 @@ class TextStyles {
|
|||||||
static TextStyle get CalloutFocus => Callout.bold;
|
static TextStyle get CalloutFocus => Callout.bold;
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
static TextStyle get Btn => quicksand.bold.size(FontSizes.s14).letterSpace(1.75);
|
static TextStyle get Btn => quicksand.bold.size(FontSizes.s16).letterSpace(1.75);
|
||||||
|
|
||||||
// ignore: non_constant_identifier_names
|
// ignore: non_constant_identifier_names
|
||||||
static TextStyle get BtnSelected => quicksand.size(FontSizes.s14).letterSpace(1.75);
|
static TextStyle get BtnSelected => quicksand.size(FontSizes.s14).letterSpace(1.75);
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import 'package:flowy_infra/size.dart';
|
|
||||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||||
@ -28,7 +27,7 @@ class FlowyButton extends StatelessWidget {
|
|||||||
return InkWell(
|
return InkWell(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: FlowyHover(
|
child: FlowyHover(
|
||||||
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: hoverColor),
|
style: HoverStyle(borderRadius: BorderRadius.zero, hoverColor: hoverColor),
|
||||||
setSelected: () => isSelected,
|
setSelected: () => isSelected,
|
||||||
builder: (context, onHover) => _render(),
|
builder: (context, onHover) => _render(),
|
||||||
),
|
),
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
// ignore: unused_import
|
// ignore: unused_import
|
||||||
import 'package:flowy_infra/time/duration.dart';
|
import 'package:flowy_infra/time/duration.dart';
|
||||||
import 'package:flowy_infra/size.dart';
|
|
||||||
import 'package:flowy_infra/theme.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
|
typedef HoverBuilder = Widget Function(BuildContext context, bool onHover);
|
||||||
|
|
||||||
@ -52,7 +49,7 @@ class _FlowyHoverState extends State<FlowyHover> {
|
|||||||
child: child,
|
child: child,
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return child;
|
return Container(child: child, color: widget.style.backgroundColor);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,12 +60,14 @@ class HoverStyle {
|
|||||||
final Color hoverColor;
|
final Color hoverColor;
|
||||||
final BorderRadius borderRadius;
|
final BorderRadius borderRadius;
|
||||||
final EdgeInsets contentMargin;
|
final EdgeInsets contentMargin;
|
||||||
|
final Color backgroundColor;
|
||||||
|
|
||||||
const HoverStyle(
|
const HoverStyle(
|
||||||
{this.borderColor = Colors.transparent,
|
{this.borderColor = Colors.transparent,
|
||||||
this.borderWidth = 0,
|
this.borderWidth = 0,
|
||||||
this.borderRadius = const BorderRadius.all(Radius.circular(6)),
|
this.borderRadius = const BorderRadius.all(Radius.circular(6)),
|
||||||
this.contentMargin = EdgeInsets.zero,
|
this.contentMargin = EdgeInsets.zero,
|
||||||
|
this.backgroundColor = Colors.transparent,
|
||||||
required this.hoverColor});
|
required this.hoverColor});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,120 +99,3 @@ class FlowyHoverContainer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
|
||||||
abstract class FlowyHoverWidget extends Widget {
|
|
||||||
const FlowyHoverWidget({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
ValueNotifier<bool>? get onFocus;
|
|
||||||
}
|
|
||||||
|
|
||||||
class FlowyHover2 extends StatefulWidget {
|
|
||||||
final FlowyHoverWidget child;
|
|
||||||
final EdgeInsets contentPadding;
|
|
||||||
const FlowyHover2({
|
|
||||||
required this.child,
|
|
||||||
this.contentPadding = EdgeInsets.zero,
|
|
||||||
Key? key,
|
|
||||||
}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<FlowyHover2> createState() => _FlowyHover2State();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _FlowyHover2State extends State<FlowyHover2> {
|
|
||||||
late FlowyHoverState _hoverState;
|
|
||||||
VoidCallback? _listenerFn;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
_hoverState = FlowyHoverState();
|
|
||||||
|
|
||||||
listener() {
|
|
||||||
_hoverState.onFocus = widget.child.onFocus?.value ?? false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_listenerFn = listener;
|
|
||||||
widget.child.onFocus?.addListener(listener);
|
|
||||||
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_hoverState.dispose();
|
|
||||||
|
|
||||||
if (_listenerFn != null) {
|
|
||||||
widget.child.onFocus?.removeListener(_listenerFn!);
|
|
||||||
_listenerFn = null;
|
|
||||||
}
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return ChangeNotifierProvider.value(
|
|
||||||
value: _hoverState,
|
|
||||||
child: MouseRegion(
|
|
||||||
cursor: SystemMouseCursors.click,
|
|
||||||
opaque: false,
|
|
||||||
onEnter: (p) => setState(() => _hoverState.onHover = true),
|
|
||||||
onExit: (p) => setState(() => _hoverState.onHover = false),
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.loose,
|
|
||||||
alignment: AlignmentDirectional.center,
|
|
||||||
children: [
|
|
||||||
const _HoverBackground(),
|
|
||||||
Padding(
|
|
||||||
padding: widget.contentPadding,
|
|
||||||
child: widget.child,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _HoverBackground extends StatelessWidget {
|
|
||||||
const _HoverBackground({Key? key}) : super(key: key);
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = context.watch<AppTheme>();
|
|
||||||
return Consumer<FlowyHoverState>(
|
|
||||||
builder: (context, state, child) {
|
|
||||||
if (state.onHover || state.onFocus) {
|
|
||||||
return FlowyHoverContainer(
|
|
||||||
style: HoverStyle(borderRadius: Corners.s6Border, hoverColor: theme.shader6),
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class FlowyHoverState extends ChangeNotifier {
|
|
||||||
bool _onHover = false;
|
|
||||||
bool _onFocus = false;
|
|
||||||
|
|
||||||
set onHover(bool value) {
|
|
||||||
if (_onHover != value) {
|
|
||||||
_onHover = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get onHover => _onHover;
|
|
||||||
|
|
||||||
set onFocus(bool value) {
|
|
||||||
if (_onFocus != value) {
|
|
||||||
_onFocus = value;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool get onFocus => _onFocus;
|
|
||||||
}
|
|
||||||
|
@ -23,7 +23,7 @@ class StyledSingleChildScrollView extends StatefulWidget {
|
|||||||
this.handleColor,
|
this.handleColor,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.scrollbarPadding,
|
this.scrollbarPadding,
|
||||||
this.barSize = 6,
|
this.barSize = 12,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -15,7 +15,7 @@ class PrimaryTextButton extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
TextStyle txtStyle = TextStyles.Footnote.textColor(Colors.white);
|
TextStyle txtStyle = TextStyles.Btn.textColor(Colors.white);
|
||||||
return PrimaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
|
return PrimaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,7 @@ class SecondaryTextButton extends StatelessWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = context.watch<AppTheme>();
|
final theme = context.watch<AppTheme>();
|
||||||
TextStyle txtStyle = TextStyles.Footnote.textColor(theme.main1);
|
TextStyle txtStyle = TextStyles.Btn.textColor(theme.main1);
|
||||||
return SecondaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
|
return SecondaryButton(bigMode: bigMode, onPressed: onPressed, child: Text(label, style: txtStyle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
|
|
||||||
/// Auto generate. Do not edit
|
/// Auto generate. Do not edit
|
||||||
part of '../../dispatch.dart';
|
part of '../../dispatch.dart';
|
||||||
class BlockEventGetBlockData {
|
class TextBlockEventGetBlockData {
|
||||||
TextBlockId request;
|
TextBlockId request;
|
||||||
BlockEventGetBlockData(this.request);
|
TextBlockEventGetBlockData(this.request);
|
||||||
|
|
||||||
Future<Either<TextBlockDelta, FlowyError>> send() {
|
Future<Either<TextBlockDelta, FlowyError>> send() {
|
||||||
final request = FFIRequest.create()
|
final request = FFIRequest.create()
|
||||||
..event = BlockEvent.GetBlockData.toString()
|
..event = TextBlockEvent.GetBlockData.toString()
|
||||||
..payload = requestToBytes(this.request);
|
..payload = requestToBytes(this.request);
|
||||||
|
|
||||||
return Dispatch.asyncRequest(request)
|
return Dispatch.asyncRequest(request)
|
||||||
@ -18,13 +18,13 @@ class BlockEventGetBlockData {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BlockEventApplyDelta {
|
class TextBlockEventApplyDelta {
|
||||||
TextBlockDelta request;
|
TextBlockDelta request;
|
||||||
BlockEventApplyDelta(this.request);
|
TextBlockEventApplyDelta(this.request);
|
||||||
|
|
||||||
Future<Either<TextBlockDelta, FlowyError>> send() {
|
Future<Either<TextBlockDelta, FlowyError>> send() {
|
||||||
final request = FFIRequest.create()
|
final request = FFIRequest.create()
|
||||||
..event = BlockEvent.ApplyDelta.toString()
|
..event = TextBlockEvent.ApplyDelta.toString()
|
||||||
..payload = requestToBytes(this.request);
|
..payload = requestToBytes(this.request);
|
||||||
|
|
||||||
return Dispatch.asyncRequest(request)
|
return Dispatch.asyncRequest(request)
|
||||||
@ -35,13 +35,13 @@ class BlockEventApplyDelta {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BlockEventExportDocument {
|
class TextBlockEventExportDocument {
|
||||||
ExportPayload request;
|
ExportPayload request;
|
||||||
BlockEventExportDocument(this.request);
|
TextBlockEventExportDocument(this.request);
|
||||||
|
|
||||||
Future<Either<ExportData, FlowyError>> send() {
|
Future<Either<ExportData, FlowyError>> send() {
|
||||||
final request = FFIRequest.create()
|
final request = FFIRequest.create()
|
||||||
..event = BlockEvent.ExportDocument.toString()
|
..event = TextBlockEvent.ExportDocument.toString()
|
||||||
..payload = requestToBytes(this.request);
|
..payload = requestToBytes(this.request);
|
||||||
|
|
||||||
return Dispatch.asyncRequest(request)
|
return Dispatch.asyncRequest(request)
|
||||||
|
@ -0,0 +1,11 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: format.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||||
|
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
|
||||||
|
export 'format.pbenum.dart';
|
||||||
|
|
@ -0,0 +1,94 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: format.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||||
|
|
||||||
|
// ignore_for_file: UNDEFINED_SHOWN_NAME
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
|
class NumberFormat extends $pb.ProtobufEnum {
|
||||||
|
static const NumberFormat Number = NumberFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Number');
|
||||||
|
static const NumberFormat USD = NumberFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'USD');
|
||||||
|
static const NumberFormat CanadianDollar = NumberFormat._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CanadianDollar');
|
||||||
|
static const NumberFormat EUR = NumberFormat._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EUR');
|
||||||
|
static const NumberFormat Pound = NumberFormat._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Pound');
|
||||||
|
static const NumberFormat Yen = NumberFormat._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yen');
|
||||||
|
static const NumberFormat Ruble = NumberFormat._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ruble');
|
||||||
|
static const NumberFormat Rupee = NumberFormat._(8, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupee');
|
||||||
|
static const NumberFormat Won = NumberFormat._(9, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Won');
|
||||||
|
static const NumberFormat Yuan = NumberFormat._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yuan');
|
||||||
|
static const NumberFormat Real = NumberFormat._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Real');
|
||||||
|
static const NumberFormat Lira = NumberFormat._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Lira');
|
||||||
|
static const NumberFormat Rupiah = NumberFormat._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupiah');
|
||||||
|
static const NumberFormat Franc = NumberFormat._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Franc');
|
||||||
|
static const NumberFormat HongKongDollar = NumberFormat._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'HongKongDollar');
|
||||||
|
static const NumberFormat NewZealandDollar = NumberFormat._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewZealandDollar');
|
||||||
|
static const NumberFormat Krona = NumberFormat._(17, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Krona');
|
||||||
|
static const NumberFormat NorwegianKrone = NumberFormat._(18, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NorwegianKrone');
|
||||||
|
static const NumberFormat MexicanPeso = NumberFormat._(19, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MexicanPeso');
|
||||||
|
static const NumberFormat Rand = NumberFormat._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rand');
|
||||||
|
static const NumberFormat NewTaiwanDollar = NumberFormat._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewTaiwanDollar');
|
||||||
|
static const NumberFormat DanishKrone = NumberFormat._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DanishKrone');
|
||||||
|
static const NumberFormat Baht = NumberFormat._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Baht');
|
||||||
|
static const NumberFormat Forint = NumberFormat._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Forint');
|
||||||
|
static const NumberFormat Koruna = NumberFormat._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Koruna');
|
||||||
|
static const NumberFormat Shekel = NumberFormat._(26, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Shekel');
|
||||||
|
static const NumberFormat ChileanPeso = NumberFormat._(27, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ChileanPeso');
|
||||||
|
static const NumberFormat PhilippinePeso = NumberFormat._(28, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PhilippinePeso');
|
||||||
|
static const NumberFormat Dirham = NumberFormat._(29, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Dirham');
|
||||||
|
static const NumberFormat ColombianPeso = NumberFormat._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ColombianPeso');
|
||||||
|
static const NumberFormat Riyal = NumberFormat._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Riyal');
|
||||||
|
static const NumberFormat Ringgit = NumberFormat._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ringgit');
|
||||||
|
static const NumberFormat Leu = NumberFormat._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Leu');
|
||||||
|
static const NumberFormat ArgentinePeso = NumberFormat._(34, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ArgentinePeso');
|
||||||
|
static const NumberFormat UruguayanPeso = NumberFormat._(35, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UruguayanPeso');
|
||||||
|
static const NumberFormat Percent = NumberFormat._(36, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Percent');
|
||||||
|
|
||||||
|
static const $core.List<NumberFormat> values = <NumberFormat> [
|
||||||
|
Number,
|
||||||
|
USD,
|
||||||
|
CanadianDollar,
|
||||||
|
EUR,
|
||||||
|
Pound,
|
||||||
|
Yen,
|
||||||
|
Ruble,
|
||||||
|
Rupee,
|
||||||
|
Won,
|
||||||
|
Yuan,
|
||||||
|
Real,
|
||||||
|
Lira,
|
||||||
|
Rupiah,
|
||||||
|
Franc,
|
||||||
|
HongKongDollar,
|
||||||
|
NewZealandDollar,
|
||||||
|
Krona,
|
||||||
|
NorwegianKrone,
|
||||||
|
MexicanPeso,
|
||||||
|
Rand,
|
||||||
|
NewTaiwanDollar,
|
||||||
|
DanishKrone,
|
||||||
|
Baht,
|
||||||
|
Forint,
|
||||||
|
Koruna,
|
||||||
|
Shekel,
|
||||||
|
ChileanPeso,
|
||||||
|
PhilippinePeso,
|
||||||
|
Dirham,
|
||||||
|
ColombianPeso,
|
||||||
|
Riyal,
|
||||||
|
Ringgit,
|
||||||
|
Leu,
|
||||||
|
ArgentinePeso,
|
||||||
|
UruguayanPeso,
|
||||||
|
Percent,
|
||||||
|
];
|
||||||
|
|
||||||
|
static final $core.Map<$core.int, NumberFormat> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||||
|
static NumberFormat? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
|
const NumberFormat._($core.int v, $core.String n) : super(v, n);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: format.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
|
||||||
|
|
||||||
|
import 'dart:core' as $core;
|
||||||
|
import 'dart:convert' as $convert;
|
||||||
|
import 'dart:typed_data' as $typed_data;
|
||||||
|
@$core.Deprecated('Use numberFormatDescriptor instead')
|
||||||
|
const NumberFormat$json = const {
|
||||||
|
'1': 'NumberFormat',
|
||||||
|
'2': const [
|
||||||
|
const {'1': 'Number', '2': 0},
|
||||||
|
const {'1': 'USD', '2': 1},
|
||||||
|
const {'1': 'CanadianDollar', '2': 2},
|
||||||
|
const {'1': 'EUR', '2': 4},
|
||||||
|
const {'1': 'Pound', '2': 5},
|
||||||
|
const {'1': 'Yen', '2': 6},
|
||||||
|
const {'1': 'Ruble', '2': 7},
|
||||||
|
const {'1': 'Rupee', '2': 8},
|
||||||
|
const {'1': 'Won', '2': 9},
|
||||||
|
const {'1': 'Yuan', '2': 10},
|
||||||
|
const {'1': 'Real', '2': 11},
|
||||||
|
const {'1': 'Lira', '2': 12},
|
||||||
|
const {'1': 'Rupiah', '2': 13},
|
||||||
|
const {'1': 'Franc', '2': 14},
|
||||||
|
const {'1': 'HongKongDollar', '2': 15},
|
||||||
|
const {'1': 'NewZealandDollar', '2': 16},
|
||||||
|
const {'1': 'Krona', '2': 17},
|
||||||
|
const {'1': 'NorwegianKrone', '2': 18},
|
||||||
|
const {'1': 'MexicanPeso', '2': 19},
|
||||||
|
const {'1': 'Rand', '2': 20},
|
||||||
|
const {'1': 'NewTaiwanDollar', '2': 21},
|
||||||
|
const {'1': 'DanishKrone', '2': 22},
|
||||||
|
const {'1': 'Baht', '2': 23},
|
||||||
|
const {'1': 'Forint', '2': 24},
|
||||||
|
const {'1': 'Koruna', '2': 25},
|
||||||
|
const {'1': 'Shekel', '2': 26},
|
||||||
|
const {'1': 'ChileanPeso', '2': 27},
|
||||||
|
const {'1': 'PhilippinePeso', '2': 28},
|
||||||
|
const {'1': 'Dirham', '2': 29},
|
||||||
|
const {'1': 'ColombianPeso', '2': 30},
|
||||||
|
const {'1': 'Riyal', '2': 31},
|
||||||
|
const {'1': 'Ringgit', '2': 32},
|
||||||
|
const {'1': 'Leu', '2': 33},
|
||||||
|
const {'1': 'ArgentinePeso', '2': 34},
|
||||||
|
const {'1': 'UruguayanPeso', '2': 35},
|
||||||
|
const {'1': 'Percent', '2': 36},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Descriptor for `NumberFormat`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
|
final $typed_data.Uint8List numberFormatDescriptor = $convert.base64Decode('CgxOdW1iZXJGb3JtYXQSCgoGTnVtYmVyEAASBwoDVVNEEAESEgoOQ2FuYWRpYW5Eb2xsYXIQAhIHCgNFVVIQBBIJCgVQb3VuZBAFEgcKA1llbhAGEgkKBVJ1YmxlEAcSCQoFUnVwZWUQCBIHCgNXb24QCRIICgRZdWFuEAoSCAoEUmVhbBALEggKBExpcmEQDBIKCgZSdXBpYWgQDRIJCgVGcmFuYxAOEhIKDkhvbmdLb25nRG9sbGFyEA8SFAoQTmV3WmVhbGFuZERvbGxhchAQEgkKBUtyb25hEBESEgoOTm9yd2VnaWFuS3JvbmUQEhIPCgtNZXhpY2FuUGVzbxATEggKBFJhbmQQFBITCg9OZXdUYWl3YW5Eb2xsYXIQFRIPCgtEYW5pc2hLcm9uZRAWEggKBEJhaHQQFxIKCgZGb3JpbnQQGBIKCgZLb3J1bmEQGRIKCgZTaGVrZWwQGhIPCgtDaGlsZWFuUGVzbxAbEhIKDlBoaWxpcHBpbmVQZXNvEBwSCgoGRGlyaGFtEB0SEQoNQ29sb21iaWFuUGVzbxAeEgkKBVJpeWFsEB8SCwoHUmluZ2dpdBAgEgcKA0xldRAhEhEKDUFyZ2VudGluZVBlc28QIhIRCg1VcnVndWF5YW5QZXNvECMSCwoHUGVyY2VudBAk');
|
@ -0,0 +1,9 @@
|
|||||||
|
///
|
||||||
|
// Generated code. Do not modify.
|
||||||
|
// source: format.proto
|
||||||
|
//
|
||||||
|
// @dart = 2.12
|
||||||
|
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
|
||||||
|
|
||||||
|
export 'format.pb.dart';
|
||||||
|
|
@ -9,13 +9,11 @@ import 'dart:core' as $core;
|
|||||||
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
import 'number_type_option.pbenum.dart';
|
import 'format.pbenum.dart' as $0;
|
||||||
|
|
||||||
export 'number_type_option.pbenum.dart';
|
|
||||||
|
|
||||||
class NumberTypeOption extends $pb.GeneratedMessage {
|
class NumberTypeOption extends $pb.GeneratedMessage {
|
||||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'NumberTypeOption', createEmptyInstance: create)
|
static final $pb.BuilderInfo _i = $pb.BuilderInfo(const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'NumberTypeOption', createEmptyInstance: create)
|
||||||
..e<NumberFormat>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format', $pb.PbFieldType.OE, defaultOrMaker: NumberFormat.Number, valueOf: NumberFormat.valueOf, enumValues: NumberFormat.values)
|
..e<$0.NumberFormat>(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'format', $pb.PbFieldType.OE, defaultOrMaker: $0.NumberFormat.Number, valueOf: $0.NumberFormat.valueOf, enumValues: $0.NumberFormat.values)
|
||||||
..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'scale', $pb.PbFieldType.OU3)
|
..a<$core.int>(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'scale', $pb.PbFieldType.OU3)
|
||||||
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'symbol')
|
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'symbol')
|
||||||
..aOB(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'signPositive')
|
..aOB(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'signPositive')
|
||||||
@ -25,7 +23,7 @@ class NumberTypeOption extends $pb.GeneratedMessage {
|
|||||||
|
|
||||||
NumberTypeOption._() : super();
|
NumberTypeOption._() : super();
|
||||||
factory NumberTypeOption({
|
factory NumberTypeOption({
|
||||||
NumberFormat? format,
|
$0.NumberFormat? format,
|
||||||
$core.int? scale,
|
$core.int? scale,
|
||||||
$core.String? symbol,
|
$core.String? symbol,
|
||||||
$core.bool? signPositive,
|
$core.bool? signPositive,
|
||||||
@ -71,9 +69,9 @@ class NumberTypeOption extends $pb.GeneratedMessage {
|
|||||||
static NumberTypeOption? _defaultInstance;
|
static NumberTypeOption? _defaultInstance;
|
||||||
|
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
NumberFormat get format => $_getN(0);
|
$0.NumberFormat get format => $_getN(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
set format(NumberFormat v) { setField(1, v); }
|
set format($0.NumberFormat v) { setField(1, v); }
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
$core.bool hasFormat() => $_has(0);
|
$core.bool hasFormat() => $_has(0);
|
||||||
@$pb.TagNumber(1)
|
@$pb.TagNumber(1)
|
||||||
|
@ -5,90 +5,3 @@
|
|||||||
// @dart = 2.12
|
// @dart = 2.12
|
||||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||||
|
|
||||||
// ignore_for_file: UNDEFINED_SHOWN_NAME
|
|
||||||
import 'dart:core' as $core;
|
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
|
||||||
|
|
||||||
class NumberFormat extends $pb.ProtobufEnum {
|
|
||||||
static const NumberFormat Number = NumberFormat._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Number');
|
|
||||||
static const NumberFormat USD = NumberFormat._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'USD');
|
|
||||||
static const NumberFormat CanadianDollar = NumberFormat._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CanadianDollar');
|
|
||||||
static const NumberFormat EUR = NumberFormat._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'EUR');
|
|
||||||
static const NumberFormat Pound = NumberFormat._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Pound');
|
|
||||||
static const NumberFormat Yen = NumberFormat._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yen');
|
|
||||||
static const NumberFormat Ruble = NumberFormat._(7, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ruble');
|
|
||||||
static const NumberFormat Rupee = NumberFormat._(8, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupee');
|
|
||||||
static const NumberFormat Won = NumberFormat._(9, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Won');
|
|
||||||
static const NumberFormat Yuan = NumberFormat._(10, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Yuan');
|
|
||||||
static const NumberFormat Real = NumberFormat._(11, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Real');
|
|
||||||
static const NumberFormat Lira = NumberFormat._(12, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Lira');
|
|
||||||
static const NumberFormat Rupiah = NumberFormat._(13, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rupiah');
|
|
||||||
static const NumberFormat Franc = NumberFormat._(14, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Franc');
|
|
||||||
static const NumberFormat HongKongDollar = NumberFormat._(15, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'HongKongDollar');
|
|
||||||
static const NumberFormat NewZealandDollar = NumberFormat._(16, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewZealandDollar');
|
|
||||||
static const NumberFormat Krona = NumberFormat._(17, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Krona');
|
|
||||||
static const NumberFormat NorwegianKrone = NumberFormat._(18, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NorwegianKrone');
|
|
||||||
static const NumberFormat MexicanPeso = NumberFormat._(19, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'MexicanPeso');
|
|
||||||
static const NumberFormat Rand = NumberFormat._(20, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Rand');
|
|
||||||
static const NumberFormat NewTaiwanDollar = NumberFormat._(21, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'NewTaiwanDollar');
|
|
||||||
static const NumberFormat DanishKrone = NumberFormat._(22, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'DanishKrone');
|
|
||||||
static const NumberFormat Baht = NumberFormat._(23, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Baht');
|
|
||||||
static const NumberFormat Forint = NumberFormat._(24, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Forint');
|
|
||||||
static const NumberFormat Koruna = NumberFormat._(25, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Koruna');
|
|
||||||
static const NumberFormat Shekel = NumberFormat._(26, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Shekel');
|
|
||||||
static const NumberFormat ChileanPeso = NumberFormat._(27, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ChileanPeso');
|
|
||||||
static const NumberFormat PhilippinePeso = NumberFormat._(28, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PhilippinePeso');
|
|
||||||
static const NumberFormat Dirham = NumberFormat._(29, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Dirham');
|
|
||||||
static const NumberFormat ColombianPeso = NumberFormat._(30, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ColombianPeso');
|
|
||||||
static const NumberFormat Riyal = NumberFormat._(31, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Riyal');
|
|
||||||
static const NumberFormat Ringgit = NumberFormat._(32, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Ringgit');
|
|
||||||
static const NumberFormat Leu = NumberFormat._(33, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Leu');
|
|
||||||
static const NumberFormat ArgentinePeso = NumberFormat._(34, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ArgentinePeso');
|
|
||||||
static const NumberFormat UruguayanPeso = NumberFormat._(35, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'UruguayanPeso');
|
|
||||||
static const NumberFormat Percent = NumberFormat._(36, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'Percent');
|
|
||||||
|
|
||||||
static const $core.List<NumberFormat> values = <NumberFormat> [
|
|
||||||
Number,
|
|
||||||
USD,
|
|
||||||
CanadianDollar,
|
|
||||||
EUR,
|
|
||||||
Pound,
|
|
||||||
Yen,
|
|
||||||
Ruble,
|
|
||||||
Rupee,
|
|
||||||
Won,
|
|
||||||
Yuan,
|
|
||||||
Real,
|
|
||||||
Lira,
|
|
||||||
Rupiah,
|
|
||||||
Franc,
|
|
||||||
HongKongDollar,
|
|
||||||
NewZealandDollar,
|
|
||||||
Krona,
|
|
||||||
NorwegianKrone,
|
|
||||||
MexicanPeso,
|
|
||||||
Rand,
|
|
||||||
NewTaiwanDollar,
|
|
||||||
DanishKrone,
|
|
||||||
Baht,
|
|
||||||
Forint,
|
|
||||||
Koruna,
|
|
||||||
Shekel,
|
|
||||||
ChileanPeso,
|
|
||||||
PhilippinePeso,
|
|
||||||
Dirham,
|
|
||||||
ColombianPeso,
|
|
||||||
Riyal,
|
|
||||||
Ringgit,
|
|
||||||
Leu,
|
|
||||||
ArgentinePeso,
|
|
||||||
UruguayanPeso,
|
|
||||||
Percent,
|
|
||||||
];
|
|
||||||
|
|
||||||
static final $core.Map<$core.int, NumberFormat> _byValue = $pb.ProtobufEnum.initByValue(values);
|
|
||||||
static NumberFormat? valueOf($core.int value) => _byValue[value];
|
|
||||||
|
|
||||||
const NumberFormat._($core.int v, $core.String n) : super(v, n);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
@ -8,51 +8,6 @@
|
|||||||
import 'dart:core' as $core;
|
import 'dart:core' as $core;
|
||||||
import 'dart:convert' as $convert;
|
import 'dart:convert' as $convert;
|
||||||
import 'dart:typed_data' as $typed_data;
|
import 'dart:typed_data' as $typed_data;
|
||||||
@$core.Deprecated('Use numberFormatDescriptor instead')
|
|
||||||
const NumberFormat$json = const {
|
|
||||||
'1': 'NumberFormat',
|
|
||||||
'2': const [
|
|
||||||
const {'1': 'Number', '2': 0},
|
|
||||||
const {'1': 'USD', '2': 1},
|
|
||||||
const {'1': 'CanadianDollar', '2': 2},
|
|
||||||
const {'1': 'EUR', '2': 4},
|
|
||||||
const {'1': 'Pound', '2': 5},
|
|
||||||
const {'1': 'Yen', '2': 6},
|
|
||||||
const {'1': 'Ruble', '2': 7},
|
|
||||||
const {'1': 'Rupee', '2': 8},
|
|
||||||
const {'1': 'Won', '2': 9},
|
|
||||||
const {'1': 'Yuan', '2': 10},
|
|
||||||
const {'1': 'Real', '2': 11},
|
|
||||||
const {'1': 'Lira', '2': 12},
|
|
||||||
const {'1': 'Rupiah', '2': 13},
|
|
||||||
const {'1': 'Franc', '2': 14},
|
|
||||||
const {'1': 'HongKongDollar', '2': 15},
|
|
||||||
const {'1': 'NewZealandDollar', '2': 16},
|
|
||||||
const {'1': 'Krona', '2': 17},
|
|
||||||
const {'1': 'NorwegianKrone', '2': 18},
|
|
||||||
const {'1': 'MexicanPeso', '2': 19},
|
|
||||||
const {'1': 'Rand', '2': 20},
|
|
||||||
const {'1': 'NewTaiwanDollar', '2': 21},
|
|
||||||
const {'1': 'DanishKrone', '2': 22},
|
|
||||||
const {'1': 'Baht', '2': 23},
|
|
||||||
const {'1': 'Forint', '2': 24},
|
|
||||||
const {'1': 'Koruna', '2': 25},
|
|
||||||
const {'1': 'Shekel', '2': 26},
|
|
||||||
const {'1': 'ChileanPeso', '2': 27},
|
|
||||||
const {'1': 'PhilippinePeso', '2': 28},
|
|
||||||
const {'1': 'Dirham', '2': 29},
|
|
||||||
const {'1': 'ColombianPeso', '2': 30},
|
|
||||||
const {'1': 'Riyal', '2': 31},
|
|
||||||
const {'1': 'Ringgit', '2': 32},
|
|
||||||
const {'1': 'Leu', '2': 33},
|
|
||||||
const {'1': 'ArgentinePeso', '2': 34},
|
|
||||||
const {'1': 'UruguayanPeso', '2': 35},
|
|
||||||
const {'1': 'Percent', '2': 36},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Descriptor for `NumberFormat`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
|
||||||
final $typed_data.Uint8List numberFormatDescriptor = $convert.base64Decode('CgxOdW1iZXJGb3JtYXQSCgoGTnVtYmVyEAASBwoDVVNEEAESEgoOQ2FuYWRpYW5Eb2xsYXIQAhIHCgNFVVIQBBIJCgVQb3VuZBAFEgcKA1llbhAGEgkKBVJ1YmxlEAcSCQoFUnVwZWUQCBIHCgNXb24QCRIICgRZdWFuEAoSCAoEUmVhbBALEggKBExpcmEQDBIKCgZSdXBpYWgQDRIJCgVGcmFuYxAOEhIKDkhvbmdLb25nRG9sbGFyEA8SFAoQTmV3WmVhbGFuZERvbGxhchAQEgkKBUtyb25hEBESEgoOTm9yd2VnaWFuS3JvbmUQEhIPCgtNZXhpY2FuUGVzbxATEggKBFJhbmQQFBITCg9OZXdUYWl3YW5Eb2xsYXIQFRIPCgtEYW5pc2hLcm9uZRAWEggKBEJhaHQQFxIKCgZGb3JpbnQQGBIKCgZLb3J1bmEQGRIKCgZTaGVrZWwQGhIPCgtDaGlsZWFuUGVzbxAbEhIKDlBoaWxpcHBpbmVQZXNvEBwSCgoGRGlyaGFtEB0SEQoNQ29sb21iaWFuUGVzbxAeEgkKBVJpeWFsEB8SCwoHUmluZ2dpdBAgEgcKA0xldRAhEhEKDUFyZ2VudGluZVBlc28QIhIRCg1VcnVndWF5YW5QZXNvECMSCwoHUGVyY2VudBAk');
|
|
||||||
@$core.Deprecated('Use numberTypeOptionDescriptor instead')
|
@$core.Deprecated('Use numberTypeOptionDescriptor instead')
|
||||||
const NumberTypeOption$json = const {
|
const NumberTypeOption$json = const {
|
||||||
'1': 'NumberTypeOption',
|
'1': 'NumberTypeOption',
|
||||||
|
@ -7,6 +7,7 @@ export './row_entities.pb.dart';
|
|||||||
export './cell_entities.pb.dart';
|
export './cell_entities.pb.dart';
|
||||||
export './url_type_option.pb.dart';
|
export './url_type_option.pb.dart';
|
||||||
export './checkbox_type_option.pb.dart';
|
export './checkbox_type_option.pb.dart';
|
||||||
|
export './format.pb.dart';
|
||||||
export './event_map.pb.dart';
|
export './event_map.pb.dart';
|
||||||
export './text_type_option.pb.dart';
|
export './text_type_option.pb.dart';
|
||||||
export './date_type_option.pb.dart';
|
export './date_type_option.pb.dart';
|
||||||
|
@ -9,20 +9,20 @@
|
|||||||
import 'dart:core' as $core;
|
import 'dart:core' as $core;
|
||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
class BlockEvent extends $pb.ProtobufEnum {
|
class TextBlockEvent extends $pb.ProtobufEnum {
|
||||||
static const BlockEvent GetBlockData = BlockEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetBlockData');
|
static const TextBlockEvent GetBlockData = TextBlockEvent._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'GetBlockData');
|
||||||
static const BlockEvent ApplyDelta = BlockEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDelta');
|
static const TextBlockEvent ApplyDelta = TextBlockEvent._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ApplyDelta');
|
||||||
static const BlockEvent ExportDocument = BlockEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ExportDocument');
|
static const TextBlockEvent ExportDocument = TextBlockEvent._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ExportDocument');
|
||||||
|
|
||||||
static const $core.List<BlockEvent> values = <BlockEvent> [
|
static const $core.List<TextBlockEvent> values = <TextBlockEvent> [
|
||||||
GetBlockData,
|
GetBlockData,
|
||||||
ApplyDelta,
|
ApplyDelta,
|
||||||
ExportDocument,
|
ExportDocument,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.Map<$core.int, BlockEvent> _byValue = $pb.ProtobufEnum.initByValue(values);
|
static final $core.Map<$core.int, TextBlockEvent> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||||
static BlockEvent? valueOf($core.int value) => _byValue[value];
|
static TextBlockEvent? valueOf($core.int value) => _byValue[value];
|
||||||
|
|
||||||
const BlockEvent._($core.int v, $core.String n) : super(v, n);
|
const TextBlockEvent._($core.int v, $core.String n) : super(v, n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,9 +8,9 @@
|
|||||||
import 'dart:core' as $core;
|
import 'dart:core' as $core;
|
||||||
import 'dart:convert' as $convert;
|
import 'dart:convert' as $convert;
|
||||||
import 'dart:typed_data' as $typed_data;
|
import 'dart:typed_data' as $typed_data;
|
||||||
@$core.Deprecated('Use blockEventDescriptor instead')
|
@$core.Deprecated('Use textBlockEventDescriptor instead')
|
||||||
const BlockEvent$json = const {
|
const TextBlockEvent$json = const {
|
||||||
'1': 'BlockEvent',
|
'1': 'TextBlockEvent',
|
||||||
'2': const [
|
'2': const [
|
||||||
const {'1': 'GetBlockData', '2': 0},
|
const {'1': 'GetBlockData', '2': 0},
|
||||||
const {'1': 'ApplyDelta', '2': 1},
|
const {'1': 'ApplyDelta', '2': 1},
|
||||||
@ -18,5 +18,5 @@ const BlockEvent$json = const {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `BlockEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
/// Descriptor for `TextBlockEvent`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
final $typed_data.Uint8List blockEventDescriptor = $convert.base64Decode('CgpCbG9ja0V2ZW50EhAKDEdldEJsb2NrRGF0YRAAEg4KCkFwcGx5RGVsdGEQARISCg5FeHBvcnREb2N1bWVudBAC');
|
final $typed_data.Uint8List textBlockEventDescriptor = $convert.base64Decode('Cg5UZXh0QmxvY2tFdmVudBIQCgxHZXRCbG9ja0RhdGEQABIOCgpBcHBseURlbHRhEAESEgoORXhwb3J0RG9jdW1lbnQQAg==');
|
||||||
|
@ -72,7 +72,7 @@ dependencies:
|
|||||||
# Use with the CupertinoIcons class for iOS style icons.
|
# Use with the CupertinoIcons class for iOS style icons.
|
||||||
cupertino_icons: ^1.0.2
|
cupertino_icons: ^1.0.2
|
||||||
device_info_plus: ^3.2.1
|
device_info_plus: ^3.2.1
|
||||||
fluttertoast: ^8.0.8
|
fluttertoast: ^8.0.9
|
||||||
table_calendar: ^3.0.5
|
table_calendar: ^3.0.5
|
||||||
reorderables: ^0.5.0
|
reorderables: ^0.5.0
|
||||||
linked_scroll_controller: ^0.2.0
|
linked_scroll_controller: ^0.2.0
|
||||||
|
1
frontend/rust-lib/Cargo.lock
generated
1
frontend/rust-lib/Cargo.lock
generated
@ -937,6 +937,7 @@ dependencies = [
|
|||||||
"flowy-revision",
|
"flowy-revision",
|
||||||
"flowy-sync",
|
"flowy-sync",
|
||||||
"flowy-test",
|
"flowy-test",
|
||||||
|
"futures",
|
||||||
"indexmap",
|
"indexmap",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"lib-dispatch",
|
"lib-dispatch",
|
||||||
|
@ -241,11 +241,11 @@ pub trait ViewDataProcessor {
|
|||||||
|
|
||||||
fn close_container(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
fn close_container(&self, view_id: &str) -> FutureResult<(), FlowyError>;
|
||||||
|
|
||||||
fn delta_bytes(&self, view_id: &str) -> FutureResult<Bytes, FlowyError>;
|
fn view_delta_data(&self, view_id: &str) -> FutureResult<Bytes, FlowyError>;
|
||||||
|
|
||||||
fn create_default_view(&self, user_id: &str, view_id: &str) -> FutureResult<Bytes, FlowyError>;
|
fn create_default_view(&self, user_id: &str, view_id: &str) -> FutureResult<Bytes, FlowyError>;
|
||||||
|
|
||||||
fn process_create_view_data(&self, user_id: &str, view_id: &str, data: Vec<u8>) -> FutureResult<Bytes, FlowyError>;
|
fn process_view_delta_data(&self, user_id: &str, view_id: &str, data: Vec<u8>) -> FutureResult<Bytes, FlowyError>;
|
||||||
|
|
||||||
fn data_type(&self) -> ViewDataType;
|
fn data_type(&self) -> ViewDataType;
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@ impl ViewController {
|
|||||||
params.data = view_data.to_vec();
|
params.data = view_data.to_vec();
|
||||||
} else {
|
} else {
|
||||||
let delta_data = processor
|
let delta_data = processor
|
||||||
.process_create_view_data(&user_id, ¶ms.view_id, params.data.clone())
|
.process_view_delta_data(&user_id, ¶ms.view_id, params.data.clone())
|
||||||
.await?;
|
.await?;
|
||||||
let _ = self
|
let _ = self
|
||||||
.create_view(¶ms.view_id, params.data_type.clone(), delta_data)
|
.create_view(¶ms.view_id, params.data_type.clone(), delta_data)
|
||||||
@ -176,7 +176,7 @@ impl ViewController {
|
|||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
let processor = self.get_data_processor(&view.data_type)?;
|
let processor = self.get_data_processor(&view.data_type)?;
|
||||||
let delta_bytes = processor.delta_bytes(view_id).await?;
|
let delta_bytes = processor.view_delta_data(view_id).await?;
|
||||||
let duplicate_params = CreateViewParams {
|
let duplicate_params = CreateViewParams {
|
||||||
belong_to_id: view.belong_to_id.clone(),
|
belong_to_id: view.belong_to_id.clone(),
|
||||||
name: format!("{} (copy)", &view.name),
|
name: format!("{} (copy)", &view.name),
|
||||||
@ -238,7 +238,7 @@ impl ViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ViewController {
|
impl ViewController {
|
||||||
#[tracing::instrument(level = "debug", skip(self), err)]
|
#[tracing::instrument(level = "debug", skip(self, params), err)]
|
||||||
async fn create_view_on_server(&self, params: CreateViewParams) -> Result<View, FlowyError> {
|
async fn create_view_on_server(&self, params: CreateViewParams) -> Result<View, FlowyError> {
|
||||||
let token = self.user.token()?;
|
let token = self.user.token()?;
|
||||||
let view = self.cloud_service.create_view(&token, params).await?;
|
let view = self.cloud_service.create_view(&token, params).await?;
|
||||||
|
@ -37,6 +37,7 @@ serde_repr = "0.1"
|
|||||||
indexmap = {version = "1.8.1", features = ["serde"]}
|
indexmap = {version = "1.8.1", features = ["serde"]}
|
||||||
fancy-regex = "0.10.0"
|
fancy-regex = "0.10.0"
|
||||||
url = { version = "2"}
|
url = { version = "2"}
|
||||||
|
futures = "0.3.15"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
flowy-test = { path = "../flowy-test" }
|
flowy-test = { path = "../flowy-test" }
|
||||||
|
@ -154,11 +154,10 @@ pub async fn make_grid_view_data(
|
|||||||
grid_manager: Arc<GridManager>,
|
grid_manager: Arc<GridManager>,
|
||||||
build_context: BuildGridContext,
|
build_context: BuildGridContext,
|
||||||
) -> FlowyResult<Bytes> {
|
) -> FlowyResult<Bytes> {
|
||||||
let block_id = build_context.block_meta.block_id.clone();
|
|
||||||
let grid_meta = GridMeta {
|
let grid_meta = GridMeta {
|
||||||
grid_id: view_id.to_string(),
|
grid_id: view_id.to_string(),
|
||||||
fields: build_context.field_metas,
|
fields: build_context.field_metas,
|
||||||
blocks: vec![build_context.block_meta],
|
blocks: build_context.blocks,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create grid
|
// Create grid
|
||||||
@ -168,19 +167,23 @@ pub async fn make_grid_view_data(
|
|||||||
Revision::initial_revision(user_id, view_id, grid_delta_data.clone()).into();
|
Revision::initial_revision(user_id, view_id, grid_delta_data.clone()).into();
|
||||||
let _ = grid_manager.create_grid(view_id, repeated_revision).await?;
|
let _ = grid_manager.create_grid(view_id, repeated_revision).await?;
|
||||||
|
|
||||||
// Indexing the block's rows
|
for block_meta_data in build_context.blocks_meta_data {
|
||||||
build_context.block_meta_data.rows.iter().for_each(|row| {
|
let block_id = block_meta_data.block_id.clone();
|
||||||
let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create grid's block
|
// Indexing the block's rows
|
||||||
let grid_block_meta_delta = make_block_meta_delta(&build_context.block_meta_data);
|
block_meta_data.rows.iter().for_each(|row| {
|
||||||
let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes();
|
let _ = grid_manager.block_index_cache.insert(&row.block_id, &row.id);
|
||||||
let repeated_revision: RepeatedRevision =
|
});
|
||||||
Revision::initial_revision(user_id, &block_id, block_meta_delta_data).into();
|
|
||||||
let _ = grid_manager
|
// Create grid's block
|
||||||
.create_grid_block_meta(&block_id, repeated_revision)
|
let grid_block_meta_delta = make_block_meta_delta(&block_meta_data);
|
||||||
.await?;
|
let block_meta_delta_data = grid_block_meta_delta.to_delta_bytes();
|
||||||
|
let repeated_revision: RepeatedRevision =
|
||||||
|
Revision::initial_revision(user_id, &block_id, block_meta_delta_data).into();
|
||||||
|
let _ = grid_manager
|
||||||
|
.create_grid_block_meta(&block_id, repeated_revision)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(grid_delta_data)
|
Ok(grid_delta_data)
|
||||||
}
|
}
|
||||||
|
207
frontend/rust-lib/flowy-grid/src/protobuf/model/format.rs
Normal file
207
frontend/rust-lib/flowy-grid/src/protobuf/model/format.rs
Normal file
@ -0,0 +1,207 @@
|
|||||||
|
// This file is generated by rust-protobuf 2.25.2. Do not edit
|
||||||
|
// @generated
|
||||||
|
|
||||||
|
// https://github.com/rust-lang/rust-clippy/issues/702
|
||||||
|
#![allow(unknown_lints)]
|
||||||
|
#![allow(clippy::all)]
|
||||||
|
|
||||||
|
#![allow(unused_attributes)]
|
||||||
|
#![cfg_attr(rustfmt, rustfmt::skip)]
|
||||||
|
|
||||||
|
#![allow(box_pointers)]
|
||||||
|
#![allow(dead_code)]
|
||||||
|
#![allow(missing_docs)]
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(non_snake_case)]
|
||||||
|
#![allow(non_upper_case_globals)]
|
||||||
|
#![allow(trivial_casts)]
|
||||||
|
#![allow(unused_imports)]
|
||||||
|
#![allow(unused_results)]
|
||||||
|
//! Generated file from `format.proto`
|
||||||
|
|
||||||
|
/// Generated files are compatible only with the same version
|
||||||
|
/// of protobuf runtime.
|
||||||
|
// const _PROTOBUF_VERSION_CHECK: () = ::protobuf::VERSION_2_25_2;
|
||||||
|
|
||||||
|
#[derive(Clone,PartialEq,Eq,Debug,Hash)]
|
||||||
|
pub enum NumberFormat {
|
||||||
|
Number = 0,
|
||||||
|
USD = 1,
|
||||||
|
CanadianDollar = 2,
|
||||||
|
EUR = 4,
|
||||||
|
Pound = 5,
|
||||||
|
Yen = 6,
|
||||||
|
Ruble = 7,
|
||||||
|
Rupee = 8,
|
||||||
|
Won = 9,
|
||||||
|
Yuan = 10,
|
||||||
|
Real = 11,
|
||||||
|
Lira = 12,
|
||||||
|
Rupiah = 13,
|
||||||
|
Franc = 14,
|
||||||
|
HongKongDollar = 15,
|
||||||
|
NewZealandDollar = 16,
|
||||||
|
Krona = 17,
|
||||||
|
NorwegianKrone = 18,
|
||||||
|
MexicanPeso = 19,
|
||||||
|
Rand = 20,
|
||||||
|
NewTaiwanDollar = 21,
|
||||||
|
DanishKrone = 22,
|
||||||
|
Baht = 23,
|
||||||
|
Forint = 24,
|
||||||
|
Koruna = 25,
|
||||||
|
Shekel = 26,
|
||||||
|
ChileanPeso = 27,
|
||||||
|
PhilippinePeso = 28,
|
||||||
|
Dirham = 29,
|
||||||
|
ColombianPeso = 30,
|
||||||
|
Riyal = 31,
|
||||||
|
Ringgit = 32,
|
||||||
|
Leu = 33,
|
||||||
|
ArgentinePeso = 34,
|
||||||
|
UruguayanPeso = 35,
|
||||||
|
Percent = 36,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::protobuf::ProtobufEnum for NumberFormat {
|
||||||
|
fn value(&self) -> i32 {
|
||||||
|
*self as i32
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_i32(value: i32) -> ::std::option::Option<NumberFormat> {
|
||||||
|
match value {
|
||||||
|
0 => ::std::option::Option::Some(NumberFormat::Number),
|
||||||
|
1 => ::std::option::Option::Some(NumberFormat::USD),
|
||||||
|
2 => ::std::option::Option::Some(NumberFormat::CanadianDollar),
|
||||||
|
4 => ::std::option::Option::Some(NumberFormat::EUR),
|
||||||
|
5 => ::std::option::Option::Some(NumberFormat::Pound),
|
||||||
|
6 => ::std::option::Option::Some(NumberFormat::Yen),
|
||||||
|
7 => ::std::option::Option::Some(NumberFormat::Ruble),
|
||||||
|
8 => ::std::option::Option::Some(NumberFormat::Rupee),
|
||||||
|
9 => ::std::option::Option::Some(NumberFormat::Won),
|
||||||
|
10 => ::std::option::Option::Some(NumberFormat::Yuan),
|
||||||
|
11 => ::std::option::Option::Some(NumberFormat::Real),
|
||||||
|
12 => ::std::option::Option::Some(NumberFormat::Lira),
|
||||||
|
13 => ::std::option::Option::Some(NumberFormat::Rupiah),
|
||||||
|
14 => ::std::option::Option::Some(NumberFormat::Franc),
|
||||||
|
15 => ::std::option::Option::Some(NumberFormat::HongKongDollar),
|
||||||
|
16 => ::std::option::Option::Some(NumberFormat::NewZealandDollar),
|
||||||
|
17 => ::std::option::Option::Some(NumberFormat::Krona),
|
||||||
|
18 => ::std::option::Option::Some(NumberFormat::NorwegianKrone),
|
||||||
|
19 => ::std::option::Option::Some(NumberFormat::MexicanPeso),
|
||||||
|
20 => ::std::option::Option::Some(NumberFormat::Rand),
|
||||||
|
21 => ::std::option::Option::Some(NumberFormat::NewTaiwanDollar),
|
||||||
|
22 => ::std::option::Option::Some(NumberFormat::DanishKrone),
|
||||||
|
23 => ::std::option::Option::Some(NumberFormat::Baht),
|
||||||
|
24 => ::std::option::Option::Some(NumberFormat::Forint),
|
||||||
|
25 => ::std::option::Option::Some(NumberFormat::Koruna),
|
||||||
|
26 => ::std::option::Option::Some(NumberFormat::Shekel),
|
||||||
|
27 => ::std::option::Option::Some(NumberFormat::ChileanPeso),
|
||||||
|
28 => ::std::option::Option::Some(NumberFormat::PhilippinePeso),
|
||||||
|
29 => ::std::option::Option::Some(NumberFormat::Dirham),
|
||||||
|
30 => ::std::option::Option::Some(NumberFormat::ColombianPeso),
|
||||||
|
31 => ::std::option::Option::Some(NumberFormat::Riyal),
|
||||||
|
32 => ::std::option::Option::Some(NumberFormat::Ringgit),
|
||||||
|
33 => ::std::option::Option::Some(NumberFormat::Leu),
|
||||||
|
34 => ::std::option::Option::Some(NumberFormat::ArgentinePeso),
|
||||||
|
35 => ::std::option::Option::Some(NumberFormat::UruguayanPeso),
|
||||||
|
36 => ::std::option::Option::Some(NumberFormat::Percent),
|
||||||
|
_ => ::std::option::Option::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn values() -> &'static [Self] {
|
||||||
|
static values: &'static [NumberFormat] = &[
|
||||||
|
NumberFormat::Number,
|
||||||
|
NumberFormat::USD,
|
||||||
|
NumberFormat::CanadianDollar,
|
||||||
|
NumberFormat::EUR,
|
||||||
|
NumberFormat::Pound,
|
||||||
|
NumberFormat::Yen,
|
||||||
|
NumberFormat::Ruble,
|
||||||
|
NumberFormat::Rupee,
|
||||||
|
NumberFormat::Won,
|
||||||
|
NumberFormat::Yuan,
|
||||||
|
NumberFormat::Real,
|
||||||
|
NumberFormat::Lira,
|
||||||
|
NumberFormat::Rupiah,
|
||||||
|
NumberFormat::Franc,
|
||||||
|
NumberFormat::HongKongDollar,
|
||||||
|
NumberFormat::NewZealandDollar,
|
||||||
|
NumberFormat::Krona,
|
||||||
|
NumberFormat::NorwegianKrone,
|
||||||
|
NumberFormat::MexicanPeso,
|
||||||
|
NumberFormat::Rand,
|
||||||
|
NumberFormat::NewTaiwanDollar,
|
||||||
|
NumberFormat::DanishKrone,
|
||||||
|
NumberFormat::Baht,
|
||||||
|
NumberFormat::Forint,
|
||||||
|
NumberFormat::Koruna,
|
||||||
|
NumberFormat::Shekel,
|
||||||
|
NumberFormat::ChileanPeso,
|
||||||
|
NumberFormat::PhilippinePeso,
|
||||||
|
NumberFormat::Dirham,
|
||||||
|
NumberFormat::ColombianPeso,
|
||||||
|
NumberFormat::Riyal,
|
||||||
|
NumberFormat::Ringgit,
|
||||||
|
NumberFormat::Leu,
|
||||||
|
NumberFormat::ArgentinePeso,
|
||||||
|
NumberFormat::UruguayanPeso,
|
||||||
|
NumberFormat::Percent,
|
||||||
|
];
|
||||||
|
values
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
|
||||||
|
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||||
|
descriptor.get(|| {
|
||||||
|
::protobuf::reflect::EnumDescriptor::new_pb_name::<NumberFormat>("NumberFormat", file_descriptor_proto())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::marker::Copy for NumberFormat {
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::std::default::Default for NumberFormat {
|
||||||
|
fn default() -> Self {
|
||||||
|
NumberFormat::Number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ::protobuf::reflect::ProtobufValue for NumberFormat {
|
||||||
|
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
||||||
|
::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||||
|
\n\x0cformat.proto*\xf8\x03\n\x0cNumberFormat\x12\n\n\x06Number\x10\0\
|
||||||
|
\x12\x07\n\x03USD\x10\x01\x12\x12\n\x0eCanadianDollar\x10\x02\x12\x07\n\
|
||||||
|
\x03EUR\x10\x04\x12\t\n\x05Pound\x10\x05\x12\x07\n\x03Yen\x10\x06\x12\t\
|
||||||
|
\n\x05Ruble\x10\x07\x12\t\n\x05Rupee\x10\x08\x12\x07\n\x03Won\x10\t\x12\
|
||||||
|
\x08\n\x04Yuan\x10\n\x12\x08\n\x04Real\x10\x0b\x12\x08\n\x04Lira\x10\x0c\
|
||||||
|
\x12\n\n\x06Rupiah\x10\r\x12\t\n\x05Franc\x10\x0e\x12\x12\n\x0eHongKongD\
|
||||||
|
ollar\x10\x0f\x12\x14\n\x10NewZealandDollar\x10\x10\x12\t\n\x05Krona\x10\
|
||||||
|
\x11\x12\x12\n\x0eNorwegianKrone\x10\x12\x12\x0f\n\x0bMexicanPeso\x10\
|
||||||
|
\x13\x12\x08\n\x04Rand\x10\x14\x12\x13\n\x0fNewTaiwanDollar\x10\x15\x12\
|
||||||
|
\x0f\n\x0bDanishKrone\x10\x16\x12\x08\n\x04Baht\x10\x17\x12\n\n\x06Forin\
|
||||||
|
t\x10\x18\x12\n\n\x06Koruna\x10\x19\x12\n\n\x06Shekel\x10\x1a\x12\x0f\n\
|
||||||
|
\x0bChileanPeso\x10\x1b\x12\x12\n\x0ePhilippinePeso\x10\x1c\x12\n\n\x06D\
|
||||||
|
irham\x10\x1d\x12\x11\n\rColombianPeso\x10\x1e\x12\t\n\x05Riyal\x10\x1f\
|
||||||
|
\x12\x0b\n\x07Ringgit\x10\x20\x12\x07\n\x03Leu\x10!\x12\x11\n\rArgentine\
|
||||||
|
Peso\x10\"\x12\x11\n\rUruguayanPeso\x10#\x12\x0b\n\x07Percent\x10$b\x06p\
|
||||||
|
roto3\
|
||||||
|
";
|
||||||
|
|
||||||
|
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||||
|
|
||||||
|
fn parse_descriptor_proto() -> ::protobuf::descriptor::FileDescriptorProto {
|
||||||
|
::protobuf::Message::parse_from_bytes(file_descriptor_proto_data).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn file_descriptor_proto() -> &'static ::protobuf::descriptor::FileDescriptorProto {
|
||||||
|
file_descriptor_proto_lazy.get(|| {
|
||||||
|
parse_descriptor_proto()
|
||||||
|
})
|
||||||
|
}
|
@ -25,6 +25,9 @@ pub use url_type_option::*;
|
|||||||
mod checkbox_type_option;
|
mod checkbox_type_option;
|
||||||
pub use checkbox_type_option::*;
|
pub use checkbox_type_option::*;
|
||||||
|
|
||||||
|
mod format;
|
||||||
|
pub use format::*;
|
||||||
|
|
||||||
mod event_map;
|
mod event_map;
|
||||||
pub use event_map::*;
|
pub use event_map::*;
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@
|
|||||||
#[derive(PartialEq,Clone,Default)]
|
#[derive(PartialEq,Clone,Default)]
|
||||||
pub struct NumberTypeOption {
|
pub struct NumberTypeOption {
|
||||||
// message fields
|
// message fields
|
||||||
pub format: NumberFormat,
|
pub format: super::format::NumberFormat,
|
||||||
pub scale: u32,
|
pub scale: u32,
|
||||||
pub symbol: ::std::string::String,
|
pub symbol: ::std::string::String,
|
||||||
pub sign_positive: bool,
|
pub sign_positive: bool,
|
||||||
@ -50,15 +50,15 @@ impl NumberTypeOption {
|
|||||||
// .NumberFormat format = 1;
|
// .NumberFormat format = 1;
|
||||||
|
|
||||||
|
|
||||||
pub fn get_format(&self) -> NumberFormat {
|
pub fn get_format(&self) -> super::format::NumberFormat {
|
||||||
self.format
|
self.format
|
||||||
}
|
}
|
||||||
pub fn clear_format(&mut self) {
|
pub fn clear_format(&mut self) {
|
||||||
self.format = NumberFormat::Number;
|
self.format = super::format::NumberFormat::Number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Param is passed by value, moved
|
// Param is passed by value, moved
|
||||||
pub fn set_format(&mut self, v: NumberFormat) {
|
pub fn set_format(&mut self, v: super::format::NumberFormat) {
|
||||||
self.format = v;
|
self.format = v;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,7 +189,7 @@ impl ::protobuf::Message for NumberTypeOption {
|
|||||||
#[allow(unused_variables)]
|
#[allow(unused_variables)]
|
||||||
fn compute_size(&self) -> u32 {
|
fn compute_size(&self) -> u32 {
|
||||||
let mut my_size = 0;
|
let mut my_size = 0;
|
||||||
if self.format != NumberFormat::Number {
|
if self.format != super::format::NumberFormat::Number {
|
||||||
my_size += ::protobuf::rt::enum_size(1, self.format);
|
my_size += ::protobuf::rt::enum_size(1, self.format);
|
||||||
}
|
}
|
||||||
if self.scale != 0 {
|
if self.scale != 0 {
|
||||||
@ -210,7 +210,7 @@ impl ::protobuf::Message for NumberTypeOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
fn write_to_with_cached_sizes(&self, os: &mut ::protobuf::CodedOutputStream<'_>) -> ::protobuf::ProtobufResult<()> {
|
||||||
if self.format != NumberFormat::Number {
|
if self.format != super::format::NumberFormat::Number {
|
||||||
os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.format))?;
|
os.write_enum(1, ::protobuf::ProtobufEnum::value(&self.format))?;
|
||||||
}
|
}
|
||||||
if self.scale != 0 {
|
if self.scale != 0 {
|
||||||
@ -263,7 +263,7 @@ impl ::protobuf::Message for NumberTypeOption {
|
|||||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::MessageDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
||||||
descriptor.get(|| {
|
descriptor.get(|| {
|
||||||
let mut fields = ::std::vec::Vec::new();
|
let mut fields = ::std::vec::Vec::new();
|
||||||
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<NumberFormat>>(
|
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeEnum<super::format::NumberFormat>>(
|
||||||
"format",
|
"format",
|
||||||
|m: &NumberTypeOption| { &m.format },
|
|m: &NumberTypeOption| { &m.format },
|
||||||
|m: &mut NumberTypeOption| { &mut m.format },
|
|m: &mut NumberTypeOption| { &mut m.format },
|
||||||
@ -304,7 +304,7 @@ impl ::protobuf::Message for NumberTypeOption {
|
|||||||
|
|
||||||
impl ::protobuf::Clear for NumberTypeOption {
|
impl ::protobuf::Clear for NumberTypeOption {
|
||||||
fn clear(&mut self) {
|
fn clear(&mut self) {
|
||||||
self.format = NumberFormat::Number;
|
self.format = super::format::NumberFormat::Number;
|
||||||
self.scale = 0;
|
self.scale = 0;
|
||||||
self.symbol.clear();
|
self.symbol.clear();
|
||||||
self.sign_positive = false;
|
self.sign_positive = false;
|
||||||
@ -325,179 +325,13 @@ impl ::protobuf::reflect::ProtobufValue for NumberTypeOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone,PartialEq,Eq,Debug,Hash)]
|
|
||||||
pub enum NumberFormat {
|
|
||||||
Number = 0,
|
|
||||||
USD = 1,
|
|
||||||
CanadianDollar = 2,
|
|
||||||
EUR = 4,
|
|
||||||
Pound = 5,
|
|
||||||
Yen = 6,
|
|
||||||
Ruble = 7,
|
|
||||||
Rupee = 8,
|
|
||||||
Won = 9,
|
|
||||||
Yuan = 10,
|
|
||||||
Real = 11,
|
|
||||||
Lira = 12,
|
|
||||||
Rupiah = 13,
|
|
||||||
Franc = 14,
|
|
||||||
HongKongDollar = 15,
|
|
||||||
NewZealandDollar = 16,
|
|
||||||
Krona = 17,
|
|
||||||
NorwegianKrone = 18,
|
|
||||||
MexicanPeso = 19,
|
|
||||||
Rand = 20,
|
|
||||||
NewTaiwanDollar = 21,
|
|
||||||
DanishKrone = 22,
|
|
||||||
Baht = 23,
|
|
||||||
Forint = 24,
|
|
||||||
Koruna = 25,
|
|
||||||
Shekel = 26,
|
|
||||||
ChileanPeso = 27,
|
|
||||||
PhilippinePeso = 28,
|
|
||||||
Dirham = 29,
|
|
||||||
ColombianPeso = 30,
|
|
||||||
Riyal = 31,
|
|
||||||
Ringgit = 32,
|
|
||||||
Leu = 33,
|
|
||||||
ArgentinePeso = 34,
|
|
||||||
UruguayanPeso = 35,
|
|
||||||
Percent = 36,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::protobuf::ProtobufEnum for NumberFormat {
|
|
||||||
fn value(&self) -> i32 {
|
|
||||||
*self as i32
|
|
||||||
}
|
|
||||||
|
|
||||||
fn from_i32(value: i32) -> ::std::option::Option<NumberFormat> {
|
|
||||||
match value {
|
|
||||||
0 => ::std::option::Option::Some(NumberFormat::Number),
|
|
||||||
1 => ::std::option::Option::Some(NumberFormat::USD),
|
|
||||||
2 => ::std::option::Option::Some(NumberFormat::CanadianDollar),
|
|
||||||
4 => ::std::option::Option::Some(NumberFormat::EUR),
|
|
||||||
5 => ::std::option::Option::Some(NumberFormat::Pound),
|
|
||||||
6 => ::std::option::Option::Some(NumberFormat::Yen),
|
|
||||||
7 => ::std::option::Option::Some(NumberFormat::Ruble),
|
|
||||||
8 => ::std::option::Option::Some(NumberFormat::Rupee),
|
|
||||||
9 => ::std::option::Option::Some(NumberFormat::Won),
|
|
||||||
10 => ::std::option::Option::Some(NumberFormat::Yuan),
|
|
||||||
11 => ::std::option::Option::Some(NumberFormat::Real),
|
|
||||||
12 => ::std::option::Option::Some(NumberFormat::Lira),
|
|
||||||
13 => ::std::option::Option::Some(NumberFormat::Rupiah),
|
|
||||||
14 => ::std::option::Option::Some(NumberFormat::Franc),
|
|
||||||
15 => ::std::option::Option::Some(NumberFormat::HongKongDollar),
|
|
||||||
16 => ::std::option::Option::Some(NumberFormat::NewZealandDollar),
|
|
||||||
17 => ::std::option::Option::Some(NumberFormat::Krona),
|
|
||||||
18 => ::std::option::Option::Some(NumberFormat::NorwegianKrone),
|
|
||||||
19 => ::std::option::Option::Some(NumberFormat::MexicanPeso),
|
|
||||||
20 => ::std::option::Option::Some(NumberFormat::Rand),
|
|
||||||
21 => ::std::option::Option::Some(NumberFormat::NewTaiwanDollar),
|
|
||||||
22 => ::std::option::Option::Some(NumberFormat::DanishKrone),
|
|
||||||
23 => ::std::option::Option::Some(NumberFormat::Baht),
|
|
||||||
24 => ::std::option::Option::Some(NumberFormat::Forint),
|
|
||||||
25 => ::std::option::Option::Some(NumberFormat::Koruna),
|
|
||||||
26 => ::std::option::Option::Some(NumberFormat::Shekel),
|
|
||||||
27 => ::std::option::Option::Some(NumberFormat::ChileanPeso),
|
|
||||||
28 => ::std::option::Option::Some(NumberFormat::PhilippinePeso),
|
|
||||||
29 => ::std::option::Option::Some(NumberFormat::Dirham),
|
|
||||||
30 => ::std::option::Option::Some(NumberFormat::ColombianPeso),
|
|
||||||
31 => ::std::option::Option::Some(NumberFormat::Riyal),
|
|
||||||
32 => ::std::option::Option::Some(NumberFormat::Ringgit),
|
|
||||||
33 => ::std::option::Option::Some(NumberFormat::Leu),
|
|
||||||
34 => ::std::option::Option::Some(NumberFormat::ArgentinePeso),
|
|
||||||
35 => ::std::option::Option::Some(NumberFormat::UruguayanPeso),
|
|
||||||
36 => ::std::option::Option::Some(NumberFormat::Percent),
|
|
||||||
_ => ::std::option::Option::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn values() -> &'static [Self] {
|
|
||||||
static values: &'static [NumberFormat] = &[
|
|
||||||
NumberFormat::Number,
|
|
||||||
NumberFormat::USD,
|
|
||||||
NumberFormat::CanadianDollar,
|
|
||||||
NumberFormat::EUR,
|
|
||||||
NumberFormat::Pound,
|
|
||||||
NumberFormat::Yen,
|
|
||||||
NumberFormat::Ruble,
|
|
||||||
NumberFormat::Rupee,
|
|
||||||
NumberFormat::Won,
|
|
||||||
NumberFormat::Yuan,
|
|
||||||
NumberFormat::Real,
|
|
||||||
NumberFormat::Lira,
|
|
||||||
NumberFormat::Rupiah,
|
|
||||||
NumberFormat::Franc,
|
|
||||||
NumberFormat::HongKongDollar,
|
|
||||||
NumberFormat::NewZealandDollar,
|
|
||||||
NumberFormat::Krona,
|
|
||||||
NumberFormat::NorwegianKrone,
|
|
||||||
NumberFormat::MexicanPeso,
|
|
||||||
NumberFormat::Rand,
|
|
||||||
NumberFormat::NewTaiwanDollar,
|
|
||||||
NumberFormat::DanishKrone,
|
|
||||||
NumberFormat::Baht,
|
|
||||||
NumberFormat::Forint,
|
|
||||||
NumberFormat::Koruna,
|
|
||||||
NumberFormat::Shekel,
|
|
||||||
NumberFormat::ChileanPeso,
|
|
||||||
NumberFormat::PhilippinePeso,
|
|
||||||
NumberFormat::Dirham,
|
|
||||||
NumberFormat::ColombianPeso,
|
|
||||||
NumberFormat::Riyal,
|
|
||||||
NumberFormat::Ringgit,
|
|
||||||
NumberFormat::Leu,
|
|
||||||
NumberFormat::ArgentinePeso,
|
|
||||||
NumberFormat::UruguayanPeso,
|
|
||||||
NumberFormat::Percent,
|
|
||||||
];
|
|
||||||
values
|
|
||||||
}
|
|
||||||
|
|
||||||
fn enum_descriptor_static() -> &'static ::protobuf::reflect::EnumDescriptor {
|
|
||||||
static descriptor: ::protobuf::rt::LazyV2<::protobuf::reflect::EnumDescriptor> = ::protobuf::rt::LazyV2::INIT;
|
|
||||||
descriptor.get(|| {
|
|
||||||
::protobuf::reflect::EnumDescriptor::new_pb_name::<NumberFormat>("NumberFormat", file_descriptor_proto())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::std::marker::Copy for NumberFormat {
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::std::default::Default for NumberFormat {
|
|
||||||
fn default() -> Self {
|
|
||||||
NumberFormat::Number
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ::protobuf::reflect::ProtobufValue for NumberFormat {
|
|
||||||
fn as_ref(&self) -> ::protobuf::reflect::ReflectValueRef {
|
|
||||||
::protobuf::reflect::ReflectValueRef::Enum(::protobuf::ProtobufEnum::descriptor(self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static file_descriptor_proto_data: &'static [u8] = b"\
|
static file_descriptor_proto_data: &'static [u8] = b"\
|
||||||
\n\x18number_type_option.proto\"\xa0\x01\n\x10NumberTypeOption\x12%\n\
|
\n\x18number_type_option.proto\x1a\x0cformat.proto\"\xa0\x01\n\x10Number\
|
||||||
\x06format\x18\x01\x20\x01(\x0e2\r.NumberFormatR\x06format\x12\x14\n\x05\
|
TypeOption\x12%\n\x06format\x18\x01\x20\x01(\x0e2\r.NumberFormatR\x06for\
|
||||||
scale\x18\x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\x18\x03\x20\x01(\
|
mat\x12\x14\n\x05scale\x18\x02\x20\x01(\rR\x05scale\x12\x16\n\x06symbol\
|
||||||
\tR\x06symbol\x12#\n\rsign_positive\x18\x04\x20\x01(\x08R\x0csignPositiv\
|
\x18\x03\x20\x01(\tR\x06symbol\x12#\n\rsign_positive\x18\x04\x20\x01(\
|
||||||
e\x12\x12\n\x04name\x18\x05\x20\x01(\tR\x04name*\xf8\x03\n\x0cNumberForm\
|
\x08R\x0csignPositive\x12\x12\n\x04name\x18\x05\x20\x01(\tR\x04nameb\x06\
|
||||||
at\x12\n\n\x06Number\x10\0\x12\x07\n\x03USD\x10\x01\x12\x12\n\x0eCanadia\
|
proto3\
|
||||||
nDollar\x10\x02\x12\x07\n\x03EUR\x10\x04\x12\t\n\x05Pound\x10\x05\x12\
|
|
||||||
\x07\n\x03Yen\x10\x06\x12\t\n\x05Ruble\x10\x07\x12\t\n\x05Rupee\x10\x08\
|
|
||||||
\x12\x07\n\x03Won\x10\t\x12\x08\n\x04Yuan\x10\n\x12\x08\n\x04Real\x10\
|
|
||||||
\x0b\x12\x08\n\x04Lira\x10\x0c\x12\n\n\x06Rupiah\x10\r\x12\t\n\x05Franc\
|
|
||||||
\x10\x0e\x12\x12\n\x0eHongKongDollar\x10\x0f\x12\x14\n\x10NewZealandDoll\
|
|
||||||
ar\x10\x10\x12\t\n\x05Krona\x10\x11\x12\x12\n\x0eNorwegianKrone\x10\x12\
|
|
||||||
\x12\x0f\n\x0bMexicanPeso\x10\x13\x12\x08\n\x04Rand\x10\x14\x12\x13\n\
|
|
||||||
\x0fNewTaiwanDollar\x10\x15\x12\x0f\n\x0bDanishKrone\x10\x16\x12\x08\n\
|
|
||||||
\x04Baht\x10\x17\x12\n\n\x06Forint\x10\x18\x12\n\n\x06Koruna\x10\x19\x12\
|
|
||||||
\n\n\x06Shekel\x10\x1a\x12\x0f\n\x0bChileanPeso\x10\x1b\x12\x12\n\x0ePhi\
|
|
||||||
lippinePeso\x10\x1c\x12\n\n\x06Dirham\x10\x1d\x12\x11\n\rColombianPeso\
|
|
||||||
\x10\x1e\x12\t\n\x05Riyal\x10\x1f\x12\x0b\n\x07Ringgit\x10\x20\x12\x07\n\
|
|
||||||
\x03Leu\x10!\x12\x11\n\rArgentinePeso\x10\"\x12\x11\n\rUruguayanPeso\x10\
|
|
||||||
#\x12\x0b\n\x07Percent\x10$b\x06proto3\
|
|
||||||
";
|
";
|
||||||
|
|
||||||
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;
|
||||||
|
40
frontend/rust-lib/flowy-grid/src/protobuf/proto/format.proto
Normal file
40
frontend/rust-lib/flowy-grid/src/protobuf/proto/format.proto
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
syntax = "proto3";
|
||||||
|
|
||||||
|
enum NumberFormat {
|
||||||
|
Number = 0;
|
||||||
|
USD = 1;
|
||||||
|
CanadianDollar = 2;
|
||||||
|
EUR = 4;
|
||||||
|
Pound = 5;
|
||||||
|
Yen = 6;
|
||||||
|
Ruble = 7;
|
||||||
|
Rupee = 8;
|
||||||
|
Won = 9;
|
||||||
|
Yuan = 10;
|
||||||
|
Real = 11;
|
||||||
|
Lira = 12;
|
||||||
|
Rupiah = 13;
|
||||||
|
Franc = 14;
|
||||||
|
HongKongDollar = 15;
|
||||||
|
NewZealandDollar = 16;
|
||||||
|
Krona = 17;
|
||||||
|
NorwegianKrone = 18;
|
||||||
|
MexicanPeso = 19;
|
||||||
|
Rand = 20;
|
||||||
|
NewTaiwanDollar = 21;
|
||||||
|
DanishKrone = 22;
|
||||||
|
Baht = 23;
|
||||||
|
Forint = 24;
|
||||||
|
Koruna = 25;
|
||||||
|
Shekel = 26;
|
||||||
|
ChileanPeso = 27;
|
||||||
|
PhilippinePeso = 28;
|
||||||
|
Dirham = 29;
|
||||||
|
ColombianPeso = 30;
|
||||||
|
Riyal = 31;
|
||||||
|
Ringgit = 32;
|
||||||
|
Leu = 33;
|
||||||
|
ArgentinePeso = 34;
|
||||||
|
UruguayanPeso = 35;
|
||||||
|
Percent = 36;
|
||||||
|
}
|
@ -1,4 +1,5 @@
|
|||||||
syntax = "proto3";
|
syntax = "proto3";
|
||||||
|
import "format.proto";
|
||||||
|
|
||||||
message NumberTypeOption {
|
message NumberTypeOption {
|
||||||
NumberFormat format = 1;
|
NumberFormat format = 1;
|
||||||
@ -7,41 +8,3 @@ message NumberTypeOption {
|
|||||||
bool sign_positive = 4;
|
bool sign_positive = 4;
|
||||||
string name = 5;
|
string name = 5;
|
||||||
}
|
}
|
||||||
enum NumberFormat {
|
|
||||||
Number = 0;
|
|
||||||
USD = 1;
|
|
||||||
CanadianDollar = 2;
|
|
||||||
EUR = 4;
|
|
||||||
Pound = 5;
|
|
||||||
Yen = 6;
|
|
||||||
Ruble = 7;
|
|
||||||
Rupee = 8;
|
|
||||||
Won = 9;
|
|
||||||
Yuan = 10;
|
|
||||||
Real = 11;
|
|
||||||
Lira = 12;
|
|
||||||
Rupiah = 13;
|
|
||||||
Franc = 14;
|
|
||||||
HongKongDollar = 15;
|
|
||||||
NewZealandDollar = 16;
|
|
||||||
Krona = 17;
|
|
||||||
NorwegianKrone = 18;
|
|
||||||
MexicanPeso = 19;
|
|
||||||
Rand = 20;
|
|
||||||
NewTaiwanDollar = 21;
|
|
||||||
DanishKrone = 22;
|
|
||||||
Baht = 23;
|
|
||||||
Forint = 24;
|
|
||||||
Koruna = 25;
|
|
||||||
Shekel = 26;
|
|
||||||
ChileanPeso = 27;
|
|
||||||
PhilippinePeso = 28;
|
|
||||||
Dirham = 29;
|
|
||||||
ColombianPeso = 30;
|
|
||||||
Riyal = 31;
|
|
||||||
Ringgit = 32;
|
|
||||||
Leu = 33;
|
|
||||||
ArgentinePeso = 34;
|
|
||||||
UruguayanPeso = 35;
|
|
||||||
Percent = 36;
|
|
||||||
}
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
use flowy_grid_data_model::entities::{CellMeta, RowMeta, RowMetaChangeset, RowOrder};
|
use flowy_grid_data_model::entities::{CellMeta, GridBlockMetaData, RowMeta, RowMetaChangeset, RowOrder};
|
||||||
use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
|
use flowy_revision::{RevisionCloudService, RevisionCompactor, RevisionManager, RevisionObjectBuilder};
|
||||||
use flowy_sync::client_grid::{GridBlockMetaChange, GridBlockMetaPad};
|
use flowy_sync::client_grid::{GridBlockMetaChange, GridBlockMetaPad};
|
||||||
use flowy_sync::entities::revision::Revision;
|
use flowy_sync::entities::revision::Revision;
|
||||||
@ -41,6 +41,10 @@ impl GridBlockMetaEditor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn duplicate_block_meta_data(&self, duplicated_block_id: &str) -> GridBlockMetaData {
|
||||||
|
self.pad.read().await.duplicate_data(duplicated_block_id).await
|
||||||
|
}
|
||||||
|
|
||||||
/// return current number of rows and the inserted index. The inserted index will be None if the start_row_id is None
|
/// return current number of rows and the inserted index. The inserted index will be None if the start_row_id is None
|
||||||
pub(crate) async fn create_row(
|
pub(crate) async fn create_row(
|
||||||
&self,
|
&self,
|
||||||
|
@ -47,7 +47,7 @@ impl GridBlockManager {
|
|||||||
debug_assert!(!block_id.is_empty());
|
debug_assert!(!block_id.is_empty());
|
||||||
match self.block_editor_map.get(block_id) {
|
match self.block_editor_map.get(block_id) {
|
||||||
None => {
|
None => {
|
||||||
tracing::error!("The is a fatal error, block is not exist");
|
tracing::error!("This is a fatal error, block with id:{} is not exist", block_id);
|
||||||
let editor = Arc::new(make_block_meta_editor(&self.user, block_id).await?);
|
let editor = Arc::new(make_block_meta_editor(&self.user, block_id).await?);
|
||||||
self.block_editor_map.insert(block_id.to_owned(), editor.clone());
|
self.block_editor_map.insert(block_id.to_owned(), editor.clone());
|
||||||
Ok(editor)
|
Ok(editor)
|
||||||
@ -267,6 +267,7 @@ async fn make_block_meta_editor_map(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn make_block_meta_editor(user: &Arc<dyn GridUser>, block_id: &str) -> FlowyResult<GridBlockMetaEditor> {
|
async fn make_block_meta_editor(user: &Arc<dyn GridUser>, block_id: &str) -> FlowyResult<GridBlockMetaEditor> {
|
||||||
|
tracing::trace!("Open block:{} meta editor", block_id);
|
||||||
let token = user.token()?;
|
let token = user.token()?;
|
||||||
let user_id = user.user_id()?;
|
let user_id = user.user_id()?;
|
||||||
let pool = user.db_pool()?;
|
let pool = user.db_pool()?;
|
||||||
|
@ -42,7 +42,7 @@ impl_type_option!(CheckboxTypeOption, FieldType::Checkbox);
|
|||||||
const YES: &str = "Yes";
|
const YES: &str = "Yes";
|
||||||
const NO: &str = "No";
|
const NO: &str = "No";
|
||||||
|
|
||||||
impl CellDataOperation<String, String> for CheckboxTypeOption {
|
impl CellDataOperation<String> for CheckboxTypeOption {
|
||||||
fn decode_cell_data<T>(
|
fn decode_cell_data<T>(
|
||||||
&self,
|
&self,
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
|
@ -1,17 +1,16 @@
|
|||||||
use crate::entities::{CellIdentifier, CellIdentifierPayload};
|
use crate::entities::{CellIdentifier, CellIdentifierPayload};
|
||||||
use crate::impl_type_option;
|
use crate::impl_type_option;
|
||||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||||
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData, EncodedCellData};
|
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use chrono::format::strftime::StrftimeItems;
|
use chrono::format::strftime::StrftimeItems;
|
||||||
use chrono::NaiveDateTime;
|
use chrono::{NaiveDateTime, Timelike};
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
||||||
use flowy_error::{internal_error, ErrorCode, FlowyError, FlowyResult};
|
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||||
use flowy_grid_data_model::entities::{
|
use flowy_grid_data_model::entities::{
|
||||||
CellChangeset, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
|
CellChangeset, CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
|
||||||
};
|
};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
// Date
|
// Date
|
||||||
@ -29,35 +28,36 @@ pub struct DateTypeOption {
|
|||||||
impl_type_option!(DateTypeOption, FieldType::DateTime);
|
impl_type_option!(DateTypeOption, FieldType::DateTime);
|
||||||
|
|
||||||
impl DateTypeOption {
|
impl DateTypeOption {
|
||||||
fn today_desc_from_timestamp(&self, timestamp: i64, time: &Option<String>) -> String {
|
|
||||||
let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
|
|
||||||
self.today_desc_from_native(native, time)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn today_desc_from_str(&self, s: String, time: &Option<String>) -> String {
|
pub fn new() -> Self {
|
||||||
match NaiveDateTime::parse_from_str(&s, &self.date_fmt(time)) {
|
Self::default()
|
||||||
Ok(native) => self.today_desc_from_native(native, time),
|
}
|
||||||
Err(_) => "".to_owned(),
|
|
||||||
|
fn today_desc_from_timestamp(&self, timestamp: i64) -> DateCellData {
|
||||||
|
let native = chrono::NaiveDateTime::from_timestamp(timestamp, 0);
|
||||||
|
self.date_from_native(native)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn date_from_native(&self, native: chrono::NaiveDateTime) -> DateCellData {
|
||||||
|
if native.timestamp() == 0 {
|
||||||
|
return DateCellData::default();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fn today_desc_from_native(&self, native: chrono::NaiveDateTime, time: &Option<String>) -> String {
|
let time = native.time();
|
||||||
|
let has_time = time.hour() != 0 || time.second() != 0;
|
||||||
|
|
||||||
let utc = self.utc_date_time_from_native(native);
|
let utc = self.utc_date_time_from_native(native);
|
||||||
// let china_timezone = FixedOffset::east(8 * 3600);
|
let fmt = self.date_format.format_str();
|
||||||
// let a = utc.with_timezone(&china_timezone);
|
let date = format!("{}", utc.format_with_items(StrftimeItems::new(fmt)));
|
||||||
let fmt = self.date_fmt(time);
|
|
||||||
let output = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt)));
|
|
||||||
output
|
|
||||||
}
|
|
||||||
|
|
||||||
fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
|
let mut time = "".to_string();
|
||||||
let native = NaiveDateTime::from_timestamp(timestamp, 0);
|
if has_time {
|
||||||
self.utc_date_time_from_native(native)
|
let fmt = format!("{} {}", self.date_format.format_str(), self.time_format.format_str());
|
||||||
}
|
time = format!("{}", utc.format_with_items(StrftimeItems::new(&fmt))).replace(&date, "");
|
||||||
|
}
|
||||||
|
|
||||||
fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime<chrono::Utc> {
|
let timestamp = native.timestamp();
|
||||||
chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
|
DateCellData { date, time, timestamp }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn date_fmt(&self, time: &Option<String>) -> String {
|
fn date_fmt(&self, time: &Option<String>) -> String {
|
||||||
@ -77,14 +77,6 @@ impl DateTypeOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn date_desc_from_timestamp(&self, serde_cell_data: &DateCellDataSerde) -> String {
|
|
||||||
if serde_cell_data.timestamp == 0 {
|
|
||||||
return "".to_owned();
|
|
||||||
}
|
|
||||||
|
|
||||||
self.today_desc_from_timestamp(serde_cell_data.timestamp, &serde_cell_data.time)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timestamp_from_utc_with_time(
|
fn timestamp_from_utc_with_time(
|
||||||
&self,
|
&self,
|
||||||
utc: &chrono::DateTime<chrono::Utc>,
|
utc: &chrono::DateTime<chrono::Utc>,
|
||||||
@ -113,9 +105,18 @@ impl DateTypeOption {
|
|||||||
|
|
||||||
Ok(utc.timestamp())
|
Ok(utc.timestamp())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn utc_date_time_from_timestamp(&self, timestamp: i64) -> chrono::DateTime<chrono::Utc> {
|
||||||
|
let native = NaiveDateTime::from_timestamp(timestamp, 0);
|
||||||
|
self.utc_date_time_from_native(native)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn utc_date_time_from_native(&self, naive: chrono::NaiveDateTime) -> chrono::DateTime<chrono::Utc> {
|
||||||
|
chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CellDataOperation<EncodedCellData<DateCellDataSerde>, DateCellDataSerde> for DateTypeOption {
|
impl CellDataOperation<String> for DateTypeOption {
|
||||||
fn decode_cell_data<T>(
|
fn decode_cell_data<T>(
|
||||||
&self,
|
&self,
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
@ -123,7 +124,7 @@ impl CellDataOperation<EncodedCellData<DateCellDataSerde>, DateCellDataSerde> fo
|
|||||||
_field_meta: &FieldMeta,
|
_field_meta: &FieldMeta,
|
||||||
) -> FlowyResult<DecodedCellData>
|
) -> FlowyResult<DecodedCellData>
|
||||||
where
|
where
|
||||||
T: Into<EncodedCellData<DateCellDataSerde>>,
|
T: Into<String>,
|
||||||
{
|
{
|
||||||
// Return default data if the type_option_cell_data is not FieldType::DateTime.
|
// Return default data if the type_option_cell_data is not FieldType::DateTime.
|
||||||
// It happens when switching from one field to another.
|
// It happens when switching from one field to another.
|
||||||
@ -133,33 +134,29 @@ impl CellDataOperation<EncodedCellData<DateCellDataSerde>, DateCellDataSerde> fo
|
|||||||
return Ok(DecodedCellData::default());
|
return Ok(DecodedCellData::default());
|
||||||
}
|
}
|
||||||
|
|
||||||
let encoded_data = encoded_data.into().try_into_inner()?;
|
let timestamp = encoded_data.into().parse::<i64>().unwrap_or(0);
|
||||||
let date = self.date_desc_from_timestamp(&encoded_data);
|
let date = self.today_desc_from_timestamp(timestamp);
|
||||||
let time = encoded_data.time.unwrap_or_else(|| "".to_owned());
|
DecodedCellData::try_from_bytes(date)
|
||||||
let timestamp = encoded_data.timestamp;
|
|
||||||
|
|
||||||
DecodedCellData::try_from_bytes(DateCellData { date, time, timestamp })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<DateCellDataSerde, FlowyError>
|
fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
|
||||||
where
|
where
|
||||||
C: Into<CellContentChangeset>,
|
C: Into<CellContentChangeset>,
|
||||||
{
|
{
|
||||||
let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
|
let content_changeset: DateCellContentChangeset = serde_json::from_str(&changeset.into())?;
|
||||||
let cell_data = match content_changeset.date_timestamp() {
|
let cell_data = match content_changeset.date_timestamp() {
|
||||||
None => DateCellDataSerde::default(),
|
None => 0,
|
||||||
Some(date_timestamp) => match (self.include_time, content_changeset.time) {
|
Some(date_timestamp) => match (self.include_time, content_changeset.time) {
|
||||||
(true, Some(time)) => {
|
(true, Some(time)) => {
|
||||||
let time = Some(time.trim().to_uppercase());
|
let time = Some(time.trim().to_uppercase());
|
||||||
let utc = self.utc_date_time_from_timestamp(date_timestamp);
|
let utc = self.utc_date_time_from_timestamp(date_timestamp);
|
||||||
let timestamp = self.timestamp_from_utc_with_time(&utc, &time)?;
|
self.timestamp_from_utc_with_time(&utc, &time)?
|
||||||
DateCellDataSerde::new(timestamp, time, &self.time_format)
|
|
||||||
}
|
}
|
||||||
_ => DateCellDataSerde::from_timestamp(date_timestamp, Some(default_time_str(&self.time_format))),
|
_ => date_timestamp,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(cell_data)
|
Ok(cell_data.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,46 +280,6 @@ pub struct DateCellData {
|
|||||||
pub timestamp: i64,
|
pub timestamp: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Serialize, Deserialize)]
|
|
||||||
pub struct DateCellDataSerde {
|
|
||||||
pub timestamp: i64,
|
|
||||||
pub time: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DateCellDataSerde {
|
|
||||||
fn new(timestamp: i64, time: Option<String>, time_format: &TimeFormat) -> Self {
|
|
||||||
Self {
|
|
||||||
timestamp,
|
|
||||||
time: Some(time.unwrap_or_else(|| default_time_str(time_format))),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_timestamp(timestamp: i64, time: Option<String>) -> Self {
|
|
||||||
Self { timestamp, time }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for DateCellDataSerde {
|
|
||||||
type Err = FlowyError;
|
|
||||||
|
|
||||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
|
||||||
serde_json::from_str::<DateCellDataSerde>(s).map_err(internal_error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ToString for DateCellDataSerde {
|
|
||||||
fn to_string(&self) -> String {
|
|
||||||
serde_json::to_string(&self).unwrap_or_else(|_| "".to_string())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn default_time_str(time_format: &TimeFormat) -> String {
|
|
||||||
match time_format {
|
|
||||||
TimeFormat::TwelveHour => "12:00 AM".to_string(),
|
|
||||||
TimeFormat::TwentyFourHour => "00:00".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Default, ProtoBuf)]
|
#[derive(Clone, Debug, Default, ProtoBuf)]
|
||||||
pub struct DateChangesetPayload {
|
pub struct DateChangesetPayload {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
@ -399,15 +356,13 @@ impl std::convert::From<DateCellContentChangeset> for CellContentChangeset {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::services::field::FieldBuilder;
|
use crate::services::field::FieldBuilder;
|
||||||
use crate::services::field::{
|
use crate::services::field::{DateCellContentChangeset, DateCellData, DateFormat, DateTypeOption, TimeFormat};
|
||||||
DateCellContentChangeset, DateCellData, DateCellDataSerde, DateFormat, DateTypeOption, TimeFormat,
|
use crate::services::row::CellDataOperation;
|
||||||
};
|
use flowy_grid_data_model::entities::{FieldMeta, FieldType, TypeOptionDataEntry};
|
||||||
use crate::services::row::{CellDataOperation, EncodedCellData};
|
|
||||||
use flowy_grid_data_model::entities::{FieldMeta, FieldType};
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn date_description_invalid_input_test() {
|
fn date_type_option_invalid_input_test() {
|
||||||
let type_option = DateTypeOption::default();
|
let type_option = DateTypeOption::default();
|
||||||
let field_type = FieldType::DateTime;
|
let field_type = FieldType::DateTime;
|
||||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
@ -424,7 +379,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn date_description_date_format_test() {
|
fn date_type_option_date_format_test() {
|
||||||
let mut type_option = DateTypeOption::default();
|
let mut type_option = DateTypeOption::default();
|
||||||
let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
|
let field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
|
||||||
for date_format in DateFormat::iter() {
|
for date_format in DateFormat::iter() {
|
||||||
@ -447,7 +402,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn date_description_time_format_test() {
|
fn date_type_option_time_format_test() {
|
||||||
let mut type_option = DateTypeOption::default();
|
let mut type_option = DateTypeOption::default();
|
||||||
let field_type = FieldType::DateTime;
|
let field_type = FieldType::DateTime;
|
||||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
@ -465,7 +420,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
&field_type,
|
&field_type,
|
||||||
&field_meta,
|
&field_meta,
|
||||||
"May 27,2022 00:00",
|
"May 27,2022",
|
||||||
);
|
);
|
||||||
assert_changeset_result(
|
assert_changeset_result(
|
||||||
&type_option,
|
&type_option,
|
||||||
@ -487,9 +442,9 @@ mod tests {
|
|||||||
},
|
},
|
||||||
&field_type,
|
&field_type,
|
||||||
&field_meta,
|
&field_meta,
|
||||||
"May 27,2022 12:00 AM",
|
"May 27,2022",
|
||||||
);
|
);
|
||||||
|
//
|
||||||
assert_changeset_result(
|
assert_changeset_result(
|
||||||
&type_option,
|
&type_option,
|
||||||
DateCellContentChangeset {
|
DateCellContentChangeset {
|
||||||
@ -517,8 +472,8 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn date_description_apply_changeset_test() {
|
fn date_type_option_apply_changeset_test() {
|
||||||
let mut type_option = DateTypeOption::default();
|
let mut type_option = DateTypeOption::new();
|
||||||
let field_type = FieldType::DateTime;
|
let field_type = FieldType::DateTime;
|
||||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
let date_timestamp = "1653609600".to_owned();
|
let date_timestamp = "1653609600".to_owned();
|
||||||
@ -543,7 +498,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
&field_type,
|
&field_type,
|
||||||
&field_meta,
|
&field_meta,
|
||||||
"May 27,2022 00:00",
|
"May 27,2022",
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_changeset_result(
|
assert_changeset_result(
|
||||||
@ -572,30 +527,53 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn date_description_apply_changeset_error_test() {
|
fn date_type_option_apply_changeset_error_test() {
|
||||||
let mut type_option = DateTypeOption::default();
|
let mut type_option = DateTypeOption::new();
|
||||||
type_option.include_time = true;
|
type_option.include_time = true;
|
||||||
let _field_meta = FieldBuilder::from_field_type(&FieldType::DateTime).build();
|
let field_meta = FieldBuilder::from_field_type(&type_option.field_type()).build();
|
||||||
let date_timestamp = "1653609600".to_owned();
|
let date_timestamp = "1653609600".to_owned();
|
||||||
|
|
||||||
let changeset = DateCellContentChangeset {
|
assert_changeset_result(
|
||||||
date: Some(date_timestamp.clone()),
|
&type_option,
|
||||||
time: Some("1:a0".to_owned()),
|
DateCellContentChangeset {
|
||||||
};
|
date: Some(date_timestamp.clone()),
|
||||||
let _ = type_option.apply_changeset(changeset, None).unwrap();
|
time: Some("1:".to_owned()),
|
||||||
|
},
|
||||||
|
&type_option.field_type(),
|
||||||
|
&field_meta,
|
||||||
|
"May 27,2022 01:00",
|
||||||
|
);
|
||||||
|
|
||||||
let changeset = DateCellContentChangeset {
|
assert_changeset_result(
|
||||||
date: Some(date_timestamp),
|
&type_option,
|
||||||
time: Some("1:".to_owned()),
|
DateCellContentChangeset {
|
||||||
};
|
date: Some(date_timestamp),
|
||||||
let _ = type_option.apply_changeset(changeset, None).unwrap();
|
time: Some("1:00".to_owned()),
|
||||||
|
},
|
||||||
|
&type_option.field_type(),
|
||||||
|
&field_meta,
|
||||||
|
"May 27,2022 01:00",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[should_panic]
|
#[should_panic]
|
||||||
fn date_description_invalid_data_test() {
|
fn date_type_option_twelve_hours_to_twenty_four_hours() {
|
||||||
let type_option = DateTypeOption::default();
|
let mut type_option = DateTypeOption::new();
|
||||||
type_option.apply_changeset("he", None).unwrap();
|
type_option.include_time = true;
|
||||||
|
let field_meta = FieldBuilder::from_field_type(&type_option.field_type()).build();
|
||||||
|
let date_timestamp = "1653609600".to_owned();
|
||||||
|
|
||||||
|
assert_changeset_result(
|
||||||
|
&type_option,
|
||||||
|
DateCellContentChangeset {
|
||||||
|
date: Some(date_timestamp),
|
||||||
|
time: Some("1:00 am".to_owned()),
|
||||||
|
},
|
||||||
|
&type_option.field_type(),
|
||||||
|
&field_meta,
|
||||||
|
"May 27,2022 01:00",
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_changeset_result(
|
fn assert_changeset_result(
|
||||||
@ -605,7 +583,7 @@ mod tests {
|
|||||||
field_meta: &FieldMeta,
|
field_meta: &FieldMeta,
|
||||||
expected: &str,
|
expected: &str,
|
||||||
) {
|
) {
|
||||||
let encoded_data = EncodedCellData(Some(type_option.apply_changeset(changeset, None).unwrap()));
|
let encoded_data = type_option.apply_changeset(changeset, None).unwrap();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected.to_owned(),
|
expected.to_owned(),
|
||||||
decode_cell_data(encoded_data, type_option, field_meta)
|
decode_cell_data(encoded_data, type_option, field_meta)
|
||||||
@ -613,24 +591,37 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn assert_decode_timestamp(timestamp: i64, type_option: &DateTypeOption, field_meta: &FieldMeta, expected: &str) {
|
fn assert_decode_timestamp(timestamp: i64, type_option: &DateTypeOption, field_meta: &FieldMeta, expected: &str) {
|
||||||
let serde_json = DateCellDataSerde { timestamp, time: None }.to_string();
|
let encoded_data = type_option
|
||||||
|
.apply_changeset(
|
||||||
|
DateCellContentChangeset {
|
||||||
|
date: Some(timestamp.to_string()),
|
||||||
|
time: None,
|
||||||
|
},
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
expected.to_owned(),
|
expected.to_owned(),
|
||||||
decode_cell_data(serde_json, type_option, field_meta)
|
decode_cell_data(encoded_data, type_option, field_meta)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_cell_data<T: Into<EncodedCellData<DateCellDataSerde>>>(
|
fn decode_cell_data<T: Into<String>>(
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
type_option: &DateTypeOption,
|
type_option: &DateTypeOption,
|
||||||
field_meta: &FieldMeta,
|
field_meta: &FieldMeta,
|
||||||
) -> String {
|
) -> String {
|
||||||
type_option
|
let decoded_data = type_option
|
||||||
.decode_cell_data(encoded_data, &FieldType::DateTime, field_meta)
|
.decode_cell_data(encoded_data, &FieldType::DateTime, field_meta)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.parse::<DateCellData>()
|
.parse::<DateCellData>()
|
||||||
.unwrap()
|
.unwrap();
|
||||||
.date
|
|
||||||
|
if type_option.include_time {
|
||||||
|
format!("{}{}", decoded_data.date, decoded_data.time)
|
||||||
|
} else {
|
||||||
|
decoded_data.date
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,182 +1,16 @@
|
|||||||
use crate::impl_type_option;
|
use flowy_derive::ProtoBuf_Enum;
|
||||||
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
|
||||||
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
|
|
||||||
use bytes::Bytes;
|
|
||||||
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
|
|
||||||
use flowy_error::{FlowyError, FlowyResult};
|
|
||||||
use flowy_grid_data_model::entities::{
|
|
||||||
CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
|
|
||||||
};
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use rust_decimal::Decimal;
|
|
||||||
use rusty_money::define_currency_set;
|
use rusty_money::define_currency_set;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::str::FromStr;
|
|
||||||
use strum::IntoEnumIterator;
|
use strum::IntoEnumIterator;
|
||||||
use strum_macros::EnumIter;
|
use strum_macros::EnumIter;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref STRIP_SYMBOL: Vec<String> = make_strip_symbol();
|
pub static ref CURRENCY_SYMBOL: Vec<String> = NumberFormat::iter()
|
||||||
}
|
.map(|format| format.symbol())
|
||||||
|
.collect::<Vec<String>>();
|
||||||
#[derive(Default)]
|
pub static ref STRIP_SYMBOL: Vec<String> = vec![",".to_owned(), ".".to_owned()];
|
||||||
pub struct NumberTypeOptionBuilder(NumberTypeOption);
|
|
||||||
impl_into_box_type_option_builder!(NumberTypeOptionBuilder);
|
|
||||||
impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption);
|
|
||||||
|
|
||||||
impl NumberTypeOptionBuilder {
|
|
||||||
pub fn name(mut self, name: &str) -> Self {
|
|
||||||
self.0.name = name.to_string();
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_format(mut self, format: NumberFormat) -> Self {
|
|
||||||
self.0.set_format(format);
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn scale(mut self, scale: u32) -> Self {
|
|
||||||
self.0.scale = scale;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn positive(mut self, positive: bool) -> Self {
|
|
||||||
self.0.sign_positive = positive;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TypeOptionBuilder for NumberTypeOptionBuilder {
|
|
||||||
fn field_type(&self) -> FieldType {
|
|
||||||
self.0.field_type()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn entry(&self) -> &dyn TypeOptionDataEntry {
|
|
||||||
&self.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Number
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
|
|
||||||
pub struct NumberTypeOption {
|
|
||||||
#[pb(index = 1)]
|
|
||||||
pub format: NumberFormat,
|
|
||||||
|
|
||||||
#[pb(index = 2)]
|
|
||||||
pub scale: u32,
|
|
||||||
|
|
||||||
#[pb(index = 3)]
|
|
||||||
pub symbol: String,
|
|
||||||
|
|
||||||
#[pb(index = 4)]
|
|
||||||
pub sign_positive: bool,
|
|
||||||
|
|
||||||
#[pb(index = 5)]
|
|
||||||
pub name: String,
|
|
||||||
}
|
|
||||||
impl_type_option!(NumberTypeOption, FieldType::Number);
|
|
||||||
|
|
||||||
impl CellDataOperation<String, String> for NumberTypeOption {
|
|
||||||
fn decode_cell_data<T>(
|
|
||||||
&self,
|
|
||||||
encoded_data: T,
|
|
||||||
decoded_field_type: &FieldType,
|
|
||||||
_field_meta: &FieldMeta,
|
|
||||||
) -> FlowyResult<DecodedCellData>
|
|
||||||
where
|
|
||||||
T: Into<String>,
|
|
||||||
{
|
|
||||||
if decoded_field_type.is_date() {
|
|
||||||
return Ok(DecodedCellData::default());
|
|
||||||
}
|
|
||||||
|
|
||||||
let cell_data = encoded_data.into();
|
|
||||||
match self.format {
|
|
||||||
NumberFormat::Number => {
|
|
||||||
if let Ok(v) = cell_data.parse::<f64>() {
|
|
||||||
return Ok(DecodedCellData::new(v.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Ok(v) = cell_data.parse::<i64>() {
|
|
||||||
return Ok(DecodedCellData::new(v.to_string()));
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(DecodedCellData::default())
|
|
||||||
}
|
|
||||||
NumberFormat::Percent => {
|
|
||||||
let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
|
|
||||||
Ok(DecodedCellData::new(content))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let content = self.money_from_str(&cell_data);
|
|
||||||
Ok(DecodedCellData::new(content))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
|
|
||||||
where
|
|
||||||
C: Into<CellContentChangeset>,
|
|
||||||
{
|
|
||||||
let changeset = changeset.into();
|
|
||||||
let mut data = changeset.trim().to_string();
|
|
||||||
|
|
||||||
if self.format != NumberFormat::Number {
|
|
||||||
data = self.strip_symbol(data);
|
|
||||||
if !data.chars().all(char::is_numeric) {
|
|
||||||
return Err(FlowyError::invalid_data().context("Should only contain numbers"));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(data)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::default::Default for NumberTypeOption {
|
|
||||||
fn default() -> Self {
|
|
||||||
let format = NumberFormat::default();
|
|
||||||
let symbol = format.symbol();
|
|
||||||
NumberTypeOption {
|
|
||||||
format,
|
|
||||||
scale: 0,
|
|
||||||
symbol,
|
|
||||||
sign_positive: true,
|
|
||||||
name: "Number".to_string(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NumberTypeOption {
|
|
||||||
pub fn set_format(&mut self, format: NumberFormat) {
|
|
||||||
self.format = format;
|
|
||||||
self.symbol = format.symbol();
|
|
||||||
}
|
|
||||||
|
|
||||||
fn money_from_str(&self, s: &str) -> String {
|
|
||||||
match Decimal::from_str(s) {
|
|
||||||
Ok(mut decimal) => {
|
|
||||||
match decimal.set_scale(self.scale) {
|
|
||||||
Ok(_) => {}
|
|
||||||
Err(e) => {
|
|
||||||
tracing::error!("Set decimal scale failed: {:?}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
decimal.set_sign_positive(self.sign_positive);
|
|
||||||
let money = rusty_money::Money::from_decimal(decimal, self.format.currency());
|
|
||||||
money.to_string()
|
|
||||||
}
|
|
||||||
Err(_) => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn strip_symbol<T: ToString>(&self, s: T) -> String {
|
|
||||||
let mut s = s.to_string();
|
|
||||||
if !s.chars().all(char::is_numeric) {
|
|
||||||
s.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
|
|
||||||
}
|
|
||||||
s
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
|
||||||
@ -609,137 +443,3 @@ impl NumberFormat {
|
|||||||
self.currency().symbol.to_string()
|
self.currency().symbol.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_strip_symbol() -> Vec<String> {
|
|
||||||
let mut symbols = vec![",".to_owned(), ".".to_owned()];
|
|
||||||
for format in NumberFormat::iter() {
|
|
||||||
symbols.push(format.symbol());
|
|
||||||
}
|
|
||||||
symbols
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use crate::services::field::FieldBuilder;
|
|
||||||
use crate::services::field::{NumberFormat, NumberTypeOption};
|
|
||||||
use crate::services::row::CellDataOperation;
|
|
||||||
use flowy_grid_data_model::entities::{FieldMeta, FieldType};
|
|
||||||
use strum::IntoEnumIterator;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn number_description_invalid_input_test() {
|
|
||||||
let type_option = NumberTypeOption::default();
|
|
||||||
let field_type = FieldType::Number;
|
|
||||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
|
||||||
assert_equal(&type_option, "", "", &field_type, &field_meta);
|
|
||||||
assert_equal(&type_option, "abc", "", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn number_description_test() {
|
|
||||||
let mut type_option = NumberTypeOption::default();
|
|
||||||
let field_type = FieldType::Number;
|
|
||||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
|
||||||
assert_eq!(type_option.strip_symbol("¥18,443"), "18443".to_owned());
|
|
||||||
assert_eq!(type_option.strip_symbol("$18,443"), "18443".to_owned());
|
|
||||||
assert_eq!(type_option.strip_symbol("€18.443"), "18443".to_owned());
|
|
||||||
|
|
||||||
for format in NumberFormat::iter() {
|
|
||||||
type_option.format = format;
|
|
||||||
match format {
|
|
||||||
NumberFormat::Number => {
|
|
||||||
assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::USD => {
|
|
||||||
assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta);
|
|
||||||
assert_equal(&type_option, "", "", &field_type, &field_meta);
|
|
||||||
assert_equal(&type_option, "abc", "", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::Yen => {
|
|
||||||
assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::Yuan => {
|
|
||||||
assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::EUR => {
|
|
||||||
assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn number_description_scale_test() {
|
|
||||||
let mut type_option = NumberTypeOption {
|
|
||||||
scale: 1,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let field_type = FieldType::Number;
|
|
||||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
|
||||||
|
|
||||||
for format in NumberFormat::iter() {
|
|
||||||
type_option.format = format;
|
|
||||||
match format {
|
|
||||||
NumberFormat::Number => {
|
|
||||||
assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::USD => {
|
|
||||||
assert_equal(&type_option, "18443", "$1,844.3", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::Yen => {
|
|
||||||
assert_equal(&type_option, "18443", "¥1,844.3", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::EUR => {
|
|
||||||
assert_equal(&type_option, "18443", "€1.844,3", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn number_description_sign_test() {
|
|
||||||
let mut type_option = NumberTypeOption {
|
|
||||||
sign_positive: false,
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
let field_type = FieldType::Number;
|
|
||||||
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
|
||||||
|
|
||||||
for format in NumberFormat::iter() {
|
|
||||||
type_option.format = format;
|
|
||||||
match format {
|
|
||||||
NumberFormat::Number => {
|
|
||||||
assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::USD => {
|
|
||||||
assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::Yen => {
|
|
||||||
assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
NumberFormat::EUR => {
|
|
||||||
assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_meta);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn assert_equal(
|
|
||||||
type_option: &NumberTypeOption,
|
|
||||||
cell_data: &str,
|
|
||||||
expected_str: &str,
|
|
||||||
field_type: &FieldType,
|
|
||||||
field_meta: &FieldMeta,
|
|
||||||
) {
|
|
||||||
assert_eq!(
|
|
||||||
type_option
|
|
||||||
.decode_cell_data(cell_data, field_type, field_meta)
|
|
||||||
.unwrap()
|
|
||||||
.to_string(),
|
|
||||||
expected_str.to_owned()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,6 @@
|
|||||||
|
#![allow(clippy::module_inception)]
|
||||||
|
mod format;
|
||||||
|
mod number_type_option;
|
||||||
|
|
||||||
|
pub use format::*;
|
||||||
|
pub use number_type_option::*;
|
@ -0,0 +1,346 @@
|
|||||||
|
use crate::impl_type_option;
|
||||||
|
|
||||||
|
use crate::services::field::type_options::number_type_option::format::*;
|
||||||
|
use crate::services::field::{BoxTypeOptionBuilder, TypeOptionBuilder};
|
||||||
|
use crate::services::row::{CellContentChangeset, CellDataOperation, DecodedCellData};
|
||||||
|
use bytes::Bytes;
|
||||||
|
use flowy_derive::ProtoBuf;
|
||||||
|
use flowy_error::{FlowyError, FlowyResult};
|
||||||
|
use flowy_grid_data_model::entities::{
|
||||||
|
CellMeta, FieldMeta, FieldType, TypeOptionDataDeserializer, TypeOptionDataEntry,
|
||||||
|
};
|
||||||
|
use rust_decimal::Decimal;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
pub struct NumberTypeOptionBuilder(NumberTypeOption);
|
||||||
|
impl_into_box_type_option_builder!(NumberTypeOptionBuilder);
|
||||||
|
impl_builder_from_json_str_and_from_bytes!(NumberTypeOptionBuilder, NumberTypeOption);
|
||||||
|
|
||||||
|
impl NumberTypeOptionBuilder {
|
||||||
|
pub fn name(mut self, name: &str) -> Self {
|
||||||
|
self.0.name = name.to_string();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_format(mut self, format: NumberFormat) -> Self {
|
||||||
|
self.0.set_format(format);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale(mut self, scale: u32) -> Self {
|
||||||
|
self.0.scale = scale;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn positive(mut self, positive: bool) -> Self {
|
||||||
|
self.0.sign_positive = positive;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TypeOptionBuilder for NumberTypeOptionBuilder {
|
||||||
|
fn field_type(&self) -> FieldType {
|
||||||
|
self.0.field_type()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry(&self) -> &dyn TypeOptionDataEntry {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Number
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
|
||||||
|
pub struct NumberTypeOption {
|
||||||
|
#[pb(index = 1)]
|
||||||
|
pub format: NumberFormat,
|
||||||
|
|
||||||
|
#[pb(index = 2)]
|
||||||
|
pub scale: u32,
|
||||||
|
|
||||||
|
#[pb(index = 3)]
|
||||||
|
pub symbol: String,
|
||||||
|
|
||||||
|
#[pb(index = 4)]
|
||||||
|
pub sign_positive: bool,
|
||||||
|
|
||||||
|
#[pb(index = 5)]
|
||||||
|
pub name: String,
|
||||||
|
}
|
||||||
|
impl_type_option!(NumberTypeOption, FieldType::Number);
|
||||||
|
|
||||||
|
impl NumberTypeOption {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cell_content_from_number_str(&self, s: &str) -> FlowyResult<String> {
|
||||||
|
match self.format {
|
||||||
|
NumberFormat::Number => {
|
||||||
|
if let Ok(v) = s.parse::<f64>() {
|
||||||
|
return Ok(v.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(v) = s.parse::<i64>() {
|
||||||
|
return Ok(v.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok("".to_string())
|
||||||
|
}
|
||||||
|
NumberFormat::Percent => {
|
||||||
|
let content = s.parse::<f64>().map_or(String::new(), |v| v.to_string());
|
||||||
|
Ok(content)
|
||||||
|
}
|
||||||
|
_ => self.money_from_number_str(s),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_format(&mut self, format: NumberFormat) {
|
||||||
|
self.format = format;
|
||||||
|
self.symbol = format.symbol();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn money_from_number_str(&self, s: &str) -> FlowyResult<String> {
|
||||||
|
let mut number = self.strip_currency_symbol(s);
|
||||||
|
|
||||||
|
if s.is_empty() {
|
||||||
|
return Ok("".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
match Decimal::from_str(&number) {
|
||||||
|
Ok(mut decimal) => {
|
||||||
|
decimal.set_sign_positive(self.sign_positive);
|
||||||
|
let money = rusty_money::Money::from_decimal(decimal, self.format.currency()).to_string();
|
||||||
|
Ok(money)
|
||||||
|
}
|
||||||
|
Err(_) => match rusty_money::Money::from_str(&number, self.format.currency()) {
|
||||||
|
Ok(money) => Ok(money.to_string()),
|
||||||
|
Err(_) => {
|
||||||
|
number.retain(|c| !STRIP_SYMBOL.contains(&c.to_string()));
|
||||||
|
if number.chars().all(char::is_numeric) {
|
||||||
|
self.money_from_number_str(&number)
|
||||||
|
} else {
|
||||||
|
Err(FlowyError::invalid_data().context("Should only contain numbers"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn strip_currency_symbol<T: ToString>(&self, s: T) -> String {
|
||||||
|
let mut s = s.to_string();
|
||||||
|
for symbol in CURRENCY_SYMBOL.iter() {
|
||||||
|
if s.starts_with(symbol) {
|
||||||
|
s = s.strip_prefix(symbol).unwrap_or("").to_string();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
s
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CellDataOperation<String> for NumberTypeOption {
|
||||||
|
fn decode_cell_data<T>(
|
||||||
|
&self,
|
||||||
|
encoded_data: T,
|
||||||
|
decoded_field_type: &FieldType,
|
||||||
|
_field_meta: &FieldMeta,
|
||||||
|
) -> FlowyResult<DecodedCellData>
|
||||||
|
where
|
||||||
|
T: Into<String>,
|
||||||
|
{
|
||||||
|
if decoded_field_type.is_date() {
|
||||||
|
return Ok(DecodedCellData::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
let cell_data = encoded_data.into();
|
||||||
|
match self.format {
|
||||||
|
NumberFormat::Number => {
|
||||||
|
if let Ok(v) = cell_data.parse::<f64>() {
|
||||||
|
return Ok(DecodedCellData::new(v.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Ok(v) = cell_data.parse::<i64>() {
|
||||||
|
return Ok(DecodedCellData::new(v.to_string()));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(DecodedCellData::default())
|
||||||
|
}
|
||||||
|
NumberFormat::Percent => {
|
||||||
|
let content = cell_data.parse::<f64>().map_or(String::new(), |v| v.to_string());
|
||||||
|
Ok(DecodedCellData::new(content))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let content = self
|
||||||
|
.money_from_number_str(&cell_data)
|
||||||
|
.unwrap_or_else(|_| "".to_string());
|
||||||
|
Ok(DecodedCellData::new(content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_changeset<C>(&self, changeset: C, _cell_meta: Option<CellMeta>) -> Result<String, FlowyError>
|
||||||
|
where
|
||||||
|
C: Into<CellContentChangeset>,
|
||||||
|
{
|
||||||
|
let changeset = changeset.into();
|
||||||
|
let data = changeset.trim().to_string();
|
||||||
|
let _ = self.cell_content_from_number_str(&data)?;
|
||||||
|
Ok(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::default::Default for NumberTypeOption {
|
||||||
|
fn default() -> Self {
|
||||||
|
let format = NumberFormat::default();
|
||||||
|
let symbol = format.symbol();
|
||||||
|
NumberTypeOption {
|
||||||
|
format,
|
||||||
|
scale: 0,
|
||||||
|
symbol,
|
||||||
|
sign_positive: true,
|
||||||
|
name: "Number".to_string(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::services::field::FieldBuilder;
|
||||||
|
use crate::services::field::{NumberFormat, NumberTypeOption};
|
||||||
|
use crate::services::row::CellDataOperation;
|
||||||
|
use flowy_grid_data_model::entities::{FieldMeta, FieldType};
|
||||||
|
use strum::IntoEnumIterator;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_type_option_invalid_input_test() {
|
||||||
|
let type_option = NumberTypeOption::default();
|
||||||
|
let field_type = FieldType::Number;
|
||||||
|
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
|
assert_equal(&type_option, "", "", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "abc", "", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_type_option_strip_symbol_test() {
|
||||||
|
let mut type_option = NumberTypeOption::new();
|
||||||
|
type_option.format = NumberFormat::USD;
|
||||||
|
assert_eq!(type_option.strip_currency_symbol("$18,443"), "18,443".to_owned());
|
||||||
|
|
||||||
|
type_option.format = NumberFormat::Yuan;
|
||||||
|
assert_eq!(type_option.strip_currency_symbol("$0.2"), "0.2".to_owned());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_type_option_format_number_test() {
|
||||||
|
let mut type_option = NumberTypeOption::default();
|
||||||
|
let field_type = FieldType::Number;
|
||||||
|
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
|
|
||||||
|
for format in NumberFormat::iter() {
|
||||||
|
type_option.format = format;
|
||||||
|
match format {
|
||||||
|
NumberFormat::Number => {
|
||||||
|
assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::USD => {
|
||||||
|
assert_equal(&type_option, "18443", "$18,443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::Yen => {
|
||||||
|
assert_equal(&type_option, "18443", "¥18,443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::Yuan => {
|
||||||
|
assert_equal(&type_option, "18443", "CN¥18,443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::EUR => {
|
||||||
|
assert_equal(&type_option, "18443", "€18.443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_type_option_format_str_test() {
|
||||||
|
let mut type_option = NumberTypeOption::default();
|
||||||
|
let field_type = FieldType::Number;
|
||||||
|
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
|
|
||||||
|
for format in NumberFormat::iter() {
|
||||||
|
type_option.format = format;
|
||||||
|
match format {
|
||||||
|
NumberFormat::Number => {
|
||||||
|
assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "0.2", "0.2", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::USD => {
|
||||||
|
assert_equal(&type_option, "$18,44", "$1,844", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "$0.2", "$0.2", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "", "", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "abc", "", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::Yen => {
|
||||||
|
assert_equal(&type_option, "¥18,44", "¥1,844", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "¥1844", "¥1,844", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::Yuan => {
|
||||||
|
assert_equal(&type_option, "CN¥18,44", "CN¥1,844", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "CN¥1844", "CN¥1,844", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::EUR => {
|
||||||
|
assert_equal(&type_option, "€18.44", "€18,44", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "€0.5", "€0,5", &field_type, &field_meta);
|
||||||
|
assert_equal(&type_option, "€1844", "€1.844", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn number_description_sign_test() {
|
||||||
|
let mut type_option = NumberTypeOption {
|
||||||
|
sign_positive: false,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
let field_type = FieldType::Number;
|
||||||
|
let field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
|
|
||||||
|
for format in NumberFormat::iter() {
|
||||||
|
type_option.format = format;
|
||||||
|
match format {
|
||||||
|
NumberFormat::Number => {
|
||||||
|
assert_equal(&type_option, "18443", "18443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::USD => {
|
||||||
|
assert_equal(&type_option, "18443", "-$18,443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::Yen => {
|
||||||
|
assert_equal(&type_option, "18443", "-¥18,443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
NumberFormat::EUR => {
|
||||||
|
assert_equal(&type_option, "18443", "-€18.443", &field_type, &field_meta);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assert_equal(
|
||||||
|
type_option: &NumberTypeOption,
|
||||||
|
cell_data: &str,
|
||||||
|
expected_str: &str,
|
||||||
|
field_type: &FieldType,
|
||||||
|
field_meta: &FieldMeta,
|
||||||
|
) {
|
||||||
|
assert_eq!(
|
||||||
|
type_option
|
||||||
|
.decode_cell_data(cell_data, field_type, field_meta)
|
||||||
|
.unwrap()
|
||||||
|
.to_string(),
|
||||||
|
expected_str.to_owned()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -95,7 +95,7 @@ impl SelectOptionOperation for SingleSelectTypeOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CellDataOperation<String, String> for SingleSelectTypeOption {
|
impl CellDataOperation<String> for SingleSelectTypeOption {
|
||||||
fn decode_cell_data<T>(
|
fn decode_cell_data<T>(
|
||||||
&self,
|
&self,
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
@ -193,7 +193,7 @@ impl SelectOptionOperation for MultiSelectTypeOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CellDataOperation<String, String> for MultiSelectTypeOption {
|
impl CellDataOperation<String> for MultiSelectTypeOption {
|
||||||
fn decode_cell_data<T>(
|
fn decode_cell_data<T>(
|
||||||
&self,
|
&self,
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
|
@ -27,11 +27,11 @@ impl TypeOptionBuilder for RichTextTypeOptionBuilder {
|
|||||||
#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
|
#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
|
||||||
pub struct RichTextTypeOption {
|
pub struct RichTextTypeOption {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
data: String, //It's not used.
|
data: String, //It's not used yet
|
||||||
}
|
}
|
||||||
impl_type_option!(RichTextTypeOption, FieldType::RichText);
|
impl_type_option!(RichTextTypeOption, FieldType::RichText);
|
||||||
|
|
||||||
impl CellDataOperation<String, String> for RichTextTypeOption {
|
impl CellDataOperation<String> for RichTextTypeOption {
|
||||||
fn decode_cell_data<T>(
|
fn decode_cell_data<T>(
|
||||||
&self,
|
&self,
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
@ -80,10 +80,10 @@ mod tests {
|
|||||||
// date
|
// date
|
||||||
let field_type = FieldType::DateTime;
|
let field_type = FieldType::DateTime;
|
||||||
let date_time_field_meta = FieldBuilder::from_field_type(&field_type).build();
|
let date_time_field_meta = FieldBuilder::from_field_type(&field_type).build();
|
||||||
let json = serde_json::to_string(&DateCellDataSerde::from_timestamp(1647251762, None)).unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
type_option
|
type_option
|
||||||
.decode_cell_data(json, &field_type, &date_time_field_meta)
|
.decode_cell_data(1647251762.to_string(), &field_type, &date_time_field_meta)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.parse::<DateCellData>()
|
.parse::<DateCellData>()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
|
@ -30,11 +30,11 @@ impl TypeOptionBuilder for URLTypeOptionBuilder {
|
|||||||
#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
|
#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
|
||||||
pub struct URLTypeOption {
|
pub struct URLTypeOption {
|
||||||
#[pb(index = 1)]
|
#[pb(index = 1)]
|
||||||
data: String, //It's not used.
|
data: String, //It's not used yet.
|
||||||
}
|
}
|
||||||
impl_type_option!(URLTypeOption, FieldType::URL);
|
impl_type_option!(URLTypeOption, FieldType::URL);
|
||||||
|
|
||||||
impl CellDataOperation<EncodedCellData<URLCellData>, String> for URLTypeOption {
|
impl CellDataOperation<EncodedCellData<URLCellData>> for URLTypeOption {
|
||||||
fn decode_cell_data<T>(
|
fn decode_cell_data<T>(
|
||||||
&self,
|
&self,
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
@ -56,28 +56,31 @@ impl CellDataOperation<EncodedCellData<URLCellData>, String> for URLTypeOption {
|
|||||||
C: Into<CellContentChangeset>,
|
C: Into<CellContentChangeset>,
|
||||||
{
|
{
|
||||||
let changeset = changeset.into();
|
let changeset = changeset.into();
|
||||||
let mut cell_data = URLCellData {
|
let mut url = "".to_string();
|
||||||
url: "".to_string(),
|
|
||||||
content: changeset.to_string(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
|
if let Ok(Some(m)) = URL_REGEX.find(&changeset) {
|
||||||
// Only support https scheme by now
|
url = auto_append_scheme(m.as_str());
|
||||||
match url::Url::parse(m.as_str()) {
|
}
|
||||||
Ok(url) => {
|
URLCellData {
|
||||||
if url.scheme() == "https" {
|
url,
|
||||||
cell_data.url = url.into();
|
content: changeset.to_string(),
|
||||||
} else {
|
}
|
||||||
cell_data.url = format!("https://{}", m.as_str());
|
.to_json()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(_) => {
|
|
||||||
cell_data.url = format!("https://{}", m.as_str());
|
fn auto_append_scheme(s: &str) -> String {
|
||||||
}
|
// Only support https scheme by now
|
||||||
|
match url::Url::parse(s) {
|
||||||
|
Ok(url) => {
|
||||||
|
if url.scheme() == "https" {
|
||||||
|
url.into()
|
||||||
|
} else {
|
||||||
|
format!("https://{}", s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Err(_) => {
|
||||||
cell_data.to_json()
|
format!("https://{}", s)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -487,6 +487,35 @@ impl GridMetaEditor {
|
|||||||
self.grid_pad.read().await.delta_bytes()
|
self.grid_pad.read().await.delta_bytes()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn duplicate_grid(&self) -> FlowyResult<BuildGridContext> {
|
||||||
|
let grid_pad = self.grid_pad.read().await;
|
||||||
|
let original_blocks = grid_pad.get_block_metas();
|
||||||
|
let (duplicated_fields, duplicated_blocks) = grid_pad.duplicate_grid_meta().await;
|
||||||
|
|
||||||
|
let mut blocks_meta_data = vec![];
|
||||||
|
if original_blocks.len() == duplicated_blocks.len() {
|
||||||
|
for (index, original_block_meta) in original_blocks.iter().enumerate() {
|
||||||
|
let grid_block_meta_editor = self.block_manager.get_editor(&original_block_meta.block_id).await?;
|
||||||
|
let duplicated_block_id = &duplicated_blocks[index].block_id;
|
||||||
|
|
||||||
|
tracing::trace!("Duplicate block:{} meta data", duplicated_block_id);
|
||||||
|
let duplicated_block_meta_data = grid_block_meta_editor
|
||||||
|
.duplicate_block_meta_data(duplicated_block_id)
|
||||||
|
.await;
|
||||||
|
blocks_meta_data.push(duplicated_block_meta_data);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
debug_assert_eq!(original_blocks.len(), duplicated_blocks.len());
|
||||||
|
}
|
||||||
|
drop(grid_pad);
|
||||||
|
|
||||||
|
Ok(BuildGridContext {
|
||||||
|
field_metas: duplicated_fields,
|
||||||
|
blocks: duplicated_blocks,
|
||||||
|
blocks_meta_data,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
async fn modify<F>(&self, f: F) -> FlowyResult<()>
|
async fn modify<F>(&self, f: F) -> FlowyResult<()>
|
||||||
where
|
where
|
||||||
F: for<'a> FnOnce(&'a mut GridMetaPad) -> FlowyResult<Option<GridChangeset>>,
|
F: for<'a> FnOnce(&'a mut GridMetaPad) -> FlowyResult<Option<GridChangeset>>,
|
||||||
|
@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
|
|||||||
use std::fmt::Formatter;
|
use std::fmt::Formatter;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
|
|
||||||
pub trait CellDataOperation<D, CO: ToString> {
|
pub trait CellDataOperation<ED> {
|
||||||
fn decode_cell_data<T>(
|
fn decode_cell_data<T>(
|
||||||
&self,
|
&self,
|
||||||
encoded_data: T,
|
encoded_data: T,
|
||||||
@ -14,14 +14,14 @@ pub trait CellDataOperation<D, CO: ToString> {
|
|||||||
field_meta: &FieldMeta,
|
field_meta: &FieldMeta,
|
||||||
) -> FlowyResult<DecodedCellData>
|
) -> FlowyResult<DecodedCellData>
|
||||||
where
|
where
|
||||||
T: Into<D>;
|
T: Into<ED>;
|
||||||
|
|
||||||
//
|
//
|
||||||
fn apply_changeset<C: Into<CellContentChangeset>>(
|
fn apply_changeset<C: Into<CellContentChangeset>>(
|
||||||
&self,
|
&self,
|
||||||
changeset: C,
|
changeset: C,
|
||||||
cell_meta: Option<CellMeta>,
|
cell_meta: Option<CellMeta>,
|
||||||
) -> FlowyResult<CO>;
|
) -> FlowyResult<String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -128,9 +128,7 @@ pub fn apply_cell_data_changeset<T: Into<CellContentChangeset>>(
|
|||||||
let s = match field_meta.field_type {
|
let s = match field_meta.field_type {
|
||||||
FieldType::RichText => RichTextTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
FieldType::RichText => RichTextTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||||
FieldType::Number => NumberTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
FieldType::Number => NumberTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||||
FieldType::DateTime => DateTypeOption::from(field_meta)
|
FieldType::DateTime => DateTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||||
.apply_changeset(changeset, cell_meta)
|
|
||||||
.map(|data| data.to_string()),
|
|
||||||
FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
FieldType::SingleSelect => SingleSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||||
FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
FieldType::MultiSelect => MultiSelectTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||||
FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
FieldType::Checkbox => CheckboxTypeOption::from(field_meta).apply_changeset(changeset, cell_meta),
|
||||||
|
@ -173,7 +173,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delta_bytes(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
fn view_delta_data(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
let manager = self.0.clone();
|
let manager = self.0.clone();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
@ -197,7 +197,7 @@ impl ViewDataProcessor for TextBlockViewDataProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_create_view_data(
|
fn process_view_delta_data(
|
||||||
&self,
|
&self,
|
||||||
_user_id: &str,
|
_user_id: &str,
|
||||||
_view_id: &str,
|
_view_id: &str,
|
||||||
@ -245,13 +245,13 @@ impl ViewDataProcessor for GridViewDataProcessor {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn delta_bytes(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
fn view_delta_data(&self, view_id: &str) -> FutureResult<Bytes, FlowyError> {
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
let grid_manager = self.0.clone();
|
let grid_manager = self.0.clone();
|
||||||
FutureResult::new(async move {
|
FutureResult::new(async move {
|
||||||
let editor = grid_manager.open_grid(view_id).await?;
|
let editor = grid_manager.open_grid(view_id).await?;
|
||||||
let delta_bytes = editor.delta_bytes().await;
|
let delta_bytes = editor.duplicate_grid().await?;
|
||||||
Ok(delta_bytes)
|
Ok(delta_bytes.into())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,7 +264,7 @@ impl ViewDataProcessor for GridViewDataProcessor {
|
|||||||
FutureResult::new(async move { make_grid_view_data(&user_id, &view_id, grid_manager, build_context).await })
|
FutureResult::new(async move { make_grid_view_data(&user_id, &view_id, grid_manager, build_context).await })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_create_view_data(&self, user_id: &str, view_id: &str, data: Vec<u8>) -> FutureResult<Bytes, FlowyError> {
|
fn process_view_delta_data(&self, user_id: &str, view_id: &str, data: Vec<u8>) -> FutureResult<Bytes, FlowyError> {
|
||||||
let user_id = user_id.to_string();
|
let user_id = user_id.to_string();
|
||||||
let view_id = view_id.to_string();
|
let view_id = view_id.to_string();
|
||||||
let grid_manager = self.0.clone();
|
let grid_manager = self.0.clone();
|
||||||
|
@ -9,16 +9,16 @@ pub fn create(block_manager: Arc<TextBlockManager>) -> Module {
|
|||||||
let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(block_manager);
|
let mut module = Module::new().name(env!("CARGO_PKG_NAME")).data(block_manager);
|
||||||
|
|
||||||
module = module
|
module = module
|
||||||
.event(BlockEvent::GetBlockData, get_block_data_handler)
|
.event(TextBlockEvent::GetBlockData, get_block_data_handler)
|
||||||
.event(BlockEvent::ApplyDelta, apply_delta_handler)
|
.event(TextBlockEvent::ApplyDelta, apply_delta_handler)
|
||||||
.event(BlockEvent::ExportDocument, export_handler);
|
.event(TextBlockEvent::ExportDocument, export_handler);
|
||||||
|
|
||||||
module
|
module
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Display, Hash, ProtoBuf_Enum, Flowy_Event)]
|
||||||
#[event_err = "FlowyError"]
|
#[event_err = "FlowyError"]
|
||||||
pub enum BlockEvent {
|
pub enum TextBlockEvent {
|
||||||
#[event(input = "TextBlockId", output = "TextBlockDelta")]
|
#[event(input = "TextBlockId", output = "TextBlockDelta")]
|
||||||
GetBlockData = 0,
|
GetBlockData = 0,
|
||||||
|
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user