| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- import 'package:flutter/material.dart';
- import 'package:flutter/services.dart';
- import 'package:flutter_riverpod/flutter_riverpod.dart';
- import 'package:go_router/go_router.dart';
- import '../../../core/l10n/app_localizations.dart';
- import '../../../core/theme/app_colors.dart';
- import '../../../core/utils/dialog_utils.dart' show extractErrorMessage;
- import '../../../core/utils/top_toast.dart';
- import '../../../data/repositories/broker_repository.dart';
- final _rewardSetListProvider = FutureProvider.autoDispose<List<Map<String, dynamic>>>((ref) {
- return ref.read(brokerRepositoryProvider).getRewardSetList();
- });
- class MyInvitationsScreen extends ConsumerWidget {
- const MyInvitationsScreen({super.key});
- @override
- Widget build(BuildContext context, WidgetRef ref) {
- final cs = Theme.of(context).colorScheme;
- final listAsync = ref.watch(_rewardSetListProvider);
- return Scaffold(
- backgroundColor: cs.surface,
- appBar: AppBar(
- backgroundColor: cs.surface,
- elevation: 0,
- leading: IconButton(
- icon: const Icon(Icons.arrow_back_ios, size: 18),
- onPressed: () => context.pop(),
- ),
- title: Text(AppLocalizations.of(context)!.myInvitations, style: TextStyle(color: cs.onSurface, fontSize: 17, fontWeight: FontWeight.w600)),
- centerTitle: true,
- ),
- body: listAsync.when(
- loading: () => const Center(child: CircularProgressIndicator()),
- error: (e, _) => Center(child: Text('${AppLocalizations.of(context)!.loadFailed}: $e')),
- data: (list) {
- if (list.isEmpty) {
- return Center(child: Text(AppLocalizations.of(context)!.noInviteRecord, style: TextStyle(color: cs.onSurface.withAlpha(120))));
- }
- return Column(
- children: [
- // 表头
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 10),
- decoration: BoxDecoration(
- color: cs.surfaceContainerHighest.withAlpha(60),
- border: Border(bottom: BorderSide(color: cs.outlineVariant.withAlpha(40))),
- ),
- child: Row(children: [
- Expanded(child: Text(AppLocalizations.of(context)!.accountLabel, style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(130)))),
- SizedBox(width: 80, child: Text(AppLocalizations.of(context)!.levelLabel, style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(130)))),
- SizedBox(width: 50, child: Text(AppLocalizations.of(context)!.perpetual, textAlign: TextAlign.center,
- style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(130)))),
- SizedBox(width: 50, child: Text(AppLocalizations.of(context)!.copyTrading, textAlign: TextAlign.center,
- style: TextStyle(fontSize: 12, color: cs.onSurface.withAlpha(130)))),
- ]),
- ),
- Expanded(
- child: ListView.builder(
- itemCount: list.length,
- itemBuilder: (context, i) => _InvitationRow(item: list[i], onRefresh: () => ref.invalidate(_rewardSetListProvider)),
- ),
- ),
- ],
- );
- },
- ),
- );
- }
- }
- class _InvitationRow extends StatelessWidget {
- const _InvitationRow({required this.item, required this.onRefresh});
- final Map<String, dynamic> item;
- final VoidCallback onRefresh;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- // id = 账号标识(安卓用 id 字段显示账户列)
- final accountId = item['id']?.toString() ?? '--';
- final superPartner = item['superPartner']?.toString() ?? '';
- final l10n = AppLocalizations.of(context)!;
- final levelName = superPartner == '1' ? l10n.brokerLevel : l10n.regularLevel;
- // rate is a plain number from API, append "%"
- final rateVal = item['rate']?.toString() ?? '0';
- final rateStr = rateVal.contains('%') ? rateVal : '$rateVal%';
- final followRate = item['followRate'];
- final followRateStr = (followRate != null && followRate.toString() != '0') ? '$followRate%' : '--';
- return Container(
- padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
- decoration: BoxDecoration(border: Border(bottom: BorderSide(color: cs.outlineVariant.withAlpha(30)))),
- child: Row(children: [
- Expanded(child: Text(accountId, style: TextStyle(fontSize: 14, color: cs.onSurface, fontWeight: FontWeight.w500))),
- SizedBox(
- width: 80,
- child: Row(children: [
- Text(levelName, style: TextStyle(fontSize: 13, color: cs.onSurface)),
- const SizedBox(width: 4),
- GestureDetector(
- onTap: () => _showEditDialog(context, item, onRefresh),
- child: Icon(Icons.edit_outlined, size: 15, color: cs.primary),
- ),
- ]),
- ),
- SizedBox(width: 50, child: Text(rateStr, textAlign: TextAlign.center,
- style: TextStyle(fontSize: 13, color: cs.onSurface))),
- SizedBox(width: 50, child: Text(followRateStr, textAlign: TextAlign.center,
- style: TextStyle(fontSize: 13, color: cs.onSurface))),
- ]),
- );
- }
- void _showEditDialog(BuildContext context, Map<String, dynamic> item, VoidCallback onRefresh) {
- showDialog(
- context: context,
- builder: (_) => _EditRateDialog(item: item, onSuccess: onRefresh),
- );
- }
- }
- class _EditRateDialog extends ConsumerStatefulWidget {
- const _EditRateDialog({required this.item, required this.onSuccess});
- final Map<String, dynamic> item;
- final VoidCallback onSuccess;
- @override
- ConsumerState<_EditRateDialog> createState() => _EditRateDialogState();
- }
- class _EditRateDialogState extends ConsumerState<_EditRateDialog> {
- late TextEditingController _rateCtrl;
- late TextEditingController _wcRateCtrl;
- bool _loading = false;
- @override
- void initState() {
- super.initState();
- final rateRaw = widget.item['rate']?.toString() ?? '';
- _rateCtrl = TextEditingController(text: rateRaw.replaceAll('%', ''));
- _wcRateCtrl = TextEditingController(text: widget.item['followRate']?.toString() ?? '');
- }
- @override
- void dispose() {
- _rateCtrl.dispose();
- _wcRateCtrl.dispose();
- super.dispose();
- }
- Future<void> _submit() async {
- final id = widget.item['id']?.toString() ?? '';
- final rate = int.tryParse(_rateCtrl.text.trim());
- final wcRate = int.tryParse(_wcRateCtrl.text.trim());
- if (rate == null) {
- showTopToast(context, message: AppLocalizations.of(context)!.enterValidPerpRate, backgroundColor: AppColors.fall);
- return;
- }
- setState(() => _loading = true);
- try {
- await ref.read(brokerRepositoryProvider).setReward(memberId: id, rate: rate, followRate: wcRate);
- if (context.mounted) {
- Navigator.of(context).pop();
- widget.onSuccess();
- showTopToast(context, message: AppLocalizations.of(context)!.setSuccess, backgroundColor: AppColors.rise);
- }
- } catch (e) {
- if (context.mounted) {
- setState(() => _loading = false);
- showTopToast(context, message: extractErrorMessage(e), backgroundColor: AppColors.fall);
- }
- }
- }
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- final memberId = widget.item['memberId']?.toString() ?? widget.item['id']?.toString() ?? '--';
- return Dialog(
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
- clipBehavior: Clip.hardEdge,
- insetPadding: const EdgeInsets.symmetric(horizontal: 16),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- // ── Body ──
- Padding(
- padding: const EdgeInsets.fromLTRB(20, 20, 20, 16),
- child: Column(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Row(children: [
- Text(AppLocalizations.of(context)!.editCommissionRate, style: TextStyle(fontSize: 14, color: cs.onSurface.withAlpha(140))),
- const Spacer(),
- GestureDetector(
- onTap: () => Navigator.of(context).pop(),
- child: Icon(Icons.close, size: 20, color: cs.onSurface.withAlpha(140)),
- ),
- ]),
- const SizedBox(height: 6),
- Text(memberId, style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700, color: cs.onSurface)),
- const SizedBox(height: 20),
- _RateInput(controller: _rateCtrl, hint: AppLocalizations.of(context)!.perpRebateRate),
- const SizedBox(height: 10),
- _RateInput(controller: _wcRateCtrl, hint: AppLocalizations.of(context)!.copyRebateRate),
- const SizedBox(height: 8),
- Text(AppLocalizations.of(context)!.commissionRateWarning,
- style: TextStyle(fontSize: 11, color: cs.onSurface.withAlpha(120), height: 1.5)),
- ],
- ),
- ),
- // ── Footer ──
- Divider(height: 1, thickness: 1, color: cs.outlineVariant.withAlpha(60)),
- SizedBox(
- width: double.infinity,
- height: 54,
- child: TextButton(
- style: TextButton.styleFrom(
- backgroundColor: Colors.black,
- foregroundColor: Colors.white,
- shape: const RoundedRectangleBorder(),
- padding: EdgeInsets.zero,
- ),
- onPressed: _loading ? null : _submit,
- child: _loading
- ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white))
- : Text(AppLocalizations.of(context)!.confirm, style: const TextStyle(fontSize: 17, fontWeight: FontWeight.w600)),
- ),
- ),
- ],
- ),
- );
- }
- }
- class _RateInput extends StatelessWidget {
- const _RateInput({required this.controller, required this.hint});
- final TextEditingController controller;
- final String hint;
- @override
- Widget build(BuildContext context) {
- final cs = Theme.of(context).colorScheme;
- return Container(
- height: 56,
- decoration: BoxDecoration(
- color: cs.surfaceContainerHighest.withAlpha(50),
- borderRadius: BorderRadius.circular(12),
- ),
- padding: const EdgeInsets.symmetric(horizontal: 16),
- child: Row(children: [
- Expanded(
- child: TextField(
- controller: controller,
- keyboardType: TextInputType.number,
- style: TextStyle(fontSize: 20, fontWeight: FontWeight.w500, color: cs.onSurface),
- decoration: InputDecoration(
- hintText: hint,
- hintStyle: TextStyle(color: cs.onSurface.withAlpha(80), fontSize: 14, fontWeight: FontWeight.w400),
- border: InputBorder.none,
- enabledBorder: InputBorder.none,
- focusedBorder: InputBorder.none,
- isDense: true,
- contentPadding: EdgeInsets.zero,
- ),
- ),
- ),
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4),
- decoration: BoxDecoration(
- color: cs.onSurface.withAlpha(25),
- borderRadius: BorderRadius.circular(8),
- ),
- child: Text('%', style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: cs.onSurface.withAlpha(200))),
- ),
- ]),
- );
- }
- }
|