Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8b294ecd4f | ||
|
|
ed889ab426 | ||
|
|
92cef81a89 | ||
|
|
5a654711ec |
No files matched your search
@@ -7,7 +7,6 @@ on:
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/**"
|
||||
- "!frontend/appflowy_tauri/**"
|
||||
|
||||
pull_request:
|
||||
branches:
|
||||
@@ -15,7 +14,6 @@ on:
|
||||
- "release/*"
|
||||
paths:
|
||||
- "frontend/**"
|
||||
- "!frontend/appflowy_tauri/**"
|
||||
|
||||
env:
|
||||
FLUTTER_VERSION: "3.7.5"
|
||||
|
||||
@@ -138,7 +138,7 @@ jobs:
|
||||
job:
|
||||
- {
|
||||
target: x86_64-apple-darwin,
|
||||
os: macos-11,
|
||||
os: macos-10.15,
|
||||
extra-build-args: "",
|
||||
}
|
||||
steps:
|
||||
@@ -236,6 +236,14 @@ jobs:
|
||||
extra-build-args: "",
|
||||
flutter_profile: production-linux-x86_64,
|
||||
}
|
||||
# - { arch: aarch64, target: aarch64-unknown-linux-gnu, os: ubuntu-20.04, extra-build-args: "", flutter_profile: production-linux-aarch64 }
|
||||
- {
|
||||
arch: x86_64,
|
||||
target: x86_64-unknown-linux-gnu,
|
||||
os: ubuntu-18.04,
|
||||
extra-build-args: "",
|
||||
flutter_profile: production-linux-x86_64,
|
||||
}
|
||||
steps:
|
||||
- name: Checkout source code
|
||||
uses: actions/checkout@v3
|
||||
|
||||
@@ -13,6 +13,4 @@ jobs:
|
||||
env:
|
||||
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
|
||||
with:
|
||||
args: |
|
||||
@appflowytranslators English UI strings has been updated.
|
||||
Link to changes: ${{github.event.compare}}
|
||||
args: '@appflowytranslators English UI strings has been updated.'
|
||||
@@ -1,17 +1,5 @@
|
||||
# Release Notes
|
||||
|
||||
## Version 0.1.5 - 11/05/2023
|
||||
|
||||
### Bug Fixes
|
||||
- Fix: calendar dates don't match with weekdays.
|
||||
- Fix: sort numbers in Grid.
|
||||
|
||||
## Version 0.1.4 - 04/05/2023
|
||||
|
||||
### New features
|
||||
- Use AppFlowy’s calendar views to plan and manage tasks and deadlines.
|
||||
- Writing can be improved with the help of OpenAI.
|
||||
|
||||
## Version 0.1.3 - 24/04/2023
|
||||
|
||||
### New features
|
||||
|
||||
@@ -23,10 +23,9 @@ You are in charge of your data and customizations.
|
||||
<a href="https://twitter.com/appflowy"><b>Twitter</b></a>
|
||||
</p>
|
||||
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664610-fc209a97-815e-4716-af07-d94a859d1907.png" alt="AppFlowy Docs & Notes & Wikis" width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664628-5def2450-914a-4b2d-b907-92b7476b9863.png" alt="AppFlowy Databases for Tasks and Projects" width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664642-22e26c1b-5eae-4635-9aa6-b12ecf1c3c46.png" alt="AppFlowy Kanban Board for To-Dos" width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/236664657-dc5291f3-67b0-4a43-a818-640e92735deb.png" alt="AppFlowy OpenAI GPT Writers" width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/200787830-96be260b-d0a0-4152-864e-6730b19095cd.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/174753177-98e4c899-2356-4137-bb42-374bba2b127b.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
|
||||
<p align="center"><img src="https://user-images.githubusercontent.com/12026239/190650183-a940f1e0-a2c5-4797-ab3a-56758f6f696c.png" alt="The Open Source Alternative To Notion." width="1000px" /></p>
|
||||
|
||||
## User Installation
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ CARGO_MAKE_EXTEND_WORKSPACE_MAKEFILE = true
|
||||
CARGO_MAKE_CRATE_FS_NAME = "dart_ffi"
|
||||
CARGO_MAKE_CRATE_NAME = "dart-ffi"
|
||||
LIB_NAME = "dart_ffi"
|
||||
CURRENT_APP_VERSION = "0.1.5"
|
||||
CURRENT_APP_VERSION = "0.1.3"
|
||||
FLUTTER_DESKTOP_FEATURES = "dart,rev-sqlite"
|
||||
PRODUCT_NAME = "AppFlowy"
|
||||
# CRATE_TYPE: https://doc.rust-lang.org/reference/linkage.html
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -1,422 +0,0 @@
|
||||
{
|
||||
"appName": "AppFlowy",
|
||||
"defaultUsername": "أنا",
|
||||
"welcomeText": "مرحبًا بك في @: appName",
|
||||
"githubStarText": "نجمة على GitHub",
|
||||
"subscribeNewsletterText": "اشترك في النشرة الإخبارية",
|
||||
"letsGoButtonText": "بداية سريعة",
|
||||
"title": "عنوان",
|
||||
"signUp": {
|
||||
"buttonText": "اشتراك",
|
||||
"title": "قم بالتسجيل في @: appName",
|
||||
"getStartedText": "البدء",
|
||||
"emptyPasswordError": "لا يمكن أن تكون كلمة المرور فارغة",
|
||||
"repeatPasswordEmptyError": "إعادة كلمة المرور لا يمكن أن تكون فارغة",
|
||||
"unmatchedPasswordError": "تكرار كلمة المرور ليس هو نفسه كلمة المرور",
|
||||
"alreadyHaveAnAccount": "هل لديك حساب؟",
|
||||
"emailHint": "بريد إلكتروني",
|
||||
"passwordHint": "كلمة المرور",
|
||||
"repeatPasswordHint": "اعد كلمة السر"
|
||||
},
|
||||
"signIn": {
|
||||
"loginTitle": "تسجيل الدخول إلى @: appName",
|
||||
"loginButtonText": "تسجيل الدخول",
|
||||
"buttonText": "تسجيل الدخول",
|
||||
"forgotPassword": "هل نسيت كلمة السر؟",
|
||||
"emailHint": "بريد إلكتروني",
|
||||
"passwordHint": "كلمة المرور",
|
||||
"dontHaveAnAccount": "ليس لديك حساب؟",
|
||||
"repeatPasswordEmptyError": "إعادة كلمة المرور لا يمكن أن تكون فارغة",
|
||||
"unmatchedPasswordError": "تكرار كلمة المرور ليس هو نفسه كلمة المرور"
|
||||
},
|
||||
"workspace": {
|
||||
"create": "قم بإنشاء مساحة عمل",
|
||||
"hint": "مساحة العمل",
|
||||
"notFoundError": "مساحة العمل غير موجودة"
|
||||
},
|
||||
"shareAction": {
|
||||
"buttonText": "مشاركه",
|
||||
"workInProgress": "قريباً",
|
||||
"markdown": "Markdown",
|
||||
"copyLink": "نسخ الرابط"
|
||||
},
|
||||
"moreAction": {
|
||||
"small": "صغير",
|
||||
"medium": "متوسط",
|
||||
"large": "كبير",
|
||||
"fontSize": "حجم الخط",
|
||||
"import": "استيراد"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "إعادة تسمية",
|
||||
"delete": "يمسح",
|
||||
"duplicate": "كرر"
|
||||
},
|
||||
"blankPageTitle": "صفحة فارغة",
|
||||
"newPageText": "صفحة جديدة",
|
||||
"trash": {
|
||||
"text": "المهملات",
|
||||
"restoreAll": "استعادة الكل",
|
||||
"deleteAll": "حذف الكل",
|
||||
"pageHeader": {
|
||||
"fileName": "اسم الملف",
|
||||
"lastModified": "آخر تعديل",
|
||||
"created": "تم انشاؤها"
|
||||
}
|
||||
},
|
||||
"deletePagePrompt": {
|
||||
"text": "هذه الصفحة في المهملات",
|
||||
"restore": "استعادة الصفحة",
|
||||
"deletePermanent": "الحذف بشكل نهائي"
|
||||
},
|
||||
"dialogCreatePageNameHint": "اسم الصفحة",
|
||||
"questionBubble": {
|
||||
"shortcuts": "الاختصارات",
|
||||
"whatsNew": "ما هو الجديد؟",
|
||||
"help": "المساعدة والدعم",
|
||||
"markdown": "Markdown",
|
||||
"debug": {
|
||||
"name": "معلومات التصحيح",
|
||||
"success": "تم نسخ معلومات التصحيح إلى الحافظة!",
|
||||
"fail": "تعذر نسخ معلومات التصحيح إلى الحافظة"
|
||||
}
|
||||
},
|
||||
"menuAppHeader": {
|
||||
"addPageTooltip": "أضف صفحة في الداخل بسرعة",
|
||||
"defaultNewPageName": "بدون عنوان",
|
||||
"renameDialog": "إعادة تسمية"
|
||||
},
|
||||
"toolbar": {
|
||||
"undo": "الغاء التحميل",
|
||||
"redo": "إعادة",
|
||||
"bold": "عريض",
|
||||
"italic": "مائل",
|
||||
"underline": "تسطير",
|
||||
"strike": "يتوسطه خط",
|
||||
"numList": "قائمة مرقمة",
|
||||
"bulletList": "قائمة نقطية",
|
||||
"checkList": "قائمة تدقيق",
|
||||
"inlineCode": "رمز مضمّن",
|
||||
"quote": "كتلة اقتباس",
|
||||
"header": "رأس",
|
||||
"highlight": "تسليط الضوء",
|
||||
"color": "لون"
|
||||
},
|
||||
"tooltip": {
|
||||
"lightMode": "قم بالتبديل إلى وضع الإضاءة",
|
||||
"darkMode": "قم بالتبديل إلى الوضع الداكن",
|
||||
"openAsPage": "فتح كصفحة",
|
||||
"addNewRow": "أضف صفًا جديدًا",
|
||||
"openMenu": "انقر لفتح القائمة",
|
||||
"viewDataBase": "عرض قاعدة البيانات",
|
||||
"referencePage": "تمت الإشارة إلى هذا {name}"
|
||||
},
|
||||
"sideBar": {
|
||||
"closeSidebar": "إغلاق الشريط الجانبي",
|
||||
"openSidebar": "فتح الشريط الجانبي"
|
||||
},
|
||||
"notifications": {
|
||||
"export": {
|
||||
"markdown": "تم تصدير ملاحظة إلى Markdown",
|
||||
"path": "Documents/flowy"
|
||||
}
|
||||
},
|
||||
"contactsPage": {
|
||||
"title": "جهات الاتصال",
|
||||
"whatsHappening": "ماذا يحدث هذا الاسبوع؟",
|
||||
"addContact": "إضافة جهة اتصال",
|
||||
"editContact": "تحرير جهة الاتصال"
|
||||
},
|
||||
"button": {
|
||||
"OK": "نعم",
|
||||
"Done": "منتهي",
|
||||
"Cancel": "إلغاء",
|
||||
"signIn": "تسجيل الدخول",
|
||||
"signOut": "خروج",
|
||||
"complete": "مكتمل",
|
||||
"save": "حفظ",
|
||||
"generate": "يولد",
|
||||
"esc": "خروج",
|
||||
"keep": "ابقاء",
|
||||
"tryAgain": "حاول ثانية",
|
||||
"discard": "تجاهل",
|
||||
"replace": "يستبدل",
|
||||
"insertBelow": "إدراج أدناه"
|
||||
},
|
||||
"label": {
|
||||
"welcome": "مرحباً!",
|
||||
"firstName": "الاسم الأول",
|
||||
"middleName": "الاسم الأوسط",
|
||||
"lastName": "اسم العائلة",
|
||||
"stepX": "الخطوة {X}"
|
||||
},
|
||||
"oAuth": {
|
||||
"err": {
|
||||
"failedTitle": "غير قادر على الاتصال بحسابك.",
|
||||
"failedMsg": "يرجى التأكد من إكمال عملية تسجيل الدخول في متصفحك."
|
||||
},
|
||||
"google": {
|
||||
"title": "تسجيل الدخول إلى GOOGLE",
|
||||
"instruction1": "لاستيراد جهات اتصال Google الخاصة بك ، ستحتاج إلى ترخيص هذا التطبيق باستخدام متصفح الويب الخاص بك.",
|
||||
"instruction2": "انسخ هذا الرمز إلى الحافظة الخاصة بك عن طريق النقر فوق الرمز أو تحديد النص:",
|
||||
"instruction3": "انتقل إلى الرابط التالي في متصفح الويب الخاص بك ، وأدخل الرمز أعلاه:",
|
||||
"instruction4": "اضغط على الزر أدناه عند الانتهاء من التسجيل:"
|
||||
}
|
||||
},
|
||||
"settings": {
|
||||
"title": "إعدادات",
|
||||
"menu": {
|
||||
"appearance": "مظهر",
|
||||
"language": "لغة",
|
||||
"user": "مستخدم",
|
||||
"files": "الملفات",
|
||||
"open": "أفتح الإعدادات"
|
||||
},
|
||||
"appearance": {
|
||||
"themeMode": {
|
||||
"label": "وضع السمة",
|
||||
"light": "وضع الضوء",
|
||||
"dark": "الوضع الداكن",
|
||||
"system": "التكيف مع النظام"
|
||||
},
|
||||
"theme": "سمة"
|
||||
},
|
||||
"files": {
|
||||
"defaultLocation": "أين يتم تخزين بياناتك الآن",
|
||||
"doubleTapToCopy": "انقر نقرًا مزدوجًا لنسخ المسار",
|
||||
"restoreLocation": "استعادة المسار الافتراضي AppFlowy",
|
||||
"customizeLocation": "افتح مجلدًا آخر",
|
||||
"restartApp": "يرجى إعادة تشغيل التطبيق لتصبح التغييرات سارية المفعول.",
|
||||
"exportDatabase": "تصدير قاعدة البيانات",
|
||||
"selectFiles": "حدد الملفات التي تريد تصديرها",
|
||||
"createNewFolder": "انشاء مجلد جديد",
|
||||
"createNewFolderDesc": "أخبرنا بالمكان الذي تريد تخزين بياناتك فيه",
|
||||
"open": "يفتح",
|
||||
"openFolder": "افتح مجلدًا موجودًا",
|
||||
"openFolderDesc": "اقرأها واكتبها في مجلد AppFlowy الموجود لديك",
|
||||
"folderHintText": "إسم الملف",
|
||||
"location": "إنشاء مجلد جديد",
|
||||
"locationDesc": "اختر اسمًا لمجلد بيانات AppFlowy",
|
||||
"browser": "تصفح",
|
||||
"create": "يخلق",
|
||||
"folderPath": "مسار لتخزين المجلد الخاص بك",
|
||||
"locationCannotBeEmpty": "لا يمكن أن يكون المسار فارغًا",
|
||||
"pathCopiedSnackbar": "تم نسخ مسار تخزين الملفات إلى الحافظة!"
|
||||
},
|
||||
"user": {
|
||||
"name": "اسم",
|
||||
"icon": "أيقونة",
|
||||
"selectAnIcon": "حدد أيقونة",
|
||||
"pleaseInputYourOpenAIKey": "الرجاء إدخال مفتاح OpenAI الخاص بك"
|
||||
}
|
||||
},
|
||||
"grid": {
|
||||
"settings": {
|
||||
"filter": "منقي",
|
||||
"sort": "نوع",
|
||||
"sortBy": "ترتيب حسب",
|
||||
"Properties": "ملكيات",
|
||||
"group": "مجموعة",
|
||||
"addFilter": "أضف عامل تصفية",
|
||||
"deleteFilter": "حذف عامل التصفية",
|
||||
"filterBy": "مصنف بواسطة...",
|
||||
"typeAValue": "اكتب قيمة ...",
|
||||
"layout": "تَخطِيط"
|
||||
},
|
||||
"textFilter": {
|
||||
"contains": "يتضمن",
|
||||
"doesNotContain": "لا يحتوي",
|
||||
"endsWith": "ينتهي بـ",
|
||||
"startWith": "ابدا ب",
|
||||
"is": "يكون",
|
||||
"isNot": "ليس",
|
||||
"isEmpty": "فارغ",
|
||||
"isNotEmpty": "ليس فارغا",
|
||||
"choicechipPrefix": {
|
||||
"isNot": "لا",
|
||||
"startWith": "ابدا ب",
|
||||
"endWith": "ينتهي بـ",
|
||||
"isEmpty": "فارغ",
|
||||
"isNotEmpty": "ليس فارغا"
|
||||
}
|
||||
},
|
||||
"checkboxFilter": {
|
||||
"isChecked": "التحقق",
|
||||
"isUnchecked": "لم يتم التحقق منه",
|
||||
"choicechipPrefix": {
|
||||
"is": "يكون"
|
||||
}
|
||||
},
|
||||
"checklistFilter": {
|
||||
"isComplete": "كاملة",
|
||||
"isIncomplted": "غير مكتمل"
|
||||
},
|
||||
"singleSelectOptionFilter": {
|
||||
"is": "يكون",
|
||||
"isNot": "ليس",
|
||||
"isEmpty": "فارغ",
|
||||
"isNotEmpty": "ليس فارغا"
|
||||
},
|
||||
"multiSelectOptionFilter": {
|
||||
"contains": "يتضمن",
|
||||
"doesNotContain": "لا يحتوي",
|
||||
"isEmpty": "فارغ",
|
||||
"isNotEmpty": "ليس فارغا"
|
||||
},
|
||||
"field": {
|
||||
"hide": "يخفي",
|
||||
"insertLeft": "أدخل اليسار",
|
||||
"insertRight": "أدخل اليمين",
|
||||
"duplicate": "ينسخ",
|
||||
"delete": "يمسح",
|
||||
"textFieldName": "نص",
|
||||
"checkboxFieldName": "خانة اختيار",
|
||||
"dateFieldName": "تاريخ",
|
||||
"numberFieldName": "أعداد",
|
||||
"singleSelectFieldName": "يختار",
|
||||
"multiSelectFieldName": "تحديد متعدد",
|
||||
"urlFieldName": "URL",
|
||||
"checklistFieldName": "قائمة تدقيق",
|
||||
"numberFormat": "تنسيق الأرقام",
|
||||
"dateFormat": "صيغة التاريخ",
|
||||
"includeTime": "أضف الوقت",
|
||||
"dateFormatFriendly": "شهر يوم سنه",
|
||||
"dateFormatISO": "سنة شهر يوم",
|
||||
"dateFormatLocal": "شهر يوم سنه",
|
||||
"dateFormatUS": "سنة شهر يوم",
|
||||
"dateFormatDayMonthYear": "يوم شهر سنة",
|
||||
"timeFormat": "تنسيق الوقت",
|
||||
"invalidTimeFormat": "تنسيق غير صالح",
|
||||
"timeFormatTwelveHour": "12 ساعة",
|
||||
"timeFormatTwentyFourHour": "24 ساعة",
|
||||
"addSelectOption": "أضف خيارًا",
|
||||
"optionTitle": "خيارات",
|
||||
"addOption": "إضافة خيار",
|
||||
"editProperty": "تحرير الملكية",
|
||||
"newProperty": "خاصية جديدة",
|
||||
"deleteFieldPromptMessage": "هل أنت متأكد؟ سيتم حذف هذه الخاصية"
|
||||
},
|
||||
"sort": {
|
||||
"ascending": "تصاعدي",
|
||||
"descending": "تنازلي",
|
||||
"deleteSort": "حذف الفرز",
|
||||
"addSort": "أضف نوعًا"
|
||||
},
|
||||
"row": {
|
||||
"duplicate": "مكرره",
|
||||
"delete": "يمسح",
|
||||
"textPlaceholder": "فارغ",
|
||||
"copyProperty": "نسخ الممتلكات إلى الحافظة",
|
||||
"count": "عدد",
|
||||
"newRow": "صف جديد"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "يخلق",
|
||||
"purpleColor": "أرجواني",
|
||||
"pinkColor": "لون القرنفل",
|
||||
"lightPinkColor": "وردي فاتح",
|
||||
"orangeColor": "البرتقالي",
|
||||
"yellowColor": "أصفر",
|
||||
"limeColor": "جير",
|
||||
"greenColor": "أخضر",
|
||||
"aquaColor": "أكوا",
|
||||
"blueColor": "أزرق",
|
||||
"deleteTag": "حذف العلامة",
|
||||
"colorPanelTitle": "الألوان",
|
||||
"panelTitle": "حدد خيارًا أو أنشئ خيارًا",
|
||||
"searchOption": "ابحث عن خيار"
|
||||
},
|
||||
"checklist": {
|
||||
"panelTitle": "أضف عنصرًا"
|
||||
},
|
||||
"menuName": "شبكة",
|
||||
"referencedGridPrefix": "نظرا ل"
|
||||
},
|
||||
"document": {
|
||||
"menuName": "وثيقة",
|
||||
"date": {
|
||||
"timeHintTextInTwelveHour": "01:00 مساءً",
|
||||
"timeHintTextInTwentyFourHour": "13:00"
|
||||
},
|
||||
"slashMenu": {
|
||||
"board": {
|
||||
"selectABoardToLinkTo": "حدد لوحة للارتباط بها",
|
||||
"createANewBoard": "قم بإنشاء لوحة جديدة"
|
||||
},
|
||||
"grid": {
|
||||
"selectAGridToLinkTo": "حدد الشبكة للارتباط بها",
|
||||
"createANewGrid": "قم بإنشاء شبكة جديدة"
|
||||
}
|
||||
},
|
||||
"plugins": {
|
||||
"referencedBoard": "المجلس المشار إليه",
|
||||
"referencedGrid": "الشبكة المشار إليها",
|
||||
"autoGeneratorMenuItemName": "كاتب OpenAI",
|
||||
"autoGeneratorTitleName": "OpenAI: اطلب من الذكاء الاصطناعي كتابة أي شيء ...",
|
||||
"autoGeneratorLearnMore": "يتعلم أكثر",
|
||||
"autoGeneratorGenerate": "يولد",
|
||||
"autoGeneratorHintText": "اسأل OpenAI ...",
|
||||
"autoGeneratorCantGetOpenAIKey": "لا يمكن الحصول على مفتاح OpenAI",
|
||||
"smartEdit": "مساعدي الذكاء الاصطناعي",
|
||||
"openAI": "OpenAI",
|
||||
"smartEditFixSpelling": "أصلح التهجئة",
|
||||
"warning": "⚠️ يمكن أن تكون استجابات الذكاء الاصطناعي غير دقيقة أو مضللة.",
|
||||
"smartEditSummarize": "لخص",
|
||||
"smartEditCouldNotFetchResult": "تعذر جلب النتيجة من OpenAI",
|
||||
"smartEditCouldNotFetchKey": "تعذر جلب مفتاح OpenAI",
|
||||
"smartEditDisabled": "قم بتوصيل OpenAI في الإعدادات",
|
||||
"discardResponse": "هل تريد تجاهل استجابات الذكاء الاصطناعي؟",
|
||||
"cover": {
|
||||
"changeCover": "تبديل الغطاء",
|
||||
"colors": "الألوان",
|
||||
"images": "الصور",
|
||||
"clearAll": "امسح الكل",
|
||||
"abstract": "خلاصة",
|
||||
"addCover": "أضف الغلاف",
|
||||
"addLocalImage": "أضف الصورة المحلية",
|
||||
"invalidImageUrl": "عنوان URL للصورة غير صالح",
|
||||
"failedToAddImageToGallery": "فشل في إضافة الصورة إلى المعرض",
|
||||
"enterImageUrl": "أدخل عنوان URL للصورة",
|
||||
"add": "يضيف",
|
||||
"back": "خلف",
|
||||
"saveToGallery": "حفظ في المعرض",
|
||||
"removeIcon": "إزالة الرمز",
|
||||
"pasteImageUrl": "لصق عنوان URL للصورة",
|
||||
"or": "أو",
|
||||
"pickFromFiles": "اختر من الملفات",
|
||||
"couldNotFetchImage": "تعذر جلب الصورة",
|
||||
"imageSavingFailed": "فشل حفظ الصورة",
|
||||
"addIcon": "إضافة أيقونة",
|
||||
"coverRemoveAlert": "ستتم إزالته من الغلاف بعد حذفه.",
|
||||
"alertDialogConfirmation": "هل أنت متأكد أنك تريد الاستمرار؟"
|
||||
},
|
||||
"mathEquation": {
|
||||
"addMathEquation": "أضف معادلة رياضية",
|
||||
"editMathEquation": "تحرير المعادلة الرياضية"
|
||||
}
|
||||
}
|
||||
},
|
||||
"board": {
|
||||
"column": {
|
||||
"create_new_card": "جديد"
|
||||
},
|
||||
"menuName": "سبورة",
|
||||
"referencedBoardPrefix": "نظرا ل"
|
||||
},
|
||||
"calendar": {
|
||||
"menuName": "تقويم",
|
||||
"defaultNewCalendarTitle": "بدون عنوان",
|
||||
"navigation": {
|
||||
"today": "اليوم",
|
||||
"jumpToday": "انتقل إلى اليوم",
|
||||
"previousMonth": "الشهر الماضى",
|
||||
"nextMonth": "الشهر القادم"
|
||||
},
|
||||
"settings": {
|
||||
"showWeekNumbers": "إظهار أرقام الأسبوع",
|
||||
"showWeekends": "عرض عطلات نهاية الأسبوع",
|
||||
"firstDayOfWeek": "اليوم الأول من الأسبوع",
|
||||
"layoutDateField": "تقويم التخطيط بواسطة"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,7 +280,7 @@
|
||||
"numberFormat": "Number format",
|
||||
"dateFormat": "Date format",
|
||||
"includeTime": "Include time",
|
||||
"dateFormatFriendly": "Month Day, Year",
|
||||
"dateFormatFriendly": "Month Day,Year",
|
||||
"dateFormatISO": "Year-Month-Day",
|
||||
"dateFormatLocal": "Month/Day/Year",
|
||||
"dateFormatUS": "Year/Month/Day",
|
||||
@@ -308,8 +308,7 @@
|
||||
"textPlaceholder": "Empty",
|
||||
"copyProperty": "Copied property to clipboard",
|
||||
"count": "Count",
|
||||
"newRow": "New row",
|
||||
"action": "Action"
|
||||
"newRow": "New row"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Create",
|
||||
@@ -363,7 +362,6 @@
|
||||
"smartEditFixSpelling": "Fix spelling",
|
||||
"warning": "⚠️ AI responses can be inaccurate or misleading.",
|
||||
"smartEditSummarize": "Summarize",
|
||||
"smartEditImproveWriting": "Improve Writing",
|
||||
"smartEditCouldNotFetchResult": "Could not fetch result from OpenAI",
|
||||
"smartEditCouldNotFetchKey": "Could not fetch OpenAI key",
|
||||
"smartEditDisabled": "Connect OpenAI in Settings",
|
||||
@@ -417,10 +415,8 @@
|
||||
"settings": {
|
||||
"showWeekNumbers": "Show week numbers",
|
||||
"showWeekends": "Show weekends",
|
||||
"firstDayOfWeek": "Start week on",
|
||||
"layoutDateField": "Layout calendar by",
|
||||
"noDateTitle": "No Date",
|
||||
"emptyNoDate": "No unscheduled events"
|
||||
"firstDayOfWeek": "First day of week",
|
||||
"layoutDateField": "Layout calendar by"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +175,7 @@
|
||||
"numberFormat": "Format angka",
|
||||
"dateFormat": "Format tanggal",
|
||||
"includeTime": "Sertakan waktu",
|
||||
"dateFormatFriendly": "Bulan Hari, Tahun",
|
||||
"dateFormatFriendly": "Bulan Hari,Tahun",
|
||||
"dateFormatISO": "Tahun-Bulan-Hari",
|
||||
"dateFormatLocal": "Bulan/Hari/Tahun",
|
||||
"dateFormatUS": "Tahun/Bulan/Hari",
|
||||
|
||||
@@ -167,7 +167,7 @@
|
||||
"numberFormat": "数値書式",
|
||||
"dateFormat": "日付書式",
|
||||
"includeTime": "時刻を含める",
|
||||
"dateFormatFriendly": "月 日, 年",
|
||||
"dateFormatFriendly": "月 日,年",
|
||||
"dateFormatISO": "年-月-日",
|
||||
"dateFormatLocal": "月/日/年",
|
||||
"dateFormatUS": "年/月/日",
|
||||
|
||||
@@ -179,7 +179,7 @@
|
||||
"numberFormat": "숫자 형식",
|
||||
"dateFormat": "날짜 형식",
|
||||
"includeTime": "시간 표시",
|
||||
"dateFormatFriendly": "월 일, 년",
|
||||
"dateFormatFriendly": "월 일,년",
|
||||
"dateFormatISO": "년-월-일",
|
||||
"dateFormatLocal": "월/일/년",
|
||||
"dateFormatUS": "년/월/일",
|
||||
|
||||
@@ -275,7 +275,7 @@
|
||||
"numberFormat": "Formato numérico",
|
||||
"dateFormat": "Formato de data",
|
||||
"includeTime": "Incluir hora",
|
||||
"dateFormatFriendly": "Mês Dia, Ano",
|
||||
"dateFormatFriendly": "Mês Dia,Ano",
|
||||
"dateFormatISO": "Ano-Mês-Dia",
|
||||
"dateFormatLocal": "Mês/Dia/Ano",
|
||||
"dateFormatUS": "Ano/Mês/Dia",
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
"small": "маленький",
|
||||
"medium": "средний",
|
||||
"large": "большой",
|
||||
"fontSize": "Размер шрифта",
|
||||
"import": "Импортировать"
|
||||
"fontSize": "Размер шрифта"
|
||||
},
|
||||
"disclosureAction": {
|
||||
"rename": "Переименовать",
|
||||
@@ -71,10 +70,8 @@
|
||||
},
|
||||
"dialogCreatePageNameHint": "Имя страницы",
|
||||
"questionBubble": {
|
||||
"shortcuts": "Комбинации клавиш",
|
||||
"whatsNew": "Что нового?",
|
||||
"help": "Помощь",
|
||||
"markdown": "Markdown",
|
||||
"debug": {
|
||||
"name": "Отладочная информация",
|
||||
"success": "Скопировано в буфер обмена!",
|
||||
@@ -129,7 +126,6 @@
|
||||
},
|
||||
"button": {
|
||||
"OK": "OK",
|
||||
"Done": "Завершить",
|
||||
"Cancel": "Отмена",
|
||||
"signIn": "Войти",
|
||||
"signOut": "Выйти",
|
||||
@@ -174,7 +170,7 @@
|
||||
},
|
||||
"appearance": {
|
||||
"themeMode": {
|
||||
"label": "Тема приложения",
|
||||
"label": "Режим темы",
|
||||
"light": "Светлая",
|
||||
"dark": "Тёмная",
|
||||
"system": "Системная"
|
||||
@@ -201,7 +197,7 @@
|
||||
"create": "Создать",
|
||||
"folderPath": "Путь к вашей папке",
|
||||
"locationCannotBeEmpty": "Путь не может быть пустым",
|
||||
"pathCopiedSnackbar": "Путь скопирован в буфер обмена!"
|
||||
"pathCopiedSnackbar": "File storage path copied to clipboard!"
|
||||
},
|
||||
"user": {
|
||||
"name": "Имя",
|
||||
@@ -220,8 +216,7 @@
|
||||
"addFilter": "Добавить фильтр",
|
||||
"deleteFilter": "Удалить фильтр",
|
||||
"filterBy": "Фильтровать по...",
|
||||
"typeAValue": "Введите значение...",
|
||||
"layout": "Раскладка"
|
||||
"typeAValue": "Введите значение..."
|
||||
},
|
||||
"textFilter": {
|
||||
"contains": "Содержит",
|
||||
@@ -308,8 +303,7 @@
|
||||
"textPlaceholder": "Пусто",
|
||||
"copyProperty": "Свойство скопировано",
|
||||
"count": "Количество",
|
||||
"newRow": "Новая строка",
|
||||
"action": "Действия"
|
||||
"newRow": "Новая строка"
|
||||
},
|
||||
"selectOption": {
|
||||
"create": "Создать",
|
||||
@@ -390,10 +384,6 @@
|
||||
"addIcon": "Добавить иконку",
|
||||
"coverRemoveAlert": "Изображение будет удалено с обложки",
|
||||
"alertDialogConfirmation": "Вы хотите продолжить?"
|
||||
},
|
||||
"mathEquation": {
|
||||
"addMathEquation": "Добавить математическое выражение",
|
||||
"editMathEquation": "Редактировать математическое выражение"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
"numberFormat": "Sifferformat",
|
||||
"dateFormat": "Datumformat",
|
||||
"includeTime": "Inkludera tid",
|
||||
"dateFormatFriendly": "Månad Dag, År",
|
||||
"dateFormatFriendly": "Månad Dag,År",
|
||||
"dateFormatISO": "År-Månad-Dag",
|
||||
"dateFormatLocal": "Månad/Dag/År",
|
||||
"dateFormatUS": "År/Månad/Dag",
|
||||
|
||||
@@ -183,7 +183,7 @@
|
||||
"numberFormat": "数字格式",
|
||||
"dateFormat": "日期格式",
|
||||
"includeTime": "包含时间",
|
||||
"dateFormatFriendly": "月 日, 年",
|
||||
"dateFormatFriendly": "月 日,年",
|
||||
"dateFormatISO": "年-月-日",
|
||||
"dateFormatLocal": "月/日/年",
|
||||
"dateFormatUS": "年/月/日",
|
||||
|
||||
@@ -278,7 +278,7 @@
|
||||
"numberFormat": "數字格式",
|
||||
"dateFormat": "日期格式",
|
||||
"includeTime": "包含時間",
|
||||
"dateFormatFriendly": "月 日, 年",
|
||||
"dateFormatFriendly": "月 日,年",
|
||||
"dateFormatISO": "年-月-日",
|
||||
"dateFormatLocal": "月/日/年",
|
||||
"dateFormatUS": "年/月/日",
|
||||
|
||||
@@ -1,54 +0,0 @@
|
||||
import 'package:flowy_infra_ui/widget/rounded_button.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'util/util.dart';
|
||||
|
||||
/// Integration tests for an empty board. The [TestWorkspaceService] will load
|
||||
/// a workspace from an empty board `assets/test/workspaces/board.zip` for all
|
||||
/// tests.
|
||||
///
|
||||
/// To create another integration test with a preconfigured workspace.
|
||||
/// Use the following steps.
|
||||
/// 1. Create a new workspace from the AppFlowy launch screen.
|
||||
/// 2. Modify the workspace until it is suitable as the starting point for
|
||||
/// the integration test you need to land.
|
||||
/// 3. Use a zip utility program to zip the workspace folder that you created.
|
||||
/// 4. Add the zip file under `assets/test/workspaces/`
|
||||
/// 5. Add a new enumeration to [TestWorkspace] in `integration_test/utils/data.dart`.
|
||||
/// For example, if you added a workspace called `empty_calendar.zip`,
|
||||
/// then [TestWorkspace] should have the following value:
|
||||
/// ```dart
|
||||
/// enum TestWorkspace {
|
||||
/// board('board'),
|
||||
/// empty_calendar('empty_calendar');
|
||||
///
|
||||
/// /* code */
|
||||
/// }
|
||||
/// ```
|
||||
/// 6. Double check that the .zip file that you added is included as an asset in
|
||||
/// the pubspec.yaml file under appflowy_flutter.
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
const service = TestWorkspaceService(TestWorkspace.coverImage);
|
||||
|
||||
group('cover image', () {
|
||||
setUpAll(() async => await service.setUpAll());
|
||||
setUp(() async => await service.setUp());
|
||||
|
||||
testWidgets(
|
||||
'hovering on cover image will display change and delete cover image buttons',
|
||||
(tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
expect(find.byType(Image), findsOneWidget);
|
||||
|
||||
final TestPointer pointer = TestPointer(1, PointerDeviceKind.mouse);
|
||||
final imageFinder = find.byType(Image);
|
||||
Offset offset = tester.getCenter(imageFinder);
|
||||
|
||||
pointer.hover(offset);
|
||||
expect(find.byType(RoundedTextButton), findsOneWidget);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:integration_test/integration_test.dart';
|
||||
import 'util/mock/mock_openai_repository.dart';
|
||||
import 'util/util.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_editor/src/render/toolbar/toolbar_widget.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
|
||||
void main() {
|
||||
IntegrationTestWidgetsFlutterBinding.ensureInitialized();
|
||||
const service = TestWorkspaceService(TestWorkspace.aiWorkSpace);
|
||||
|
||||
group('integration tests for open-ai smart menu', () {
|
||||
setUpAll(() async => await service.setUpAll());
|
||||
setUp(() async => await service.setUp());
|
||||
|
||||
testWidgets('testing selection on open-ai smart menu replace', (tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 4),
|
||||
end: Position(path: [1], offset: 10),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||
|
||||
await tester.tap(find.byTooltip('AI Assistants'));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).first);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
editorState.service.selectionService.currentSelection.value,
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 4),
|
||||
end: Position(path: [1], offset: 84),
|
||||
),
|
||||
);
|
||||
});
|
||||
testWidgets('testing selection on open-ai smart menu insert', (tester) async {
|
||||
final appFlowyEditor = await setUpOpenAITesting(tester);
|
||||
final editorState = appFlowyEditor.editorState;
|
||||
|
||||
editorState.service.selectionService.updateSelection(
|
||||
Selection(
|
||||
start: Position(path: [1], offset: 0),
|
||||
end: Position(path: [1], offset: 5),
|
||||
),
|
||||
);
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.byType(ToolbarWidget), findsAtLeastNWidgets(1));
|
||||
|
||||
await tester.tap(find.byTooltip('AI Assistants'));
|
||||
await tester.pumpAndSettle(const Duration(milliseconds: 500));
|
||||
|
||||
await tester.tap(find.text('Summarize'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.byType(FlowyRichTextButton, skipOffstage: false).at(1));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(
|
||||
editorState.service.selectionService.currentSelection.value,
|
||||
Selection(
|
||||
start: Position(path: [2], offset: 0),
|
||||
end: Position(path: [3], offset: 0),
|
||||
),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Future<AppFlowyEditor> setUpOpenAITesting(WidgetTester tester) async {
|
||||
await tester.initializeAppFlowy();
|
||||
await mockOpenAIRepository();
|
||||
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.controlLeft);
|
||||
await simulateKeyDownEvent(LogicalKeyboardKey.backslash);
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
final Finder editor = find.byType(AppFlowyEditor);
|
||||
await tester.tap(editor);
|
||||
await tester.pumpAndSettle();
|
||||
return (tester.state(editor).widget as AppFlowyEditor);
|
||||
}
|
||||
|
||||
Future<void> mockOpenAIRepository() async {
|
||||
await getIt.unregister<OpenAIRepository>();
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
() => Future.value(
|
||||
MockOpenAIRepository(),
|
||||
),
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -3,7 +3,6 @@ import 'package:integration_test/integration_test.dart';
|
||||
import 'board_test.dart' as board_test;
|
||||
import 'switch_folder_test.dart' as switch_folder_test;
|
||||
import 'empty_document_test.dart' as empty_document_test;
|
||||
import 'open_ai_smart_menu_test.dart' as smart_menu_test;
|
||||
|
||||
/// The main task runner for all integration tests in AppFlowy.
|
||||
///
|
||||
@@ -17,5 +16,4 @@ void main() {
|
||||
switch_folder_test.main();
|
||||
board_test.main();
|
||||
empty_document_test.main();
|
||||
smart_menu_test.main();
|
||||
}
|
||||
@@ -9,9 +9,7 @@ import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
enum TestWorkspace {
|
||||
board("board"),
|
||||
emptyDocument("empty_document"),
|
||||
aiWorkSpace("ai_workspace"),
|
||||
coverImage("cover_image");
|
||||
emptyDocument("empty_document");
|
||||
|
||||
const TestWorkspace(this._name);
|
||||
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||
import 'package:mocktail/mocktail.dart';
|
||||
import 'dart:convert';
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/text_completion.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/error.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'dart:async';
|
||||
|
||||
class MyMockClient extends Mock implements http.Client {
|
||||
@override
|
||||
Future<http.StreamedResponse> send(http.BaseRequest request) async {
|
||||
final requestType = request.method;
|
||||
final requestUri = request.url;
|
||||
|
||||
if (requestType == 'POST' && requestUri == OpenAIRequestType.textCompletion.uri) {
|
||||
final responseHeaders = <String, String>{'content-type': 'text/event-stream'};
|
||||
final responseBody = Stream.fromIterable([
|
||||
utf8.encode(
|
||||
'{ "choices": [{"text": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula ", "index": 0, "logprobs": null, "finish_reason": null}]}',
|
||||
),
|
||||
utf8.encode('\n'),
|
||||
utf8.encode('[DONE]'),
|
||||
]);
|
||||
|
||||
// Return a mocked response with the expected data
|
||||
return http.StreamedResponse(responseBody, 200, headers: responseHeaders);
|
||||
}
|
||||
|
||||
// Return an error response for any other request
|
||||
return http.StreamedResponse(const Stream.empty(), 404);
|
||||
}
|
||||
}
|
||||
|
||||
class MockOpenAIRepository extends HttpOpenAIRepository {
|
||||
MockOpenAIRepository() : super(apiKey: 'dummyKey', client: MyMockClient());
|
||||
|
||||
@override
|
||||
Future<void> getStreamedCompletions({
|
||||
required String prompt,
|
||||
required Future<void> Function() onStart,
|
||||
required Future<void> Function(TextCompletionResponse response) onProcess,
|
||||
required Future<void> Function() onEnd,
|
||||
required void Function(OpenAIError error) onError,
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
}) async {
|
||||
final request = http.Request('POST', OpenAIRequestType.textCompletion.uri);
|
||||
final response = await client.send(request);
|
||||
|
||||
var previousSyntax = '';
|
||||
if (response.statusCode == 200) {
|
||||
await for (final chunk in response.stream.transform(const Utf8Decoder()).transform(const LineSplitter())) {
|
||||
await onStart();
|
||||
final data = chunk.trim().split('data: ');
|
||||
if (data[0] != '[DONE]') {
|
||||
final response = TextCompletionResponse.fromJson(
|
||||
json.decode(data[0]),
|
||||
);
|
||||
if (response.choices.isNotEmpty) {
|
||||
final text = response.choices.first.text;
|
||||
if (text == previousSyntax && text == '\n') {
|
||||
continue;
|
||||
}
|
||||
await onProcess(response);
|
||||
previousSyntax = response.choices.first.text;
|
||||
}
|
||||
} else {
|
||||
await onEnd();
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -1,2 +1 @@
|
||||
export 'target_platform.dart';
|
||||
export 'url_validator.dart';
|
||||
@@ -1,21 +0,0 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
part 'url_validator.freezed.dart';
|
||||
|
||||
Either<UriFailure, Uri> parseValidUrl(String url) {
|
||||
try {
|
||||
final uri = Uri.parse(url);
|
||||
if (uri.scheme.isEmpty || uri.host.isEmpty) {
|
||||
return left(const UriFailure.invalidSchemeHost());
|
||||
}
|
||||
return right(uri);
|
||||
} on FormatException {
|
||||
return left(const UriFailure.invalidUriFormat());
|
||||
}
|
||||
}
|
||||
|
||||
@freezed
|
||||
class UriFailure with _$UriFailure {
|
||||
const factory UriFailure.invalidSchemeHost() = _InvalidSchemeHost;
|
||||
const factory UriFailure.invalidUriFormat() = _InvalidUriFormat;
|
||||
}
|
||||
+4
-1
@@ -76,7 +76,10 @@ class CellController<T, D> extends Equatable {
|
||||
_cellListener?.start(
|
||||
onCellChanged: (result) {
|
||||
result.fold(
|
||||
(_) => _loadData(),
|
||||
(_) {
|
||||
_cellCache.remove(_cacheKey);
|
||||
_loadData();
|
||||
},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
},
|
||||
|
||||
+3
-9
@@ -115,7 +115,7 @@ class DatabaseController {
|
||||
}
|
||||
}
|
||||
|
||||
void setListener({
|
||||
void addListener({
|
||||
DatabaseCallbacks? onDatabaseChanged,
|
||||
LayoutCallbacks? onLayoutChanged,
|
||||
GroupCallbacks? onGroupChanged,
|
||||
@@ -211,11 +211,6 @@ class DatabaseController {
|
||||
await _databaseViewBackendSvc.closeView();
|
||||
await fieldController.dispose();
|
||||
await groupListener.stop();
|
||||
await _viewCache.dispose();
|
||||
_databaseCallbacks = null;
|
||||
_groupCallbacks = null;
|
||||
_layoutCallbacks = null;
|
||||
_calendarLayoutCallbacks = null;
|
||||
}
|
||||
|
||||
Future<void> _loadGroups() async {
|
||||
@@ -256,7 +251,7 @@ class DatabaseController {
|
||||
_databaseCallbacks?.onRowsCreated?.call(ids);
|
||||
},
|
||||
);
|
||||
_viewCache.setListener(callbacks);
|
||||
_viewCache.addListener(callbacks);
|
||||
}
|
||||
|
||||
void _listenOnFieldsChanged() {
|
||||
@@ -341,10 +336,9 @@ class RowDataBuilder {
|
||||
_cellDataByFieldId[fieldInfo.field.id] = num.toString();
|
||||
}
|
||||
|
||||
/// The date should use the UTC timezone. Becuase the backend uses UTC timezone to format the time string.
|
||||
void insertDate(FieldInfo fieldInfo, DateTime date) {
|
||||
assert(fieldInfo.fieldType == FieldType.DateTime);
|
||||
final timestamp = (date.toUtc().millisecondsSinceEpoch ~/ 1000);
|
||||
final timestamp = (date.millisecondsSinceEpoch ~/ 1000);
|
||||
_cellDataByFieldId[fieldInfo.field.id] = timestamp.toString();
|
||||
}
|
||||
|
||||
|
||||
+1
-2
@@ -111,10 +111,9 @@ class DatabaseViewCache {
|
||||
Future<void> dispose() async {
|
||||
await _databaseViewListener.stop();
|
||||
await _rowCache.dispose();
|
||||
_callbacks = null;
|
||||
}
|
||||
|
||||
void setListener(DatabaseViewCallbacks callbacks) {
|
||||
void addListener(DatabaseViewCallbacks callbacks) {
|
||||
_callbacks = callbacks;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -236,7 +236,7 @@ class BoardBloc extends Bloc<BoardEvent, BoardState> {
|
||||
},
|
||||
);
|
||||
|
||||
_databaseController.setListener(
|
||||
_databaseController.addListener(
|
||||
onDatabaseChanged: onDatabaseChanged,
|
||||
onGroupChanged: onGroupChanged,
|
||||
);
|
||||
|
||||
+5
-5
@@ -78,7 +78,7 @@ class BoardContent extends StatefulWidget {
|
||||
|
||||
class _BoardContentState extends State<BoardContent> {
|
||||
late AppFlowyBoardScrollController scrollManager;
|
||||
final renderHook = RowCardRenderHook<String>();
|
||||
final cardConfiguration = CardConfiguration<String>();
|
||||
|
||||
final config = const AppFlowyBoardConfig(
|
||||
groupBackgroundColor: Color(0xffF7F8FC),
|
||||
@@ -87,7 +87,7 @@ class _BoardContentState extends State<BoardContent> {
|
||||
@override
|
||||
void initState() {
|
||||
scrollManager = AppFlowyBoardScrollController();
|
||||
renderHook.addSelectOptionHook((options, groupId, _) {
|
||||
cardConfiguration.addSelectOptionHook((options, groupId) {
|
||||
// The cell should hide if the option id is equal to the groupId.
|
||||
final isInGroup =
|
||||
options.where((element) => element.id == groupId).isNotEmpty;
|
||||
@@ -254,15 +254,15 @@ class _BoardContentState extends State<BoardContent> {
|
||||
key: ValueKey(groupItemId),
|
||||
margin: config.cardPadding,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: RowCard<String>(
|
||||
child: Card<String>(
|
||||
row: rowPB,
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
cardData: groupData.group.groupId,
|
||||
groupingFieldId: groupItem.fieldInfo.id,
|
||||
fieldId: groupItem.fieldInfo.id,
|
||||
isEditing: isEditing,
|
||||
cellBuilder: cellBuilder,
|
||||
renderHook: renderHook,
|
||||
configuration: cardConfiguration,
|
||||
openCard: (context) => _openCard(
|
||||
viewId,
|
||||
fieldController,
|
||||
|
||||
+51
-106
@@ -46,13 +46,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
emit(state.copyWith(database: Some(database)));
|
||||
},
|
||||
didLoadAllEvents: (events) {
|
||||
final calenderEvents = _calendarEventDataFromEventPBs(events);
|
||||
emit(
|
||||
state.copyWith(
|
||||
initialEvents: calenderEvents,
|
||||
allEvents: calenderEvents,
|
||||
),
|
||||
);
|
||||
emit(state.copyWith(initialEvents: events, allEvents: events));
|
||||
},
|
||||
didReceiveNewLayoutField: (CalendarLayoutSettingsPB layoutSettings) {
|
||||
_loadAllEvents();
|
||||
@@ -61,11 +55,6 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
createEvent: (DateTime date, String title) async {
|
||||
await _createEvent(date, title);
|
||||
},
|
||||
didCreateEvent: (CalendarEventData<CalendarDayEvent> event) {
|
||||
emit(
|
||||
state.copyWith(editEvent: event),
|
||||
);
|
||||
},
|
||||
updateCalendarLayoutSetting:
|
||||
(CalendarLayoutSettingsPB layoutSetting) async {
|
||||
await _updateCalendarLayoutSetting(layoutSetting);
|
||||
@@ -73,7 +62,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
didUpdateEvent: (CalendarEventData<CalendarDayEvent> eventData) {
|
||||
var allEvents = [...state.allEvents];
|
||||
final index = allEvents.indexWhere(
|
||||
(element) => element.event!.eventId == eventData.event!.eventId,
|
||||
(element) => element.event!.cellId == eventData.event!.cellId,
|
||||
);
|
||||
if (index != -1) {
|
||||
allEvents[index] = eventData;
|
||||
@@ -81,13 +70,22 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
emit(
|
||||
state.copyWith(
|
||||
allEvents: allEvents,
|
||||
updateEvent: eventData,
|
||||
),
|
||||
);
|
||||
},
|
||||
didReceiveNewEvent: (CalendarEventData<CalendarDayEvent> event) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
allEvents: [...state.allEvents, event],
|
||||
newEvent: event,
|
||||
),
|
||||
);
|
||||
},
|
||||
didDeleteEvents: (List<String> deletedRowIds) {
|
||||
var events = [...state.allEvents];
|
||||
events.retainWhere(
|
||||
(element) => !deletedRowIds.contains(element.event!.eventId),
|
||||
(element) => !deletedRowIds.contains(element.event!.cellId.rowId),
|
||||
);
|
||||
emit(
|
||||
state.copyWith(
|
||||
@@ -96,25 +94,11 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
),
|
||||
);
|
||||
},
|
||||
didReceiveEvent: (CalendarEventData<CalendarDayEvent> event) {
|
||||
emit(
|
||||
state.copyWith(
|
||||
allEvents: [...state.allEvents, event],
|
||||
newEvent: event,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> close() async {
|
||||
await _databaseController.dispose();
|
||||
return super.close();
|
||||
}
|
||||
|
||||
FieldInfo? _getCalendarFieldInfo(String fieldId) {
|
||||
final fieldInfos = _databaseController.fieldController.fieldInfos;
|
||||
final index = fieldInfos.indexWhere(
|
||||
@@ -158,27 +142,17 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
final dateField = _getCalendarFieldInfo(settings.layoutFieldId);
|
||||
final titleField = _getTitleFieldInfo();
|
||||
if (dateField != null && titleField != null) {
|
||||
final newRow = await _databaseController.createRow(
|
||||
final result = await _databaseController.createRow(
|
||||
withCells: (builder) {
|
||||
builder.insertDate(dateField, date);
|
||||
builder.insertText(titleField, title);
|
||||
},
|
||||
).then(
|
||||
(result) => result.fold(
|
||||
(newRow) => newRow,
|
||||
(err) {
|
||||
Log.error(err);
|
||||
return null;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
if (newRow != null) {
|
||||
final event = await _loadEvent(newRow.id);
|
||||
if (event != null && !isClosed) {
|
||||
add(CalendarEvent.didCreateEvent(event));
|
||||
}
|
||||
}
|
||||
return result.fold(
|
||||
(newRow) {},
|
||||
(err) => Log.error(err),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
@@ -212,7 +186,15 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
result.fold(
|
||||
(events) {
|
||||
if (!isClosed) {
|
||||
add(CalendarEvent.didLoadAllEvents(events.items));
|
||||
final calendarEvents = <CalendarEventData<CalendarDayEvent>>[];
|
||||
for (final eventPB in events.items) {
|
||||
final calendarEvent = _calendarEventDataFromEventPB(eventPB);
|
||||
if (calendarEvent != null) {
|
||||
calendarEvents.add(calendarEvent);
|
||||
}
|
||||
}
|
||||
|
||||
add(CalendarEvent.didLoadAllEvents(calendarEvents));
|
||||
}
|
||||
},
|
||||
(r) => Log.error(r),
|
||||
@@ -220,32 +202,22 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
});
|
||||
}
|
||||
|
||||
List<CalendarEventData<CalendarDayEvent>> _calendarEventDataFromEventPBs(
|
||||
List<CalendarEventPB> eventPBs,
|
||||
) {
|
||||
final calendarEvents = <CalendarEventData<CalendarDayEvent>>[];
|
||||
for (final eventPB in eventPBs) {
|
||||
final event = _calendarEventDataFromEventPB(eventPB);
|
||||
if (event != null) {
|
||||
calendarEvents.add(event);
|
||||
}
|
||||
}
|
||||
return calendarEvents;
|
||||
}
|
||||
|
||||
CalendarEventData<CalendarDayEvent>? _calendarEventDataFromEventPB(
|
||||
CalendarEventPB eventPB,
|
||||
) {
|
||||
final fieldInfo = fieldInfoByFieldId[eventPB.dateFieldId];
|
||||
final fieldInfo = fieldInfoByFieldId[eventPB.titleFieldId];
|
||||
if (fieldInfo != null) {
|
||||
final cellId = CellIdentifier(
|
||||
viewId: viewId,
|
||||
rowId: eventPB.rowId,
|
||||
fieldInfo: fieldInfo,
|
||||
);
|
||||
|
||||
final eventData = CalendarDayEvent(
|
||||
event: eventPB,
|
||||
eventId: eventPB.rowId,
|
||||
dateFieldId: eventPB.dateFieldId,
|
||||
cellId: cellId,
|
||||
);
|
||||
|
||||
// The timestamp is using UTC in the backend, so we need to convert it
|
||||
// to local time.
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(
|
||||
eventPB.timestamp.toInt() * 1000,
|
||||
);
|
||||
@@ -270,29 +242,25 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
for (var fieldInfo in fieldInfos) fieldInfo.field.id: fieldInfo
|
||||
};
|
||||
},
|
||||
onRowsCreated: ((rowIds) async {
|
||||
for (final id in rowIds) {
|
||||
onRowsChanged: ((onRowsChanged, rowByRowId, reason) {}),
|
||||
onRowsCreated: ((ids) async {
|
||||
for (final id in ids) {
|
||||
final event = await _loadEvent(id);
|
||||
if (event != null && !isClosed) {
|
||||
add(CalendarEvent.didReceiveEvent(event));
|
||||
add(CalendarEvent.didReceiveNewEvent(event));
|
||||
}
|
||||
}
|
||||
}),
|
||||
onRowsDeleted: (rowIds) {
|
||||
onRowsDeleted: (ids) {
|
||||
if (isClosed) return;
|
||||
add(CalendarEvent.didDeleteEvents(rowIds));
|
||||
add(CalendarEvent.didDeleteEvents(ids));
|
||||
},
|
||||
onRowsUpdated: (rowIds) async {
|
||||
onRowsUpdated: (ids) async {
|
||||
if (isClosed) return;
|
||||
for (final id in rowIds) {
|
||||
for (final id in ids) {
|
||||
final event = await _loadEvent(id);
|
||||
if (event != null && isEventDayChanged(event)) {
|
||||
if (isEventDayChanged(event)) {
|
||||
add(CalendarEvent.didDeleteEvents([id]));
|
||||
add(CalendarEvent.didReceiveEvent(event));
|
||||
} else {
|
||||
add(CalendarEvent.didUpdateEvent(event));
|
||||
}
|
||||
if (event != null) {
|
||||
add(CalendarEvent.didUpdateEvent(event));
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -307,7 +275,7 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
onCalendarLayoutChanged: _didReceiveNewLayoutField,
|
||||
);
|
||||
|
||||
_databaseController.setListener(
|
||||
_databaseController.addListener(
|
||||
onDatabaseChanged: onDatabaseChanged,
|
||||
onLayoutChanged: onLayoutChanged,
|
||||
onCalendarLayoutChanged: onCalendarLayoutFieldChanged,
|
||||
@@ -327,19 +295,6 @@ class CalendarBloc extends Bloc<CalendarEvent, CalendarState> {
|
||||
add(CalendarEvent.didReceiveNewLayoutField(layoutSetting.calendar));
|
||||
}
|
||||
}
|
||||
|
||||
bool isEventDayChanged(
|
||||
CalendarEventData<CalendarDayEvent> event,
|
||||
) {
|
||||
final index = state.allEvents.indexWhere(
|
||||
(element) => element.event!.eventId == event.event!.eventId,
|
||||
);
|
||||
if (index != -1) {
|
||||
return state.allEvents[index].date.day != event.date.day;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef Events = List<CalendarEventData<CalendarDayEvent>>;
|
||||
@@ -354,7 +309,7 @@ class CalendarEvent with _$CalendarEvent {
|
||||
) = _ReceiveCalendarSettings;
|
||||
|
||||
// Called after loading all the current evnets
|
||||
const factory CalendarEvent.didLoadAllEvents(List<CalendarEventPB> events) =
|
||||
const factory CalendarEvent.didLoadAllEvents(Events events) =
|
||||
_ReceiveCalendarEvents;
|
||||
|
||||
// Called when specific event was updated
|
||||
@@ -363,15 +318,10 @@ class CalendarEvent with _$CalendarEvent {
|
||||
) = _DidUpdateEvent;
|
||||
|
||||
// Called after creating a new event
|
||||
const factory CalendarEvent.didCreateEvent(
|
||||
const factory CalendarEvent.didReceiveNewEvent(
|
||||
CalendarEventData<CalendarDayEvent> event,
|
||||
) = _DidReceiveNewEvent;
|
||||
|
||||
// Called when receive a new event
|
||||
const factory CalendarEvent.didReceiveEvent(
|
||||
CalendarEventData<CalendarDayEvent> event,
|
||||
) = _DidReceiveEvent;
|
||||
|
||||
// Called when deleting events
|
||||
const factory CalendarEvent.didDeleteEvents(List<String> rowIds) =
|
||||
_DidDeleteEvents;
|
||||
@@ -397,12 +347,11 @@ class CalendarEvent with _$CalendarEvent {
|
||||
class CalendarState with _$CalendarState {
|
||||
const factory CalendarState({
|
||||
required Option<DatabasePB> database,
|
||||
// events by row id
|
||||
required Events allEvents,
|
||||
required Events initialEvents,
|
||||
CalendarEventData<CalendarDayEvent>? editEvent,
|
||||
CalendarEventData<CalendarDayEvent>? newEvent,
|
||||
required List<String> deleteEventIds,
|
||||
CalendarEventData<CalendarDayEvent>? updateEvent,
|
||||
required Option<CalendarLayoutSettingsPB> settings,
|
||||
required DatabaseLoadingState loadingState,
|
||||
required Option<FlowyError> noneOrError,
|
||||
@@ -439,12 +388,8 @@ class CalendarEditingRow {
|
||||
|
||||
class CalendarDayEvent {
|
||||
final CalendarEventPB event;
|
||||
final String dateFieldId;
|
||||
final String eventId;
|
||||
final CellIdentifier cellId;
|
||||
|
||||
CalendarDayEvent({
|
||||
required this.dateFieldId,
|
||||
required this.eventId,
|
||||
required this.event,
|
||||
});
|
||||
String get eventId => cellId.rowId;
|
||||
CalendarDayEvent({required this.cellId, required this.event});
|
||||
}
|
||||
-1
@@ -51,6 +51,5 @@ class CalendarSettingEvent with _$CalendarSettingEvent {
|
||||
}
|
||||
|
||||
enum CalendarSettingAction {
|
||||
properties,
|
||||
layout,
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class CalendarPluginBuilder extends PluginBuilder {
|
||||
|
||||
class CalendarPluginConfig implements PluginConfig {
|
||||
@override
|
||||
bool get creatable => true;
|
||||
bool get creatable => false;
|
||||
}
|
||||
|
||||
class CalendarPlugin extends Plugin {
|
||||
|
||||
+121
-197
@@ -1,9 +1,9 @@
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/card.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/card_cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/cells/card_cell.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/cells/number_card_cell.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/cells/url_card_cell.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/card/cells/text_card_cell.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/cell_builder.dart';
|
||||
import 'package:appflowy/plugins/database_view/widgets/row/row_detail.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@@ -15,9 +15,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../grid/presentation/layout/sizes.dart';
|
||||
import '../../widgets/row/cells/select_option_cell/extension.dart';
|
||||
import '../application/calendar_bloc.dart';
|
||||
import 'calendar_page.dart';
|
||||
|
||||
class CalendarDayCard extends StatelessWidget {
|
||||
final String viewId;
|
||||
@@ -25,10 +23,11 @@ class CalendarDayCard extends StatelessWidget {
|
||||
final bool isInMonth;
|
||||
final DateTime date;
|
||||
final RowCache _rowCache;
|
||||
final CardCellBuilder _cellBuilder;
|
||||
final List<CalendarDayEvent> events;
|
||||
final void Function(DateTime) onCreateEvent;
|
||||
|
||||
const CalendarDayCard({
|
||||
CalendarDayCard({
|
||||
required this.viewId,
|
||||
required this.isToday,
|
||||
required this.isInMonth,
|
||||
@@ -38,6 +37,7 @@ class CalendarDayCard extends StatelessWidget {
|
||||
required this.events,
|
||||
Key? key,
|
||||
}) : _rowCache = rowCache,
|
||||
_cellBuilder = CardCellBuilder(rowCache.cellCache),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
@@ -49,182 +49,79 @@ class CalendarDayCard extends StatelessWidget {
|
||||
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => _CardEnterNotifier(),
|
||||
builder: (context, child) {
|
||||
Widget? multipleCards;
|
||||
if (events.isNotEmpty) {
|
||||
multipleCards = Flexible(
|
||||
child: ListView.separated(
|
||||
itemBuilder: (BuildContext context, int index) =>
|
||||
_buildCard(context, events[index]),
|
||||
itemCount: events.length,
|
||||
padding: const EdgeInsets.fromLTRB(8.0, 0, 8.0, 8.0),
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
builder: ((context, child) {
|
||||
final children = events.map((event) {
|
||||
return _DayEventCell(
|
||||
event: event,
|
||||
viewId: viewId,
|
||||
onClick: () => _showRowDetailPage(event, context),
|
||||
child: _cellBuilder.buildCell(
|
||||
cellId: event.cellId,
|
||||
styles: {FieldType.RichText: TextCardCellStyle(10)},
|
||||
),
|
||||
);
|
||||
}
|
||||
}).toList();
|
||||
|
||||
final child = Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
_Header(
|
||||
date: date,
|
||||
isInMonth: isInMonth,
|
||||
isToday: isToday,
|
||||
onCreate: () => onCreateEvent(date),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: _Header(
|
||||
date: date,
|
||||
isInMonth: isInMonth,
|
||||
isToday: isToday,
|
||||
onCreate: () => onCreateEvent(date),
|
||||
),
|
||||
),
|
||||
|
||||
// Add a separator between the header and the content.
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
|
||||
// Use SizedBox instead of ListView if there are no cards.
|
||||
multipleCards ?? const SizedBox(),
|
||||
Flexible(
|
||||
child: ListView.separated(
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return children[index];
|
||||
},
|
||||
itemCount: children.length,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
separatorBuilder: (BuildContext context, int index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
return Container(
|
||||
color: backgroundColor,
|
||||
child: GestureDetector(
|
||||
onDoubleTap: () => onCreateEvent(date),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.basic,
|
||||
onEnter: (p) => notifyEnter(context, true),
|
||||
onExit: (p) => notifyEnter(context, false),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8.0),
|
||||
child: child,
|
||||
),
|
||||
child: MouseRegion(
|
||||
cursor: SystemMouseCursors.click,
|
||||
onEnter: (p) => notifyEnter(context, true),
|
||||
onExit: (p) => notifyEnter(context, false),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCard(BuildContext context, CalendarDayEvent event) {
|
||||
final styles = <FieldType, CardCellStyle>{
|
||||
FieldType.Number: NumberCardCellStyle(10),
|
||||
FieldType.URL: URLCardCellStyle(10),
|
||||
};
|
||||
|
||||
final cellBuilder = CardCellBuilder<String>(
|
||||
_rowCache.cellCache,
|
||||
styles: styles,
|
||||
);
|
||||
|
||||
final rowInfo = _rowCache.getRow(event.eventId);
|
||||
final renderHook = RowCardRenderHook<String>();
|
||||
renderHook.addTextCellHook((cellData, primaryFieldId, _) {
|
||||
if (cellData.isEmpty) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.medium(
|
||||
cellData,
|
||||
textAlign: TextAlign.left,
|
||||
fontSize: 11,
|
||||
maxLines: null, // Enable multiple lines
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
renderHook.addDateCellHook((cellData, cardData, _) {
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 3,
|
||||
child: FlowyText.regular(
|
||||
cellData.date,
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).hintColor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
Flexible(
|
||||
child: FlowyText.regular(
|
||||
cellData.time,
|
||||
fontSize: 10,
|
||||
color: Theme.of(context).hintColor,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
renderHook.addSelectOptionHook((selectedOptions, cardData, _) {
|
||||
final children = selectedOptions.map(
|
||||
(option) {
|
||||
return SelectOptionTag.fromOption(
|
||||
context: context,
|
||||
option: option,
|
||||
);
|
||||
},
|
||||
).toList();
|
||||
|
||||
return IntrinsicHeight(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: SizedBox.expand(
|
||||
child: Wrap(spacing: 4, runSpacing: 4, children: children),
|
||||
),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
// renderHook.addDateFieldHook((cellData, cardData) {
|
||||
|
||||
final card = RowCard<String>(
|
||||
// Add the key here to make sure the card is rebuilt when the cells
|
||||
// in this row are updated.
|
||||
key: ValueKey(event.eventId),
|
||||
row: rowInfo!.rowPB,
|
||||
void _showRowDetailPage(CalendarDayEvent event, BuildContext context) {
|
||||
final dataController = RowController(
|
||||
rowId: event.cellId.rowId,
|
||||
viewId: viewId,
|
||||
rowCache: _rowCache,
|
||||
cardData: event.dateFieldId,
|
||||
isEditing: false,
|
||||
cellBuilder: cellBuilder,
|
||||
openCard: (context) => showEventDetails(
|
||||
context: context,
|
||||
event: event,
|
||||
viewId: viewId,
|
||||
rowCache: _rowCache,
|
||||
),
|
||||
styleConfiguration: const RowCardStyleConfiguration(
|
||||
showAccessory: false,
|
||||
cellPadding: EdgeInsets.zero,
|
||||
),
|
||||
renderHook: renderHook,
|
||||
onStartEditing: () {},
|
||||
onEndEditing: () {},
|
||||
);
|
||||
|
||||
return FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
foregroundColorOnHover: Theme.of(context).colorScheme.onBackground,
|
||||
),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 2),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.5,
|
||||
),
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RowDetailPage(
|
||||
cellBuilder: GridCellBuilder(
|
||||
cellCache: _rowCache.cellCache,
|
||||
),
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
child: card,
|
||||
),
|
||||
dataController: dataController,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -236,6 +133,42 @@ class CalendarDayCard extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _DayEventCell extends StatelessWidget {
|
||||
final String viewId;
|
||||
final CalendarDayEvent event;
|
||||
final VoidCallback onClick;
|
||||
final Widget child;
|
||||
const _DayEventCell({
|
||||
required this.viewId,
|
||||
required this.event,
|
||||
required this.onClick,
|
||||
required this.child,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyHover(
|
||||
child: GestureDetector(
|
||||
onTap: onClick,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
border: Border.fromBorderSide(
|
||||
BorderSide(
|
||||
color: Theme.of(context).dividerColor,
|
||||
width: 1.0,
|
||||
),
|
||||
),
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Header extends StatelessWidget {
|
||||
final bool isToday;
|
||||
final bool isInMonth;
|
||||
@@ -258,16 +191,12 @@ class _Header extends StatelessWidget {
|
||||
isInMonth: isInMonth,
|
||||
date: date,
|
||||
);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
if (notifier.onEnter) _NewEventButton(onClick: onCreate),
|
||||
const Spacer(),
|
||||
badge,
|
||||
],
|
||||
),
|
||||
return Row(
|
||||
children: [
|
||||
if (notifier.onEnter) _NewEventButton(onClick: onCreate),
|
||||
const Spacer(),
|
||||
badge,
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -286,8 +215,10 @@ class _NewEventButton extends StatelessWidget {
|
||||
return FlowyIconButton(
|
||||
onPressed: onClick,
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: const FlowySvg(name: "home/add"),
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
icon: svgWidget(
|
||||
"home/add",
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
width: 22,
|
||||
);
|
||||
}
|
||||
@@ -306,38 +237,31 @@ class _DayBadge extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color dayTextColor = Theme.of(context).colorScheme.onBackground;
|
||||
String monthString =
|
||||
DateFormat("MMM ", context.locale.toLanguageTag()).format(date);
|
||||
String dayString = date.day.toString();
|
||||
Color dayTextColor = Theme.of(context).colorScheme.onSurface;
|
||||
String dayString = date.day == 1
|
||||
? DateFormat('MMM d', context.locale.toLanguageTag()).format(date)
|
||||
: date.day.toString();
|
||||
|
||||
if (!isInMonth) {
|
||||
dayTextColor = Theme.of(context).disabledColor;
|
||||
}
|
||||
if (isToday) {
|
||||
dayTextColor = Theme.of(context).colorScheme.onPrimary;
|
||||
}
|
||||
if (!isInMonth) {
|
||||
dayTextColor = Theme.of(context).disabledColor;
|
||||
}
|
||||
|
||||
return Row(
|
||||
children: [
|
||||
if (date.day == 1) FlowyText.medium(monthString),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isToday ? Theme.of(context).colorScheme.primary : null,
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
width: isToday ? 26 : null,
|
||||
height: isToday ? 26 : null,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: Center(
|
||||
child: FlowyText.medium(
|
||||
dayString,
|
||||
color: dayTextColor,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
Widget day = Container(
|
||||
decoration: BoxDecoration(
|
||||
color: isToday ? Theme.of(context).colorScheme.primary : null,
|
||||
borderRadius: Corners.s6Border,
|
||||
),
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: FlowyText.medium(
|
||||
dayString,
|
||||
color: dayTextColor,
|
||||
),
|
||||
);
|
||||
|
||||
return day;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+14
-51
@@ -9,10 +9,6 @@ import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../application/row/row_cache.dart';
|
||||
import '../../application/row/row_data_controller.dart';
|
||||
import '../../widgets/row/cell_builder.dart';
|
||||
import '../../widgets/row/row_detail.dart';
|
||||
import 'calendar_day.dart';
|
||||
import 'layout/sizes.dart';
|
||||
import 'toolbar/calendar_toolbar.dart';
|
||||
@@ -74,21 +70,19 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
},
|
||||
),
|
||||
BlocListener<CalendarBloc, CalendarState>(
|
||||
listenWhen: (p, c) => p.editEvent != c.editEvent,
|
||||
listenWhen: (p, c) => p.updateEvent != c.updateEvent,
|
||||
listener: (context, state) {
|
||||
if (state.editEvent != null) {
|
||||
showEventDetails(
|
||||
context: context,
|
||||
event: state.editEvent!.event!,
|
||||
viewId: widget.view.id,
|
||||
rowCache: _calendarBloc.rowCache,
|
||||
if (state.updateEvent != null) {
|
||||
_eventController.removeWhere(
|
||||
(element) =>
|
||||
state.updateEvent!.event!.eventId ==
|
||||
element.event!.eventId,
|
||||
);
|
||||
_eventController.add(state.updateEvent!);
|
||||
}
|
||||
},
|
||||
),
|
||||
BlocListener<CalendarBloc, CalendarState>(
|
||||
// Event create by click the + button or double click on the
|
||||
// calendar
|
||||
listenWhen: (p, c) => p.newEvent != c.newEvent,
|
||||
listener: (context, state) {
|
||||
if (state.newEvent != null) {
|
||||
@@ -122,7 +116,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
child: MonthView(
|
||||
key: _calendarState,
|
||||
controller: _eventController,
|
||||
cellAspectRatio: .6,
|
||||
cellAspectRatio: .9,
|
||||
startDay: _weekdayFromInt(firstDayOfWeek),
|
||||
borderColor: Theme.of(context).dividerColor,
|
||||
headerBuilder: _headerNavigatorBuilder,
|
||||
@@ -143,7 +137,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
FlowyIconButton(
|
||||
width: CalendarSize.navigatorButtonWidth,
|
||||
height: CalendarSize.navigatorButtonHeight,
|
||||
icon: const FlowySvg(name: 'home/arrow_left'),
|
||||
icon: svgWidget('home/arrow_left'),
|
||||
tooltipText: LocaleKeys.calendar_navigation_previousMonth.tr(),
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
onPressed: () => _calendarState?.currentState?.previousPage(),
|
||||
@@ -161,7 +155,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
FlowyIconButton(
|
||||
width: CalendarSize.navigatorButtonWidth,
|
||||
height: CalendarSize.navigatorButtonHeight,
|
||||
icon: const FlowySvg(name: 'home/arrow_right'),
|
||||
icon: svgWidget('home/arrow_right'),
|
||||
tooltipText: LocaleKeys.calendar_navigation_nextMonth.tr(),
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
onPressed: () => _calendarState?.currentState?.nextPage(),
|
||||
@@ -171,9 +165,8 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
}
|
||||
|
||||
Widget _headerWeekDayBuilder(day) {
|
||||
// incoming day starts from Monday, the symbols start from Sunday
|
||||
final symbols = DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
||||
final weekDayString = symbols.WEEKDAYS[(day + 1) % 7];
|
||||
final weekDayString = symbols.WEEKDAYS[day];
|
||||
return Center(
|
||||
child: Padding(
|
||||
padding: CalendarSize.daysOfWeekInsets,
|
||||
@@ -192,12 +185,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
isInMonth,
|
||||
) {
|
||||
final events = calenderEvents.map((value) => value.event!).toList();
|
||||
// Sort the events by timestamp. Because the database view is not
|
||||
// reserving the order of the events. Reserving the order of the rows/events
|
||||
// is implemnted in the develop branch(WIP). Will be replaced with that.
|
||||
events.sort(
|
||||
(a, b) => a.event.timestamp.compareTo(b.event.timestamp),
|
||||
);
|
||||
|
||||
return CalendarDayCard(
|
||||
viewId: widget.view.id,
|
||||
isToday: isToday,
|
||||
@@ -217,32 +205,7 @@ class _CalendarPageState extends State<CalendarPage> {
|
||||
}
|
||||
|
||||
WeekDays _weekdayFromInt(int dayOfWeek) {
|
||||
// dayOfWeek starts from Sunday, WeekDays starts from Monday
|
||||
return WeekDays.values[(dayOfWeek - 1) % 7];
|
||||
// MonthView places the first day of week on the second column for some reason.
|
||||
return WeekDays.values[(dayOfWeek + 1) % 7];
|
||||
}
|
||||
}
|
||||
|
||||
void showEventDetails({
|
||||
required BuildContext context,
|
||||
required CalendarDayEvent event,
|
||||
required String viewId,
|
||||
required RowCache rowCache,
|
||||
}) {
|
||||
final dataController = RowController(
|
||||
rowId: event.eventId,
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
|
||||
FlowyOverlay.show(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return RowDetailPage(
|
||||
cellBuilder: GridCellBuilder(
|
||||
cellCache: rowCache.cellCache,
|
||||
),
|
||||
dataController: dataController,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
+5
-8
@@ -217,7 +217,6 @@ class LayoutDateField extends StatelessWidget {
|
||||
direction: PopoverDirection.leftWithTopAligned,
|
||||
constraints: BoxConstraints.loose(const Size(300, 400)),
|
||||
mutex: popoverMutex,
|
||||
offset: const Offset(-16, 0),
|
||||
popupBuilder: (context) {
|
||||
return BlocProvider(
|
||||
create: (context) => getIt<DatabasePropertyBloc>(
|
||||
@@ -238,9 +237,9 @@ class LayoutDateField extends StatelessWidget {
|
||||
onUpdated(fieldInfo.id);
|
||||
popoverMutex.close();
|
||||
},
|
||||
leftIcon: const FlowySvg(name: 'grid/field/date'),
|
||||
leftIcon: svgWidget('grid/field/date'),
|
||||
rightIcon: fieldInfo.id == fieldId
|
||||
? const FlowySvg(name: 'grid/checkmark')
|
||||
? svgWidget('grid/checkmark')
|
||||
: null,
|
||||
),
|
||||
);
|
||||
@@ -334,13 +333,12 @@ class FirstDayOfWeek extends StatelessWidget {
|
||||
direction: PopoverDirection.leftWithTopAligned,
|
||||
constraints: BoxConstraints.loose(const Size(300, 400)),
|
||||
mutex: popoverMutex,
|
||||
offset: const Offset(-16, 0),
|
||||
popupBuilder: (context) {
|
||||
final symbols =
|
||||
DateFormat.EEEE(context.locale.toLanguageTag()).dateSymbols;
|
||||
// starts from sunday
|
||||
final items = symbols.WEEKDAYS.asMap().entries.map((entry) {
|
||||
final index = entry.key;
|
||||
final index = (entry.key - 1) % 7;
|
||||
final string = entry.value;
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
@@ -350,9 +348,8 @@ class FirstDayOfWeek extends StatelessWidget {
|
||||
onUpdated(index);
|
||||
popoverMutex.close();
|
||||
},
|
||||
rightIcon: firstDayOfWeek == index
|
||||
? const FlowySvg(name: 'grid/checkmark')
|
||||
: null,
|
||||
rightIcon:
|
||||
firstDayOfWeek == index ? svgWidget('grid/checkmark') : null,
|
||||
),
|
||||
);
|
||||
}).toList();
|
||||
|
||||
-25
@@ -2,10 +2,8 @@ import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/calendar/application/calendar_setting_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/toolbar/grid_property.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/protobuf.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/scrolling/styled_list.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
@@ -40,11 +38,6 @@ class CalendarSetting extends StatelessWidget {
|
||||
final CalendarSettingAction? action =
|
||||
state.selectedAction.foldLeft(null, (previous, action) => action);
|
||||
switch (action) {
|
||||
case CalendarSettingAction.properties:
|
||||
return GridPropertyList(
|
||||
viewId: settingContext.viewId,
|
||||
fieldController: settingContext.fieldController,
|
||||
);
|
||||
case CalendarSettingAction.layout:
|
||||
return CalendarLayoutSetting(
|
||||
onUpdated: onUpdated,
|
||||
@@ -85,16 +78,9 @@ class AllCalendarSettings extends StatelessWidget {
|
||||
}
|
||||
|
||||
Widget _settingItem(BuildContext context, CalendarSettingAction action) {
|
||||
Widget? icon;
|
||||
if (action.iconName() != null) {
|
||||
icon = FlowySvg(
|
||||
name: action.iconName()!,
|
||||
);
|
||||
}
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
leftIcon: icon,
|
||||
text: FlowyText.medium(action.title()),
|
||||
onTap: () {
|
||||
context
|
||||
@@ -107,19 +93,8 @@ class AllCalendarSettings extends StatelessWidget {
|
||||
}
|
||||
|
||||
extension _SettingExtension on CalendarSettingAction {
|
||||
String? iconName() {
|
||||
switch (this) {
|
||||
case CalendarSettingAction.properties:
|
||||
return 'grid/setting/properties';
|
||||
case CalendarSettingAction.layout:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
String title() {
|
||||
switch (this) {
|
||||
case CalendarSettingAction.properties:
|
||||
return LocaleKeys.grid_settings_Properties.tr();
|
||||
case CalendarSettingAction.layout:
|
||||
return LocaleKeys.grid_settings_layout.tr();
|
||||
}
|
||||
|
||||
+7
-103
@@ -1,8 +1,6 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/plugins/database_view/calendar/presentation/calendar_page.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/layout/sizes.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:calendar_view/calendar_view.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -21,8 +19,7 @@ class CalendarToolbar extends StatelessWidget {
|
||||
height: 40,
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: const [
|
||||
_UnscheduleEventsButton(),
|
||||
children: [
|
||||
_SettingButton(),
|
||||
],
|
||||
),
|
||||
@@ -31,22 +28,25 @@ class CalendarToolbar extends StatelessWidget {
|
||||
}
|
||||
|
||||
class _SettingButton extends StatefulWidget {
|
||||
const _SettingButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _SettingButtonState();
|
||||
}
|
||||
|
||||
class _SettingButtonState extends State<_SettingButton> {
|
||||
late PopoverController popoverController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
popoverController = PopoverController();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppFlowyPopover(
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
triggerActions: PopoverTriggerFlags.none,
|
||||
constraints: BoxConstraints.loose(const Size(300, 400)),
|
||||
margin: EdgeInsets.zero,
|
||||
child: FlowyTextButton(
|
||||
@@ -54,6 +54,7 @@ class _SettingButtonState extends State<_SettingButton> {
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
onPressed: () => popoverController.show(),
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
final bloc = context.watch<CalendarBloc>();
|
||||
@@ -80,100 +81,3 @@ class _SettingButtonState extends State<_SettingButton> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UnscheduleEventsButton extends StatefulWidget {
|
||||
const _UnscheduleEventsButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<_UnscheduleEventsButton> createState() =>
|
||||
_UnscheduleEventsButtonState();
|
||||
}
|
||||
|
||||
class _UnscheduleEventsButtonState extends State<_UnscheduleEventsButton> {
|
||||
late final PopoverController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = PopoverController();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocBuilder<CalendarBloc, CalendarState>(
|
||||
builder: (context, state) {
|
||||
final unscheduledEvents = state.allEvents
|
||||
.where((e) => e.date == DateTime.fromMillisecondsSinceEpoch(0))
|
||||
.toList();
|
||||
final viewId = context.read<CalendarBloc>().viewId;
|
||||
final rowCache = context.read<CalendarBloc>().rowCache;
|
||||
return AppFlowyPopover(
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
controller: _controller,
|
||||
offset: const Offset(0, 8),
|
||||
child: FlowyTextButton(
|
||||
"${LocaleKeys.calendar_settings_noDateTitle.tr()} (${unscheduledEvents.length})",
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
),
|
||||
popupBuilder: (context) {
|
||||
if (unscheduledEvents.isEmpty) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Center(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.calendar_settings_emptyNoDate.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
return ListView.separated(
|
||||
itemBuilder: (context, index) => _UnscheduledEventItem(
|
||||
event: unscheduledEvents[index],
|
||||
onPressed: () {
|
||||
showEventDetails(
|
||||
context: context,
|
||||
event: unscheduledEvents[index].event!,
|
||||
viewId: viewId,
|
||||
rowCache: rowCache,
|
||||
);
|
||||
_controller.close();
|
||||
},
|
||||
),
|
||||
itemCount: unscheduledEvents.length,
|
||||
separatorBuilder: (context, index) =>
|
||||
VSpace(GridSize.typeOptionSeparatorHeight),
|
||||
shrinkWrap: true,
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _UnscheduledEventItem extends StatelessWidget {
|
||||
final CalendarEventData<CalendarDayEvent> event;
|
||||
final VoidCallback onPressed;
|
||||
const _UnscheduledEventItem({
|
||||
required this.event,
|
||||
required this.onPressed,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyTextButton(
|
||||
event.title,
|
||||
fillColor: Colors.transparent,
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
onPressed: onPressed,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,7 @@ class GridBloc extends Bloc<GridEvent, GridState> {
|
||||
}
|
||||
},
|
||||
);
|
||||
databaseController.setListener(onDatabaseChanged: onDatabaseChanged);
|
||||
databaseController.addListener(onDatabaseChanged: onDatabaseChanged);
|
||||
}
|
||||
|
||||
Future<void> _openGrid(Emitter<GridState> emit) async {
|
||||
|
||||
+7
-18
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_service.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'dart:async';
|
||||
@@ -8,39 +7,31 @@ import '../../../application/row/row_data_controller.dart';
|
||||
part 'row_detail_bloc.freezed.dart';
|
||||
|
||||
class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
final RowBackendService rowService;
|
||||
final RowController dataController;
|
||||
|
||||
RowDetailBloc({
|
||||
required this.dataController,
|
||||
}) : rowService = RowBackendService(viewId: dataController.viewId),
|
||||
super(RowDetailState.initial()) {
|
||||
}) : super(RowDetailState.initial()) {
|
||||
on<RowDetailEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
await event.map(
|
||||
initial: (_Initial value) async {
|
||||
await _startListening();
|
||||
final cells = dataController.loadData();
|
||||
if (!isClosed) {
|
||||
add(RowDetailEvent.didReceiveCellDatas(cells.values.toList()));
|
||||
}
|
||||
},
|
||||
didReceiveCellDatas: (cells) {
|
||||
emit(state.copyWith(gridCells: cells));
|
||||
didReceiveCellDatas: (_DidReceiveCellDatas value) {
|
||||
emit(state.copyWith(gridCells: value.gridCells));
|
||||
},
|
||||
deleteField: (fieldId) {
|
||||
deleteField: (_DeleteField value) {
|
||||
final fieldService = FieldBackendService(
|
||||
viewId: dataController.viewId,
|
||||
fieldId: fieldId,
|
||||
fieldId: value.fieldId,
|
||||
);
|
||||
fieldService.deleteField();
|
||||
},
|
||||
deleteRow: (rowId) async {
|
||||
await rowService.deleteRow(rowId);
|
||||
},
|
||||
duplicateRow: (String rowId) async {
|
||||
await rowService.duplicateRow(rowId);
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -67,8 +58,6 @@ class RowDetailBloc extends Bloc<RowDetailEvent, RowDetailState> {
|
||||
class RowDetailEvent with _$RowDetailEvent {
|
||||
const factory RowDetailEvent.initial() = _Initial;
|
||||
const factory RowDetailEvent.deleteField(String fieldId) = _DeleteField;
|
||||
const factory RowDetailEvent.deleteRow(String rowId) = _DeleteRow;
|
||||
const factory RowDetailEvent.duplicateRow(String rowId) = _DuplicateRow;
|
||||
const factory RowDetailEvent.didReceiveCellDatas(
|
||||
List<CellIdentifier> gridCells,
|
||||
) = _DidReceiveCellDatas;
|
||||
|
||||
+2
-1
@@ -147,6 +147,8 @@ class _FieldNameTextFieldState extends State<_FieldNameTextField> {
|
||||
widget.popoverMutex.listenOnPopoverChanged(() {
|
||||
if (focusNode.hasFocus) {
|
||||
focusNode.unfocus();
|
||||
} else {
|
||||
focusNode.requestFocus();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -203,7 +205,6 @@ class _DeleteFieldButton extends StatelessWidget {
|
||||
builder: (context, state) {
|
||||
final enable = !state.canDelete && !state.isGroupField;
|
||||
Widget button = FlowyButton(
|
||||
disable: !enable,
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_delete.tr(),
|
||||
color: enable ? null : Theme.of(context).disabledColor,
|
||||
|
||||
+6
-2
@@ -1,5 +1,6 @@
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -57,12 +58,15 @@ class FieldTypeCell extends StatelessWidget {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
fieldType.title(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
onTap: () => onSelectField(fieldType),
|
||||
leftIcon: FlowySvg(
|
||||
name: fieldType.iconName(),
|
||||
leftIcon: svgWidget(
|
||||
fieldType.iconName(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+11
-2
@@ -4,6 +4,7 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:dartz/dartz.dart' show Either;
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-error/errors.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pb.dart';
|
||||
@@ -112,12 +113,20 @@ class _SwitchFieldButton extends StatelessWidget {
|
||||
Widget _buildMoreButton(BuildContext context) {
|
||||
final bloc = context.read<FieldTypeOptionEditBloc>();
|
||||
return FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
bloc.state.field.fieldType.title(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
margin: GridSize.typeOptionContentInsets,
|
||||
leftIcon: FlowySvg(name: bloc.state.field.fieldType.iconName()),
|
||||
rightIcon: const FlowySvg(name: 'grid/more'),
|
||||
leftIcon: svgWidget(
|
||||
bloc.state.field.fieldType.iconName(),
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
rightIcon: svgWidget(
|
||||
"grid/more",
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
-1
@@ -186,7 +186,6 @@ class CreateFieldButton extends StatelessWidget {
|
||||
return AppFlowyPopover(
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
asBarrier: true,
|
||||
margin: EdgeInsets.zero,
|
||||
constraints: BoxConstraints.loose(const Size(240, 600)),
|
||||
child: FlowyButton(
|
||||
radius: BorderRadius.zero,
|
||||
|
||||
+43
-2
@@ -1,5 +1,7 @@
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/date_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/toggle/toggle_style.dart';
|
||||
import 'package:easy_localization/easy_localization.dart' hide DateFormat;
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
@@ -52,6 +54,7 @@ class DateTypeOptionWidget extends TypeOptionWidget {
|
||||
const TypeOptionSeparator(),
|
||||
_renderDateFormatButton(context, state.typeOption.dateFormat),
|
||||
_renderTimeFormatButton(context, state.typeOption.timeFormat),
|
||||
const _IncludeTimeButton(),
|
||||
];
|
||||
|
||||
return ListView.separated(
|
||||
@@ -182,6 +185,44 @@ class TimeFormatButton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _IncludeTimeButton extends StatelessWidget {
|
||||
const _IncludeTimeButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return BlocSelector<DateTypeOptionBloc, DateTypeOptionState, bool>(
|
||||
selector: (state) => state.typeOption.includeTime,
|
||||
builder: (context, includeTime) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: Padding(
|
||||
padding: GridSize.typeOptionContentInsets,
|
||||
child: Row(
|
||||
children: [
|
||||
FlowyText.medium(LocaleKeys.grid_field_includeTime.tr()),
|
||||
const Spacer(),
|
||||
Toggle(
|
||||
value: includeTime,
|
||||
onChanged: (value) {
|
||||
context
|
||||
.read<DateTypeOptionBloc>()
|
||||
.add(DateTypeOptionEvent.includeTime(!value));
|
||||
},
|
||||
style: ToggleStyle.big,
|
||||
padding: EdgeInsets.zero,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DateFormatList extends StatelessWidget {
|
||||
final DateFormat selectedFormat;
|
||||
final Function(DateFormat format) onSelected;
|
||||
@@ -233,7 +274,7 @@ class DateFormatCell extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(name: 'grid/checkmark');
|
||||
checkmark = svgWidget("grid/checkmark");
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
@@ -317,7 +358,7 @@ class TimeFormatCell extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(name: 'grid/checkmark');
|
||||
checkmark = svgWidget("grid/checkmark");
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
|
||||
+9
-4
@@ -3,6 +3,7 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/num
|
||||
import 'package:appflowy/plugins/database_view/application/field/type_option/type_option_context.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/format.pbenum.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -59,10 +60,15 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
final selectNumUnitButton = SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
margin: GridSize.typeOptionContentInsets,
|
||||
rightIcon: const FlowySvg(name: 'grid/more'),
|
||||
rightIcon: svgWidget(
|
||||
"grid/more",
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
text: FlowyText.regular(
|
||||
state.typeOption.format.title(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -73,6 +79,7 @@ class NumberTypeOptionWidget extends TypeOptionWidget {
|
||||
alignment: Alignment.centerLeft,
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.grid_field_numberFormat.tr(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
);
|
||||
return Padding(
|
||||
@@ -181,9 +188,7 @@ class NumberFormatCell extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
Widget? checkmark;
|
||||
if (isSelected) {
|
||||
checkmark = const FlowySvg(
|
||||
name: 'grid/checkmark',
|
||||
);
|
||||
checkmark = svgWidget("grid/checkmark");
|
||||
}
|
||||
|
||||
return SizedBox(
|
||||
|
||||
+7
-6
@@ -105,10 +105,15 @@ class _DeleteTag extends StatelessWidget {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_selectOption_deleteTag.tr(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
leftIcon: svgWidget(
|
||||
"grid/delete",
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
leftIcon: const FlowySvg(name: 'grid/delete'),
|
||||
onTap: () {
|
||||
context
|
||||
.read<EditSelectOptionBloc>()
|
||||
@@ -221,11 +226,7 @@ class _SelectOptionColorCell extends StatelessWidget {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
text: FlowyText.medium(
|
||||
color.optionName(),
|
||||
color: AFThemeExtension.of(context).textColor,
|
||||
),
|
||||
text: FlowyText.medium(color.optionName()),
|
||||
leftIcon: colorIcon,
|
||||
rightIcon: checkmark,
|
||||
onTap: () {
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_cache.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/presentation/widgets/row/action.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
||||
@@ -14,40 +13,23 @@ import 'card_cell_builder.dart';
|
||||
import 'container/accessory.dart';
|
||||
import 'container/card_container.dart';
|
||||
|
||||
/// Edit a database row with card style widget
|
||||
class RowCard<CustomCardData> extends StatefulWidget {
|
||||
class Card<CustomCardData> extends StatefulWidget {
|
||||
final RowPB row;
|
||||
final String viewId;
|
||||
final String? groupingFieldId;
|
||||
|
||||
/// Allows passing a custom card data object to the card. The card will be
|
||||
/// returned in the [CardCellBuilder] and can be used to build the card.
|
||||
final String fieldId;
|
||||
final CustomCardData? cardData;
|
||||
final bool isEditing;
|
||||
final RowCache rowCache;
|
||||
|
||||
/// The [CardCellBuilder] is used to build the card cells.
|
||||
final CardCellBuilder<CustomCardData> cellBuilder;
|
||||
|
||||
/// Called when the user taps on the card.
|
||||
final void Function(BuildContext) openCard;
|
||||
|
||||
/// Called when the user starts editing the card.
|
||||
final VoidCallback onStartEditing;
|
||||
|
||||
/// Called when the user ends editing the card.
|
||||
final VoidCallback onEndEditing;
|
||||
final CardConfiguration<CustomCardData>? configuration;
|
||||
|
||||
/// The [RowCardRenderHook] is used to render the card's cell. Other than
|
||||
/// using the default cell builder. For example the [SelectOptionCardCell]
|
||||
final RowCardRenderHook<CustomCardData>? renderHook;
|
||||
|
||||
final RowCardStyleConfiguration styleConfiguration;
|
||||
|
||||
const RowCard({
|
||||
const Card({
|
||||
required this.row,
|
||||
required this.viewId,
|
||||
this.groupingFieldId,
|
||||
required this.fieldId,
|
||||
required this.isEditing,
|
||||
required this.rowCache,
|
||||
required this.cellBuilder,
|
||||
@@ -55,19 +37,15 @@ class RowCard<CustomCardData> extends StatefulWidget {
|
||||
required this.onStartEditing,
|
||||
required this.onEndEditing,
|
||||
this.cardData,
|
||||
this.styleConfiguration = const RowCardStyleConfiguration(
|
||||
showAccessory: true,
|
||||
),
|
||||
this.renderHook,
|
||||
this.configuration,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<RowCard<CustomCardData>> createState() =>
|
||||
_RowCardState<CustomCardData>();
|
||||
State<Card<CustomCardData>> createState() => _CardState<CustomCardData>();
|
||||
}
|
||||
|
||||
class _RowCardState<T> extends State<RowCard<T>> {
|
||||
class _CardState<T> extends State<Card<T>> {
|
||||
late CardBloc _cardBloc;
|
||||
late EditableRowNotifier rowNotifier;
|
||||
late PopoverController popoverController;
|
||||
@@ -78,15 +56,15 @@ class _RowCardState<T> extends State<RowCard<T>> {
|
||||
rowNotifier = EditableRowNotifier(isEditing: widget.isEditing);
|
||||
_cardBloc = CardBloc(
|
||||
viewId: widget.viewId,
|
||||
groupFieldId: widget.groupingFieldId,
|
||||
groupFieldId: widget.fieldId,
|
||||
isEditing: widget.isEditing,
|
||||
row: widget.row,
|
||||
rowCache: widget.rowCache,
|
||||
)..add(const RowCardEvent.initial());
|
||||
)..add(const BoardCardEvent.initial());
|
||||
|
||||
rowNotifier.isEditing.addListener(() {
|
||||
if (!mounted) return;
|
||||
_cardBloc.add(RowCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
||||
_cardBloc.add(BoardCardEvent.setIsEditing(rowNotifier.isEditing.value));
|
||||
|
||||
if (rowNotifier.isEditing.value) {
|
||||
widget.onStartEditing();
|
||||
@@ -103,7 +81,7 @@ class _RowCardState<T> extends State<RowCard<T>> {
|
||||
Widget build(BuildContext context) {
|
||||
return BlocProvider.value(
|
||||
value: _cardBloc,
|
||||
child: BlocBuilder<CardBloc, RowCardState>(
|
||||
child: BlocBuilder<CardBloc, BoardCardState>(
|
||||
buildWhen: (previous, current) {
|
||||
// Rebuild when:
|
||||
// 1.If the length of the cells is not the same
|
||||
@@ -128,26 +106,21 @@ class _RowCardState<T> extends State<RowCard<T>> {
|
||||
context,
|
||||
popoverContext,
|
||||
),
|
||||
child: RowCardContainer(
|
||||
child: BoardCardContainer(
|
||||
buildAccessoryWhen: () => state.isEditing == false,
|
||||
accessoryBuilder: (context) {
|
||||
if (widget.styleConfiguration.showAccessory == false) {
|
||||
return [];
|
||||
} else {
|
||||
return [
|
||||
_CardEditOption(rowNotifier: rowNotifier),
|
||||
_CardMoreOption(),
|
||||
];
|
||||
}
|
||||
return [
|
||||
_CardEditOption(rowNotifier: rowNotifier),
|
||||
_CardMoreOption(),
|
||||
];
|
||||
},
|
||||
openAccessory: _handleOpenAccessory,
|
||||
openCard: (context) => widget.openCard(context),
|
||||
child: _CardContent<T>(
|
||||
rowNotifier: rowNotifier,
|
||||
cellBuilder: widget.cellBuilder,
|
||||
styleConfiguration: widget.styleConfiguration,
|
||||
cells: state.cells,
|
||||
renderHook: widget.renderHook,
|
||||
cardConfiguration: widget.configuration,
|
||||
cardData: widget.cardData,
|
||||
),
|
||||
),
|
||||
@@ -193,17 +166,15 @@ class _RowCardState<T> extends State<RowCard<T>> {
|
||||
class _CardContent<CustomCardData> extends StatelessWidget {
|
||||
final CardCellBuilder<CustomCardData> cellBuilder;
|
||||
final EditableRowNotifier rowNotifier;
|
||||
final List<CellIdentifier> cells;
|
||||
final RowCardRenderHook<CustomCardData>? renderHook;
|
||||
final List<BoardCellEquatable> cells;
|
||||
final CardConfiguration<CustomCardData>? cardConfiguration;
|
||||
final CustomCardData? cardData;
|
||||
final RowCardStyleConfiguration styleConfiguration;
|
||||
const _CardContent({
|
||||
required this.rowNotifier,
|
||||
required this.cellBuilder,
|
||||
required this.cells,
|
||||
required this.cardData,
|
||||
required this.styleConfiguration,
|
||||
this.renderHook,
|
||||
this.cardConfiguration,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -217,30 +188,30 @@ class _CardContent<CustomCardData> extends StatelessWidget {
|
||||
|
||||
List<Widget> _makeCells(
|
||||
BuildContext context,
|
||||
List<CellIdentifier> cells,
|
||||
List<BoardCellEquatable> cells,
|
||||
) {
|
||||
final List<Widget> children = [];
|
||||
// Remove all the cell listeners.
|
||||
rowNotifier.unbind();
|
||||
|
||||
cells.asMap().forEach(
|
||||
(int index, CellIdentifier cell) {
|
||||
(int index, BoardCellEquatable cell) {
|
||||
final isEditing = index == 0 ? rowNotifier.isEditing.value : false;
|
||||
final cellNotifier = EditableCardNotifier(isEditing: isEditing);
|
||||
|
||||
if (index == 0) {
|
||||
// Only use the first cell to receive user's input when click the edit
|
||||
// button
|
||||
rowNotifier.bindCell(cell, cellNotifier);
|
||||
rowNotifier.bindCell(cell.identifier, cellNotifier);
|
||||
}
|
||||
|
||||
final child = Padding(
|
||||
key: cell.key(),
|
||||
padding: styleConfiguration.cellPadding,
|
||||
key: cell.identifier.key(),
|
||||
padding: const EdgeInsets.only(left: 4, right: 4),
|
||||
child: cellBuilder.buildCell(
|
||||
cellId: cell,
|
||||
cellId: cell.identifier,
|
||||
cellNotifier: cellNotifier,
|
||||
renderHook: renderHook,
|
||||
cardConfiguration: cardConfiguration,
|
||||
cardData: cardData,
|
||||
),
|
||||
);
|
||||
@@ -294,13 +265,3 @@ class _CardEditOption extends StatelessWidget with CardAccessory {
|
||||
@override
|
||||
AccessoryType get type => AccessoryType.edit;
|
||||
}
|
||||
|
||||
class RowCardStyleConfiguration {
|
||||
final bool showAccessory;
|
||||
final EdgeInsets cellPadding;
|
||||
|
||||
const RowCardStyleConfiguration({
|
||||
this.showAccessory = true,
|
||||
this.cellPadding = const EdgeInsets.only(left: 4, right: 4),
|
||||
});
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'dart:collection';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/row_entities.pb.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -11,9 +12,9 @@ import '../../application/row/row_service.dart';
|
||||
|
||||
part 'card_bloc.freezed.dart';
|
||||
|
||||
class CardBloc extends Bloc<RowCardEvent, RowCardState> {
|
||||
class CardBloc extends Bloc<BoardCardEvent, BoardCardState> {
|
||||
final RowPB row;
|
||||
final String? groupFieldId;
|
||||
final String groupFieldId;
|
||||
final RowBackendService _rowBackendSvc;
|
||||
final RowCache _rowCache;
|
||||
VoidCallback? _rowCallback;
|
||||
@@ -27,13 +28,13 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
|
||||
}) : _rowBackendSvc = RowBackendService(viewId: viewId),
|
||||
_rowCache = rowCache,
|
||||
super(
|
||||
RowCardState.initial(
|
||||
BoardCardState.initial(
|
||||
row,
|
||||
_makeCells(groupFieldId, rowCache.loadGridCells(row.id)),
|
||||
isEditing,
|
||||
),
|
||||
) {
|
||||
on<RowCardEvent>(
|
||||
on<BoardCardEvent>(
|
||||
(event, emit) async {
|
||||
await event.when(
|
||||
initial: () async {
|
||||
@@ -68,7 +69,7 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
|
||||
return RowInfo(
|
||||
viewId: _rowBackendSvc.viewId,
|
||||
fields: UnmodifiableListView(
|
||||
state.cells.map((cell) => cell.fieldInfo).toList(),
|
||||
state.cells.map((cell) => cell.identifier.fieldInfo).toList(),
|
||||
),
|
||||
rowPB: state.rowPB,
|
||||
);
|
||||
@@ -80,58 +81,70 @@ class CardBloc extends Bloc<RowCardEvent, RowCardState> {
|
||||
onCellUpdated: (cellMap, reason) {
|
||||
if (!isClosed) {
|
||||
final cells = _makeCells(groupFieldId, cellMap);
|
||||
add(RowCardEvent.didReceiveCells(cells, reason));
|
||||
add(BoardCardEvent.didReceiveCells(cells, reason));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<CellIdentifier> _makeCells(
|
||||
String? groupFieldId,
|
||||
List<BoardCellEquatable> _makeCells(
|
||||
String groupFieldId,
|
||||
CellByFieldId originalCellMap,
|
||||
) {
|
||||
List<CellIdentifier> cells = [];
|
||||
List<BoardCellEquatable> cells = [];
|
||||
for (final entry in originalCellMap.entries) {
|
||||
// Filter out the cell if it's fieldId equal to the groupFieldId
|
||||
if (groupFieldId != null) {
|
||||
if (entry.value.fieldId == groupFieldId) {
|
||||
continue;
|
||||
}
|
||||
if (entry.value.fieldId != groupFieldId) {
|
||||
cells.add(BoardCellEquatable(entry.value));
|
||||
}
|
||||
|
||||
cells.add(entry.value);
|
||||
}
|
||||
return cells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowCardEvent with _$RowCardEvent {
|
||||
const factory RowCardEvent.initial() = _InitialRow;
|
||||
const factory RowCardEvent.setIsEditing(bool isEditing) = _IsEditing;
|
||||
const factory RowCardEvent.didReceiveCells(
|
||||
List<CellIdentifier> cells,
|
||||
class BoardCardEvent with _$BoardCardEvent {
|
||||
const factory BoardCardEvent.initial() = _InitialRow;
|
||||
const factory BoardCardEvent.setIsEditing(bool isEditing) = _IsEditing;
|
||||
const factory BoardCardEvent.didReceiveCells(
|
||||
List<BoardCellEquatable> cells,
|
||||
RowsChangedReason reason,
|
||||
) = _DidReceiveCells;
|
||||
}
|
||||
|
||||
@freezed
|
||||
class RowCardState with _$RowCardState {
|
||||
const factory RowCardState({
|
||||
class BoardCardState with _$BoardCardState {
|
||||
const factory BoardCardState({
|
||||
required RowPB rowPB,
|
||||
required List<CellIdentifier> cells,
|
||||
required List<BoardCellEquatable> cells,
|
||||
required bool isEditing,
|
||||
RowsChangedReason? changeReason,
|
||||
}) = _RowCardState;
|
||||
}) = _BoardCardState;
|
||||
|
||||
factory RowCardState.initial(
|
||||
factory BoardCardState.initial(
|
||||
RowPB rowPB,
|
||||
List<CellIdentifier> cells,
|
||||
List<BoardCellEquatable> cells,
|
||||
bool isEditing,
|
||||
) =>
|
||||
RowCardState(
|
||||
BoardCardState(
|
||||
rowPB: rowPB,
|
||||
cells: cells,
|
||||
isEditing: isEditing,
|
||||
);
|
||||
}
|
||||
|
||||
class BoardCellEquatable extends Equatable {
|
||||
final CellIdentifier identifier;
|
||||
|
||||
const BoardCellEquatable(this.identifier);
|
||||
|
||||
@override
|
||||
List<Object?> get props {
|
||||
return [
|
||||
identifier.fieldInfo.id,
|
||||
identifier.fieldInfo.fieldType,
|
||||
identifier.fieldInfo.visibility,
|
||||
identifier.fieldInfo.width,
|
||||
];
|
||||
}
|
||||
}
|
||||
+9
-15
@@ -15,15 +15,15 @@ import 'cells/url_card_cell.dart';
|
||||
// T represents as the Generic card data
|
||||
class CardCellBuilder<CustomCardData> {
|
||||
final CellCache cellCache;
|
||||
final Map<FieldType, CardCellStyle>? styles;
|
||||
|
||||
CardCellBuilder(this.cellCache, {this.styles});
|
||||
CardCellBuilder(this.cellCache);
|
||||
|
||||
Widget buildCell({
|
||||
CustomCardData? cardData,
|
||||
required CellIdentifier cellId,
|
||||
EditableCardNotifier? cellNotifier,
|
||||
RowCardRenderHook<CustomCardData>? renderHook,
|
||||
CardConfiguration<CustomCardData>? cardConfiguration,
|
||||
Map<FieldType, CardCellStyle>? styles,
|
||||
}) {
|
||||
final cellControllerBuilder = CellControllerBuilder(
|
||||
cellId: cellId,
|
||||
@@ -39,21 +39,20 @@ class CardCellBuilder<CustomCardData> {
|
||||
key: key,
|
||||
);
|
||||
case FieldType.DateTime:
|
||||
return DateCardCell<CustomCardData>(
|
||||
renderHook: renderHook?.renderHook[FieldType.DateTime],
|
||||
return DateCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.SingleSelect:
|
||||
return SelectOptionCardCell<CustomCardData>(
|
||||
renderHook: renderHook?.renderHook[FieldType.SingleSelect],
|
||||
renderHook: cardConfiguration?.renderHook[FieldType.SingleSelect],
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
cardData: cardData,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.MultiSelect:
|
||||
return SelectOptionCardCell<CustomCardData>(
|
||||
renderHook: renderHook?.renderHook[FieldType.MultiSelect],
|
||||
renderHook: cardConfiguration?.renderHook[FieldType.MultiSelect],
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
cardData: cardData,
|
||||
editableNotifier: cellNotifier,
|
||||
@@ -65,24 +64,19 @@ class CardCellBuilder<CustomCardData> {
|
||||
key: key,
|
||||
);
|
||||
case FieldType.Number:
|
||||
return NumberCardCell<CustomCardData>(
|
||||
renderHook: renderHook?.renderHook[FieldType.Number],
|
||||
style: isStyleOrNull<NumberCardCellStyle>(style),
|
||||
return NumberCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
case FieldType.RichText:
|
||||
return TextCardCell<CustomCardData>(
|
||||
renderHook: renderHook?.renderHook[FieldType.RichText],
|
||||
return TextCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
editableNotifier: cellNotifier,
|
||||
cardData: cardData,
|
||||
style: isStyleOrNull<TextCardCellStyle>(style),
|
||||
key: key,
|
||||
);
|
||||
case FieldType.URL:
|
||||
return URLCardCell<CustomCardData>(
|
||||
style: isStyleOrNull<URLCardCellStyle>(style),
|
||||
return URLCardCell(
|
||||
cellControllerBuilder: cellControllerBuilder,
|
||||
key: key,
|
||||
);
|
||||
|
||||
+9
-54
@@ -1,71 +1,26 @@
|
||||
import 'package:appflowy/plugins/database_view/application/cell/cell_service.dart';
|
||||
import 'package:appflowy_backend/log.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/date_type_option_entities.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/field_entities.pbenum.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-database/select_type_option.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
typedef CellRenderHook<C, CustomCardData> = Widget? Function(
|
||||
C cellData,
|
||||
CustomCardData cardData,
|
||||
BuildContext buildContext,
|
||||
);
|
||||
typedef CellRenderHook<C, T> = Widget? Function(C cellData, T cardData);
|
||||
typedef RenderHookByFieldType<C> = Map<FieldType, CellRenderHook<dynamic, C>>;
|
||||
|
||||
/// The [RowCardRenderHook] is used to customize the rendering of the
|
||||
/// card cell. Each cell has itw own field type. So the [renderHook]
|
||||
/// is a map of [FieldType] to [CellRenderHook].
|
||||
class RowCardRenderHook<CustomCardData> {
|
||||
class CardConfiguration<CustomCardData> {
|
||||
final RenderHookByFieldType<CustomCardData> renderHook = {};
|
||||
RowCardRenderHook();
|
||||
CardConfiguration();
|
||||
|
||||
/// Add render hook for the FieldType.SingleSelect and FieldType.MultiSelect
|
||||
void addSelectOptionHook(
|
||||
CellRenderHook<List<SelectOptionPB>, CustomCardData?> hook,
|
||||
CellRenderHook<List<SelectOptionPB>, CustomCardData> hook,
|
||||
) {
|
||||
final hookFn = _typeSafeHook<List<SelectOptionPB>>(hook);
|
||||
renderHook[FieldType.SingleSelect] = hookFn;
|
||||
renderHook[FieldType.MultiSelect] = hookFn;
|
||||
}
|
||||
|
||||
/// Add a render hook for the [FieldType.RichText]
|
||||
void addTextCellHook(
|
||||
CellRenderHook<String, CustomCardData?> hook,
|
||||
) {
|
||||
renderHook[FieldType.RichText] = _typeSafeHook<String>(hook);
|
||||
}
|
||||
|
||||
/// Add a render hook for the [FieldType.Number]
|
||||
void addNumberCellHook(
|
||||
CellRenderHook<String, CustomCardData?> hook,
|
||||
) {
|
||||
renderHook[FieldType.Number] = _typeSafeHook<String>(hook);
|
||||
}
|
||||
|
||||
/// Add a render hook for the [FieldType.Date]
|
||||
void addDateCellHook(
|
||||
CellRenderHook<DateCellDataPB, CustomCardData?> hook,
|
||||
) {
|
||||
renderHook[FieldType.DateTime] = _typeSafeHook<DateCellDataPB>(hook);
|
||||
}
|
||||
|
||||
CellRenderHook<dynamic, CustomCardData> _typeSafeHook<C>(
|
||||
CellRenderHook<C, CustomCardData?> hook,
|
||||
) {
|
||||
hookFn(cellData, cardData, buildContext) {
|
||||
if (cellData == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (cellData is C) {
|
||||
return hook(cellData, cardData, buildContext);
|
||||
} else {
|
||||
Log.debug("Unexpected cellData type: ${cellData.runtimeType}");
|
||||
return null;
|
||||
selectOptionHook(cellData, cardData) {
|
||||
if (cellData is List<SelectOptionPB>) {
|
||||
hook(cellData, cardData);
|
||||
}
|
||||
}
|
||||
|
||||
return hookFn;
|
||||
renderHook[FieldType.SingleSelect] = selectOptionHook;
|
||||
renderHook[FieldType.MultiSelect] = selectOptionHook;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+7
-10
@@ -44,16 +44,13 @@ class _CheckboxCardCellState extends State<CheckboxCardCell> {
|
||||
: svgWidget('editor/editor_uncheck');
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2),
|
||||
child: FlowyIconButton(
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: icon,
|
||||
width: 20,
|
||||
onPressed: () => context
|
||||
.read<CheckboxCardCellBloc>()
|
||||
.add(const CheckboxCardCellEvent.select()),
|
||||
),
|
||||
child: FlowyIconButton(
|
||||
iconPadding: EdgeInsets.zero,
|
||||
icon: icon,
|
||||
width: 20,
|
||||
onPressed: () => context
|
||||
.read<CheckboxCardCellBloc>()
|
||||
.add(const CheckboxCardCellEvent.select()),
|
||||
),
|
||||
);
|
||||
},
|
||||
|
||||
+1
-12
@@ -7,13 +7,11 @@ import '../bloc/date_card_cell_bloc.dart';
|
||||
import '../define.dart';
|
||||
import 'card_cell.dart';
|
||||
|
||||
class DateCardCell<CustomCardData> extends CardCell {
|
||||
class DateCardCell extends CardCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final CellRenderHook<dynamic, CustomCardData>? renderHook;
|
||||
|
||||
const DateCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
this.renderHook,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -44,15 +42,6 @@ class _DateCardCellState extends State<DateCardCell> {
|
||||
if (state.dateStr.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
Widget? custom = widget.renderHook?.call(
|
||||
state.data,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
|
||||
+3
-23
@@ -7,24 +7,13 @@ import '../bloc/number_card_cell_bloc.dart';
|
||||
import '../define.dart';
|
||||
import 'card_cell.dart';
|
||||
|
||||
class NumberCardCellStyle extends CardCellStyle {
|
||||
final double fontSize;
|
||||
|
||||
NumberCardCellStyle(this.fontSize);
|
||||
}
|
||||
|
||||
class NumberCardCell<CustomCardData>
|
||||
extends CardCell<CustomCardData, NumberCardCellStyle> {
|
||||
final CellRenderHook<String, CustomCardData>? renderHook;
|
||||
class NumberCardCell extends CardCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const NumberCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
CustomCardData? cardData,
|
||||
NumberCardCellStyle? style,
|
||||
this.renderHook,
|
||||
Key? key,
|
||||
}) : super(key: key, style: style, cardData: cardData);
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<NumberCardCell> createState() => _NumberCardCellState();
|
||||
@@ -53,15 +42,6 @@ class _NumberCardCellState extends State<NumberCardCell> {
|
||||
if (state.content.isEmpty) {
|
||||
return const SizedBox();
|
||||
} else {
|
||||
Widget? custom = widget.renderHook?.call(
|
||||
state.content,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
return Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Padding(
|
||||
@@ -70,7 +50,7 @@ class _NumberCardCellState extends State<NumberCardCell> {
|
||||
),
|
||||
child: FlowyText.medium(
|
||||
state.content,
|
||||
fontSize: widget.style?.fontSize ?? 14,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+3
-5
@@ -11,18 +11,17 @@ import 'card_cell.dart';
|
||||
|
||||
class SelectOptionCardCellStyle extends CardCellStyle {}
|
||||
|
||||
class SelectOptionCardCell<CustomCardData>
|
||||
extends CardCell<CustomCardData, SelectOptionCardCellStyle>
|
||||
class SelectOptionCardCell<T> extends CardCell<T, SelectOptionCardCellStyle>
|
||||
with EditableCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final CellRenderHook<List<SelectOptionPB>, CustomCardData>? renderHook;
|
||||
final CellRenderHook<List<SelectOptionPB>, T>? renderHook;
|
||||
|
||||
@override
|
||||
final EditableCardNotifier? editableNotifier;
|
||||
|
||||
SelectOptionCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
required CustomCardData? cardData,
|
||||
required T? cardData,
|
||||
this.renderHook,
|
||||
this.editableNotifier,
|
||||
Key? key,
|
||||
@@ -58,7 +57,6 @@ class _SelectOptionCardCellState extends State<SelectOptionCardCell> {
|
||||
Widget? custom = widget.renderHook?.call(
|
||||
state.selectedOptions,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
|
||||
+3
-16
@@ -14,21 +14,18 @@ class TextCardCellStyle extends CardCellStyle {
|
||||
TextCardCellStyle(this.fontSize);
|
||||
}
|
||||
|
||||
class TextCardCell<CustomCardData>
|
||||
extends CardCell<CustomCardData, TextCardCellStyle> with EditableCell {
|
||||
class TextCardCell extends CardCell<String, TextCardCellStyle>
|
||||
with EditableCell {
|
||||
@override
|
||||
final EditableCardNotifier? editableNotifier;
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
final CellRenderHook<String, CustomCardData>? renderHook;
|
||||
|
||||
const TextCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
required CustomCardData? cardData,
|
||||
this.editableNotifier,
|
||||
this.renderHook,
|
||||
TextCardCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key, style: style, cardData: cardData);
|
||||
}) : super(key: key, style: style);
|
||||
|
||||
@override
|
||||
State<TextCardCell> createState() => _TextCardCellState();
|
||||
@@ -107,16 +104,6 @@ class _TextCardCellState extends State<TextCardCell> {
|
||||
return previous != current;
|
||||
},
|
||||
builder: (context, state) {
|
||||
// Returns a custom render widget
|
||||
Widget? custom = widget.renderHook?.call(
|
||||
state.content,
|
||||
widget.cardData,
|
||||
context,
|
||||
);
|
||||
if (custom != null) {
|
||||
return custom;
|
||||
}
|
||||
|
||||
if (state.content.isEmpty &&
|
||||
state.enableEdit == false &&
|
||||
focusWhenInit == false) {
|
||||
|
||||
+3
-11
@@ -8,21 +8,13 @@ import '../bloc/url_card_cell_bloc.dart';
|
||||
import '../define.dart';
|
||||
import 'card_cell.dart';
|
||||
|
||||
class URLCardCellStyle extends CardCellStyle {
|
||||
final double fontSize;
|
||||
|
||||
URLCardCellStyle(this.fontSize);
|
||||
}
|
||||
|
||||
class URLCardCell<CustomCardData>
|
||||
extends CardCell<CustomCardData, URLCardCellStyle> {
|
||||
class URLCardCell extends CardCell {
|
||||
final CellControllerBuilder cellControllerBuilder;
|
||||
|
||||
const URLCardCell({
|
||||
required this.cellControllerBuilder,
|
||||
URLCardCellStyle? style,
|
||||
Key? key,
|
||||
}) : super(key: key, style: style);
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<URLCardCell> createState() => _URLCardCellState();
|
||||
@@ -63,7 +55,7 @@ class _URLCardCellState extends State<URLCardCell> {
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium!
|
||||
.size(widget.style?.fontSize ?? FontSizes.s14)
|
||||
.size(FontSizes.s14)
|
||||
.textColor(Theme.of(context).colorScheme.primary)
|
||||
.underline,
|
||||
),
|
||||
|
||||
+2
-3
@@ -4,13 +4,13 @@ import 'package:styled_widget/styled_widget.dart';
|
||||
|
||||
import 'accessory.dart';
|
||||
|
||||
class RowCardContainer extends StatelessWidget {
|
||||
class BoardCardContainer extends StatelessWidget {
|
||||
final Widget child;
|
||||
final CardAccessoryBuilder? accessoryBuilder;
|
||||
final bool Function()? buildAccessoryWhen;
|
||||
final void Function(BuildContext) openCard;
|
||||
final void Function(AccessoryType) openAccessory;
|
||||
const RowCardContainer({
|
||||
const BoardCardContainer({
|
||||
required this.child,
|
||||
required this.openCard,
|
||||
required this.openAccessory,
|
||||
@@ -43,7 +43,6 @@ class RowCardContainer extends StatelessWidget {
|
||||
}
|
||||
|
||||
return GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: () => openCard(context),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8),
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@ class ChecklistProgressBar extends StatelessWidget {
|
||||
percent: percent,
|
||||
padding: EdgeInsets.zero,
|
||||
progressColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: AFThemeExtension.of(context).progressBarBGColor,
|
||||
backgroundColor: AFThemeExtension.of(context).progressBarBGcolor,
|
||||
barRadius: const Radius.circular(5),
|
||||
);
|
||||
}
|
||||
|
||||
+10
-30
@@ -84,23 +84,15 @@ class DateCellCalendarBloc
|
||||
bool? includeTime,
|
||||
}) {
|
||||
final DateCellData newDateData = state.dateCellData.fold(
|
||||
() {
|
||||
DateTime newDate = DateTime.now();
|
||||
if (date != null) {
|
||||
newDate = _utcToLocalAddTime(date);
|
||||
}
|
||||
return DateCellData(
|
||||
date: newDate,
|
||||
time: time,
|
||||
includeTime: includeTime ?? false,
|
||||
);
|
||||
},
|
||||
() => DateCellData(
|
||||
date: date ?? DateTime.now(),
|
||||
time: time,
|
||||
includeTime: includeTime ?? false,
|
||||
),
|
||||
(dateData) {
|
||||
var newDateData = dateData;
|
||||
if (date != null && !isSameDay(newDateData.date, date)) {
|
||||
newDateData = newDateData.copyWith(
|
||||
date: _utcToLocalAddTime(date),
|
||||
);
|
||||
newDateData = newDateData.copyWith(date: date);
|
||||
}
|
||||
|
||||
if (newDateData.time != time) {
|
||||
@@ -159,21 +151,6 @@ class DateCellCalendarBloc
|
||||
);
|
||||
}
|
||||
|
||||
DateTime _utcToLocalAddTime(DateTime date) {
|
||||
final now = DateTime.now();
|
||||
// the incoming date is Utc. this trick converts it into Local
|
||||
// and add the current time, though
|
||||
// the time may be overwritten by explicitly provided time string
|
||||
return DateTime(
|
||||
date.year,
|
||||
date.month,
|
||||
date.day,
|
||||
now.hour,
|
||||
now.minute,
|
||||
now.second,
|
||||
);
|
||||
}
|
||||
|
||||
String timeFormatPrompt(FlowyError error) {
|
||||
String msg = "${LocaleKeys.grid_field_invalidTimeFormat.tr()}.";
|
||||
switch (state.dateTypeOptionPB.timeFormat) {
|
||||
@@ -313,7 +290,10 @@ Option<DateCellData> calDataFromCellData(DateCellDataPB? cellData) {
|
||||
Option<DateCellData> dateData = none();
|
||||
if (cellData != null) {
|
||||
final timestamp = cellData.timestamp * 1000;
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(timestamp.toInt());
|
||||
final date = DateTime.fromMillisecondsSinceEpoch(
|
||||
timestamp.toInt(),
|
||||
isUtc: true,
|
||||
);
|
||||
dateData = Some(
|
||||
DateCellData(
|
||||
date: date,
|
||||
|
||||
+1
-7
@@ -8,13 +8,9 @@ import '../../cell_builder.dart';
|
||||
|
||||
class GridTextCellStyle extends GridCellStyle {
|
||||
String? placeholder;
|
||||
TextStyle? textStyle;
|
||||
bool? autofocus;
|
||||
|
||||
GridTextCellStyle({
|
||||
this.placeholder,
|
||||
this.textStyle,
|
||||
this.autofocus,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -70,9 +66,7 @@ class _GridTextCellState extends GridFocusNodeCellState<GridTextCell> {
|
||||
controller: _controller,
|
||||
focusNode: focusNode,
|
||||
maxLines: null,
|
||||
style: widget.cellStyle?.textStyle ??
|
||||
Theme.of(context).textTheme.bodyMedium,
|
||||
autofocus: widget.cellStyle?.autofocus ?? false,
|
||||
style: Theme.of(context).textTheme.bodyMedium,
|
||||
decoration: InputDecoration(
|
||||
contentPadding: EdgeInsets.only(
|
||||
top: GridSize.cellContentInsets.top,
|
||||
|
||||
@@ -3,7 +3,6 @@ import 'package:appflowy/plugins/database_view/application/field/type_option/typ
|
||||
import 'package:appflowy/plugins/database_view/application/row/row_data_controller.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/row/row_detail_bloc.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/dialogs.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -44,85 +43,83 @@ class RowDetailPage extends StatefulWidget with FlowyOverlayDelegate {
|
||||
}
|
||||
|
||||
class _RowDetailPageState extends State<RowDetailPage> {
|
||||
final padding = const EdgeInsets.symmetric(
|
||||
horizontal: 40,
|
||||
vertical: 20,
|
||||
);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyDialog(
|
||||
child: BlocProvider(
|
||||
create: (context) {
|
||||
return RowDetailBloc(dataController: widget.dataController)
|
||||
..add(const RowDetailEvent.initial());
|
||||
final bloc = RowDetailBloc(
|
||||
dataController: widget.dataController,
|
||||
);
|
||||
bloc.add(const RowDetailEvent.initial());
|
||||
return bloc;
|
||||
},
|
||||
child: ListView(
|
||||
children: [
|
||||
// using ListView here for future expansion:
|
||||
// - header and cover image
|
||||
// - lower rich text area
|
||||
IntrinsicHeight(child: _responsiveRowInfo()),
|
||||
const Divider(height: 1.0),
|
||||
const SizedBox(height: 10),
|
||||
],
|
||||
child: Padding(
|
||||
padding: padding,
|
||||
child: Column(
|
||||
children: [
|
||||
const _Header(),
|
||||
Expanded(
|
||||
child: _PropertyColumn(
|
||||
cellBuilder: widget.cellBuilder,
|
||||
viewId: widget.dataController.viewId,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Widget _responsiveRowInfo() {
|
||||
final rowDataColumn = _PropertyColumn(
|
||||
cellBuilder: widget.cellBuilder,
|
||||
viewId: widget.dataController.viewId,
|
||||
class _Header extends StatelessWidget {
|
||||
const _Header({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 30,
|
||||
child: Row(
|
||||
children: const [Spacer(), _CloseButton()],
|
||||
),
|
||||
);
|
||||
final rowOptionColumn = _RowOptionColumn(
|
||||
viewId: widget.dataController.viewId,
|
||||
rowId: widget.dataController.rowId,
|
||||
}
|
||||
}
|
||||
|
||||
class _CloseButton extends StatelessWidget {
|
||||
const _CloseButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyIconButton(
|
||||
hoverColor: AFThemeExtension.of(context).lightGreyHover,
|
||||
width: 24,
|
||||
onPressed: () => FlowyOverlay.pop(context),
|
||||
iconPadding: const EdgeInsets.fromLTRB(2, 2, 2, 2),
|
||||
icon: svgWidget(
|
||||
"home/close",
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
);
|
||||
if (MediaQuery.of(context).size.width > 800) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Flexible(
|
||||
flex: 4,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(50, 50, 20, 20),
|
||||
child: rowDataColumn,
|
||||
),
|
||||
),
|
||||
const VerticalDivider(width: 1.0),
|
||||
Flexible(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 50, 20, 20),
|
||||
child: rowOptionColumn,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
} else {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(20, 50, 20, 20),
|
||||
child: rowDataColumn,
|
||||
),
|
||||
const Divider(height: 1.0),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: rowOptionColumn,
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _PropertyColumn extends StatelessWidget {
|
||||
final String viewId;
|
||||
final GridCellBuilder cellBuilder;
|
||||
const _PropertyColumn({
|
||||
final ScrollController _scrollController;
|
||||
_PropertyColumn({
|
||||
required this.viewId,
|
||||
required this.cellBuilder,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
}) : _scrollController = ScrollController(),
|
||||
super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
@@ -130,61 +127,63 @@ class _PropertyColumn extends StatelessWidget {
|
||||
buildWhen: (previous, current) => previous.gridCells != current.gridCells,
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_RowTitle(
|
||||
cellId: state.gridCells
|
||||
.firstWhereOrNull((e) => e.fieldInfo.isPrimary),
|
||||
cellBuilder: cellBuilder,
|
||||
Expanded(child: _wrapScrollbar(buildPropertyCells(state))),
|
||||
const VSpace(10),
|
||||
_CreatePropertyButton(
|
||||
viewId: viewId,
|
||||
onClosed: _scrollToNewProperty,
|
||||
),
|
||||
const VSpace(20),
|
||||
...state.gridCells
|
||||
.where((element) => !element.fieldInfo.isPrimary)
|
||||
.map(
|
||||
(cell) => Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: _PropertyCell(
|
||||
cellId: cell,
|
||||
cellBuilder: cellBuilder,
|
||||
),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
const VSpace(20),
|
||||
_CreatePropertyButton(viewId: viewId),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _RowTitle extends StatelessWidget {
|
||||
final CellIdentifier? cellId;
|
||||
final GridCellBuilder cellBuilder;
|
||||
const _RowTitle({this.cellId, required this.cellBuilder, Key? key})
|
||||
: super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (cellId == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
final style = GridTextCellStyle(
|
||||
placeholder: LocaleKeys.grid_row_textPlaceholder.tr(),
|
||||
textStyle: Theme.of(context).textTheme.titleLarge,
|
||||
autofocus: true,
|
||||
Widget buildPropertyCells(RowDetailState state) {
|
||||
return ListView.separated(
|
||||
controller: _scrollController,
|
||||
itemCount: state.gridCells.length,
|
||||
itemBuilder: (BuildContext context, int index) {
|
||||
return _PropertyCell(
|
||||
cellId: state.gridCells[index],
|
||||
cellBuilder: cellBuilder,
|
||||
);
|
||||
},
|
||||
separatorBuilder: (BuildContext context, int index) {
|
||||
return const VSpace(2);
|
||||
},
|
||||
);
|
||||
return cellBuilder.build(cellId!, style: style);
|
||||
}
|
||||
|
||||
Widget _wrapScrollbar(Widget child) {
|
||||
return ScrollbarListStack(
|
||||
axis: Axis.vertical,
|
||||
controller: _scrollController,
|
||||
barSize: GridSize.scrollBarSize,
|
||||
autoHideScrollbar: false,
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
|
||||
void _scrollToNewProperty() {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_scrollController.animateTo(
|
||||
_scrollController.position.maxScrollExtent,
|
||||
duration: const Duration(milliseconds: 250),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class _CreatePropertyButton extends StatefulWidget {
|
||||
final String viewId;
|
||||
final VoidCallback onClosed;
|
||||
|
||||
const _CreatePropertyButton({
|
||||
required this.viewId,
|
||||
required this.onClosed,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@@ -207,9 +206,10 @@ class _CreatePropertyButtonState extends State<_CreatePropertyButton> {
|
||||
constraints: BoxConstraints.loose(const Size(240, 200)),
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.topWithLeftAligned,
|
||||
margin: EdgeInsets.zero,
|
||||
child: SizedBox(
|
||||
onClose: widget.onClosed,
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: _makeBoxDecoration(context),
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
LocaleKeys.grid_field_newProperty.tr(),
|
||||
@@ -243,6 +243,14 @@ class _CreatePropertyButtonState extends State<_CreatePropertyButton> {
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
BoxDecoration _makeBoxDecoration(BuildContext context) {
|
||||
final borderSide =
|
||||
BorderSide(color: Theme.of(context).dividerColor, width: 1.0);
|
||||
return BoxDecoration(
|
||||
border: Border(top: borderSide),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _PropertyCell extends StatefulWidget {
|
||||
@@ -368,69 +376,3 @@ GridCellStyle? _customCellStyle(FieldType fieldType) {
|
||||
}
|
||||
throw UnimplementedError;
|
||||
}
|
||||
|
||||
class _RowOptionColumn extends StatelessWidget {
|
||||
final String rowId;
|
||||
const _RowOptionColumn({
|
||||
required String viewId,
|
||||
required this.rowId,
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 10),
|
||||
child: FlowyText(LocaleKeys.grid_row_action.tr()),
|
||||
),
|
||||
const VSpace(15),
|
||||
_DeleteButton(rowId: rowId),
|
||||
_DuplicateButton(rowId: rowId),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DeleteButton extends StatelessWidget {
|
||||
final String rowId;
|
||||
const _DeleteButton({required this.rowId, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.regular(LocaleKeys.grid_row_delete.tr()),
|
||||
leftIcon: const FlowySvg(name: "home/trash"),
|
||||
onTap: () {
|
||||
context.read<RowDetailBloc>().add(RowDetailEvent.deleteRow(rowId));
|
||||
FlowyOverlay.pop(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DuplicateButton extends StatelessWidget {
|
||||
final String rowId;
|
||||
const _DuplicateButton({required this.rowId, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: GridSize.popoverItemHeight,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.regular(LocaleKeys.grid_row_duplicate.tr()),
|
||||
leftIcon: const FlowySvg(name: "grid/duplicate"),
|
||||
onTap: () {
|
||||
context.read<RowDetailBloc>().add(RowDetailEvent.duplicateRow(rowId));
|
||||
FlowyOverlay.pop(context);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -226,9 +226,6 @@ class _AppFlowyEditorPageState extends State<_AppFlowyEditorPage> {
|
||||
if (temporaryNodeTypes.contains(node.type)) {
|
||||
transaction.deleteNode(node);
|
||||
}
|
||||
if (kCoverType == node.type && !node.path.equals([0])) {
|
||||
transaction.deleteNode(node);
|
||||
}
|
||||
}
|
||||
if (transaction.operations.isNotEmpty) {
|
||||
await editorState.apply(transaction, withUpdateCursor: false);
|
||||
|
||||
+3
-12
@@ -1,5 +1,4 @@
|
||||
import 'package:appflowy/plugins/document/presentation/more/cubit/document_appearance_cubit.dart';
|
||||
import 'package:flowy_infra/theme_extension.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
@@ -25,8 +24,6 @@ class _FontSizeSwitcherState extends State<FontSizeSwitcher> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final selectedBgColor = AFThemeExtension.of(context).toggleButtonBGColor;
|
||||
final foregroundColor = Theme.of(context).colorScheme.onBackground;
|
||||
return BlocBuilder<DocumentAppearanceCubit, DocumentAppearance>(
|
||||
builder: (context, state) {
|
||||
return Column(
|
||||
@@ -46,16 +43,10 @@ class _FontSizeSwitcherState extends State<FontSizeSwitcher> {
|
||||
onPressed: (int index) {
|
||||
_updateSelectedFontSize(_fontSizes[index].item2);
|
||||
},
|
||||
color: foregroundColor,
|
||||
borderRadius: const BorderRadius.all(Radius.circular(5)),
|
||||
borderColor: foregroundColor,
|
||||
borderWidth: 0.5,
|
||||
// when selected
|
||||
selectedColor: foregroundColor,
|
||||
selectedBorderColor: foregroundColor,
|
||||
fillColor: selectedBgColor,
|
||||
// when hover
|
||||
hoverColor: selectedBgColor.withOpacity(0.3),
|
||||
selectedColor: Theme.of(context).colorScheme.tertiary,
|
||||
fillColor: Theme.of(context).colorScheme.primary,
|
||||
color: Theme.of(context).hintColor,
|
||||
constraints: const BoxConstraints(
|
||||
minHeight: 40.0,
|
||||
minWidth: 80.0,
|
||||
|
||||
@@ -12,7 +12,6 @@ class DocumentMoreButton extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return PopupMenuButton<int>(
|
||||
color: Theme.of(context).colorScheme.surfaceVariant,
|
||||
offset: const Offset(0, 30),
|
||||
itemBuilder: (context) {
|
||||
return [
|
||||
|
||||
+35
-62
@@ -393,32 +393,21 @@ class _CoverImageState extends State<_CoverImage> {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
AppFlowyPopover(
|
||||
onClose: () {
|
||||
setOverlayButtonsHidden(true);
|
||||
},
|
||||
offset: const Offset(-125, 10),
|
||||
controller: popoverController,
|
||||
direction: PopoverDirection.bottomWithCenterAligned,
|
||||
constraints: BoxConstraints.loose(const Size(380, 450)),
|
||||
margin: EdgeInsets.zero,
|
||||
child: Visibility(
|
||||
maintainState: true,
|
||||
maintainAnimation: true,
|
||||
maintainSize: true,
|
||||
visible: !isOverlayButtonsHidden,
|
||||
child: RoundedTextButton(
|
||||
onPressed: () {
|
||||
popoverController.show();
|
||||
setOverlayButtonsHidden(true);
|
||||
},
|
||||
hoverColor: Theme.of(context).colorScheme.surface,
|
||||
textColor: Theme.of(context).colorScheme.tertiary,
|
||||
fillColor:
|
||||
Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
||||
width: 120,
|
||||
height: 28,
|
||||
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
),
|
||||
child: RoundedTextButton(
|
||||
onPressed: () {
|
||||
popoverController.show();
|
||||
},
|
||||
hoverColor: Theme.of(context).colorScheme.surface,
|
||||
textColor: Theme.of(context).colorScheme.tertiary,
|
||||
fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.8),
|
||||
width: 120,
|
||||
height: 28,
|
||||
title: LocaleKeys.document_plugins_cover_changeCover.tr(),
|
||||
),
|
||||
popupBuilder: (BuildContext popoverContext) {
|
||||
return ChangeCoverPopover(
|
||||
@@ -429,24 +418,18 @@ class _CoverImageState extends State<_CoverImage> {
|
||||
},
|
||||
),
|
||||
const SizedBox(width: 10),
|
||||
Visibility(
|
||||
maintainAnimation: true,
|
||||
maintainSize: true,
|
||||
maintainState: true,
|
||||
visible: !isOverlayButtonsHidden,
|
||||
child: FlowyIconButton(
|
||||
hoverColor: Theme.of(context).colorScheme.surface,
|
||||
fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.5),
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
width: 28,
|
||||
icon: svgWidget(
|
||||
'editor/delete',
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.onCoverChanged(CoverSelectionType.initial, null);
|
||||
},
|
||||
FlowyIconButton(
|
||||
fillColor: Theme.of(context).colorScheme.surface.withOpacity(0.8),
|
||||
hoverColor: Theme.of(context).colorScheme.surface,
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
width: 28,
|
||||
icon: svgWidget(
|
||||
'editor/delete',
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
onPressed: () {
|
||||
widget.onCoverChanged(CoverSelectionType.initial, null);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -494,30 +477,20 @@ class _CoverImageState extends State<_CoverImage> {
|
||||
break;
|
||||
}
|
||||
//OverflowBox needs to be wraped by a widget with constraints(or from its parent) first,otherwise it will occur an error
|
||||
return MouseRegion(
|
||||
onEnter: (event) {
|
||||
setOverlayButtonsHidden(false);
|
||||
},
|
||||
onExit: (event) {
|
||||
setOverlayButtonsHidden(true);
|
||||
},
|
||||
child: SizedBox(
|
||||
height: height,
|
||||
child: OverflowBox(
|
||||
maxWidth: screenSize.width,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
child: coverImage,
|
||||
),
|
||||
hasCover
|
||||
? _buildCoverOverlayButtons(context)
|
||||
: const SizedBox.shrink()
|
||||
],
|
||||
),
|
||||
return SizedBox(
|
||||
height: height,
|
||||
child: OverflowBox(
|
||||
maxWidth: screenSize.width,
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
padding: const EdgeInsets.only(bottom: 10),
|
||||
height: double.infinity,
|
||||
width: double.infinity,
|
||||
child: coverImage,
|
||||
),
|
||||
hasCover ? _buildCoverOverlayButtons(context) : const SizedBox()
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
+50
-45
@@ -1,10 +1,11 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/emoji_picker.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart' hide FlowySvg;
|
||||
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/default_emoji_picker_view.dart';
|
||||
import 'package:appflowy/workspace/presentation/widgets/emoji_picker/src/emoji_view_state.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EmojiPopover extends StatefulWidget {
|
||||
@@ -32,53 +33,57 @@ class _EmojiPopoverState extends State<EmojiPopover> {
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(15),
|
||||
child: Column(
|
||||
child: EmojiPicker(
|
||||
onEmojiSelected: (category, emoji) {
|
||||
widget.onEmojiChanged(emoji);
|
||||
},
|
||||
customWidget: (Config config, EmojiViewState state) {
|
||||
return Stack(
|
||||
alignment: Alignment.topRight,
|
||||
children: [
|
||||
Container(
|
||||
padding: EdgeInsets.only(top: widget.showRemoveButton ? 25 : 0),
|
||||
child: DefaultEmojiPickerView(config, state),
|
||||
),
|
||||
_buildDeleteButtonIfNeed(),
|
||||
],
|
||||
);
|
||||
},
|
||||
config: Config(
|
||||
columns: 8,
|
||||
emojiSizeMax: 28,
|
||||
bgColor: Colors.transparent,
|
||||
iconColor: Theme.of(context).iconTheme.color!,
|
||||
iconColorSelected: Theme.of(context).colorScheme.onSurface,
|
||||
selectedHoverColor: Theme.of(context).colorScheme.secondary,
|
||||
progressIndicatorColor: Theme.of(context).iconTheme.color!,
|
||||
buttonMode: ButtonMode.CUPERTINO,
|
||||
initCategory: Category.RECENT,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDeleteButtonIfNeed() {
|
||||
if (!widget.showRemoveButton) {
|
||||
return const SizedBox();
|
||||
}
|
||||
return FlowyButton(
|
||||
onTap: () => widget.removeIcon(),
|
||||
useIntrinsicWidth: true,
|
||||
text: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (widget.showRemoveButton)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(bottom: 4.0),
|
||||
child: Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: DeleteButton(onPressed: widget.removeIcon),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: EmojiPicker(
|
||||
onEmojiSelected: (category, emoji) {
|
||||
widget.onEmojiChanged(emoji);
|
||||
},
|
||||
config: Config(
|
||||
columns: 8,
|
||||
emojiSizeMax: 28,
|
||||
bgColor: Colors.transparent,
|
||||
iconColor: Theme.of(context).iconTheme.color!,
|
||||
iconColorSelected: Theme.of(context).colorScheme.onSurface,
|
||||
selectedHoverColor: Theme.of(context).colorScheme.secondary,
|
||||
progressIndicatorColor: Theme.of(context).iconTheme.color!,
|
||||
buttonMode: ButtonMode.CUPERTINO,
|
||||
initCategory: Category.RECENT,
|
||||
),
|
||||
),
|
||||
const FlowySvg(name: 'editor/delete'),
|
||||
const SizedBox(
|
||||
width: 5,
|
||||
),
|
||||
FlowyText(
|
||||
LocaleKeys.document_plugins_cover_removeIcon.tr(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DeleteButton extends StatelessWidget {
|
||||
final VoidCallback onPressed;
|
||||
const DeleteButton({required this.onPressed, Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FlowyButton(
|
||||
onTap: () => onPressed,
|
||||
useIntrinsicWidth: true,
|
||||
text: FlowyText(
|
||||
LocaleKeys.document_plugins_cover_removeIcon.tr(),
|
||||
),
|
||||
leftIcon: const FlowySvg(name: 'editor/delete'),
|
||||
);
|
||||
}
|
||||
}
|
||||
+19
-23
@@ -29,32 +29,29 @@ void _showEmojiSelectionMenu(
|
||||
menuService.dismiss();
|
||||
|
||||
_emojiSelectionMenu?.remove();
|
||||
_emojiSelectionMenu = OverlayEntry(
|
||||
builder: (context) {
|
||||
return Positioned(
|
||||
top: alignment == Alignment.bottomLeft ? offset.dy : null,
|
||||
bottom: alignment == Alignment.topLeft ? offset.dy : null,
|
||||
left: offset.dx,
|
||||
child: Material(
|
||||
child: EmojiSelectionMenu(
|
||||
editorState: editorState,
|
||||
onSubmitted: (text) {
|
||||
// insert emoji
|
||||
editorState.insertEmoji(text);
|
||||
},
|
||||
onExit: () {
|
||||
_dismissEmojiSelectionMenu();
|
||||
//close emoji panel
|
||||
},
|
||||
),
|
||||
_emojiSelectionMenu = OverlayEntry(builder: (context) {
|
||||
return Positioned(
|
||||
top: alignment == Alignment.bottomLeft ? offset.dy : null,
|
||||
bottom: alignment == Alignment.topLeft ? offset.dy : null,
|
||||
left: offset.dx,
|
||||
child: Material(
|
||||
child: EmojiSelectionMenu(
|
||||
editorState: editorState,
|
||||
onSubmitted: (text) {
|
||||
// insert emoji
|
||||
editorState.insertEmoji(text);
|
||||
},
|
||||
onExit: () {
|
||||
_dismissEmojiSelectionMenu();
|
||||
//close emoji panel
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
),
|
||||
);
|
||||
},);
|
||||
|
||||
Overlay.of(context).insert(_emojiSelectionMenu!);
|
||||
|
||||
_editorState = editorState;
|
||||
editorState.service.selectionService.currentSelection
|
||||
.addListener(_dismissEmojiSelectionMenu);
|
||||
}
|
||||
@@ -65,7 +62,6 @@ void _dismissEmojiSelectionMenu() {
|
||||
|
||||
_editorState?.service.selectionService.currentSelection
|
||||
.removeListener(_dismissEmojiSelectionMenu);
|
||||
_editorState?.service.keyboardService?.enable();
|
||||
_editorState = null;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -270,7 +270,7 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
||||
widget.state.onEmojiSelected(categoryEmoji.category, emoji);
|
||||
},
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
fit: BoxFit.fill,
|
||||
child: Text(
|
||||
emoji.emoji,
|
||||
textScaleFactor: 1.0,
|
||||
|
||||
-1
@@ -50,7 +50,6 @@ abstract class OpenAIRepository {
|
||||
String? suffix,
|
||||
int maxTokens = 2048,
|
||||
double temperature = 0.3,
|
||||
bool useAction = false,
|
||||
});
|
||||
|
||||
/// Get edits from GPT-3
|
||||
|
||||
+1
-10
@@ -5,8 +5,7 @@ import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
enum SmartEditAction {
|
||||
summarize,
|
||||
fixSpelling,
|
||||
improveWriting;
|
||||
fixSpelling;
|
||||
|
||||
String get toInstruction {
|
||||
switch (this) {
|
||||
@@ -14,8 +13,6 @@ enum SmartEditAction {
|
||||
return 'Tl;dr';
|
||||
case SmartEditAction.fixSpelling:
|
||||
return 'Correct this to standard English:';
|
||||
case SmartEditAction.improveWriting:
|
||||
return 'Rewrite this in your own words:';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +22,6 @@ enum SmartEditAction {
|
||||
return '$input\n\nTl;dr';
|
||||
case SmartEditAction.fixSpelling:
|
||||
return 'Correct this to standard English:\n\n$input';
|
||||
case SmartEditAction.improveWriting:
|
||||
return 'Rewrite this:\n\n$input';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +31,6 @@ enum SmartEditAction {
|
||||
return SmartEditAction.summarize;
|
||||
case 1:
|
||||
return SmartEditAction.fixSpelling;
|
||||
case 2:
|
||||
return SmartEditAction.improveWriting;
|
||||
}
|
||||
return SmartEditAction.fixSpelling;
|
||||
}
|
||||
@@ -48,8 +41,6 @@ enum SmartEditAction {
|
||||
return LocaleKeys.document_plugins_smartEditSummarize.tr();
|
||||
case SmartEditAction.fixSpelling:
|
||||
return LocaleKeys.document_plugins_smartEditFixSpelling.tr();
|
||||
case SmartEditAction.improveWriting:
|
||||
return LocaleKeys.document_plugins_smartEditImproveWriting.tr();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+51
-71
@@ -4,7 +4,7 @@ import 'package:appflowy/plugins/document/presentation/plugins/openai/service/op
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/util/learn_more_action.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/discard_dialog.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/widgets/smart_edit_action.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy_editor/appflowy_editor.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
@@ -242,7 +242,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
),
|
||||
onPressed: () async {
|
||||
await _onReplace();
|
||||
await _onExit();
|
||||
_onExit();
|
||||
},
|
||||
),
|
||||
const Space(10, 0),
|
||||
@@ -257,7 +257,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
),
|
||||
onPressed: () async {
|
||||
await _onInsertBelow();
|
||||
await _onExit();
|
||||
_onExit();
|
||||
},
|
||||
),
|
||||
const Space(10, 0),
|
||||
@@ -272,13 +272,10 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
),
|
||||
onPressed: () async => await _onExit(),
|
||||
),
|
||||
const Spacer(flex: 2),
|
||||
Expanded(
|
||||
child: FlowyText.regular(
|
||||
overflow: TextOverflow.ellipsis,
|
||||
LocaleKeys.document_plugins_warning.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
const Spacer(),
|
||||
FlowyText.regular(
|
||||
LocaleKeys.document_plugins_warning.tr(),
|
||||
color: Theme.of(context).hintColor,
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -301,22 +298,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
selection,
|
||||
texts,
|
||||
);
|
||||
await widget.editorState.apply(transaction);
|
||||
|
||||
int endOffset = texts.last.length;
|
||||
if (texts.length == 1) {
|
||||
endOffset += selection.start.offset;
|
||||
}
|
||||
|
||||
await widget.editorState.updateCursorSelection(
|
||||
Selection(
|
||||
start: selection.start,
|
||||
end: Position(
|
||||
path: [selection.start.path.first + texts.length - 1],
|
||||
offset: endOffset,
|
||||
),
|
||||
),
|
||||
);
|
||||
return widget.editorState.apply(transaction);
|
||||
}
|
||||
|
||||
Future<void> _onInsertBelow() async {
|
||||
@@ -335,16 +317,7 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
),
|
||||
),
|
||||
);
|
||||
await widget.editorState.apply(transaction);
|
||||
|
||||
await widget.editorState.updateCursorSelection(
|
||||
Selection(
|
||||
start: Position(path: selection.end.path.next, offset: 0),
|
||||
end: Position(
|
||||
path: [selection.end.path.next.first + texts.length],
|
||||
),
|
||||
),
|
||||
);
|
||||
return widget.editorState.apply(transaction);
|
||||
}
|
||||
|
||||
Future<void> _onExit() async {
|
||||
@@ -360,42 +333,49 @@ class _SmartEditInputState extends State<_SmartEditInput> {
|
||||
}
|
||||
|
||||
Future<void> _requestCompletions() async {
|
||||
final openAIRepository = await getIt.getAsync<OpenAIRepository>();
|
||||
|
||||
var lines = input.split('\n\n');
|
||||
if (action == SmartEditAction.summarize) {
|
||||
lines = [lines.join('\n')];
|
||||
}
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
final element = lines[i];
|
||||
await openAIRepository.getStreamedCompletions(
|
||||
useAction: true,
|
||||
prompt: action.prompt(element),
|
||||
onStart: () async {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
},
|
||||
onProcess: (response) async {
|
||||
setState(() {
|
||||
if (response.choices.first.text != '\n') {
|
||||
result += response.choices.first.text;
|
||||
}
|
||||
});
|
||||
},
|
||||
onEnd: () async {
|
||||
setState(() {
|
||||
if (i != lines.length - 1) {
|
||||
result += '\n';
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: (error) async {
|
||||
await _showError(error.message);
|
||||
await _onExit();
|
||||
},
|
||||
final result = await UserBackendService.getCurrentUserProfile();
|
||||
return result.fold((l) async {
|
||||
final openAIRepository = HttpOpenAIRepository(
|
||||
client: client,
|
||||
apiKey: l.openaiKey,
|
||||
);
|
||||
}
|
||||
|
||||
var lines = input.split('\n\n');
|
||||
if (action == SmartEditAction.summarize) {
|
||||
lines = [lines.join('\n')];
|
||||
}
|
||||
for (var i = 0; i < lines.length; i++) {
|
||||
final element = lines[i];
|
||||
await openAIRepository.getStreamedCompletions(
|
||||
useAction: true,
|
||||
prompt: action.prompt(element),
|
||||
onStart: () async {
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
},
|
||||
onProcess: (response) async {
|
||||
setState(() {
|
||||
this.result += response.choices.first.text;
|
||||
});
|
||||
},
|
||||
onEnd: () async {
|
||||
setState(() {
|
||||
if (i != lines.length - 1) {
|
||||
this.result += '\n';
|
||||
}
|
||||
});
|
||||
},
|
||||
onError: (error) async {
|
||||
await _showError(error.message);
|
||||
await _onExit();
|
||||
},
|
||||
);
|
||||
}
|
||||
}, (r) async {
|
||||
await _showError(r.msg);
|
||||
await _onExit();
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _showError(String message) async {
|
||||
|
||||
@@ -4,14 +4,10 @@ class TrashSizes {
|
||||
static double get fileNameWidth => 320 * scale;
|
||||
static double get lashModifyWidth => 230 * scale;
|
||||
static double get createTimeWidth => 230 * scale;
|
||||
// padding between createTime and action icon
|
||||
static double get padding => 40 * scale;
|
||||
static double get actionIconWidth => 40 * scale;
|
||||
static double get padding => 100 * scale;
|
||||
static double get totalWidth =>
|
||||
TrashSizes.fileNameWidth +
|
||||
TrashSizes.lashModifyWidth +
|
||||
TrashSizes.createTimeWidth +
|
||||
TrashSizes.padding +
|
||||
// restore and delete icon
|
||||
2 * TrashSizes.actionIconWidth;
|
||||
TrashSizes.padding;
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/icon_button.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-folder/trash.pb.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
@@ -36,19 +38,23 @@ class TrashCell extends StatelessWidget {
|
||||
),
|
||||
const Spacer(),
|
||||
FlowyIconButton(
|
||||
iconColorOnHover: Theme.of(context).colorScheme.onSurface,
|
||||
width: TrashSizes.actionIconWidth,
|
||||
width: 26,
|
||||
onPressed: onRestore,
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
icon: const FlowySvg(name: 'editor/restore'),
|
||||
icon: svgWidget(
|
||||
"editor/restore",
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
const HSpace(20),
|
||||
FlowyIconButton(
|
||||
iconColorOnHover: Theme.of(context).colorScheme.onSurface,
|
||||
width: TrashSizes.actionIconWidth,
|
||||
width: 26,
|
||||
onPressed: onDelete,
|
||||
iconPadding: const EdgeInsets.all(5),
|
||||
icon: const FlowySvg(name: 'editor/delete'),
|
||||
icon: svgWidget(
|
||||
"editor/delete",
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@@ -96,7 +96,10 @@ class _TrashPageState extends State<TrashPage> {
|
||||
IntrinsicWidth(
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.trash_restoreAll.tr()),
|
||||
leftIcon: const FlowySvg(name: 'editor/restore'),
|
||||
leftIcon: svgWidget(
|
||||
'editor/restore',
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
onTap: () => context.read<TrashBloc>().add(
|
||||
const TrashEvent.restoreAll(),
|
||||
),
|
||||
@@ -106,7 +109,10 @@ class _TrashPageState extends State<TrashPage> {
|
||||
IntrinsicWidth(
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(LocaleKeys.trash_deleteAll.tr()),
|
||||
leftIcon: const FlowySvg(name: 'editor/delete'),
|
||||
leftIcon: svgWidget(
|
||||
'editor/delete',
|
||||
color: Theme.of(context).iconTheme.color,
|
||||
),
|
||||
onTap: () =>
|
||||
context.read<TrashBloc>().add(const TrashEvent.deleteAll()),
|
||||
),
|
||||
|
||||
@@ -4,7 +4,6 @@ import 'package:appflowy/plugins/database_view/application/field/field_controlle
|
||||
import 'package:appflowy/plugins/database_view/application/field/field_service.dart';
|
||||
import 'package:appflowy/plugins/database_view/application/setting/property_bloc.dart';
|
||||
import 'package:appflowy/plugins/database_view/grid/application/grid_header_bloc.dart';
|
||||
import 'package:appflowy/plugins/document/presentation/plugins/openai/service/openai_client.dart';
|
||||
import 'package:appflowy/user/application/user_listener.dart';
|
||||
import 'package:appflowy/user/application/user_service.dart';
|
||||
import 'package:appflowy/util/file_picker/file_picker_impl.dart';
|
||||
@@ -28,7 +27,6 @@ import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
|
||||
class DependencyResolver {
|
||||
static Future<void> resolve(GetIt getIt) async {
|
||||
@@ -46,25 +44,8 @@ class DependencyResolver {
|
||||
}
|
||||
}
|
||||
|
||||
void _resolveCommonService(GetIt getIt) async {
|
||||
void _resolveCommonService(GetIt getIt) {
|
||||
getIt.registerFactory<FilePickerService>(() => FilePicker());
|
||||
|
||||
getIt.registerFactoryAsync<OpenAIRepository>(
|
||||
() async {
|
||||
final result = await UserBackendService.getCurrentUserProfile();
|
||||
return result.fold(
|
||||
(l) {
|
||||
return HttpOpenAIRepository(
|
||||
client: http.Client(),
|
||||
apiKey: l.openaiKey,
|
||||
);
|
||||
},
|
||||
(r) {
|
||||
throw Exception('Failed to get user profile: ${r.msg}');
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
void _resolveUserDeps(GetIt getIt) {
|
||||
@@ -179,4 +160,4 @@ void _resolveGridDeps(GetIt getIt) {
|
||||
(viewId, cache) =>
|
||||
DatabasePropertyBloc(viewId: viewId, fieldController: cache),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,6 @@ class InitAppWidgetTask extends LaunchTask {
|
||||
EasyLocalization(
|
||||
supportedLocales: const [
|
||||
// In alphabetical order
|
||||
Locale('ar', 'AR'),
|
||||
Locale('ca', 'ES'),
|
||||
Locale('de', 'DE'),
|
||||
Locale('en'),
|
||||
|
||||
@@ -242,15 +242,8 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
shadow: theme.shadow,
|
||||
);
|
||||
|
||||
const Set<MaterialState> scrollbarInteractiveStates = <MaterialState>{
|
||||
MaterialState.pressed,
|
||||
MaterialState.hovered,
|
||||
MaterialState.dragged,
|
||||
};
|
||||
|
||||
return ThemeData(
|
||||
brightness: brightness,
|
||||
dialogBackgroundColor: theme.surface,
|
||||
textTheme: _getTextTheme(fontFamily: fontFamily, fontColor: theme.text),
|
||||
textSelectionTheme: TextSelectionThemeData(
|
||||
cursorColor: theme.main2,
|
||||
@@ -271,20 +264,20 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
contentTextStyle: TextStyle(color: colorScheme.onSurface),
|
||||
),
|
||||
scrollbarTheme: ScrollbarThemeData(
|
||||
thumbColor: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.any(scrollbarInteractiveStates.contains)) {
|
||||
return theme.shader7;
|
||||
}
|
||||
return theme.shader5;
|
||||
}),
|
||||
thumbColor: MaterialStateProperty.all(theme.shader3),
|
||||
thickness: MaterialStateProperty.resolveWith((states) {
|
||||
if (states.any(scrollbarInteractiveStates.contains)) {
|
||||
return 4;
|
||||
const Set<MaterialState> interactiveStates = <MaterialState>{
|
||||
MaterialState.pressed,
|
||||
MaterialState.hovered,
|
||||
MaterialState.dragged,
|
||||
};
|
||||
if (states.any(interactiveStates.contains)) {
|
||||
return 5.0;
|
||||
}
|
||||
return 3.0;
|
||||
}),
|
||||
crossAxisMargin: 0.0,
|
||||
mainAxisMargin: 6.0,
|
||||
mainAxisMargin: 0.0,
|
||||
radius: Corners.s10Radius,
|
||||
),
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
@@ -317,8 +310,7 @@ class AppearanceSettingsState with _$AppearanceSettingsState {
|
||||
greySelect: theme.bg3,
|
||||
lightGreyHover: theme.hoverBG3,
|
||||
toggleOffFill: theme.shader5,
|
||||
progressBarBGColor: theme.progressBarBGColor,
|
||||
toggleButtonBGColor: theme.toggleButtonBGColor,
|
||||
progressBarBGcolor: theme.progressBarBGcolor,
|
||||
code: _getFontStyle(
|
||||
fontFamily: monospaceFontFamily,
|
||||
fontColor: theme.shader3,
|
||||
|
||||
-2
@@ -25,8 +25,6 @@ class SettingsLocation {
|
||||
if (Platform.isMacOS) {
|
||||
// remove the prefix `/Volumes/*`
|
||||
return _path?.replaceFirst(RegExp(r'^/Volumes/[^/]+'), '');
|
||||
} else if (Platform.isWindows) {
|
||||
return _path?.replaceAll("/", "\\");
|
||||
}
|
||||
return _path;
|
||||
}
|
||||
|
||||
-4
@@ -61,10 +61,6 @@ class ViewSection extends StatelessWidget {
|
||||
.add(ViewSectionEvent.moveView(oldIndex, index));
|
||||
},
|
||||
ignorePrimaryScrollController: true,
|
||||
buildDraggableFeedback: (context, constraints, child) => ConstrainedBox(
|
||||
constraints: constraints,
|
||||
child: Material(color: Colors.transparent, child: child),
|
||||
),
|
||||
children: children,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -149,8 +149,6 @@ class HomeMenu extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
},
|
||||
proxyDecorator: (child, index, animation) =>
|
||||
Material(color: Colors.transparent, child: child),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
+4
-4
@@ -77,8 +77,8 @@ class ThemeSetting extends StatelessWidget {
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(theme),
|
||||
rightIcon: currentTheme == theme
|
||||
? const FlowySvg(name: 'grid/checkmark')
|
||||
: null,
|
||||
? svgWidget("grid/checkmark")
|
||||
: const SizedBox(),
|
||||
onTap: () {
|
||||
if (currentTheme != theme) {
|
||||
context.read<AppearanceSettingsCubit>().setTheme(theme);
|
||||
@@ -134,8 +134,8 @@ class ThemeModeSetting extends StatelessWidget {
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(_themeModeLabelText(themeMode)),
|
||||
rightIcon: currentThemeMode == themeMode
|
||||
? const FlowySvg(name: 'grid/checkmark')
|
||||
: null,
|
||||
? svgWidget("grid/checkmark")
|
||||
: const SizedBox(),
|
||||
onTap: () {
|
||||
if (currentThemeMode != themeMode) {
|
||||
context.read<AppearanceSettingsCubit>().setThemeMode(themeMode);
|
||||
|
||||
+3
-9
@@ -35,8 +35,9 @@ class SettingsFileLocationCustomzierState
|
||||
child: BlocBuilder<SettingsLocationCubit, SettingsLocation>(
|
||||
builder: (context, state) {
|
||||
return ListTile(
|
||||
title: FlowyText.medium(
|
||||
title: FlowyText.regular(
|
||||
LocaleKeys.settings_files_defaultLocation.tr(),
|
||||
fontSize: 15.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Tooltip(
|
||||
@@ -62,6 +63,7 @@ class SettingsFileLocationCustomzierState
|
||||
},
|
||||
child: FlowyText.regular(
|
||||
state.path ?? '',
|
||||
fontSize: 10.0,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
@@ -72,11 +74,7 @@ class SettingsFileLocationCustomzierState
|
||||
Tooltip(
|
||||
message: LocaleKeys.settings_files_restoreLocation.tr(),
|
||||
child: FlowyIconButton(
|
||||
height: 40,
|
||||
width: 40,
|
||||
icon: const Icon(Icons.restore_outlined),
|
||||
hoverColor:
|
||||
Theme.of(context).colorScheme.secondaryContainer,
|
||||
onPressed: () async {
|
||||
final result = await appFlowyDocumentDirectory();
|
||||
await _setCustomLocation(result.path);
|
||||
@@ -98,11 +96,7 @@ class SettingsFileLocationCustomzierState
|
||||
Tooltip(
|
||||
message: LocaleKeys.settings_files_customizeLocation.tr(),
|
||||
child: FlowyIconButton(
|
||||
height: 40,
|
||||
width: 40,
|
||||
icon: const Icon(Icons.folder_open_outlined),
|
||||
hoverColor:
|
||||
Theme.of(context).colorScheme.secondaryContainer,
|
||||
onPressed: () async {
|
||||
final result =
|
||||
await getIt<FilePickerService>().getDirectoryPath();
|
||||
|
||||
+78
-96
@@ -1,9 +1,7 @@
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/workspace/application/appearance.dart';
|
||||
import 'package:appflowy_popover/appflowy_popover.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flowy_infra/language.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
@@ -14,102 +12,86 @@ class SettingsLanguageView extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SingleChildScrollView(
|
||||
child: BlocBuilder<AppearanceSettingsCubit, AppearanceSettingsState>(
|
||||
builder: (context, state) => Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: FlowyText.medium(
|
||||
LocaleKeys.settings_menu_language.tr(),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
children: [
|
||||
FlowyText.medium(LocaleKeys.settings_menu_language.tr()),
|
||||
const LanguageSelectorDropdown(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageSelectorDropdown extends StatefulWidget {
|
||||
const LanguageSelectorDropdown({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<LanguageSelectorDropdown> createState() =>
|
||||
_LanguageSelectorDropdownState();
|
||||
}
|
||||
|
||||
class _LanguageSelectorDropdownState extends State<LanguageSelectorDropdown> {
|
||||
Color currHoverColor = Colors.white.withOpacity(0.0);
|
||||
void hoverExitLanguage() {
|
||||
setState(() {
|
||||
currHoverColor = Colors.white.withOpacity(0.0);
|
||||
});
|
||||
}
|
||||
|
||||
void hoverEnterLanguage() {
|
||||
setState(() {
|
||||
currHoverColor = Theme.of(context).colorScheme.secondaryContainer;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MouseRegion(
|
||||
onEnter: (_) => hoverEnterLanguage(),
|
||||
onExit: (_) => hoverExitLanguage(),
|
||||
child: Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: currHoverColor,
|
||||
),
|
||||
child: DropdownButtonHideUnderline(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6),
|
||||
child: DropdownButton<Locale>(
|
||||
value: context.locale,
|
||||
dropdownColor: Theme.of(context).cardColor,
|
||||
onChanged: (locale) {
|
||||
context
|
||||
.read<AppearanceSettingsCubit>()
|
||||
.setLocale(context, locale!);
|
||||
},
|
||||
autofocus: true,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
items:
|
||||
EasyLocalization.of(context)!.supportedLocales.map((locale) {
|
||||
return DropdownMenuItem<Locale>(
|
||||
value: locale,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: FlowyText.medium(
|
||||
languageFromLocale(locale),
|
||||
color: Theme.of(context).colorScheme.tertiary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
LanguageSelector(currentLocale: state.locale),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageSelector extends StatelessWidget {
|
||||
final Locale currentLocale;
|
||||
const LanguageSelector({
|
||||
super.key,
|
||||
required this.currentLocale,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return AppFlowyPopover(
|
||||
direction: PopoverDirection.bottomWithRightAligned,
|
||||
child: FlowyTextButton(
|
||||
languageFromLocale(currentLocale),
|
||||
fontColor: Theme.of(context).colorScheme.onBackground,
|
||||
fillColor: Colors.transparent,
|
||||
onPressed: () {},
|
||||
),
|
||||
popupBuilder: (BuildContext context) {
|
||||
final allLocales = EasyLocalization.of(context)!.supportedLocales;
|
||||
|
||||
return LanguageItemsListView(
|
||||
allLocales: allLocales,
|
||||
currentLocale: currentLocale,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageItemsListView extends StatelessWidget {
|
||||
const LanguageItemsListView({
|
||||
super.key,
|
||||
required this.allLocales,
|
||||
required this.currentLocale,
|
||||
});
|
||||
|
||||
final List<Locale> allLocales;
|
||||
final Locale currentLocale;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxHeight: 400),
|
||||
child: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final locale = allLocales[index];
|
||||
return LanguageItem(locale: locale, currentLocale: currentLocale);
|
||||
},
|
||||
itemCount: allLocales.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class LanguageItem extends StatelessWidget {
|
||||
final Locale locale;
|
||||
final Locale currentLocale;
|
||||
const LanguageItem({
|
||||
super.key,
|
||||
required this.locale,
|
||||
required this.currentLocale,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
height: 32,
|
||||
child: FlowyButton(
|
||||
text: FlowyText.medium(
|
||||
languageFromLocale(locale),
|
||||
),
|
||||
rightIcon: currentLocale == locale
|
||||
? const FlowySvg(name: 'grid/checkmark')
|
||||
: null,
|
||||
onTap: () {
|
||||
if (currentLocale != locale) {
|
||||
context.read<AppearanceSettingsCubit>().setLocale(context, locale);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+70
-120
@@ -1,20 +1,19 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:appflowy/startup/startup.dart';
|
||||
import 'package:appflowy/util/debounce.dart';
|
||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/flowy_infra_ui.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/text.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:appflowy/workspace/application/user/settings_user_bloc.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flowy_infra_ui/widget/spacing.dart';
|
||||
import 'package:appflowy_backend/protobuf/flowy-user/user_profile.pb.dart';
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:appflowy/generated/locale_keys.g.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
const defaultUserAvatar = '1F600';
|
||||
const _iconSize = Size(60, 60);
|
||||
|
||||
class SettingsUserView extends StatelessWidget {
|
||||
final UserProfilePB user;
|
||||
@@ -64,67 +63,27 @@ class SettingsUserView extends StatelessWidget {
|
||||
}
|
||||
|
||||
@visibleForTesting
|
||||
class UserNameInput extends StatefulWidget {
|
||||
class UserNameInput extends StatelessWidget {
|
||||
final String name;
|
||||
|
||||
const UserNameInput(
|
||||
this.name, {
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
UserNameInputState createState() => UserNameInputState();
|
||||
}
|
||||
|
||||
class UserNameInputState extends State<UserNameInput> {
|
||||
late TextEditingController _controller;
|
||||
|
||||
Timer? _debounce;
|
||||
final Duration _debounceDuration = const Duration(milliseconds: 500);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller = TextEditingController(text: widget.name);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextField(
|
||||
controller: _controller,
|
||||
controller: TextEditingController()..text = name,
|
||||
decoration: InputDecoration(
|
||||
labelText: LocaleKeys.settings_user_name.tr(),
|
||||
labelStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.w500),
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.onBackground),
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
),
|
||||
onChanged: (val) {
|
||||
if (_debounce?.isActive ?? false) {
|
||||
_debounce!.cancel();
|
||||
}
|
||||
|
||||
_debounce = Timer(_debounceDuration, () {
|
||||
context
|
||||
.read<SettingsUserViewBloc>()
|
||||
.add(SettingsUserEvent.updateUserName(val));
|
||||
});
|
||||
onSubmitted: (val) {
|
||||
context
|
||||
.read<SettingsUserViewBloc>()
|
||||
.add(SettingsUserEvent.updateUserName(val));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class _OpenaiKeyInput extends StatefulWidget {
|
||||
@@ -156,26 +115,14 @@ class _OpenaiKeyInputState extends State<_OpenaiKeyInput> {
|
||||
controller: textEditingController,
|
||||
obscureText: !visible,
|
||||
decoration: InputDecoration(
|
||||
enabledBorder: UnderlineInputBorder(
|
||||
borderSide:
|
||||
BorderSide(color: Theme.of(context).colorScheme.onBackground),
|
||||
),
|
||||
focusedBorder: UnderlineInputBorder(
|
||||
borderSide: BorderSide(color: Theme.of(context).colorScheme.primary),
|
||||
),
|
||||
labelText: 'OpenAI Key',
|
||||
labelStyle: Theme.of(context)
|
||||
.textTheme
|
||||
.titleMedium!
|
||||
.copyWith(fontWeight: FontWeight.w500),
|
||||
hintText: LocaleKeys.settings_user_pleaseInputYourOpenAIKey.tr(),
|
||||
suffixIcon: FlowyIconButton(
|
||||
width: 40,
|
||||
height: 40,
|
||||
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
icon: Icon(
|
||||
visible ? Icons.visibility : Icons.visibility_off,
|
||||
),
|
||||
suffixIcon: IconButton(
|
||||
iconSize: 15.0,
|
||||
icon: Icon(visible ? Icons.visibility : Icons.visibility_off),
|
||||
padding: EdgeInsets.zero,
|
||||
hoverColor: Colors.transparent,
|
||||
splashColor: Colors.transparent,
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
visible = !visible;
|
||||
@@ -213,48 +160,50 @@ class _CurrentIcon extends StatelessWidget {
|
||||
Navigator.of(context).pop();
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
LocaleKeys.settings_user_icon.tr(),
|
||||
style: Theme.of(context).textTheme.titleSmall!.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
fontSize: 13,
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return SimpleDialog(
|
||||
title: FlowyText.medium(
|
||||
LocaleKeys.settings_user_selectAnIcon.tr(),
|
||||
fontSize: FontSizes.s16,
|
||||
),
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 300,
|
||||
width: 300,
|
||||
child: IconGallery(setIcon),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: Text(
|
||||
LocaleKeys.settings_user_icon.tr(),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
),
|
||||
InkWell(
|
||||
borderRadius: Corners.s6Border,
|
||||
hoverColor: Theme.of(context).colorScheme.secondaryContainer,
|
||||
onTap: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (BuildContext context) {
|
||||
return SimpleDialog(
|
||||
title: FlowyText.medium(
|
||||
LocaleKeys.settings_user_selectAnIcon.tr(),
|
||||
fontSize: FontSizes.s16,
|
||||
),
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
height: 300,
|
||||
width: 300,
|
||||
child: IconGallery(setIcon),
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
child: Container(
|
||||
margin: const EdgeInsets.fromLTRB(0, 5, 5, 5),
|
||||
child: svgWidget(
|
||||
'emoji/$iconUrl',
|
||||
size: _iconSize,
|
||||
),
|
||||
),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Container(
|
||||
margin: const EdgeInsets.all(5.0),
|
||||
decoration:
|
||||
BoxDecoration(border: Border.all(color: Colors.grey)),
|
||||
child: svgWidget('emoji/$iconUrl', size: const Size(60, 60)),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -312,13 +261,14 @@ class IconOption extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return InkWell(
|
||||
borderRadius: Corners.s6Border,
|
||||
hoverColor: Theme.of(context).colorScheme.tertiaryContainer,
|
||||
onTap: () {
|
||||
setIcon(iconUrl);
|
||||
},
|
||||
child: svgWidget('emoji/$iconUrl', size: _iconSize),
|
||||
return Material(
|
||||
color: Colors.transparent,
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
setIcon(iconUrl);
|
||||
},
|
||||
child: svgWidget('emoji/$iconUrl'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -295,7 +295,7 @@ class DefaultEmojiPickerViewState extends State<DefaultEmojiPickerView>
|
||||
widget.state.onEmojiSelected(categoryEmoji.category, emoji);
|
||||
},
|
||||
child: FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
fit: BoxFit.fill,
|
||||
child: Text(
|
||||
emoji.emoji,
|
||||
textScaleFactor: 1.0,
|
||||
|
||||
@@ -73,10 +73,9 @@ abstract class FlowyColorScheme {
|
||||
//the text color when it is hovered
|
||||
final Color hoverFG;
|
||||
final Color questionBubbleBG;
|
||||
final Color progressBarBGColor;
|
||||
final Color progressBarBGcolor;
|
||||
//editor toolbar BG color
|
||||
final Color toolbarColor;
|
||||
final Color toggleButtonBGColor;
|
||||
|
||||
const FlowyColorScheme({
|
||||
required this.surface,
|
||||
@@ -122,9 +121,8 @@ abstract class FlowyColorScheme {
|
||||
required this.hoverBG3,
|
||||
required this.hoverFG,
|
||||
required this.questionBubbleBG,
|
||||
required this.progressBarBGColor,
|
||||
required this.progressBarBGcolor,
|
||||
required this.toolbarColor,
|
||||
required this.toggleButtonBGColor,
|
||||
});
|
||||
|
||||
factory FlowyColorScheme.builtIn(String themeName, Brightness brightness) {
|
||||
|
||||
@@ -10,7 +10,6 @@ const _lightBg1 = Color(0xFFFFD13E);
|
||||
const _lightBg2 = Color(0xffedeef2);
|
||||
const _lightShader1 = Color(0xff333333);
|
||||
const _lightShader3 = Color(0xff828282);
|
||||
const _lightShader5 = Color(0xffe0e0e0);
|
||||
const _lightShader6 = Color(0xfff2f2f2);
|
||||
const _lightMain1 = Color(0xffe21f74);
|
||||
const _lightTint9 = Color(0xffe1fbff);
|
||||
@@ -36,7 +35,7 @@ class DandelionColorScheme extends FlowyColorScheme {
|
||||
shader2: const Color(0xff4f4f4f),
|
||||
shader3: const Color(0xff828282),
|
||||
shader4: const Color(0xffbdbdbd),
|
||||
shader5: _lightShader5,
|
||||
shader5: const Color(0xffe0e0e0),
|
||||
shader6: const Color(0xfff2f2f2),
|
||||
shader7: _black,
|
||||
bg1: _lightBg1,
|
||||
@@ -69,58 +68,55 @@ class DandelionColorScheme extends FlowyColorScheme {
|
||||
hoverBG3: _lightShader6,
|
||||
hoverFG: _lightShader1,
|
||||
questionBubbleBG: _lightSelector,
|
||||
progressBarBGColor: _lightTint9,
|
||||
progressBarBGcolor: _lightTint9,
|
||||
toolbarColor: _lightShader1,
|
||||
toggleButtonBGColor: _lightShader5,
|
||||
);
|
||||
|
||||
const DandelionColorScheme.dark()
|
||||
: super(
|
||||
surface: const Color(0xff292929),
|
||||
hover: const Color(0xff1f1f1f),
|
||||
selector: const Color(0xff333333),
|
||||
red: const Color(0xfffb006d),
|
||||
yellow: const Color(0xffffd667),
|
||||
green: const Color(0xff66cf80),
|
||||
shader1: _white,
|
||||
shader2: _darkShader2,
|
||||
shader3: const Color(0xff828282),
|
||||
shader4: const Color(0xffbdbdbd),
|
||||
shader5: _darkShader5,
|
||||
shader6: _darkShader6,
|
||||
shader7: _white,
|
||||
bg1: const Color(0xFFD5A200),
|
||||
bg2: _black,
|
||||
bg3: const Color(0xff4f4f4f),
|
||||
bg4: const Color(0xff2c144b),
|
||||
tint1: const Color(0xff8738F5),
|
||||
tint2: const Color(0xffE6336E),
|
||||
tint3: const Color(0xffFF2D9E),
|
||||
tint4: const Color(0xffE9973E),
|
||||
tint5: const Color(0xffFBF000),
|
||||
tint6: const Color(0xffC0F000),
|
||||
tint7: const Color(0xff15F74E),
|
||||
tint8: const Color(0xff00F0E2),
|
||||
tint9: const Color(0xff00BCF0),
|
||||
main1: _darkMain1,
|
||||
main2: const Color(0xffe0196f),
|
||||
shadow: _black,
|
||||
sidebarBg: const Color(0xff232B38),
|
||||
divider: _darkShader3,
|
||||
topbarBg: _darkShader1,
|
||||
icon: _darkShader5,
|
||||
text: _darkShader5,
|
||||
input: _darkInput,
|
||||
hint: _darkShader5,
|
||||
primary: _darkMain1,
|
||||
onPrimary: _darkShader1,
|
||||
hoverBG1: _darkMain1,
|
||||
hoverBG2: _darkMain1,
|
||||
hoverBG3: _darkShader3,
|
||||
hoverFG: _darkShader1,
|
||||
questionBubbleBG: _darkShader3,
|
||||
progressBarBGColor: _darkShader3,
|
||||
toolbarColor: _darkInput,
|
||||
toggleButtonBGColor: _darkShader1,
|
||||
);
|
||||
surface: const Color(0xff292929),
|
||||
hover: const Color(0xff1f1f1f),
|
||||
selector: const Color(0xff333333),
|
||||
red: const Color(0xfffb006d),
|
||||
yellow: const Color(0xffffd667),
|
||||
green: const Color(0xff66cf80),
|
||||
shader1: _white,
|
||||
shader2: _darkShader2,
|
||||
shader3: const Color(0xff828282),
|
||||
shader4: const Color(0xffbdbdbd),
|
||||
shader5: _darkShader5,
|
||||
shader6: _darkShader6,
|
||||
shader7: _white,
|
||||
bg1: const Color(0xFFD5A200),
|
||||
bg2: _black,
|
||||
bg3: const Color(0xff4f4f4f),
|
||||
bg4: const Color(0xff2c144b),
|
||||
tint1: const Color(0xff8738F5),
|
||||
tint2: const Color(0xffE6336E),
|
||||
tint3: const Color(0xffFF2D9E),
|
||||
tint4: const Color(0xffE9973E),
|
||||
tint5: const Color(0xffFBF000),
|
||||
tint6: const Color(0xffC0F000),
|
||||
tint7: const Color(0xff15F74E),
|
||||
tint8: const Color(0xff00F0E2),
|
||||
tint9: const Color(0xff00BCF0),
|
||||
main1: _darkMain1,
|
||||
main2: const Color(0xffe0196f),
|
||||
shadow: _black,
|
||||
sidebarBg: const Color(0xff232B38),
|
||||
divider: _darkShader3,
|
||||
topbarBg: _darkShader1,
|
||||
icon: _darkShader5,
|
||||
text: _darkShader5,
|
||||
input: _darkInput,
|
||||
hint: _darkShader5,
|
||||
primary: _darkMain1,
|
||||
onPrimary: _darkShader1,
|
||||
hoverBG1: _darkMain1,
|
||||
hoverBG2: _darkMain1,
|
||||
hoverBG3: _darkShader3,
|
||||
hoverFG: _darkShader1,
|
||||
questionBubbleBG: _darkShader3,
|
||||
progressBarBGcolor: _darkShader3,
|
||||
toolbarColor: _darkInput);
|
||||
}
|
||||
+3
-6
@@ -9,7 +9,6 @@ const _lightBg1 = Color(0xfff7f8fc);
|
||||
const _lightBg2 = Color(0xffedeef2);
|
||||
const _lightShader1 = Color(0xff333333);
|
||||
const _lightShader3 = Color(0xff828282);
|
||||
const _lightShader5 = Color(0xffe0e0e0);
|
||||
const _lightShader6 = Color(0xfff2f2f2);
|
||||
const _lightMain1 = Color(0xff00bcf0);
|
||||
const _lightTint9 = Color(0xffe1fbff);
|
||||
@@ -34,7 +33,7 @@ class DefaultColorScheme extends FlowyColorScheme {
|
||||
shader2: const Color(0xff4f4f4f),
|
||||
shader3: _lightShader3,
|
||||
shader4: const Color(0xffbdbdbd),
|
||||
shader5: _lightShader5,
|
||||
shader5: const Color(0xffe0e0e0),
|
||||
shader6: _lightShader6,
|
||||
shader7: _lightShader1,
|
||||
bg1: _lightBg1,
|
||||
@@ -67,9 +66,8 @@ class DefaultColorScheme extends FlowyColorScheme {
|
||||
hoverFG: _lightShader1,
|
||||
questionBubbleBG: _lightSelector,
|
||||
hoverBG3: _lightShader6,
|
||||
progressBarBGColor: _lightTint9,
|
||||
progressBarBGcolor: _lightTint9,
|
||||
toolbarColor: _lightShader1,
|
||||
toggleButtonBGColor: _lightShader5,
|
||||
);
|
||||
|
||||
const DefaultColorScheme.dark()
|
||||
@@ -117,8 +115,7 @@ class DefaultColorScheme extends FlowyColorScheme {
|
||||
hoverBG3: _darkShader3,
|
||||
hoverFG: _darkShader1,
|
||||
questionBubbleBG: _darkShader3,
|
||||
progressBarBGColor: _darkShader3,
|
||||
progressBarBGcolor: _darkShader3,
|
||||
toolbarColor: _darkInput,
|
||||
toggleButtonBGColor: _darkShader1,
|
||||
);
|
||||
}
|
||||
@@ -11,7 +11,6 @@ const _lightBg1 = Color(0xfff7f8fc);
|
||||
const _lightBg2 = Color(0xffedeef2);
|
||||
const _lightShader1 = Color(0xff333333);
|
||||
const _lightShader3 = Color(0xff828282);
|
||||
const _lightShader5 = Color(0xffe0e0e0);
|
||||
const _lightShader6 = Color(0xfff2f2f2);
|
||||
const _lightMain1 = Color(0xffA652FB);
|
||||
const _lightTint9 = Color(0xffe1fbff);
|
||||
@@ -37,7 +36,7 @@ class LavenderColorScheme extends FlowyColorScheme {
|
||||
shader2: const Color(0xff4f4f4f),
|
||||
shader3: const Color(0xff828282),
|
||||
shader4: const Color(0xffbdbdbd),
|
||||
shader5: _lightShader5,
|
||||
shader5: const Color(0xffe0e0e0),
|
||||
shader6: const Color(0xfff2f2f2),
|
||||
shader7: _black,
|
||||
bg1: const Color(0xffAC59FF),
|
||||
@@ -70,9 +69,8 @@ class LavenderColorScheme extends FlowyColorScheme {
|
||||
hoverBG3: _lightShader6,
|
||||
hoverFG: _lightShader1,
|
||||
questionBubbleBG: _lightSelector,
|
||||
progressBarBGColor: _lightTint9,
|
||||
progressBarBGcolor: _lightTint9,
|
||||
toolbarColor: _lightShader1,
|
||||
toggleButtonBGColor: _lightShader5,
|
||||
);
|
||||
|
||||
const LavenderColorScheme.dark()
|
||||
@@ -120,8 +118,7 @@ class LavenderColorScheme extends FlowyColorScheme {
|
||||
hoverBG3: _darkShader3,
|
||||
hoverFG: _darkShader1,
|
||||
questionBubbleBG: _darkShader3,
|
||||
progressBarBGColor: _darkShader3,
|
||||
progressBarBGcolor: _darkShader3,
|
||||
toolbarColor: _darkInput,
|
||||
toggleButtonBGColor: _darkShader1,
|
||||
);
|
||||
}
|
||||
@@ -16,8 +16,6 @@ String languageFromLocale(Locale locale) {
|
||||
}
|
||||
|
||||
// Then in alphabetical order
|
||||
case "ar":
|
||||
return "العربية";
|
||||
case "ca":
|
||||
return "Català";
|
||||
case "de":
|
||||
|
||||
@@ -20,8 +20,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
final Color greySelect;
|
||||
final Color lightGreyHover;
|
||||
final Color toggleOffFill;
|
||||
final Color progressBarBGColor;
|
||||
final Color toggleButtonBGColor;
|
||||
final Color progressBarBGcolor;
|
||||
|
||||
final TextStyle code;
|
||||
final TextStyle callout;
|
||||
@@ -47,8 +46,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
required this.code,
|
||||
required this.callout,
|
||||
required this.caption,
|
||||
required this.progressBarBGColor,
|
||||
required this.toggleButtonBGColor,
|
||||
required this.progressBarBGcolor,
|
||||
});
|
||||
|
||||
static AFThemeExtension of(BuildContext context) {
|
||||
@@ -73,8 +71,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
Color? greySelect,
|
||||
Color? lightGreyHover,
|
||||
Color? toggleOffFill,
|
||||
Color? progressBarBGColor,
|
||||
Color? toggleButtonBGColor,
|
||||
Color? progressBarBGcolor,
|
||||
TextStyle? code,
|
||||
TextStyle? callout,
|
||||
TextStyle? caption,
|
||||
@@ -96,8 +93,7 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
greySelect: greySelect ?? this.greySelect,
|
||||
lightGreyHover: lightGreyHover ?? this.lightGreyHover,
|
||||
toggleOffFill: toggleOffFill ?? this.toggleOffFill,
|
||||
progressBarBGColor: progressBarBGColor ?? this.progressBarBGColor,
|
||||
toggleButtonBGColor: toggleButtonBGColor ?? this.toggleButtonBGColor,
|
||||
progressBarBGcolor: progressBarBGcolor ?? this.progressBarBGcolor,
|
||||
code: code ?? this.code,
|
||||
callout: callout ?? this.callout,
|
||||
caption: caption ?? this.caption,
|
||||
@@ -127,10 +123,8 @@ class AFThemeExtension extends ThemeExtension<AFThemeExtension> {
|
||||
greySelect: Color.lerp(greySelect, other.greySelect, t)!,
|
||||
lightGreyHover: Color.lerp(lightGreyHover, other.lightGreyHover, t)!,
|
||||
toggleOffFill: Color.lerp(toggleOffFill, other.toggleOffFill, t)!,
|
||||
progressBarBGColor:
|
||||
Color.lerp(progressBarBGColor, other.progressBarBGColor, t)!,
|
||||
toggleButtonBGColor:
|
||||
Color.lerp(toggleButtonBGColor, other.toggleButtonBGColor, t)!,
|
||||
progressBarBGcolor:
|
||||
Color.lerp(progressBarBGcolor, other.progressBarBGcolor, t)!,
|
||||
code: other.code,
|
||||
callout: other.callout,
|
||||
caption: other.caption,
|
||||
|
||||
@@ -99,7 +99,6 @@ class HoverStyle {
|
||||
final Color borderColor;
|
||||
final double borderWidth;
|
||||
final Color? hoverColor;
|
||||
final Color? foregroundColorOnHover;
|
||||
final BorderRadius borderRadius;
|
||||
final EdgeInsets contentMargin;
|
||||
final Color backgroundColor;
|
||||
@@ -111,7 +110,6 @@ class HoverStyle {
|
||||
this.contentMargin = EdgeInsets.zero,
|
||||
this.backgroundColor = Colors.transparent,
|
||||
this.hoverColor,
|
||||
this.foregroundColorOnHover,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,21 +130,6 @@ class FlowyHoverContainer extends StatelessWidget {
|
||||
width: style.borderWidth,
|
||||
);
|
||||
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
final iconTheme = theme.iconTheme;
|
||||
// override text's theme with foregroundColorOnHover when it is hovered
|
||||
final hoverTheme = theme.copyWith(
|
||||
textTheme: textTheme.copyWith(
|
||||
bodyMedium: textTheme.bodyMedium?.copyWith(
|
||||
color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
iconTheme: iconTheme.copyWith(
|
||||
color: style.foregroundColorOnHover ?? theme.colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
|
||||
return Container(
|
||||
margin: style.contentMargin,
|
||||
decoration: BoxDecoration(
|
||||
@@ -154,8 +137,20 @@ class FlowyHoverContainer extends StatelessWidget {
|
||||
color: style.hoverColor ?? Theme.of(context).colorScheme.secondary,
|
||||
borderRadius: style.borderRadius,
|
||||
),
|
||||
child: Theme(
|
||||
data: hoverTheme,
|
||||
child:
|
||||
//override text's theme with new color when it is hovered
|
||||
Theme(
|
||||
data: Theme.of(context).copyWith(
|
||||
textTheme: Theme.of(context).textTheme.copyWith(
|
||||
bodyMedium: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
iconTheme: Theme.of(context)
|
||||
.iconTheme
|
||||
.copyWith(color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
);
|
||||
|
||||
+3
-14
@@ -2,7 +2,6 @@ import 'dart:math';
|
||||
|
||||
import 'package:flowy_infra/image.dart';
|
||||
import 'package:flowy_infra/size.dart';
|
||||
import 'package:flowy_infra_ui/style_widget/hover.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class FlowyIconButton extends StatelessWidget {
|
||||
@@ -12,7 +11,6 @@ class FlowyIconButton extends StatelessWidget {
|
||||
final VoidCallback? onPressed;
|
||||
final Color? fillColor;
|
||||
final Color? hoverColor;
|
||||
final Color? iconColorOnHover;
|
||||
final EdgeInsets iconPadding;
|
||||
final BorderRadius? radius;
|
||||
final String? tooltipText;
|
||||
@@ -25,7 +23,6 @@ class FlowyIconButton extends StatelessWidget {
|
||||
this.onPressed,
|
||||
this.fillColor = Colors.transparent,
|
||||
this.hoverColor,
|
||||
this.iconColorOnHover,
|
||||
this.iconPadding = EdgeInsets.zero,
|
||||
this.radius,
|
||||
this.tooltipText,
|
||||
@@ -65,17 +62,9 @@ class FlowyIconButton extends StatelessWidget {
|
||||
highlightColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
onPressed: onPressed,
|
||||
child: FlowyHover(
|
||||
style: HoverStyle(
|
||||
hoverColor: hoverColor,
|
||||
foregroundColorOnHover:
|
||||
iconColorOnHover ?? Theme.of(context).iconTheme.color,
|
||||
backgroundColor: fillColor ?? Colors.transparent,
|
||||
),
|
||||
child: Padding(
|
||||
padding: iconPadding,
|
||||
child: SizedBox.fromSize(size: childSize, child: child),
|
||||
),
|
||||
child: Padding(
|
||||
padding: iconPadding,
|
||||
child: SizedBox.fromSize(size: childSize, child: child),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void showSnapBar(BuildContext context, String title, [Color? backgroundColor]) {
|
||||
void showSnapBar(BuildContext context, String title) {
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
|
||||
ScaffoldMessenger.of(context)
|
||||
@@ -18,7 +18,6 @@ void showSnapBar(BuildContext context, String title, [Color? backgroundColor]) {
|
||||
),
|
||||
),
|
||||
),
|
||||
backgroundColor: backgroundColor,
|
||||
),
|
||||
)
|
||||
.closed
|
||||
|
||||
@@ -45,10 +45,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: appflowy_editor
|
||||
sha256: "6f7d2b0b54ca1049cb396229549d228b5bbd7ea6d09f1f7325a20db2d7586a5f"
|
||||
sha256: a1dbca3d7d33f4669f1d44bfa1e06b6646f46726c219921b8a59d9ee22bf252d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.12"
|
||||
version: "0.1.9"
|
||||
appflowy_popover:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -172,10 +172,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: calendar_view
|
||||
sha256: "58a8b851ac0a2d62770fd06ad30f06683bd40848a5dd1a1eca332f5a6064bd82"
|
||||
sha256: "7f2c460d38cda782e0852ca7706086c24f6e9407a9f0cfcdef034fb81e6d9f3e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "1.0.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -809,7 +809,7 @@ packages:
|
||||
source: hosted
|
||||
version: "1.0.4"
|
||||
mocktail:
|
||||
dependency: "direct main"
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mocktail
|
||||
sha256: "80a996cd9a69284b3dc521ce185ffe9150cde69767c2d3a0720147d93c0cef53"
|
||||
|
||||
@@ -15,7 +15,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
|
||||
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
|
||||
# Read more about iOS versioning at
|
||||
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
|
||||
version: 0.1.5
|
||||
version: 0.1.3
|
||||
|
||||
environment:
|
||||
sdk: ">=2.19.0 <3.0.0"
|
||||
@@ -42,7 +42,7 @@ dependencies:
|
||||
git:
|
||||
url: https://github.com/AppFlowy-IO/appflowy-board.git
|
||||
ref: a183c57
|
||||
appflowy_editor: ^0.1.12
|
||||
appflowy_editor: "^0.1.9"
|
||||
appflowy_popover:
|
||||
path: packages/appflowy_popover
|
||||
|
||||
@@ -98,7 +98,6 @@ dependencies:
|
||||
http: ^0.13.5
|
||||
json_annotation: ^4.7.0
|
||||
path: ^1.8.2
|
||||
mocktail: ^0.3.0
|
||||
archive: ^3.3.0
|
||||
flutter_svg: ^2.0.5
|
||||
|
||||
@@ -173,4 +172,4 @@ flutter:
|
||||
# The following assets will be excluded in release.
|
||||
# BEGIN: EXCLUDE_IN_RELEASE
|
||||
- assets/test/workspaces/
|
||||
# END: EXCLUDE_IN_RELEASE
|
||||
# END: EXCLUDE_IN_RELEASE
|
||||
@@ -108,7 +108,7 @@ pub struct CalendarEventPB {
|
||||
pub row_id: String,
|
||||
|
||||
#[pb(index = 2)]
|
||||
pub date_field_id: String,
|
||||
pub title_field_id: String,
|
||||
|
||||
#[pb(index = 3)]
|
||||
pub title: String,
|
||||
|
||||
@@ -313,9 +313,7 @@ pub(crate) async fn duplicate_row_handler(
|
||||
) -> Result<(), FlowyError> {
|
||||
let params: RowIdParams = data.into_inner().try_into()?;
|
||||
let editor = manager.get_database_editor(¶ms.view_id).await?;
|
||||
editor
|
||||
.duplicate_row(¶ms.view_id, ¶ms.row_id)
|
||||
.await?;
|
||||
editor.duplicate_row(¶ms.row_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -248,11 +248,11 @@ pub fn insert_checkbox_cell(is_check: bool, field_rev: &FieldRevision) -> CellRe
|
||||
CellRevision::new(data)
|
||||
}
|
||||
|
||||
pub fn insert_date_cell(date_cell_data: DateCellData, field_rev: &FieldRevision) -> CellRevision {
|
||||
pub fn insert_date_cell(timestamp: i64, field_rev: &FieldRevision) -> CellRevision {
|
||||
let cell_data = serde_json::to_string(&DateCellChangeset {
|
||||
date: date_cell_data.timestamp.map(|t| t.to_string()),
|
||||
date: Some(timestamp.to_string()),
|
||||
time: None,
|
||||
include_time: Some(date_cell_data.include_time),
|
||||
include_time: Some(false),
|
||||
is_utc: true,
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Loaded 100 of 114 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user