feat: add profit analysis page with monthly calendar view

Introduce a new profit analysis page featuring a monthly calendar view that displays daily profit/loss data. The page includes navigation controls to switch between months, loading states, and responsive design that adapts to both light and dark themes. Implements data fetching from asset service and visual indicators for profit trends.
This commit is contained in:
2026-04-06 00:08:00 +08:00
parent a1e56887e5
commit 40ed445ae5
4 changed files with 457 additions and 90 deletions

View File

@@ -1,5 +1,6 @@
import 'package:flutter/material.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme.dart';
/// 信息行组件(用于关于对话框)
class InfoRow extends StatelessWidget {
@@ -18,8 +19,7 @@ class InfoRow extends StatelessWidget {
SizedBox(width: AppSpacing.sm),
Text(
text,
style: TextStyle(
fontSize: 12,
style: AppTextStyles.bodyMedium(context).copyWith(
color: colorScheme.onSurfaceVariant,
),
),

View File

@@ -1,5 +1,4 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../../../../core/theme/app_color_scheme.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../core/theme/app_theme.dart';
@@ -26,9 +25,7 @@ class LogoutButton extends StatelessWidget {
child: Center(
child: Text(
'退出登录',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w600,
style: AppTextStyles.headlineMedium(context).copyWith(
color: AppColorScheme.down,
),
),

View File

@@ -1,17 +1,17 @@
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:lucide_icons_flutter/lucide_icons.dart';
import 'package:provider/provider.dart';
import '../../../../core/theme/app_color_scheme.dart';
import '../../../../core/theme/app_spacing.dart';
import '../../../../providers/theme_provider.dart';
import '../../../../core/theme/app_theme.dart';
import '../../../../core/providers/theme_provider.dart';
/// KYC 状态徽章 (e.g. "已认证" green badge + chevron)
///
///
/// 根据 [kycStatus] 显示不同状态:
/// - 2: 已认证(绿色
/// - 1: 审核中橙色
/// - 其他: 仅显示 chevron
/// - 2: 已认证(绿色)
/// - 1: 审核中(橙色)
/// - 其他: 仅显示 chevron
*/
class KycBadge extends StatelessWidget {
final int kycStatus;
const KycBadge({super.key, required this.kycStatus});
@@ -21,63 +21,58 @@ class KycBadge extends StatelessWidget {
final isDark = Theme.of(context).brightness == Brightness.dark;
final green = AppColorScheme.getUpColor(isDark);
if (kycStatus == 2) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: green.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'已认证',
style: GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w500,
color: green,
),
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: green.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'已认证',
style: AppTextStyles.labelMedium(context).copyWith(
color: green,
),
),
const SizedBox(width: 8),
Icon(
LucideIcons.chevronRight,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
],
);
}
),
const SizedBox(width: 8),
Icon(
LucideIcons.chevronRight,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
],
);
}
if (kycStatus == 1) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColorScheme.warning.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'审核中',
style: GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.w500,
color: AppColorScheme.warning,
),
mainAxisSize: MainAxisSize.min,
children: [
Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
decoration: BoxDecoration(
color: AppColorScheme.warning.withOpacity(0.1),
borderRadius: BorderRadius.circular(AppRadius.sm),
),
child: Text(
'审核中',
style: AppTextStyles.labelMedium(context).copyWith(
color: AppColorScheme.warning,
),
),
const SizedBox(width: 8),
Icon(
LucideIcons.chevronRight,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
],
);
}
),
const SizedBox(width: 8),
Icon(
LucideIcons.chevronRight,
size: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
],
);
}
return Icon(
LucideIcons.chevronRight,
@@ -100,9 +95,8 @@ class RedDotIndicator extends StatelessWidget {
width: 8,
height: 8,
decoration: BoxDecoration(
color: AppColorScheme.down,
shape: BoxShape.circle,
),
color: AppColorScheme.down,
shape: BoxShape.circle,
),
const SizedBox(width: 8),
Icon(
@@ -135,8 +129,8 @@ class DarkModeRow extends StatelessWidget {
height: 36,
decoration: BoxDecoration(
color: isDark
? colorScheme.surfaceContainerHigh
: colorScheme.surfaceContainerHighest,
? colorScheme.surfaceContainerHigh
: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(8),
),
child: Center(
@@ -151,14 +145,13 @@ class DarkModeRow extends StatelessWidget {
Expanded(
child: Text(
'深色模式',
style: GoogleFonts.inter(
fontSize: 14,
fontWeight: FontWeight.w500,
color: colorScheme.onSurface,
),
style: AppTextStyles.headlineMedium(context),
),
),
// Toggle switch - matching .pen design (44x24 rounded pill)
// thumb
custom radius
12)
GestureDetector(
onTap: () => themeProvider.toggleTheme(),
child: AnimatedContainer(
@@ -168,28 +161,26 @@ class DarkModeRow extends StatelessWidget {
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: isDark
? colorScheme.surfaceContainerHigh
: colorScheme.surfaceContainerHighest,
? colorScheme.surfaceContainerHigh
: colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12),
),
child: AnimatedAlign(
duration: const Duration(milliseconds: 200),
alignment:
themeProvider.isDarkMode
? Alignment.centerRight
: Alignment.centerLeft,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: colorScheme.onSurface,
shape: BoxShape.circle,
),
child: AnimatedAlign(
duration: const Duration(milliseconds: 200),
alignment:
themeProvider.isDarkMode
? Alignment.centerRight : Alignment.centerLeft,
child: Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: colorScheme.onSurface,
shape: BoxShape.circle,
),
),
),
),
],
],
),
),
);
}