This commit is contained in:
2026-10-16 01:25:50 +05:30
commit c60ea2d370
89 changed files with 4661 additions and 0 deletions
+211
View File
@@ -0,0 +1,211 @@
import 'dart:async';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'api/support_api.dart';
import 'app_scope.dart';
import 'inbox.dart';
import 'push.dart';
import 'screens/inbox_screen.dart';
import 'screens/login_screen.dart';
import 'screens/thread_screen.dart';
import 'session.dart';
class KickKingsApp extends StatefulWidget {
const KickKingsApp({
super.key,
required this.firebaseReady,
this.session,
this.inbox,
this.push,
this.api,
});
final bool firebaseReady;
final SessionController? session;
final InboxController? inbox;
final PushService? push;
final SupportApi? api;
@override
State<KickKingsApp> createState() => _KickKingsAppState();
}
class _KickKingsAppState extends State<KickKingsApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
final _messengerKey = GlobalKey<ScaffoldMessengerState>();
late final RouteObserver<ModalRoute<void>> _routeObserver;
late final SupportApi _api;
late final SessionController _session;
late final InboxController _inbox;
late final PushService _push;
var _wasSignedIn = false;
var _openScheduled = false;
@override
void initState() {
super.initState();
_routeObserver = RouteObserver<ModalRoute<void>>();
_api = widget.api ?? SupportApi();
_session =
widget.session ?? SessionController(store: SessionStore(), api: _api);
_inbox = widget.inbox ?? InboxController(api: _api);
_push = widget.push ?? PushService(firebaseReady: widget.firebaseReady);
_api.onUnauthorized = _session.handleUnauthorized;
_session.addListener(_onSession);
unawaited(_boot());
}
Future<void> _boot() async {
await _session.restore();
await _push.listen(
onOpenTicket: _rememberTicket,
onForegroundMessage: _onForegroundMessage,
onToken: _registerToken,
isSignedIn: () => _session.isSignedIn,
);
if (_session.isSignedIn) {
await _registerCurrentDevice();
}
_openPendingTicket();
}
void _onSession() {
final signedIn = _session.isSignedIn;
if (signedIn && !_wasSignedIn) {
unawaited(_registerCurrentDevice());
_openPendingTicket();
}
if (!signedIn && _wasSignedIn) {
_inbox.reset();
WidgetsBinding.instance.addPostFrameCallback((_) {
_navigatorKey.currentState?.popUntil((route) => route.isFirst);
});
}
_wasSignedIn = signedIn;
}
Future<void> _registerCurrentDevice() async {
if (!_session.isSignedIn) {
return;
}
try {
await _push.register(_registerToken);
} on Object {
// A denied permission or a missing Firebase config leaves the inbox usable.
}
}
Future<void> _registerToken(String token, String platform) async {
if (!_session.isSignedIn) {
return;
}
await _api.registerDevice(token: token, platform: platform);
await _session.saveFcmToken(token);
}
void _onForegroundMessage(RemoteMessage message) {
if (!_session.isSignedIn) {
return;
}
unawaited(_inbox.refreshList());
final ticketId = message.data['ticketId'];
if (ticketId is String &&
ticketId.isNotEmpty &&
ticketId == _inbox.activeTicketId) {
unawaited(_inbox.refreshThread());
}
final title = message.notification?.title;
if (title != null && title.isNotEmpty) {
_messengerKey.currentState?.showSnackBar(SnackBar(content: Text(title)));
}
}
void _rememberTicket(String ticketId) {
_session.pendingTicketId = ticketId;
_openPendingTicket();
}
void _openPendingTicket() {
if (_openScheduled) {
return;
}
final ticketId = _session.pendingTicketId;
if (ticketId == null || !_session.isSignedIn) {
return;
}
_openScheduled = true;
WidgetsBinding.instance.addPostFrameCallback((_) {
_openScheduled = false;
if (!mounted ||
_session.pendingTicketId != ticketId ||
!_session.isSignedIn) {
return;
}
if (_inbox.activeTicketId == ticketId) {
_session.pendingTicketId = null;
unawaited(_inbox.refreshThread());
return;
}
_session.pendingTicketId = null;
_navigatorKey.currentState?.push(
MaterialPageRoute<void>(
builder: (_) => ThreadScreen(ticketId: ticketId),
),
);
});
}
@override
void dispose() {
_session.removeListener(_onSession);
super.dispose();
}
@override
Widget build(BuildContext context) {
return AppScope(
session: _session,
inbox: _inbox,
routeObserver: _routeObserver,
child: MaterialApp(
title: 'Kick Kings',
navigatorKey: _navigatorKey,
scaffoldMessengerKey: _messengerKey,
navigatorObservers: [_routeObserver],
theme: _theme(Brightness.light),
darkTheme: _theme(Brightness.dark),
home: ListenableBuilder(
listenable: _session,
builder: (context, _) {
if (!_session.ready) {
return const Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
if (!_session.isSignedIn) {
return const LoginScreen();
}
return const InboxScreen();
},
),
),
);
}
}
ThemeData _theme(Brightness brightness) {
final scheme = ColorScheme.fromSeed(
seedColor: const Color(0xFF0F6B4C),
brightness: brightness,
);
return ThemeData(
colorScheme: scheme,
useMaterial3: true,
appBarTheme: AppBarTheme(
backgroundColor: scheme.surface,
foregroundColor: scheme.onSurface,
),
);
}