import 'dart:async'; 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 '../auth/auth_api.dart'; import '../brand/app_tokens.dart'; import '../directory/directory_api.dart'; import 'state_views.dart'; class AddColleagueLogic extends GetxController { final staffCtrl = TextEditingController(); final msgCtrl = TextEditingController(text: '你好,我想加你为同事'); final sending = false.obs; final staffError = RxnString(); final directoryHint = RxnString(); final loading = true.obs; final loadFailed = false.obs; final _directory = DirectoryApi(); Timer? _lookupDebounce; @override void onReady() { reload(); super.onReady(); } @override void onClose() { _lookupDebounce?.cancel(); staffCtrl.dispose(); msgCtrl.dispose(); super.onClose(); } void onStaffChanged(String _) { staffError.value = null; _lookupDebounce?.cancel(); _lookupDebounce = Timer(const Duration(milliseconds: 300), lookupDirectory); } Future reload() async { loading.value = true; loadFailed.value = false; try { await _directory.search(keyword: '', limit: 1); } catch (e) { loadFailed.value = true; } finally { loading.value = false; } } Future lookupDirectory() async { final id = staffCtrl.text.trim(); if (id.isEmpty) { directoryHint.value = null; return; } try { final items = await _directory.search(keyword: id, limit: 5); final hit = items.where((e) => e.userID == id).toList(); if (hit.isNotEmpty) { directoryHint.value = hit.first.nickname; } else if (items.isNotEmpty) { directoryHint.value = items.map((e) => '${e.nickname}(${e.userID})').join('、'); } else { directoryHint.value = null; } } catch (_) { directoryHint.value = null; } } Future send() async { if (sending.value) return; final id = staffCtrl.text.trim(); if (id.isEmpty) { staffError.value = '请输入对方工号'; return; } staffError.value = null; sending.value = true; try { await OpenIM.iMManager.friendshipManager.addFriend( userID: id, reason: msgCtrl.text.trim().isEmpty ? '你好,我想加你为同事' : msgCtrl.text.trim(), ); IMViews.showToast('申请已发送,等对方通过'); Get.back(); } on AuthException catch (e) { IMViews.showToast(e.message); } catch (_) { IMViews.showToast('发送失败,请确认工号正确、网络正常'); } finally { sending.value = false; } } } class AddColleaguePage extends StatelessWidget { const AddColleaguePage({super.key}); @override Widget build(BuildContext context) { final logic = Get.find(); return Scaffold( backgroundColor: AppColors.pageBg, appBar: AppBar( title: const Text('添加同事'), leading: IconButton( icon: const Icon(Icons.arrow_back_ios_new, size: AppIconSize.md), onPressed: Get.back, ), ), body: Obx(() { if (logic.loading.value) { return const CompanyLoadingView(); } if (logic.loadFailed.value) { return CompanyFailView(onRetry: logic.reload); } return Padding( padding: const EdgeInsets.all(AppGap.x4), child: Column( children: [ _box( controller: logic.staffCtrl, hint: '请输入对方工号', height: 48, error: logic.staffError.value, enabled: !logic.sending.value, onChanged: logic.onStaffChanged, ), Obx(() { final hint = logic.directoryHint.value; if (hint == null) return const SizedBox.shrink(); return Padding( padding: const EdgeInsets.only(top: AppGap.x2), child: Align( alignment: Alignment.centerLeft, child: Text(hint, style: const TextStyle( fontSize: AppFont.sub, color: AppColors.textSecondary)), ), ); }), const SizedBox(height: AppGap.x3), _box( controller: logic.msgCtrl, hint: '你好,我想加你为同事', height: 96, maxLines: 4, enabled: !logic.sending.value, ), const Spacer(), Obx(() { final busy = logic.sending.value; return SizedBox( width: double.infinity, height: 48, child: FilledButton( onPressed: busy ? null : logic.send, style: FilledButton.styleFrom( backgroundColor: AppColors.primary, disabledBackgroundColor: AppColors.primary.withOpacity(0.4), shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(AppRadius.control)), ), child: busy ? const SizedBox( width: 16, height: 16, child: CircularProgressIndicator( strokeWidth: 2, color: Colors.white), ) : const Text('发送申请', style: TextStyle( fontSize: AppFont.body, color: Colors.white)), ), ); }), const SizedBox(height: AppGap.x4), ], ), ); }), ); } Widget _box({ required TextEditingController controller, required String hint, required double height, int maxLines = 1, String? error, bool enabled = true, ValueChanged? onChanged, }) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( height: height, padding: const EdgeInsets.symmetric( horizontal: AppGap.x3, vertical: AppGap.x2), decoration: BoxDecoration( color: AppColors.searchBg, borderRadius: BorderRadius.circular(AppRadius.control), border: Border.all( color: error != null ? AppColors.danger : Colors.transparent), ), child: TextField( controller: controller, maxLines: maxLines, enabled: enabled, onChanged: onChanged, style: const TextStyle( fontSize: AppFont.body, color: AppColors.textPrimary), decoration: InputDecoration( isDense: true, border: InputBorder.none, hintText: hint, hintStyle: const TextStyle( fontSize: AppFont.body, color: AppColors.textSecondary), ), ), ), if (error != null) Padding( padding: const EdgeInsets.only(top: AppGap.x1), child: Text(error, style: const TextStyle( fontSize: AppFont.small, color: AppColors.danger)), ), ], ); } }