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:
379
flutter_monisuo/lib/ui/pages/home/profit_analysis_page.dart
Normal file
379
flutter_monisuo/lib/ui/pages/home/profit_analysis_page.dart
Normal file
@@ -0,0 +1,379 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
import '../../../core/theme/app_color_scheme.dart';
|
||||
import '../../../core/theme/app_spacing.dart';
|
||||
import '../../../data/services/asset_service.dart';
|
||||
|
||||
/// 盈亏分析页面 - 月度盈亏日历
|
||||
class ProfitAnalysisPage extends StatefulWidget {
|
||||
const ProfitAnalysisPage({super.key});
|
||||
|
||||
@override
|
||||
State<ProfitAnalysisPage> createState() => _ProfitAnalysisPageState();
|
||||
}
|
||||
|
||||
class _ProfitAnalysisPageState extends State<ProfitAnalysisPage> {
|
||||
late DateTime _currentMonth;
|
||||
Map<String, dynamic>? _profitData;
|
||||
bool _isLoading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_currentMonth = DateTime.now();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadProfit());
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 主题感知颜色
|
||||
// ============================================
|
||||
|
||||
bool get _isDark => Theme.of(context).brightness == Brightness.dark;
|
||||
|
||||
Color get _upColor => AppColorScheme.getUpColor(_isDark);
|
||||
Color get _downColor => AppColorScheme.getDownColor(_isDark);
|
||||
|
||||
Color get _scaffoldBg =>
|
||||
_isDark ? AppColorScheme.darkBackground : AppColorScheme.lightBackground;
|
||||
|
||||
Color get _cardBg => _isDark
|
||||
? AppColorScheme.darkSurfaceContainer
|
||||
: AppColorScheme.lightSurfaceLowest;
|
||||
|
||||
Color get _cardBorder => _isDark
|
||||
? AppColorScheme.darkOutlineVariant.withValues(alpha: 0.15)
|
||||
: AppColorScheme.lightOutlineVariant.withValues(alpha: 0.5);
|
||||
|
||||
// ============================================
|
||||
// 数据加载
|
||||
// ============================================
|
||||
|
||||
Future<void> _loadProfit() async {
|
||||
setState(() => _isLoading = true);
|
||||
try {
|
||||
final assetService = context.read<AssetService>();
|
||||
final response = await assetService.getDailyProfit(
|
||||
year: _currentMonth.year,
|
||||
month: _currentMonth.month,
|
||||
);
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_profitData = response.data;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
} catch (_) {
|
||||
if (mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
}
|
||||
|
||||
void _previousMonth() {
|
||||
setState(() {
|
||||
_currentMonth = DateTime(_currentMonth.year, _currentMonth.month - 1);
|
||||
});
|
||||
_loadProfit();
|
||||
}
|
||||
|
||||
void _nextMonth() {
|
||||
setState(() {
|
||||
_currentMonth = DateTime(_currentMonth.year, _currentMonth.month + 1);
|
||||
});
|
||||
_loadProfit();
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 盈亏数据解析
|
||||
// ============================================
|
||||
|
||||
double? _getDayProfit(int day) {
|
||||
if (_profitData == null) return null;
|
||||
final daily = _profitData!['daily'] as Map<String, dynamic>?;
|
||||
if (daily == null) return null;
|
||||
final dateStr =
|
||||
'${_currentMonth.year}-${_currentMonth.month.toString().padLeft(2, '0')}-${day.toString().padLeft(2, '0')}';
|
||||
final value = daily[dateStr];
|
||||
if (value == null) return null;
|
||||
return (value is num) ? value.toDouble() : double.tryParse(value.toString());
|
||||
}
|
||||
|
||||
double get _monthProfit {
|
||||
if (_profitData == null) return 0;
|
||||
final value = _profitData!['totalProfit'];
|
||||
if (value == null) return 0;
|
||||
return (value is num) ? value.toDouble() : double.tryParse(value.toString()) ?? 0;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// 构建 UI
|
||||
// ============================================
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final now = DateTime.now();
|
||||
final isCurrentMonth =
|
||||
_currentMonth.year == now.year && _currentMonth.month == now.month;
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: _scaffoldBg,
|
||||
appBar: AppBar(
|
||||
title: const Text('盈亏分析'),
|
||||
backgroundColor: _scaffoldBg,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
centerTitle: true,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(AppSpacing.lg),
|
||||
decoration: BoxDecoration(
|
||||
color: _cardBg,
|
||||
borderRadius: BorderRadius.circular(AppRadius.xl),
|
||||
border: Border.all(color: _cardBorder),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// 月度盈亏摘要
|
||||
_buildSummarySection(colorScheme),
|
||||
SizedBox(height: AppSpacing.md),
|
||||
|
||||
// 月份导航
|
||||
_buildMonthNavigation(colorScheme, isCurrentMonth),
|
||||
SizedBox(height: AppSpacing.sm),
|
||||
|
||||
// 星期标题
|
||||
_buildWeekdayHeaders(colorScheme),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
|
||||
// 日历网格
|
||||
if (_isLoading)
|
||||
_buildLoadingIndicator(colorScheme)
|
||||
else
|
||||
..._buildCalendarGrid(
|
||||
colorScheme,
|
||||
now,
|
||||
isCurrentMonth,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 月度盈亏摘要
|
||||
Widget _buildSummarySection(ColorScheme colorScheme) {
|
||||
final isProfit = _monthProfit >= 0;
|
||||
final color = _isLoading ? colorScheme.onSurfaceVariant : (isProfit ? _upColor : _downColor);
|
||||
|
||||
return Column(
|
||||
children: [
|
||||
Text(
|
||||
'月度盈亏',
|
||||
style: AppTextStyles.bodyMedium(context).copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
SizedBox(height: AppSpacing.xs),
|
||||
Text(
|
||||
_isLoading
|
||||
? '--'
|
||||
: '${isProfit ? '+' : ''}${_monthProfit.toStringAsFixed(2)} USDT',
|
||||
style: AppTextStyles.displaySmall(context).copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
color: color,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 月份导航行
|
||||
Widget _buildMonthNavigation(ColorScheme colorScheme, bool isCurrentMonth) {
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
// 上一月
|
||||
GestureDetector(
|
||||
onTap: _previousMonth,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(AppSpacing.xs + 1),
|
||||
decoration: BoxDecoration(
|
||||
color: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.chevronLeft,
|
||||
size: 16,
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
// 当前年月
|
||||
Text(
|
||||
'${_currentMonth.year}年${_currentMonth.month}月',
|
||||
style: AppTextStyles.headlineMedium(context).copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
// 下一月(当前月禁用)
|
||||
GestureDetector(
|
||||
onTap: isCurrentMonth ? null : _nextMonth,
|
||||
child: Container(
|
||||
padding: EdgeInsets.all(AppSpacing.xs + 1),
|
||||
decoration: BoxDecoration(
|
||||
color: isCurrentMonth
|
||||
? colorScheme.surfaceContainerHigh.withValues(alpha: 0.5)
|
||||
: colorScheme.surfaceContainerHigh,
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
),
|
||||
child: Icon(
|
||||
LucideIcons.chevronRight,
|
||||
size: 16,
|
||||
color: isCurrentMonth
|
||||
? colorScheme.onSurfaceVariant.withValues(alpha: 0.4)
|
||||
: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
/// 星期标题行
|
||||
Widget _buildWeekdayHeaders(ColorScheme colorScheme) {
|
||||
return Row(
|
||||
children: ['一', '二', '三', '四', '五', '六', '日'].map((d) {
|
||||
return Expanded(
|
||||
child: Center(
|
||||
child: Text(
|
||||
d,
|
||||
style: AppTextStyles.bodySmall(context).copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
/// 加载指示器
|
||||
Widget _buildLoadingIndicator(ColorScheme colorScheme) {
|
||||
return Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: AppSpacing.xxl),
|
||||
child: Center(
|
||||
child: SizedBox(
|
||||
width: 20,
|
||||
height: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/// 日历网格
|
||||
List<Widget> _buildCalendarGrid(
|
||||
ColorScheme colorScheme,
|
||||
DateTime now,
|
||||
bool isCurrentMonth,
|
||||
) {
|
||||
final firstDayOfMonth = DateTime(_currentMonth.year, _currentMonth.month, 1);
|
||||
final daysInMonth =
|
||||
DateTime(_currentMonth.year, _currentMonth.month + 1, 0).day;
|
||||
final startWeekday = firstDayOfMonth.weekday;
|
||||
|
||||
final List<Widget> rows = [];
|
||||
List<Widget> cells = [];
|
||||
|
||||
// 填充月初空白
|
||||
for (int i = 1; i < startWeekday; i++) {
|
||||
cells.add(const Expanded(child: SizedBox.shrink()));
|
||||
}
|
||||
|
||||
// 填充每天
|
||||
for (int day = 1; day <= daysInMonth; day++) {
|
||||
final profit = _getDayProfit(day);
|
||||
final isToday = isCurrentMonth && day == now.day;
|
||||
final hasProfit = profit != null && profit != 0;
|
||||
|
||||
cells.add(
|
||||
Expanded(
|
||||
child: AspectRatio(
|
||||
aspectRatio: 1,
|
||||
child: Container(
|
||||
margin: EdgeInsets.all(1),
|
||||
decoration: BoxDecoration(
|
||||
color: isToday
|
||||
? colorScheme.primary.withValues(alpha: 0.12)
|
||||
: hasProfit
|
||||
? (profit! > 0
|
||||
? _upColor.withValues(alpha: 0.08)
|
||||
: _downColor.withValues(alpha: 0.08))
|
||||
: Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(AppRadius.sm),
|
||||
border: isToday
|
||||
? Border.all(
|
||||
color: colorScheme.primary.withValues(alpha: 0.4),
|
||||
width: 1,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
'$day',
|
||||
style: AppTextStyles.bodySmall(context).copyWith(
|
||||
fontSize: 10,
|
||||
fontWeight: isToday ? FontWeight.bold : FontWeight.w400,
|
||||
color: isToday
|
||||
? colorScheme.primary
|
||||
: colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
if (hasProfit) ...[
|
||||
SizedBox(height: 1),
|
||||
Text(
|
||||
'${profit! > 0 ? '+' : ''}${profit.abs() < 10 ? profit.toStringAsFixed(2) : profit.toStringAsFixed(1)}',
|
||||
style: TextStyle(
|
||||
fontSize: 7,
|
||||
fontWeight: FontWeight.w600,
|
||||
color: profit > 0 ? _upColor : _downColor,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
if (cells.length == 7) {
|
||||
rows.add(Row(children: cells));
|
||||
cells = [];
|
||||
}
|
||||
}
|
||||
|
||||
// 填充月末空白
|
||||
if (cells.isNotEmpty) {
|
||||
while (cells.length < 7) {
|
||||
cells.add(const Expanded(child: SizedBox.shrink()));
|
||||
}
|
||||
rows.add(Row(children: cells));
|
||||
}
|
||||
|
||||
return rows;
|
||||
}
|
||||
}
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user