feat: integrate invite API and improve notification handling
- Add invite code display and binding functionality via API - Fix notification unread count sync on auth state change - Improve notification mark read with server state validation - Add auth state listener to trigger notification refresh - Add YaoCoinConverter for coin-to-yao type conversion - Remove YaoLegend from divination screens (UI cleanup) - Abbreviate relation labels in yao detail view - Add re-register notice to account delete screen - Update 'coins' terminology to 'points' in localization - Fix backend points consumption to only run in CHAT mode - Add HttpxAuthNoiseFilter to suppress auth endpoint logging - Fix notification static_schema import path - Add test coverage for notification bloc error handling - Update AGENTS.md page header rules and image handling - Delete deprecated run-dev.sh script
This commit is contained in:
@@ -1,44 +1,125 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import '../../../../core/logging/logger.dart';
|
||||
import '../../../../l10n/app_localizations.dart';
|
||||
import '../../../../shared/theme/app_color_palette.dart';
|
||||
import '../../../../shared/theme/design_tokens.dart';
|
||||
import '../../../../shared/widgets/toast/toast.dart';
|
||||
import '../../../../shared/widgets/toast/toast_type.dart';
|
||||
import '../../data/repositories/invite_repository.dart';
|
||||
|
||||
class InviteScreen extends StatefulWidget {
|
||||
const InviteScreen({super.key});
|
||||
const InviteScreen({super.key, required this.inviteRepository});
|
||||
|
||||
final InviteRepository inviteRepository;
|
||||
|
||||
@override
|
||||
State<InviteScreen> createState() => _InviteScreenState();
|
||||
}
|
||||
|
||||
class _InviteScreenState extends State<InviteScreen> {
|
||||
final Logger _logger = getLogger('features.settings.invite_screen');
|
||||
final _bindCodeController = TextEditingController();
|
||||
final _formKey = GlobalKey<FormState>();
|
||||
|
||||
bool _isBinding = false;
|
||||
bool _isGenerating = false;
|
||||
bool _isLoading = true;
|
||||
bool _hasError = false;
|
||||
|
||||
// Mock data - will be replaced with API calls
|
||||
final String _myInviteCode = 'ABC123';
|
||||
final int _invitedCount = 3;
|
||||
String? _myInviteCode;
|
||||
int _invitedCount = 0;
|
||||
final bool _hasInviter = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadInviteCode();
|
||||
}
|
||||
|
||||
Future<void> _loadInviteCode() async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_hasError = false;
|
||||
});
|
||||
try {
|
||||
final result = await widget.inviteRepository.getMyInviteCode();
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_myInviteCode = result.code;
|
||||
_invitedCount = result.usedCount;
|
||||
_isLoading = false;
|
||||
});
|
||||
} catch (error, stackTrace) {
|
||||
_logger.error(
|
||||
message: 'Failed to load invite code',
|
||||
error: error,
|
||||
stackTrace: stackTrace,
|
||||
);
|
||||
if (!mounted) return;
|
||||
setState(() {
|
||||
_hasError = true;
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_bindCodeController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
bool get _hasMyInviteCode => _myInviteCode.isNotEmpty;
|
||||
bool get _hasMyInviteCode =>
|
||||
_myInviteCode != null && _myInviteCode!.isNotEmpty;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final colors = Theme.of(context).colorScheme;
|
||||
|
||||
if (_isLoading) {
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settingsInviteTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: const Center(child: CircularProgressIndicator()),
|
||||
);
|
||||
}
|
||||
|
||||
if (_hasError) {
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
title: Text(l10n.settingsInviteTitle),
|
||||
centerTitle: true,
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
surfaceTintColor: colors.surfaceContainerLow,
|
||||
),
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text(
|
||||
l10n.settingsInviteEmptyTitle,
|
||||
style: Theme.of(context).textTheme.bodyLarge,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.md),
|
||||
FilledButton(
|
||||
onPressed: _loadInviteCode,
|
||||
child: const Text('Retry'),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
backgroundColor: colors.surfaceContainerLow,
|
||||
appBar: AppBar(
|
||||
@@ -51,7 +132,10 @@ class _InviteScreenState extends State<InviteScreen> {
|
||||
padding: const EdgeInsets.all(AppSpacing.lg),
|
||||
children: [
|
||||
if (_hasMyInviteCode) ...[
|
||||
_InviteCodeCard(inviteCode: _myInviteCode, onCopy: _copyInviteCode),
|
||||
_InviteCodeCard(
|
||||
inviteCode: _myInviteCode!,
|
||||
onCopy: _copyInviteCode,
|
||||
),
|
||||
const SizedBox(height: AppSpacing.lg),
|
||||
_InviteStatsCard(count: _invitedCount),
|
||||
const SizedBox(height: AppSpacing.xl),
|
||||
@@ -79,7 +163,7 @@ class _InviteScreenState extends State<InviteScreen> {
|
||||
|
||||
void _copyInviteCode() {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
Clipboard.setData(ClipboardData(text: _myInviteCode));
|
||||
Clipboard.setData(ClipboardData(text: _myInviteCode!));
|
||||
Toast.show(
|
||||
context,
|
||||
l10n.settingsInviteCopySuccess,
|
||||
|
||||
Reference in New Issue
Block a user