feat:参照研听的基础工程
This commit is contained in:
@@ -0,0 +1,43 @@
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
|
||||
import '../../../common/domain/metal_type.dart';
|
||||
import '../data/profile_models.dart';
|
||||
|
||||
final profileSettingsControllerProvider =
|
||||
StateNotifierProvider<ProfileSettingsController, ProfileSettings>(
|
||||
(ref) => ProfileSettingsController(),
|
||||
);
|
||||
|
||||
class ProfileSettingsController extends StateNotifier<ProfileSettings> {
|
||||
ProfileSettingsController()
|
||||
: super(
|
||||
const ProfileSettings(
|
||||
amountHidden: false,
|
||||
recycleDiscounts: {
|
||||
MetalType.gold: 0.97,
|
||||
MetalType.platinum: 0.93,
|
||||
MetalType.silver: 0.88,
|
||||
},
|
||||
purityPresets: {
|
||||
'足金9999': 0.9999,
|
||||
'足金999': 0.999,
|
||||
'PT950': 0.95,
|
||||
'999银': 0.999,
|
||||
},
|
||||
priceColorMode: PriceColorMode.redUpGreenDown,
|
||||
mockLoggedIn: false,
|
||||
),
|
||||
);
|
||||
|
||||
void toggleAmountHidden() {
|
||||
state = state.copyWith(amountHidden: !state.amountHidden);
|
||||
}
|
||||
|
||||
void setMockLoggedIn(bool value) {
|
||||
state = state.copyWith(mockLoggedIn: value);
|
||||
}
|
||||
|
||||
void setPriceColorMode(PriceColorMode mode) {
|
||||
state = state.copyWith(priceColorMode: mode);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import '../../../common/domain/metal_type.dart';
|
||||
|
||||
enum PriceColorMode {
|
||||
redUpGreenDown('红涨绿跌'),
|
||||
greenUpRedDown('绿涨红跌');
|
||||
|
||||
const PriceColorMode(this.label);
|
||||
|
||||
final String label;
|
||||
}
|
||||
|
||||
class ProfileSettings {
|
||||
const ProfileSettings({
|
||||
required this.amountHidden,
|
||||
required this.recycleDiscounts,
|
||||
required this.purityPresets,
|
||||
required this.priceColorMode,
|
||||
required this.mockLoggedIn,
|
||||
});
|
||||
|
||||
final bool amountHidden;
|
||||
final Map<MetalType, double> recycleDiscounts;
|
||||
final Map<String, double> purityPresets;
|
||||
final PriceColorMode priceColorMode;
|
||||
final bool mockLoggedIn;
|
||||
|
||||
ProfileSettings copyWith({
|
||||
bool? amountHidden,
|
||||
Map<MetalType, double>? recycleDiscounts,
|
||||
Map<String, double>? purityPresets,
|
||||
PriceColorMode? priceColorMode,
|
||||
bool? mockLoggedIn,
|
||||
}) {
|
||||
return ProfileSettings(
|
||||
amountHidden: amountHidden ?? this.amountHidden,
|
||||
recycleDiscounts: recycleDiscounts ?? this.recycleDiscounts,
|
||||
purityPresets: purityPresets ?? this.purityPresets,
|
||||
priceColorMode: priceColorMode ?? this.priceColorMode,
|
||||
mockLoggedIn: mockLoggedIn ?? this.mockLoggedIn,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:shadcn_ui/shadcn_ui.dart';
|
||||
|
||||
import '../../../common/router/app_router.dart';
|
||||
import '../../../common/widgets/adaptive.dart';
|
||||
import '../../auth/application/auth_controller.dart';
|
||||
import '../../auth/data/auth_models.dart';
|
||||
import '../../profile/application/profile_settings_controller.dart';
|
||||
|
||||
class ProfilePage extends ConsumerWidget {
|
||||
const ProfilePage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final settings = ref.watch(profileSettingsControllerProvider);
|
||||
final auth = ref.watch(authProvider);
|
||||
final cs = ShadTheme.of(context).colorScheme;
|
||||
final loggedIn = auth.valueOrNull is LoggedInAuthState;
|
||||
final currentUser = auth.valueOrNull is LoggedInAuthState
|
||||
? (auth.valueOrNull as LoggedInAuthState).user
|
||||
: null;
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.fromLTRB(18, 18, 18, 24),
|
||||
children: [
|
||||
Center(
|
||||
child: ConstrainedBox(
|
||||
constraints: const BoxConstraints(
|
||||
maxWidth: Adaptive.contentMaxWidth,
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
_Card(
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
currentUser?.nickname ?? '未登录',
|
||||
style: TextStyle(
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.w700,
|
||||
color: cs.foreground,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 6),
|
||||
Text(
|
||||
currentUser?.phone ?? '登录后可使用本地同步、云备份占位和账户管理。',
|
||||
style: TextStyle(color: cs.mutedForeground),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: () => context.push(AppRoutes.settings),
|
||||
child: const Text('设置'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_Card(
|
||||
child: Column(
|
||||
children: [
|
||||
_SettingRow(
|
||||
label: '隐藏金额',
|
||||
value: settings.amountHidden ? '已隐藏' : '显示中',
|
||||
trailing: Switch(
|
||||
value: settings.amountHidden,
|
||||
onChanged: (_) => ref
|
||||
.read(
|
||||
profileSettingsControllerProvider.notifier,
|
||||
)
|
||||
.toggleAmountHidden(),
|
||||
),
|
||||
),
|
||||
_SettingRow(
|
||||
label: '回收折扣',
|
||||
value:
|
||||
'金 ${(settings.recycleDiscounts.values.first * 100).toStringAsFixed(0)}%',
|
||||
),
|
||||
_SettingRow(
|
||||
label: '涨跌颜色',
|
||||
value: settings.priceColorMode.label,
|
||||
),
|
||||
_SettingRow(
|
||||
label: '纯度系数',
|
||||
value: '${settings.purityPresets.length} 项',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_Card(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
FilledButton.tonal(
|
||||
onPressed: loggedIn
|
||||
? () => context.push(AppRoutes.login)
|
||||
: () => context.push(AppRoutes.login),
|
||||
child: Text(loggedIn ? '切换账号' : '去登录'),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
OutlinedButton(
|
||||
onPressed: () => context.push(AppRoutes.settings),
|
||||
child: const Text('打开设置'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_Card(
|
||||
child: Text(
|
||||
'风险提示 · 数据来源 · 免责\n价格、资讯与估值均为本地 mock 数据,仅用于产品原型验证,不构成投资建议。',
|
||||
style: TextStyle(
|
||||
color: cs.mutedForeground,
|
||||
height: 1.6,
|
||||
fontSize: 13,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _SettingRow extends StatelessWidget {
|
||||
const _SettingRow({required this.label, required this.value, this.trailing});
|
||||
|
||||
final String label;
|
||||
final String value;
|
||||
final Widget? trailing;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = ShadTheme.of(context).colorScheme;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 10),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 15, color: cs.foreground),
|
||||
),
|
||||
),
|
||||
Text(value, style: TextStyle(color: cs.mutedForeground)),
|
||||
if (trailing != null) ...[const SizedBox(width: 8), trailing!],
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _Card extends StatelessWidget {
|
||||
const _Card({required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = ShadTheme.of(context).colorScheme;
|
||||
return DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: cs.card,
|
||||
border: Border.all(color: cs.border),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Padding(padding: const EdgeInsets.all(16), child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user