Files
tongxunruanjian/mobile/lib/main.dart
T
编码工程师andmultica-agent f1fe4a3928 feat(mobile,deploy): 接入安卓极光与华为通道并准备公网部署配置
客户端默认指向 jxd.jinniu.ink 的 HTTPS/WSS;登录后把 OpenIM userID 设为极光 alias。
华为 agconnect-services.json 不伪造、不入库。Master Secret 只从服务器文件注入。

Co-authored-by: multica-agent <github@multica.ai>
2026-08-30 21:32:58 +08:00

124 lines
3.9 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'nav.dart';
import 'screens/call_screen.dart';
import 'screens/home_screen.dart';
import 'screens/login_screen.dart';
import 'services/call_service.dart';
import 'services/im_service.dart';
import 'services/push_service.dart';
import 'theme.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
PushService.instance.init();
runApp(const ChanglianApp());
}
class ChanglianApp extends StatefulWidget {
const ChanglianApp({super.key});
@override
State<ChanglianApp> createState() => _ChanglianAppState();
}
class _ChanglianAppState extends State<ChanglianApp> {
@override
void initState() {
super.initState();
// 被踢下线 / token 失效:清凭证、回登录页
IMService.instance.onForceLogout = () async {
await PushService.instance.unbindUser();
await LoginScreen.clearCredential();
navigatorKey.currentState?.pushAndRemoveUntil(
MaterialPageRoute(builder: (_) => const LoginScreen()),
(route) => false,
);
};
// 来电时弹出通话界面
CallService.instance.onIncomingCall = () {
navigatorKey.currentState?.push(
MaterialPageRoute(builder: (_) => const CallScreen(), fullscreenDialog: true),
);
};
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '畅联',
navigatorKey: navigatorKey,
theme: buildAppTheme(),
debugShowCheckedModeBanner: false,
localizationsDelegates: const [
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [Locale('zh', 'CN')],
locale: const Locale('zh', 'CN'),
home: const SplashGate(),
);
}
}
/// 启动页:初始化 SDK,有本地凭证则自动登录进主页,否则进登录页
class SplashGate extends StatefulWidget {
const SplashGate({super.key});
@override
State<SplashGate> createState() => _SplashGateState();
}
class _SplashGateState extends State<SplashGate> {
@override
void initState() {
super.initState();
_boot();
}
Future<void> _boot() async {
final im = IMService.instance;
// 先读本地凭证:没有凭证(首次打开)直接去登录页,
// 不在启动页等 SDK 初始化——服务器连不上时 initSDK 可能长时间不返回,
// 不能让启动页一直转圈。
final credential = await LoginScreen.readCredential();
if (credential == null) {
if (!mounted) return;
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const LoginScreen()));
return;
}
try {
await im.init().timeout(const Duration(seconds: 20));
await im.login(userID: credential.$1, token: credential.$2).timeout(const Duration(seconds: 20));
CallService.instance.start();
await PushService.instance.bindUser(credential.$1);
if (!mounted) return;
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const HomeScreen()));
return;
} catch (_) {
// 自动登录失败(token 失效、服务器连不上等):清凭证回登录页
await LoginScreen.clearCredential();
}
if (!mounted) return;
Navigator.of(context).pushReplacement(MaterialPageRoute(builder: (_) => const LoginScreen()));
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('畅联', style: TextStyle(fontSize: 32, fontWeight: FontWeight.w600, color: AppColors.primary)),
SizedBox(height: AppGap.x6),
SizedBox(width: 28, height: 28, child: CircularProgressIndicator(strokeWidth: 3, color: AppColors.primary)),
],
),
),
);
}
}