feat(mobile-next): 导入官方 openim-flutter-demo 固定基线 3.8.3-patch.3 并完成双构建

- 上游: github.com/OpenIMSDK/openim-flutter-demo tag 3.8.3-patch.3
  commit b3dfdb1e8aaeaaf6f0793e10cadd20d5c184a31f
- 并行目录 mobile-next/ 原样导入(含 LICENSE),不覆盖现网 mobile/
- 锁定 Flutter 3.24.5 / Dart 3.5.4 / JDK 17 / Gradle 7.6.3 / AGP 7.3.1
- 实测 Android debug 与 release 构建均成功(见 docs/mobile-next-baseline-import.md)
- 本提交可单独回退:git revert 1db1229(重写前)

Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
编码工程师
2026-08-19 15:24:33 +08:00
co-authored by multica-agent
parent fa8584a73f
commit 8046ec7483
767 changed files with 57658 additions and 0 deletions
@@ -0,0 +1,18 @@
import 'package:get/get.dart';
import '../contacts/contacts_logic.dart';
import '../conversation/conversation_logic.dart';
import '../mine/mine_logic.dart';
import '../discover/discover_logic.dart';
import 'home_logic.dart';
class HomeBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => HomeLogic());
Get.lazyPut(() => ConversationLogic());
Get.lazyPut(() => ContactsLogic());
Get.lazyPut(() => MineLogic());
Get.lazyPut(() => DiscoverLogic());
}
}
+198
View File
@@ -0,0 +1,198 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:flutter_screen_lock/flutter_screen_lock.dart';
import 'package:get/get.dart';
import 'package:local_auth/local_auth.dart';
import 'package:openim_common/openim_common.dart';
import 'package:rxdart/rxdart.dart';
import '../../core/controller/app_controller.dart';
import '../../core/controller/im_controller.dart';
import '../../core/im_callback.dart';
import '../../routes/app_navigator.dart';
import '../../widgets/screen_lock_title.dart';
class HomeLogic extends SuperController {
final pushLogic = Get.find<PushController>();
final imLogic = Get.find<IMController>();
final cacheLogic = Get.find<CacheController>();
final initLogic = Get.find<AppController>();
final index = 0.obs;
final unreadMsgCount = 0.obs;
final unhandledFriendApplicationCount = 0.obs;
final unhandledGroupApplicationCount = 0.obs;
final unhandledCount = 0.obs;
String? _lockScreenPwd;
bool _isShowScreenLock = false;
bool? _isAutoLogin;
final auth = LocalAuthentication();
final _errorController = PublishSubject<String>();
var conversationsAtFirstPage = <ConversationInfo>[];
Function()? onScrollToUnreadMessage;
switchTab(index) {
this.index.value = index;
}
scrollToUnreadMessage() {
onScrollToUnreadMessage?.call();
}
_getUnreadMsgCount() {
OpenIM.iMManager.conversationManager.getTotalUnreadMsgCount().then((count) {
unreadMsgCount.value = int.tryParse(count) ?? 0;
initLogic.showBadge(unreadMsgCount.value);
});
}
void getUnhandledFriendApplicationCount() async {
var i = 0;
var list = await OpenIM.iMManager.friendshipManager.getFriendApplicationListAsRecipient();
var haveReadList = DataSp.getHaveReadUnHandleFriendApplication();
haveReadList ??= <String>[];
for (var info in list) {
var id = IMUtils.buildFriendApplicationID(info);
if (!haveReadList.contains(id)) {
if (info.handleResult == 0) i++;
}
}
unhandledFriendApplicationCount.value = i;
unhandledCount.value = unhandledGroupApplicationCount.value + i;
}
void getUnhandledGroupApplicationCount() async {
var i = 0;
var list = await OpenIM.iMManager.groupManager.getGroupApplicationListAsRecipient();
var haveReadList = DataSp.getHaveReadUnHandleGroupApplication();
haveReadList ??= <String>[];
for (var info in list) {
var id = IMUtils.buildGroupApplicationID(info);
if (!haveReadList.contains(id)) {
if (info.handleResult == 0) i++;
}
}
unhandledGroupApplicationCount.value = i;
unhandledCount.value = unhandledFriendApplicationCount.value + i;
}
@override
void onInit() {
_isAutoLogin = Get.arguments != null ? Get.arguments['isAutoLogin'] : false;
if (_isAutoLogin == true) {
WidgetsBinding.instance.addPostFrameCallback((_) => _showLockScreenPwd());
}
if (Get.arguments != null) {
conversationsAtFirstPage = Get.arguments['conversations'] ?? [];
}
imLogic.unreadMsgCountEventSubject.listen((value) {
unreadMsgCount.value = value;
});
imLogic.friendApplicationChangedSubject.listen((value) {
getUnhandledFriendApplicationCount();
});
imLogic.groupApplicationChangedSubject.listen((value) {
getUnhandledGroupApplicationCount();
});
imLogic.imSdkStatusPublishSubject.listen((value) {
if (value.status == IMSdkStatus.syncStart) {
_getRTCInvitationStart();
}
});
Apis.kickoffController.stream.listen((event) {
DataSp.removeLoginCertificate();
PushController.logout();
AppNavigator.startLogin();
});
super.onInit();
}
@override
void onReady() {
_getRTCInvitationStart();
_getUnreadMsgCount();
getUnhandledFriendApplicationCount();
getUnhandledGroupApplicationCount();
cacheLogic.initCallRecords();
cacheLogic.initFavoriteEmoji();
super.onReady();
}
@override
void onClose() {
_errorController.close();
super.onClose();
}
_localAuth() async {
final didAuthenticate = await IMUtils.checkingBiometric(auth);
if (didAuthenticate) {
Get.back();
}
}
_showLockScreenPwd() async {
if (_isShowScreenLock) return;
_lockScreenPwd = DataSp.getLockScreenPassword();
if (null != _lockScreenPwd) {
final isEnabledBiometric = DataSp.isEnabledBiometric() == true;
bool enabled = false;
if (isEnabledBiometric) {
final isSupportedBiometrics = await auth.isDeviceSupported();
final canCheckBiometrics = await auth.canCheckBiometrics;
enabled = isSupportedBiometrics && canCheckBiometrics;
}
_isShowScreenLock = true;
screenLock(
context: Get.context!,
correctString: _lockScreenPwd!,
maxRetries: 3,
title: ScreenLockTitle(stream: _errorController.stream),
canCancel: false,
customizedButtonChild: enabled ? const Icon(Icons.fingerprint) : null,
customizedButtonTap: enabled ? () async => await _localAuth() : null,
onUnlocked: () {
_isShowScreenLock = false;
Get.back();
},
onMaxRetries: (_) async {
Get.back();
await LoadingView.singleton.wrap(asyncFunction: () async {
await imLogic.logout();
await DataSp.removeLoginCertificate();
await DataSp.clearLockScreenPassword();
await DataSp.closeBiometric();
PushController.logout();
});
AppNavigator.startLogin();
},
onError: (retries) {
_errorController.sink.add(
retries.toString(),
);
},
);
}
}
@override
void onDetached() {}
@override
void onInactive() {}
@override
void onPaused() {}
@override
void onResumed() {}
void _getRTCInvitationStart() async {}
@override
void onHidden() {}
}
+99
View File
@@ -0,0 +1,99 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../contacts/contacts_view.dart';
import '../conversation/conversation_view.dart';
import '../mine/mine_view.dart';
import '../discover/discover_view.dart';
import 'home_logic.dart';
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
class HomePage extends StatelessWidget {
final logic = Get.find<HomeLogic>();
HomePage({super.key});
List<PersistentTabConfig> _tabs() => [
PersistentTabConfig(
screen: ConversationPage(),
item: ItemConfig(
icon: GestureDetector(
onDoubleTap: () {
logic.scrollToUnreadMessage();
},
child: _setupIcon(ImageRes.homeTab1Sel.toImage, logic.unreadMsgCount.value),
),
inactiveIcon: _setupIcon(ImageRes.homeTab1Nor.toImage, logic.unreadMsgCount.value),
title: StrRes.home,
textStyle: Styles.ts_0089FF_10sp_semibold,
),
),
PersistentTabConfig(
screen: ContactsPage(),
item: ItemConfig(
icon: _setupIcon(ImageRes.homeTab2Sel.toImage, logic.unhandledCount.value),
inactiveIcon: _setupIcon(ImageRes.homeTab2Nor.toImage, logic.unhandledCount.value),
title: StrRes.contacts,
textStyle: Styles.ts_0089FF_10sp_semibold,
),
),
PersistentTabConfig(
screen: DiscoverPage(),
item: ItemConfig(
icon: ImageRes.homeTab3Sel.toImage,
inactiveIcon: ImageRes.homeTab3Nor.toImage,
title: StrRes.workbench,
textStyle: Styles.ts_0089FF_10sp_semibold,
),
),
PersistentTabConfig(
screen: MinePage(),
item: ItemConfig(
icon: ImageRes.homeTab4Sel.toImage,
inactiveIcon: ImageRes.homeTab4Nor.toImage,
title: StrRes.mine,
textStyle: Styles.ts_0089FF_10sp_semibold,
),
),
];
Widget _setupIcon(Widget icon, int unReadCount) {
return Stack(
alignment: Alignment.center,
children: [
icon,
Positioned(
top: 0,
right: 0,
child: Transform.translate(
offset: const Offset(2, -2),
child: UnreadCountView(count: unReadCount),
),
),
],
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Styles.c_FFFFFF,
body: Obx(
() => PersistentTabView(
tabs: _tabs(),
navBarBuilder: (navBarConfig) => Style1BottomNavBar(
navBarConfig: navBarConfig,
navBarDecoration: const NavBarDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(color: Colors.black12, blurRadius: 0.5, spreadRadius: 0.5),
],
),
),
navBarOverlap: const NavBarOverlap.none(),
screenTransitionAnimation: const ScreenTransitionAnimation.none(),
),
),
);
}
}