212 lines
5.6 KiB
Dart
212 lines
5.6 KiB
Dart
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,
|
|
),
|
|
);
|
|
}
|