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,10 @@
import 'package:get/get.dart';
import 'friend_list_logic.dart';
class FriendListBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => FriendListLogic());
}
}
@@ -0,0 +1,109 @@
import 'dart:async';
import 'package:azlistview/azlistview.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim/routes/app_navigator.dart';
import 'package:openim_common/openim_common.dart';
import '../../../core/controller/im_controller.dart';
class FriendListLogic extends GetxController {
final imLoic = Get.find<IMController>();
final friendList = <ISUserInfo>[].obs;
final userIDList = <String>[];
late StreamSubscription delSub;
late StreamSubscription addSub;
late StreamSubscription infoChangedSub;
int _count = 10000;
@override
void onInit() {
delSub = imLoic.friendDelSubject.listen(_delFriend);
addSub = imLoic.friendAddSubject.listen(_addFriend);
infoChangedSub = imLoic.friendInfoChangedSubject.listen(_friendInfoChanged);
imLoic.onBlacklistAdd = _delFriend;
imLoic.onBlacklistDeleted = _addFriend;
super.onInit();
}
@override
void onReady() {
_getFriendList();
super.onReady();
}
@override
void onClose() {
delSub.cancel();
addSub.cancel();
infoChangedSub.cancel();
super.onClose();
}
_getFriendList() async {
List<FriendInfo> list = [];
for (int i = 0;; i++) {
final temp = await OpenIM.iMManager.friendshipManager.getFriendListPage(
offset: list.length,
count: _count,
filterBlack: true,
);
list.addAll(temp);
if (temp.length < _count) {
break;
}
_count = 1000;
}
final result = list.map((e) {
userIDList.add(e.userID!);
return ISUserInfo.fromJson(e.toJson());
}).toList();
final convertResult = IMUtils.convertToAZList(result);
onUserIDList(userIDList);
friendList.assignAll(convertResult.cast<ISUserInfo>());
}
void onUserIDList(List<String> userIDList) {}
_addFriend(dynamic user) {
if (user is FriendInfo || user is BlacklistInfo) {
_addUser(user.toJson());
}
}
_delFriend(dynamic user) {
if (user is FriendInfo || user is BlacklistInfo) {
friendList.removeWhere((e) => e.userID == user.userID);
}
}
_friendInfoChanged(FriendInfo user) {
friendList.removeWhere((e) => e.userID == user.userID);
_addUser(user.toJson());
}
void _addUser(Map<String, dynamic> json) {
final info = ISUserInfo.fromJson(json);
friendList.add(IMUtils.setAzPinyinAndTag(info) as ISUserInfo);
SuspensionUtil.sortListBySuspensionTag(friendList);
SuspensionUtil.setShowSuspensionStatus(friendList);
}
void viewFriendInfo(ISUserInfo info) => AppNavigator.startUserProfilePane(
userID: info.userID!,
nickname: info.nickname,
faceURL: info.faceURL,
);
void searchFriend() => AppNavigator.startSearchFriend();
}
@@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'friend_list_logic.dart';
class FriendListPage extends StatelessWidget {
final logic = Get.find<FriendListLogic>();
FriendListPage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: TitleBar.back(title: StrRes.myFriend),
backgroundColor: Styles.c_F8F9FA,
body: Column(
children: [
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: logic.searchFriend,
child: Container(
color: Styles.c_FFFFFF,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
child: const SearchBox(),
),
),
Flexible(
child: Obx(
() => WrapAzListView<ISUserInfo>(
data: logic.friendList,
itemCount: logic.friendList.length,
itemBuilder: (_, data, index) => _buildItemView(data),
),
),
),
],
),
);
}
Widget _buildItemView(ISUserInfo info) => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: () => logic.viewFriendInfo(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
AvatarView(
url: info.faceURL,
text: info.showName,
),
10.horizontalSpace,
info.showName.toText..style = Styles.ts_0C1C33_17sp,
],
),
),
),
);
}
@@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'search_friend_logic.dart';
class SearchFriendBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => SearchFriendLogic());
}
}
@@ -0,0 +1,53 @@
import 'package:flutter/material.dart';
import 'package:flutter_openim_sdk/flutter_openim_sdk.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import '../../../../routes/app_navigator.dart';
import '../friend_list_logic.dart';
class SearchFriendLogic extends GetxController {
final logic = Get.find<FriendListLogic>();
final focusNode = FocusNode();
final searchCtrl = TextEditingController();
final resultList = <ISUserInfo>[].obs;
late bool isMultiModel;
@override
void onInit() {
searchCtrl.addListener(_clearInput);
super.onInit();
}
@override
void onClose() {
focusNode.dispose();
searchCtrl.dispose();
super.onClose();
}
bool get isSearchNotResult => searchCtrl.text.trim().isNotEmpty && resultList.isEmpty;
_clearInput() {
final key = searchCtrl.text.trim();
if (key.isEmpty) {
resultList.clear();
}
}
search() async {
var key = searchCtrl.text.trim();
resultList.clear();
if (key.isNotEmpty) {
final result = await OpenIM.iMManager.friendshipManager
.searchFriends(keywordList: [key], isSearchNickname: true, isSearchRemark: true, isSearchUserID: true);
final users = result.map((e) => ISUserInfo.fromJson(e.toJson())).toList();
resultList.addAll(users);
}
}
viewFriendInfo(ISUserInfo info) => AppNavigator.startUserProfilePane(
userID: info.userID!,
offAndToNamed: true,
);
}
@@ -0,0 +1,71 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:openim_common/openim_common.dart';
import 'package:search_keyword_text/search_keyword_text.dart';
import 'search_friend_logic.dart';
class SearchFriendPage extends StatelessWidget {
final logic = Get.find<SearchFriendLogic>();
SearchFriendPage({super.key});
@override
Widget build(BuildContext context) {
return TouchCloseSoftKeyboard(
child: Scaffold(
appBar: TitleBar.search(
focusNode: logic.focusNode,
controller: logic.searchCtrl,
onSubmitted: (_) => logic.search(),
onCleared: () => logic.focusNode.requestFocus(),
),
backgroundColor: Styles.c_F8F9FA,
body: Obx(() => logic.isSearchNotResult
? _emptyListView
: ListView.builder(
itemCount: logic.resultList.length,
itemBuilder: (_, index) => _buildItemView(logic.resultList[index]),
)),
),
);
}
Widget _buildItemView(ISUserInfo info) => Ink(
height: 64.h,
color: Styles.c_FFFFFF,
child: InkWell(
onTap: () => logic.viewFriendInfo(info),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w),
child: Row(
children: [
AvatarView(
url: info.faceURL,
text: info.showName,
),
10.horizontalSpace,
SearchKeywordText(
text: info.showName,
keyText: logic.searchCtrl.text.trim(),
style: Styles.ts_0C1C33_17sp,
keyStyle: Styles.ts_0089FF_17sp,
),
],
),
),
),
);
Widget get _emptyListView => SizedBox(
width: 1.sw,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
44.verticalSpace,
StrRes.searchNotFound.toText..style = Styles.ts_8E9AB0_17sp,
],
),
);
}