diff --git a/README.md b/README.md
index 4a862070..60604346 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@
-[🌐 官网](https://count.beejz.com) · [📖 文档](https://count.beejz.com/docs/intro) · [💝 捐赠](#-捐赠支持) · [💬 Telegram](https://t.me/beecount) · [📦 APK](https://github.com/TNT-Likely/BeeCount/releases/latest) · [🚀 TestFlight](https://testflight.apple.com/join/Eaw2rWxa)
+[🌐 官网](https://count.beejz.com) · [📖 文档](https://count.beejz.com/docs/intro) · [💝 捐赠](#-捐赠支持) · [👥 微信群](docs/community/README_ZH.md) · [💬 Telegram](https://t.me/beecount) · [📦 APK](https://github.com/TNT-Likely/BeeCount/releases/latest) · [🚀 TestFlight](https://testflight.apple.com/join/Eaw2rWxa)
diff --git a/README_EN.md b/README_EN.md
index 58c8dd8c..315a9f3d 100644
--- a/README_EN.md
+++ b/README_EN.md
@@ -28,7 +28,7 @@ Sync via BeeCount Cloud (self-hosted) / iCloud / Supabase / WebDAV / S3
-[🌐 Website](https://count.beejz.com/en/) · [📖 Docs](https://count.beejz.com/en/docs/intro) · [💝 Donate](#-donate) · [💬 Telegram](https://t.me/beecount) · [📦 APK](https://github.com/TNT-Likely/BeeCount/releases/latest) · [🚀 TestFlight](https://testflight.apple.com/join/Eaw2rWxa)
+[🌐 Website](https://count.beejz.com/en/) · [📖 Docs](https://count.beejz.com/en/docs/intro) · [💝 Donate](#-donate) · [👥 WeChat Group](docs/community/README_EN.md) · [💬 Telegram](https://t.me/beecount) · [📦 APK](https://github.com/TNT-Likely/BeeCount/releases/latest) · [🚀 TestFlight](https://testflight.apple.com/join/Eaw2rWxa)
diff --git a/docs/community/README_EN.md b/docs/community/README_EN.md
new file mode 100644
index 00000000..95365cc9
--- /dev/null
+++ b/docs/community/README_EN.md
@@ -0,0 +1,22 @@
+# WeChat Group
+
+The BeeCount user community lives on **WeChat** (most users are based in mainland China). Scan the QR code below to join — the developer hangs out in the group daily.
+
+## QR code
+
+
+
+> ⚠️ The WeChat QR code is **valid for 7 days**. This page is refreshed before each expiry. If the code says expired or the group is full, leave a comment on [GitHub Issues](https://github.com/TNT-Likely/BeeCount/issues) and we'll add you manually.
+
+## Prefer not using WeChat?
+
+The same questions can be asked through:
+
+- [GitHub Issues](https://github.com/TNT-Likely/BeeCount/issues) — bug reports & feature requests with structured templates
+- [Telegram](https://t.me/beecount) — community channel for users outside China
+
+The group is Chinese-speaking; for English-language support, GitHub Issues or Telegram are usually a smoother fit.
+
+---
+
+> **Privacy note**: when discussing issues, please avoid posting **full sync credentials / API keys / server URLs**. For sensitive debugging, DM the admin directly.
diff --git a/docs/community/README_ZH.md b/docs/community/README_ZH.md
new file mode 100644
index 00000000..3f7fc2e8
--- /dev/null
+++ b/docs/community/README_ZH.md
@@ -0,0 +1,28 @@
+# 微信交流群
+
+欢迎扫码加入 **BeeCount 蜜蜂记账交流群** —— 反馈使用问题、提建议、跟其他用户交流记账方法,作者也常在群里。
+
+## 入群二维码
+
+
+
+> ⚠️ 微信群二维码 **7 天有效**,过期后本页会重新更新。如果扫码提示已过期 / 满员,请到 [GitHub Issues](https://github.com/TNT-Likely/BeeCount/issues) 留言或加管理员个人微信,我们会拉你进群。
+
+## 群里聊什么
+
+- 🐛 **使用问题反馈**:同步异常、AI 识别不准、界面 bug 等
+- 💡 **功能建议**:有想要的新功能直接说,采纳率挺高
+- 💬 **使用心得**:多账本怎么用、分类怎么搭、AI 提示词调参等
+- 📢 **版本更新**:新版本发布会在群里通知,可以第一时间体验
+
+## 其他联系方式
+
+如果你不用微信,也可以走这些渠道:
+
+- [GitHub Issues](https://github.com/TNT-Likely/BeeCount/issues) —— 报 bug / 提需求的首选,有结构化模板
+- [Telegram](https://t.me/beecount) —— 海外用户社群
+- 小红书 / 抖音 —— 视频教程 + 评论区互动(在 App「关于」页有入口)
+
+---
+
+> **隐私提示**:群里讨论问题时,请避免贴**完整的同步配置 / API Key / 服务器地址**等敏感信息。需要私下排查时单独私聊管理员。
diff --git a/docs/community/wechat-group.png b/docs/community/wechat-group.png
new file mode 100644
index 00000000..5e9fd5e4
Binary files /dev/null and b/docs/community/wechat-group.png differ
diff --git a/l10n.yaml b/l10n.yaml
index b8bf72e1..fa1a9703 100644
--- a/l10n.yaml
+++ b/l10n.yaml
@@ -1,6 +1,6 @@
arb-dir: lib/l10n
template-arb-file: app_en.arb
output-localization-file: app_localizations.dart
+output-dir: lib/l10n
nullable-getter: false
-synthetic-package: false
preferred-supported-locales: ["en"]
\ No newline at end of file
diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb
index 0239b4d3..a13cfcf6 100644
--- a/lib/l10n/app_en.arb
+++ b/lib/l10n/app_en.arb
@@ -150,24 +150,104 @@
"searchNoResults": "No matching results found",
"searchBatchMode": "Batch Operations",
"searchBatchModeWithCount": "Batch Operations ({selected}/{total})",
+ "@searchBatchModeWithCount": {
+ "placeholders": {
+ "selected": {
+ "type": "int"
+ },
+ "total": {
+ "type": "int"
+ }
+ }
+ },
"searchExitBatchMode": "Exit Batch Mode",
"searchSelectAll": "Select All",
"searchDeselectAll": "Deselect All",
"searchSelectedCount": "{count} selected",
+ "@searchSelectedCount": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"searchBatchSetNote": "Set Note",
"searchBatchChangeCategory": "Change Category",
"searchBatchDeleteConfirmTitle": "Confirm Delete",
"searchBatchDeleteConfirmMessage": "Are you sure you want to delete the selected {count} transactions?\nThis action cannot be undone.",
+ "@searchBatchDeleteConfirmMessage": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"searchBatchSetNoteTitle": "Batch Set Note",
"searchBatchSetNoteMessage": "Set the same note for the selected {count} transactions",
+ "@searchBatchSetNoteMessage": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"searchBatchSetNoteHint": "Enter note content (leave empty to clear notes)",
"searchBatchDeleteSuccess": "Successfully deleted {count} transactions",
+ "@searchBatchDeleteSuccess": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"searchBatchDeleteFailed": "Delete failed: {error}",
+ "@searchBatchDeleteFailed": {
+ "placeholders": {
+ "error": {
+ "type": "String"
+ }
+ }
+ },
"searchBatchSetNoteSuccess": "Successfully set note for {count} transactions",
+ "@searchBatchSetNoteSuccess": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"searchBatchSetNoteFailed": "Set note failed: {error}",
+ "@searchBatchSetNoteFailed": {
+ "placeholders": {
+ "error": {
+ "type": "String"
+ }
+ }
+ },
"searchBatchChangeCategorySuccess": "Successfully changed category for {count} transactions",
+ "@searchBatchChangeCategorySuccess": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"searchBatchChangeCategoryFailed": "Change category failed: {error}",
+ "@searchBatchChangeCategoryFailed": {
+ "placeholders": {
+ "error": {
+ "type": "String"
+ }
+ }
+ },
"searchResultsCount": "{count} results",
+ "@searchResultsCount": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"searchFilterTitle": "Filter",
"searchAmountFilter": "Amount Filter",
"searchDateFilter": "Date Filter",
@@ -209,6 +289,13 @@
"ledgersNew": "New Ledger",
"ledgersClear": "Clear Ledger",
"ledgersClearMessage": "Are you sure to clear all transactions in ledger \"{name}\"? This action cannot be undone.\\nThe ledger will be kept, only transaction data will be deleted.",
+ "@ledgersClearMessage": {
+ "placeholders": {
+ "name": {
+ "type": "String"
+ }
+ }
+ },
"ledgerDefaultName": "Default Ledger",
"ledgersEdit": "Edit Ledger",
"ledgersDelete": "Delete Ledger",
@@ -221,6 +308,13 @@
"ledgersDeleteLocal": "Delete Local Ledger Only",
"ledgersDeleteLocalTitle": "Delete Local Ledger",
"ledgersDeleteLocalMessage": "Are you sure to delete local ledger \"{name}\"?\\nCloud backup will be kept and you can restore it anytime.",
+ "@ledgersDeleteLocalMessage": {
+ "placeholders": {
+ "name": {
+ "type": "String"
+ }
+ }
+ },
"ledgersDeleteLocalSuccess": "Local ledger deleted",
"ledgersName": "Name",
"ledgersDefaultLedgerName": "Default Ledger",
@@ -379,6 +473,13 @@
"importCompleted": "Import Completed{cancelled}, success {ok}, failed {fail}",
"importSkippedNonTransactionTypes": "Skipped {count} non-transaction records (debts, etc.)",
"importTransactionFailed": "Import failed, all changes have been rolled back: {error}",
+ "@importTransactionFailed": {
+ "placeholders": {
+ "error": {
+ "type": "String"
+ }
+ }
+ },
"importFileOpenError": "Unable to open file picker: {error}",
"@importFileOpenError": {
"placeholders": {
@@ -686,15 +787,36 @@
"categoryClearUnused": "Clear Unused Categories",
"categoryClearUnusedTitle": "Clear Unused Categories",
"categoryClearUnusedMessage": "Are you sure you want to delete {count} unused categories? This action cannot be undone.",
+ "@categoryClearUnusedMessage": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"categoryClearUnusedListTitle": "Categories to be deleted:",
"categoryClearUnusedEmpty": "No unused categories",
"categoryClearUnusedSuccess": "Deleted {count} categories",
+ "@categoryClearUnusedSuccess": {
+ "placeholders": {
+ "count": {
+ "type": "int"
+ }
+ }
+ },
"categoryClearUnusedFailed": "Clear failed",
"categoryShareScopeTitle": "Select Scope",
"categoryShareScopeExpense": "Expense categories only",
"categoryShareScopeIncome": "Income categories only",
"categoryShareScopeAll": "All categories",
"categoryShareSuccess": "Saved to {path}",
+ "@categoryShareSuccess": {
+ "placeholders": {
+ "path": {
+ "type": "String"
+ }
+ }
+ },
"categoryShareSubject": "BeeCount Category Configuration",
"categoryShareFailed": "Share failed",
"categoryImportInvalidFile": "Please select a category package file (.zip)",
@@ -895,6 +1017,13 @@
"categoryMigrationCompleteMessage": "Successfully migrated {count} transactions from \"{fromName}\" to \"{toName}\".",
"categoryMigrationFailedTitle": "Migration Failed",
"categoryMigrationFailedMessage": "Migration error: {error}",
+ "@categoryMigrationFailedMessage": {
+ "placeholders": {
+ "error": {
+ "type": "String"
+ }
+ }
+ },
"categoryMigrationTransactionLabel": "{count} records",
"@categoryMigrationTransactionLabel": {
"placeholders": {
@@ -1785,6 +1914,8 @@
"aboutGitHubRepo": "GitHub Repository",
"aboutXiaohongshu": "Xiaohongshu",
"aboutDouyin": "Douyin",
+ "aboutWechatGroup": "WeChat Group",
+ "aboutWechatGroupSubtitle": "Scan to join, developer hangs out here",
"aboutSupportDevelopment": "Support Development",
"aboutSupportDevelopmentSubtitle": "Buy me a coffee",
"aboutDeveloperStoryTitle": "From the Developer",
diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart
index 93ef97c6..66653b93 100644
--- a/lib/l10n/app_localizations.dart
+++ b/lib/l10n/app_localizations.dart
@@ -8022,6 +8022,18 @@ abstract class AppLocalizations {
/// **'Douyin'**
String get aboutDouyin;
+ /// No description provided for @aboutWechatGroup.
+ ///
+ /// In en, this message translates to:
+ /// **'WeChat Group'**
+ String get aboutWechatGroup;
+
+ /// No description provided for @aboutWechatGroupSubtitle.
+ ///
+ /// In en, this message translates to:
+ /// **'Scan to join, developer hangs out here'**
+ String get aboutWechatGroupSubtitle;
+
/// No description provided for @aboutSupportDevelopment.
///
/// In en, this message translates to:
diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart
index 16d4c5ed..4b9c1ef3 100644
--- a/lib/l10n/app_localizations_en.dart
+++ b/lib/l10n/app_localizations_en.dart
@@ -4201,6 +4201,12 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get aboutDouyin => 'Douyin';
+ @override
+ String get aboutWechatGroup => 'WeChat Group';
+
+ @override
+ String get aboutWechatGroupSubtitle => 'Scan to join, developer hangs out here';
+
@override
String get aboutSupportDevelopment => 'Support Development';
diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart
index 9de78daf..e85aeace 100644
--- a/lib/l10n/app_localizations_zh.dart
+++ b/lib/l10n/app_localizations_zh.dart
@@ -4201,6 +4201,12 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get aboutDouyin => '抖音';
+ @override
+ String get aboutWechatGroup => '微信交流群';
+
+ @override
+ String get aboutWechatGroupSubtitle => '扫码加群,作者常在';
+
@override
String get aboutSupportDevelopment => '支持开发';
@@ -10698,6 +10704,12 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override
String get aboutDouyin => '抖音';
+ @override
+ String get aboutWechatGroup => '微信交流群';
+
+ @override
+ String get aboutWechatGroupSubtitle => '掃碼加群,作者常在';
+
@override
String get aboutSupportDevelopment => '支持開發';
diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb
index 812d7ce3..28e1896a 100644
--- a/lib/l10n/app_zh.arb
+++ b/lib/l10n/app_zh.arb
@@ -1679,6 +1679,8 @@
"aboutGitHubRepo": "GitHub 仓库",
"aboutXiaohongshu": "小红书",
"aboutDouyin": "抖音",
+ "aboutWechatGroup": "微信交流群",
+ "aboutWechatGroupSubtitle": "扫码加群,作者常在",
"aboutSupportDevelopment": "支持开发",
"aboutSupportDevelopmentSubtitle": "请开发者喝杯咖啡",
"aboutDeveloperStoryTitle": "开发者的话",
diff --git a/lib/l10n/app_zh_TW.arb b/lib/l10n/app_zh_TW.arb
index b616941c..880dab98 100644
--- a/lib/l10n/app_zh_TW.arb
+++ b/lib/l10n/app_zh_TW.arb
@@ -40,6 +40,8 @@
"aboutSupportDevelopment": "支持開發",
"aboutSupportDevelopmentSubtitle": "請開發者喝杯咖啡",
"aboutWebsite": "官方網站",
+ "aboutWechatGroup": "微信交流群",
+ "aboutWechatGroupSubtitle": "掃碼加群,作者常在",
"aboutWidget": "關于小組件",
"aboutXiaohongshu": "小紅書",
"accountAddButton": "添加帳戶",
diff --git a/lib/pages/calendar/calendar_page.dart b/lib/pages/calendar/calendar_page.dart
index d603104d..a52d31f6 100644
--- a/lib/pages/calendar/calendar_page.dart
+++ b/lib/pages/calendar/calendar_page.dart
@@ -11,6 +11,7 @@ import '../../widgets/category_icon.dart';
import '../../styles/tokens.dart';
import '../../utils/ui_scale_extensions.dart';
import '../../utils/transaction_edit_utils.dart';
+import '../../utils/category_utils.dart';
import '../../utils/currencies.dart';
import '../../providers.dart';
import '../../providers/calendar_providers.dart';
@@ -433,6 +434,12 @@ class _CalendarPageState extends ConsumerState {
transactionsByDateProvider((ledgerId: ledgerId, date: date)),
);
+ final allCategories = ref.watch(categoriesProvider).valueOrNull ?? [];
+ final Map categoryNameById = {
+ for (var c in allCategories)
+ c.id: CategoryUtils.getDisplayName(c.name, context, kind: c.kind),
+ };
+
final header = Padding(
padding: const EdgeInsets.fromLTRB(4, 0, 4, 10),
child: Row(
@@ -536,104 +543,15 @@ class _CalendarPageState extends ConsumerState {
final isTransfer = item.t.type == 'transfer';
// 分类名称
- final categoryName = category?.name ?? l10n.commonUncategorized;
-
- // 备注作为副标题
- final subtitle = item.t.note ?? '';
-
- // 标签列表
- final tagsList = item.tags
- .map((tag) => (id: tag.id, name: tag.name, color: tag.color))
- .toList();
-
- return TransactionListItem(
- icon: getCategoryIconData(category: category, categoryName: categoryName),
- category: category,
- title: isTransfer
- ? (subtitle.isNotEmpty ? subtitle : l10n.transferTitle)
- : (subtitle.isNotEmpty ? subtitle : categoryName),
- categoryName: isTransfer
- ? null
- : (subtitle.isNotEmpty ? categoryName : null),
- amount: item.t.amount,
- isExpense: isExpense,
- isTransfer: isTransfer,
- happenedAt: item.t.happenedAt,
- accountName: item.account?.name,
- tags: tagsList.isNotEmpty ? tagsList : null,
- attachmentCount: item.attachments.length,
- onTap: () async {
- await TransactionEditUtils.editTransaction(
- context,
- ref,
- item.t,
- item.category,
- );
- },
- );
- },
- );
- },
- loading: () => _buildTransactionsSkeleton(context),
- error: (err, stack) => Padding(
- padding: const EdgeInsets.all(24),
- child: Center(child: Text('Error: $err')),
- ),
- ),
- );
-
- return Column(
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: [header, card],
- );
- }
-
- // 构建当月交易列表(不显示日期和统计)
- Widget _buildMonthTransactionsList(
- BuildContext context, int ledgerId, DateTime month) {
- final l10n = AppLocalizations.of(context);
+ final displayName = category != null
+ ? CategoryUtils.getDisplayName(category.name, context)
+ : l10n.commonUncategorized;
- // 使用 Provider 查询当月交易
- final startDate = DateTime(month.year, month.month, 1);
- final endDate = DateTime(month.year, month.month + 1, 0, 23, 59, 59);
-
- final transactionsAsync = ref.watch(
- monthTransactionsProvider(
- (ledgerId: ledgerId, startDate: startDate, endDate: endDate)),
- );
-
- return SectionCard(
- margin: EdgeInsets.zero,
- child: transactionsAsync.when(
- data: (transactions) {
- if (transactions.isEmpty) {
- return Padding(
- padding: EdgeInsets.all(24.0.scaled(context, ref)),
- child: Center(
- child: Text(
- l10n.calendarNoTransactions,
- style: TextStyle(
- color: BeeTokens.textTertiary(context),
- ),
- ),
- ),
- );
- }
-
- // 直接显示交易列表
- return ListView.builder(
- shrinkWrap: true,
- physics: const NeverScrollableScrollPhysics(),
- padding: EdgeInsets.zero,
- itemCount: transactions.length,
- itemBuilder: (context, index) {
- final item = transactions[index];
- final category = item.category;
- final isExpense = item.t.type == 'expense';
- final isTransfer = item.t.type == 'transfer';
-
- // 分类名称
- final categoryName = category?.name ?? l10n.commonUncategorized;
+ // 父级分类名称(二级分类时显示)
+ final parentCategoryName = (!isTransfer && category != null)
+ ? (CategoryUtils.getParentDisplayName(category.name, category.kind, context)
+ ?? categoryNameById[category.parentId])
+ : null;
// 备注作为副标题
final subtitle = item.t.note ?? '';
@@ -644,14 +562,13 @@ class _CalendarPageState extends ConsumerState {
.toList();
return TransactionListItem(
- icon: getCategoryIconData(category: category, categoryName: categoryName),
+ icon: getCategoryIconData(category: category, categoryName: displayName),
category: category,
title: isTransfer
? (subtitle.isNotEmpty ? subtitle : l10n.transferTitle)
- : (subtitle.isNotEmpty ? subtitle : categoryName),
- categoryName: isTransfer
- ? null
- : (subtitle.isNotEmpty ? categoryName : null),
+ : (subtitle.isNotEmpty ? subtitle : displayName),
+ categoryName: isTransfer ? null : displayName,
+ parentCategoryName: isTransfer ? null : parentCategoryName,
amount: item.t.amount,
isExpense: isExpense,
isTransfer: isTransfer,
@@ -681,6 +598,11 @@ class _CalendarPageState extends ConsumerState {
),
),
);
+
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.stretch,
+ children: [header, card],
+ );
}
String _formatDate(DateTime date) {
diff --git a/lib/pages/settings/about_page.dart b/lib/pages/settings/about_page.dart
index 08434d60..34d54f52 100644
--- a/lib/pages/settings/about_page.dart
+++ b/lib/pages/settings/about_page.dart
@@ -159,7 +159,25 @@ class _AboutPageState extends ConsumerState {
await _tryOpenUrl(url);
},
),
- // 小红书号(仅简体中文显示)
+ // 微信群入口:所有语言都显示(群本身中文社区,但海外用户
+ // 也允许加,落地页按 locale 选 ZH / EN 版)。
+ const Divider(height: 1, thickness: 0.5),
+ AppListTile(
+ leading: Icons.groups_outlined,
+ title: AppLocalizations.of(context).aboutWechatGroup,
+ subtitle: AppLocalizations.of(context).aboutWechatGroupSubtitle,
+ onTap: () async {
+ // 群二维码 7 天有效,挂在 docs/community/ 下,每次过期前
+ // 仓库里替换 wechat-group.png 就行,app 不用发版。
+ final isZh = Localizations.localeOf(context).languageCode == 'zh';
+ final docUrl = isZh
+ ? 'https://github.com/TNT-Likely/BeeCount/blob/main/docs/community/README_ZH.md'
+ : 'https://github.com/TNT-Likely/BeeCount/blob/main/docs/community/README_EN.md';
+ final url = Uri.parse(docUrl);
+ await _tryOpenUrl(url);
+ },
+ ),
+ // 小红书号 / 抖音(仅简体中文显示 —— 国内向短视频渠道)
if (Localizations.localeOf(context).languageCode == 'zh') ...[
const Divider(height: 1, thickness: 0.5),
AppListTile(
diff --git a/lib/pages/tag/tag_detail_page.dart b/lib/pages/tag/tag_detail_page.dart
index 1ced2116..dbf23c06 100644
--- a/lib/pages/tag/tag_detail_page.dart
+++ b/lib/pages/tag/tag_detail_page.dart
@@ -303,13 +303,19 @@ class _TagDetailPageState extends ConsumerState {
final categoryName = CategoryUtils.getDisplayName(category?.name, context);
final isTransfer = transaction.type == 'transfer';
+ // 获取父级分类名称(二级分类时显示)
+ final parentCategoryName = (!isTransfer && category != null)
+ ? CategoryUtils.getParentDisplayName(category.name, category.kind, context)
+ : null;
+
// 和首页保持一致:有备注显示备注,无备注显示分类名称
final hasNote = transaction.note?.isNotEmpty == true;
return TransactionListItem(
icon: getCategoryIconData(category: category, categoryName: categoryName),
category: category,
title: hasNote ? transaction.note! : categoryName,
- categoryName: hasNote ? null : categoryName,
+ categoryName: isTransfer ? null : categoryName,
+ parentCategoryName: isTransfer ? null : parentCategoryName,
amount: transaction.amount,
isExpense: transaction.type == 'expense',
happenedAt: transaction.happenedAt,
diff --git a/lib/pages/transaction/search_page.dart b/lib/pages/transaction/search_page.dart
index be44baa1..fddedde9 100644
--- a/lib/pages/transaction/search_page.dart
+++ b/lib/pages/transaction/search_page.dart
@@ -9,6 +9,7 @@ import '../../widgets/ui/ui.dart';
import '../../styles/tokens.dart';
import '../../utils/category_utils.dart';
import '../../l10n/app_localizations.dart';
+import '../../providers/database_providers.dart';
import '../../utils/transaction_edit_utils.dart';
import '../../widgets/category_icon.dart';
import 'category_detail_page.dart';
@@ -593,6 +594,11 @@ class _SearchPageState extends ConsumerState {
final ledgerId = ref.watch(currentLedgerIdProvider);
final hide = ref.watch(hideAmountsProvider);
final l10n = AppLocalizations.of(context);
+ final allCategories = ref.watch(categoriesProvider).valueOrNull ?? [];
+ final Map categoryNameById = {
+ for (var c in allCategories)
+ c.id: CategoryUtils.getDisplayName(c.name, context, kind: c.kind),
+ };
return Scaffold(
backgroundColor: BeeTokens.scaffoldBackground(context),
@@ -954,6 +960,12 @@ class _SearchPageState extends ConsumerState {
// 获取分类显示名称
final categoryName = CategoryUtils.getDisplayName(item.category?.name, context);
+ // 获取父级分类名称(二级分类时显示)
+ final parentCategoryName = (!isTransfer && item.category != null)
+ ? (CategoryUtils.getParentDisplayName(item.category!.name, item.category!.kind, context)
+ ?? categoryNameById[item.category!.parentId])
+ : null;
+
final subtitle = item.t.note ?? '';
final isSelected = _selectedIds.contains(item.t.id);
@@ -969,8 +981,8 @@ class _SearchPageState extends ConsumerState {
title: subtitle.isNotEmpty
? subtitle
: categoryName,
- categoryName:
- subtitle.isNotEmpty ? null : categoryName,
+ categoryName: isTransfer ? null : categoryName,
+ parentCategoryName: isTransfer ? null : parentCategoryName,
amount: item.t.amount,
isExpense: isExpense,
hide: hide,
diff --git a/lib/utils/category_utils.dart b/lib/utils/category_utils.dart
index ad20b2c6..730d22c5 100644
--- a/lib/utils/category_utils.dart
+++ b/lib/utils/category_utils.dart
@@ -196,4 +196,15 @@ class CategoryUtils {
return translationString.split(separator).map((e) => e.trim()).toList();
}
+
+ /// 获取父级分类的显示名称
+ ///
+ /// 对于二级分类的key格式(如 "dining_breakfast"),提取父级key("dining")
+ /// 并返回其翻译后的显示名称("餐饮")。
+ /// 如果是一级分类,返回null。
+ static String? getParentDisplayName(String? categoryName, String kind, BuildContext context) {
+ if (categoryName == null || !categoryName.contains('_')) return null;
+ final parentKey = categoryName.split('_').first;
+ return getDisplayName(parentKey, context, kind: kind);
+ }
}
diff --git a/lib/widgets/biz/transaction_list.dart b/lib/widgets/biz/transaction_list.dart
index 4bd8e9e7..b69c79f2 100644
--- a/lib/widgets/biz/transaction_list.dart
+++ b/lib/widgets/biz/transaction_list.dart
@@ -339,6 +339,13 @@ class TransactionListState extends ConsumerState {
// —— account / toAccount 由 Drift JOIN + SharedLedger* table-watch 自动
// 推送,UI 直接读 it.account?.name。
+ // 加载全部分类,构建分类名称查找表,用于通过 parentId 查找父级分类名称
+ final allCategories = ref.watch(categoriesProvider).valueOrNull ?? [];
+ final Map categoryNameById = {
+ for (final c in allCategories)
+ c.id: CategoryUtils.getDisplayName(c.name, context, kind: c.kind),
+ };
+
_buildFlatItems();
// 无数据时展示空状态
@@ -423,6 +430,16 @@ class TransactionListState extends ConsumerState {
? AppLocalizations.of(context).adjustmentTransaction
: CategoryUtils.getDisplayName(it.category?.name, context);
+ // 获取父级分类名称(二级分类时显示)
+ // 优先通过名称中的下划线格式解析(内置分类如 "dining_breakfast" → "dining")
+ // 如果解析失败且有 parentId,从全部分类表中查询
+ final parentCategoryName = (!isTransfer && !isAdjustment && it.category != null)
+ ? (CategoryUtils.getParentDisplayName(it.category!.name, it.category!.kind, context)
+ ?? (it.category!.parentId != null
+ ? categoryNameById[it.category!.parentId]
+ : null))
+ : null;
+
final subtitle = it.t.note ?? '';
// 检查是否是当天最后一项
@@ -502,9 +519,8 @@ class TransactionListState extends ConsumerState {
: isAdjustment
? categoryName
: (subtitle.isNotEmpty ? subtitle : categoryName),
- categoryName: (isTransfer || isAdjustment)
- ? null
- : (subtitle.isNotEmpty ? null : categoryName),
+ categoryName: (isTransfer || isAdjustment) ? null : categoryName,
+ parentCategoryName: isTransfer || isAdjustment ? null : parentCategoryName,
amount: it.t.amount,
isExpense: isExpense,
isTransfer: isTransfer,
diff --git a/lib/widgets/biz/transaction_list_item.dart b/lib/widgets/biz/transaction_list_item.dart
index a5971dc8..60d1f00f 100644
--- a/lib/widgets/biz/transaction_list_item.dart
+++ b/lib/widgets/biz/transaction_list_item.dart
@@ -20,6 +20,7 @@ class TransactionListItem extends ConsumerWidget {
final VoidCallback? onTap;
final VoidCallback? onCategoryTap; // 点击分类图标/名称的回调
final String? categoryName; // 分类名称,用于显示
+ final String? parentCategoryName; // 父级分类名称(二级分类时显示)
final VoidCallback? onDelete; // 删除回调
final String? accountName; // 账户名称,用于显示
final DateTime? happenedAt; // 交易时间,用于显示时分
@@ -39,46 +40,51 @@ class TransactionListItem extends ConsumerWidget {
final VoidCallback? onAttachmentTap; // 点击附件图标回调
const TransactionListItem({
- super.key,
- required this.icon,
- this.category,
- required this.title,
- required this.amount,
- required this.isExpense,
- this.isTransfer = false,
- this.isAdjustment = false,
- this.hide,
- this.onTap,
- this.onCategoryTap,
- this.categoryName,
- this.onDelete,
- this.accountName,
- this.happenedAt,
- this.isSelectionMode = false,
- this.isSelected = false,
- this.onSelectionChanged,
- this.showFullDate = false,
- this.tags,
- this.onTagTap,
- this.attachmentCount = 0,
- this.onAttachmentTap,
+ super.key,
+ required this.icon,
+ this.category,
+ required this.title,
+ required this.amount,
+ required this.isExpense,
+ this.isTransfer = false,
+ this.isAdjustment = false,
+ this.hide,
+ this.onTap,
+ this.onCategoryTap,
+ this.categoryName,
+ this.parentCategoryName,
+ this.onDelete,
+ this.accountName,
+ this.happenedAt,
+ this.isSelectionMode = false,
+ this.isSelected = false,
+ this.onSelectionChanged,
+ this.showFullDate = false,
+ this.tags,
+ this.onTagTap,
+ this.attachmentCount = 0,
+ this.onAttachmentTap,
});
-
- /// 检查是否有次要信息需要显示(时间、账户或附件)
+ /// 检查是否有次要信息需要显示(分类标签、时间、账户或附件)
bool _hasSecondaryInfo(WidgetRef ref) {
+ // 有分类名时始终显示标签,保持风格一致性
+ if (categoryName != null) return true;
+
// 显示完整日期模式
if (showFullDate && happenedAt != null) return true;
// 显示时间(设置开启 + 有数据 + 不是00:00:00)
final showTime = ref.watch(showTransactionTimeProvider) &&
happenedAt != null &&
- (happenedAt!.hour != 0 || happenedAt!.minute != 0 || happenedAt!.second != 0);
+ (happenedAt!.hour != 0 ||
+ happenedAt!.minute != 0 ||
+ happenedAt!.second != 0);
return showTime || accountName != null || attachmentCount > 0;
}
- /// 构建次要信息小部件(时间 · 账户 + 附件图标)
+ /// 构建次要信息小部件(分类标签 · 时间 · 账户 + 附件图标)
Widget _buildSecondaryInfo(BuildContext context, WidgetRef ref) {
final parts = [];
@@ -91,7 +97,9 @@ class TransactionListItem extends ConsumerWidget {
'${happenedAt!.hour.toString().padLeft(2, '0')}:${happenedAt!.minute.toString().padLeft(2, '0')}',
);
} else if (ref.watch(showTransactionTimeProvider) &&
- (happenedAt!.hour != 0 || happenedAt!.minute != 0 || happenedAt!.second != 0)) {
+ (happenedAt!.hour != 0 ||
+ happenedAt!.minute != 0 ||
+ happenedAt!.second != 0)) {
// 完整时间模式(HH:mm:ss)
parts.add(
'${happenedAt!.hour.toString().padLeft(2, '0')}:${happenedAt!.minute.toString().padLeft(2, '0')}:${happenedAt!.second.toString().padLeft(2, '0')}',
@@ -105,9 +113,9 @@ class TransactionListItem extends ConsumerWidget {
}
final textStyle = Theme.of(context).textTheme.bodySmall?.copyWith(
- color: BeeTokens.textTertiary(context),
- fontSize: 11,
- );
+ color: BeeTokens.textTertiary(context),
+ fontSize: 11,
+ );
// 构建附件图标部件(可点击)
Widget buildAttachmentWidget() {
@@ -136,21 +144,208 @@ class TransactionListItem extends ConsumerWidget {
return widget;
}
- // 如果只有附件,没有其他信息
- if (parts.isEmpty && attachmentCount > 0) {
- return buildAttachmentWidget();
+ // 组装行内容
+ final rowChildren = [];
+
+ // 前置:分类标签
+ if (categoryName != null) {
+ rowChildren.add(_buildCategoryLabels(context));
}
- // 有其他信息时
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Text(parts.join(' · '), style: textStyle),
- if (attachmentCount > 0) ...[
- Text(' · ', style: textStyle),
- buildAttachmentWidget(),
- ],
- ],
+ // 中间:时间 · 账户
+ if (parts.isNotEmpty) {
+ if (rowChildren.isNotEmpty) {
+ rowChildren.add(Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 2),
+ child: Text('·', style: textStyle),
+ ));
+ }
+ rowChildren.add(Text(parts.join(' · '), style: textStyle));
+ }
+
+ // 尾部:附件图标
+ if (attachmentCount > 0) {
+ if (rowChildren.isNotEmpty) {
+ rowChildren.add(Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 2),
+ child: Text('·', style: textStyle),
+ ));
+ }
+ rowChildren.add(buildAttachmentWidget());
+ }
+
+ if (rowChildren.isEmpty) return const SizedBox.shrink();
+
+ final hasCategory = categoryName != null;
+ final hasTimeOrAccount = parts.isNotEmpty;
+ final hasAttach = attachmentCount > 0;
+
+ // 用于宽度预估的分类标签样式(与 _buildCategoryLabels 保持一致)
+ final colorScheme = Theme.of(context).colorScheme;
+ final primaryColor = colorScheme.primary;
+ final labelTextStyle = Theme.of(context).textTheme.bodySmall?.copyWith(
+ fontSize: 10,
+ color: primaryColor,
+ height: 1.2,
+ fontWeight: FontWeight.w500,
+ );
+ final parentTextStyle = parentCategoryName != null
+ ? labelTextStyle?.copyWith(color: primaryColor.withValues(alpha: 0.8))
+ : null;
+
+ return LayoutBuilder(
+ builder: (ctx, constraints) {
+ final availableWidth = constraints.maxWidth;
+
+ // 预估一行总宽度
+ double totalWidth = 0;
+
+ // 分类标签宽度(Container padding 6+6=12 + 文字 + 间距)
+ if (hasCategory) {
+ totalWidth += 12;
+ if (parentCategoryName != null) {
+ totalWidth += _textWidth(parentCategoryName!, parentTextStyle, ctx);
+ totalWidth += _textWidth('>', labelTextStyle, ctx) + 2;
+ }
+ totalWidth += _textWidth(categoryName!, labelTextStyle, ctx);
+ totalWidth += 12;
+ }
+
+ // 分隔符 · 和 时间·账户
+ if (hasCategory && hasTimeOrAccount) {
+ totalWidth += _textWidth('·', textStyle, ctx) + 4;
+ }
+ if (hasTimeOrAccount) {
+ totalWidth += _textWidth(parts.join(' · '), textStyle, ctx);
+ }
+
+ // 分隔符 · 和附件图标
+ if (hasAttach) {
+ if (hasTimeOrAccount || hasCategory) {
+ totalWidth += _textWidth('·', textStyle, ctx) + 4;
+ }
+ totalWidth += 12 + 2 + _textWidth('$attachmentCount', textStyle, ctx);
+ }
+
+ // 留 8px 余量
+ if (totalWidth <= availableWidth + 8) {
+ // 一行放得下
+ return Row(
+ mainAxisSize: MainAxisSize.min,
+ children: rowChildren,
+ );
+ }
+
+ // 放不下 → 拆两行:分类标签一行,时间·账户·附件一行
+ return Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (hasCategory) _buildCategoryLabels(context),
+ if (hasTimeOrAccount || hasAttach)
+ Padding(
+ padding: EdgeInsets.only(top: hasCategory ? 2 : 0),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ if (hasTimeOrAccount)
+ Text(parts.join(' · '), style: textStyle),
+ if (hasAttach) ...[
+ if (hasTimeOrAccount)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 2),
+ child: Text('·', style: textStyle),
+ ),
+ buildAttachmentWidget(),
+ ],
+ ],
+ ),
+ ),
+ ],
+ );
+ },
+ );
+ }
+
+ /// 测量文本宽度(TextPainter)
+ double _textWidth(String text, TextStyle? style, BuildContext context) {
+ if (text.isEmpty || style == null) return 0;
+ final tp = TextPainter(
+ text: TextSpan(text: text, style: style),
+ maxLines: 1,
+ textDirection: Directionality.of(context),
+ )..layout();
+ return tp.width;
+ }
+
+ /// 构建分类标签小部件(用于内嵌在次要信息行中)
+ /// 风格:单一标签 + 统一样式,二级分类用「 > 」分隔
+ Widget _buildCategoryLabels(BuildContext context) {
+ final colorScheme = Theme.of(context).colorScheme;
+ final primaryColor = colorScheme.primary;
+
+ // 统一样式:浅底色 + 主色文字
+ final labelTextStyle = Theme.of(context).textTheme.bodySmall?.copyWith(
+ fontSize: 10,
+ color: primaryColor,
+ height: 1.2,
+ fontWeight: FontWeight.w500,
+ );
+
+ // 有二级分类时,父类文字稍淡
+ final parentTextStyle = parentCategoryName != null
+ ? labelTextStyle?.copyWith(color: primaryColor.withValues(alpha: 0.8))
+ : null;
+
+ // 构建标签文字样式
+ final TextStyle? labelStyle = labelTextStyle;
+
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
+ decoration: BoxDecoration(
+ color: primaryColor.withValues(alpha: 0.08),
+ borderRadius: BorderRadius.circular(4),
+ border: Border.all(
+ color: primaryColor.withValues(alpha: 0.22),
+ width: 0.8,
+ ),
+ ),
+ child: parentCategoryName != null
+ ? Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Text(
+ parentCategoryName!,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: parentTextStyle,
+ ),
+ Padding(
+ padding: const EdgeInsets.only(left: 1, right: 1),
+ child: Text(
+ '>',
+ style: labelStyle?.copyWith(
+ color: primaryColor.withValues(alpha: 0.45),
+ ),
+ ),
+ ),
+ Text(
+ categoryName!,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: labelStyle,
+ ),
+ ],
+ )
+ : ConstrainedBox(
+ constraints: const BoxConstraints(maxWidth: 72),
+ child: Text(
+ categoryName!,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: labelStyle,
+ ),
+ ),
);
}
@@ -191,7 +386,7 @@ class TransactionListItem extends ConsumerWidget {
),
),
const SizedBox(width: 12),
- // 左侧:分类名称 + 备注 + 时间·账户
+ // 左侧:分类名称 + 备注 + 分类标签 + 时间·账户
Expanded(
child: Padding(
padding: const EdgeInsets.only(right: 12),
@@ -200,27 +395,25 @@ class TransactionListItem extends ConsumerWidget {
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
- // 第一行:分类名称(始终显示)
- Text(
- categoryName ?? title,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: BeeTextTokens.title(context),
- ),
- // 第二行:备注(当title与categoryName不同时显示)
- if (categoryName != null && categoryName != title)
- Padding(
- padding: const EdgeInsets.only(top: 2),
- child: Text(
- title,
- maxLines: 1,
- overflow: TextOverflow.ellipsis,
- style: Theme.of(context).textTheme.bodySmall?.copyWith(
- color: BeeTokens.textSecondary(context),
- ),
- ),
+ if (categoryName != null &&
+ categoryName != title &&
+ title.isNotEmpty)
+ // 有备注时:第一行显示备注,分类名作为标签显示在下面
+ Text(
+ title,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: BeeTextTokens.title(context),
+ )
+ else
+ // 无备注时:显示分类名称(或transfer等标题)
+ Text(
+ categoryName ?? title,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ style: BeeTextTokens.title(context),
),
- // 第三行:时间 · 账户 · 附件
+ // 时间 · 账户 · 附件 · 分类标签
if (_hasSecondaryInfo(ref))
Padding(
padding: const EdgeInsets.only(top: 2),
@@ -240,7 +433,9 @@ class TransactionListItem extends ConsumerWidget {
AmountText(
value: isAdjustment
? amount // adjustment 直接显示原始值(含正负)
- : isExpense ? -amount : amount,
+ : isExpense
+ ? -amount
+ : amount,
hide: hide,
signed: !isTransfer, // 转账不显示正负号
decimals: 2,
@@ -292,10 +487,11 @@ class TransactionListItem extends ConsumerWidget {
confirmDismiss: (direction) async {
// 显示确认对话框
return await AppDialog.confirm(
- context,
- title: '确认删除',
- message: '确定要删除这笔交易吗?此操作无法撤销。',
- ) ?? false;
+ context,
+ title: '确认删除',
+ message: '确定要删除这笔交易吗?此操作无法撤销。',
+ ) ??
+ false;
},
onDismissed: (direction) {
onDelete!();
diff --git a/packages/flutter_cloud_sync_webdav/lib/src/webdav_storage_service.dart b/packages/flutter_cloud_sync_webdav/lib/src/webdav_storage_service.dart
index 5125c91c..d69a2ddf 100644
--- a/packages/flutter_cloud_sync_webdav/lib/src/webdav_storage_service.dart
+++ b/packages/flutter_cloud_sync_webdav/lib/src/webdav_storage_service.dart
@@ -2,6 +2,7 @@ library;
import 'dart:convert';
+import 'package:dio/dio.dart';
import 'package:flutter_cloud_sync/flutter_cloud_sync.dart';
import 'package:webdav_client/webdav_client.dart' as webdav;
@@ -43,20 +44,57 @@ class WebDAVStorageService implements CloudStorageService {
@override
Future download({required String path}) async {
try {
- // Build full path
final fullPath = _buildPath(path);
- // Download file
- final bytes = await _client.read(fullPath);
+ try {
+ // Try standard read (OPTIONS + GET). Some WebDAV servers
+ // (e.g. Synology NAS) return 404 for OPTIONS on individual files,
+ // so fall back to direct GET if that happens.
+ final bytes = await _client.read(fullPath);
+ return utf8.decode(bytes);
+ } catch (e) {
+ final msg = e.toString().toLowerCase();
+ final isNotFound = msg.contains('404') || msg.contains('not found');
- // Convert bytes to string
- return utf8.decode(bytes);
+ if (!isNotFound) rethrow;
+
+ // OPTIONS pre-check failed — fall back to direct GET.
+ return _directGet(fullPath);
+ }
} catch (e) {
- // Return null if file not found
- if (e.toString().contains('404') || e.toString().contains('not found')) {
+ throw CloudStorageException('Download failed: $e', e);
+ }
+ }
+
+ /// Bypass OPTIONS pre-check and download file directly via GET.
+ Future _directGet(String fullPath) async {
+ try {
+ final response = await _client.c.req>(
+ _client,
+ 'GET',
+ fullPath,
+ optionsHandler: (options) {
+ options.responseType = ResponseType.bytes;
+ options.headers?['accept'] = '*/*';
+ },
+ );
+
+ if (response.statusCode == 404) return null;
+ if (response.statusCode != 200) {
+ throw CloudStorageException(
+ 'Download failed: HTTP ${response.statusCode}');
+ }
+
+ final data = response.data;
+ if (data == null || data.isEmpty) return '';
+
+ return utf8.decode(data);
+ } catch (e) {
+ final msg = e.toString().toLowerCase();
+ if (msg.contains('404') || msg.contains('not found')) {
return null;
}
- throw CloudStorageException('Download failed: $e', e);
+ rethrow;
}
}
diff --git a/packages/flutter_cloud_sync_webdav/pubspec.lock b/packages/flutter_cloud_sync_webdav/pubspec.lock
index cb95eeb2..c0907d97 100644
--- a/packages/flutter_cloud_sync_webdav/pubspec.lock
+++ b/packages/flutter_cloud_sync_webdav/pubspec.lock
@@ -89,8 +89,24 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.7"
- dio:
+ device_info_plus:
+ dependency: transitive
+ description:
+ name: device_info_plus
+ sha256: "72d146c6d7098689ff5c5f66bcf593ac11efc530095385356e131070333e64da"
+ url: "https://pub.dev"
+ source: hosted
+ version: "11.3.0"
+ device_info_plus_platform_interface:
dependency: transitive
+ description:
+ name: device_info_plus_platform_interface
+ sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "7.0.2"
+ dio:
+ dependency: "direct main"
description:
name: dio
sha256: d90ee57923d1828ac14e492ca49440f65477f4bb1263575900be731a3dac66a9
@@ -141,6 +157,20 @@ packages:
relative: true
source: path
version: "0.1.0"
+ flutter_cloud_sync_icloud:
+ dependency: transitive
+ description:
+ path: "../flutter_cloud_sync_icloud"
+ relative: true
+ source: path
+ version: "0.1.0"
+ flutter_cloud_sync_s3:
+ dependency: transitive
+ description:
+ path: "../flutter_cloud_sync_s3"
+ relative: true
+ source: path
+ version: "0.1.0"
flutter_cloud_sync_supabase:
dependency: transitive
description:
@@ -286,6 +316,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
+ package_info_plus:
+ dependency: transitive
+ description:
+ name: package_info_plus
+ sha256: "16eee997588c60225bda0488b6dcfac69280a6b7a3cf02c741895dd370a02968"
+ url: "https://pub.dev"
+ source: hosted
+ version: "8.3.1"
+ package_info_plus_platform_interface:
+ dependency: transitive
+ description:
+ name: package_info_plus_platform_interface
+ sha256: "202a487f08836a592a6bd4f901ac69b3a8f146af552bbd14407b6b41e1c3f086"
+ url: "https://pub.dev"
+ source: hosted
+ version: "3.2.1"
path:
dependency: transitive
description:
@@ -651,6 +697,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.2"
+ win32:
+ dependency: transitive
+ description:
+ name: win32
+ sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.10.1"
+ win32_registry:
+ dependency: transitive
+ description:
+ name: win32_registry
+ sha256: "21ec76dfc731550fd3e2ce7a33a9ea90b828fdf19a5c3bcf556fa992cfa99852"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.5"
xdg_directories:
dependency: transitive
description:
diff --git a/packages/flutter_cloud_sync_webdav/pubspec.yaml b/packages/flutter_cloud_sync_webdav/pubspec.yaml
index 51920f28..b1596c38 100644
--- a/packages/flutter_cloud_sync_webdav/pubspec.yaml
+++ b/packages/flutter_cloud_sync_webdav/pubspec.yaml
@@ -16,6 +16,7 @@ dependencies:
path: ../flutter_cloud_sync
webdav_client: ^1.2.2
http: ^1.1.0
+ dio: ^5.0.0
dev_dependencies:
flutter_test: