user_friend_add_page.dart 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. import 'dart:async';
  2. import 'dart:convert';
  3. import 'package:flutter/cupertino.dart';
  4. import 'package:flutter/material.dart' hide NestedScrollView;
  5. import 'package:flutter/services.dart';
  6. import 'package:flutter_easyrefresh/easy_refresh.dart';
  7. import 'package:get_it/get_it.dart';
  8. import 'package:permission_handler/permission_handler.dart';
  9. import 'package:qrscanner/flutter_plugin_qr_scanner.dart';
  10. import 'package:provider/provider.dart';
  11. import 'package:qr_flutter/qr_flutter.dart';
  12. import 'package:sport/bean/post_user.dart';
  13. import 'package:sport/bean/user_friend.dart';
  14. import 'package:sport/bean/user_info.dart';
  15. // import 'package:sport/pages/social/qr_view.dart';
  16. import 'package:sport/pages/social/user_detail_page.dart';
  17. import 'package:sport/provider/lib/provider_widget.dart';
  18. import 'package:sport/provider/lib/simple_model.dart';
  19. import 'package:sport/provider/lib/view_state_lifecycle.dart';
  20. import 'package:sport/provider/user_model.dart';
  21. import 'package:sport/router/navigator_util.dart';
  22. import 'package:sport/services/api/inject_api.dart';
  23. import 'package:sport/services/api/resp.dart';
  24. import 'package:sport/services/api/rest_client.dart';
  25. import 'package:sport/utils/click.dart';
  26. import 'package:sport/utils/toast.dart';
  27. import 'package:sport/widgets/appbar.dart';
  28. import 'package:sport/widgets/dialog/request_dialog.dart';
  29. import 'package:sport/widgets/dialog/scan_add_new_friend_dialog.dart';
  30. import 'package:sport/widgets/error.dart';
  31. import 'package:sport/widgets/image.dart';
  32. import 'package:sport/widgets/loading.dart';
  33. import 'package:sport/widgets/misc.dart';
  34. class UserFriendAddPage extends StatefulWidget {
  35. @override
  36. State<StatefulWidget> createState() => _PageState();
  37. }
  38. class _PageState extends ViewStateLifecycle<UserFriendAddPage, SimpleModel> with InjectApi {
  39. late TextEditingController _controller;
  40. late FocusNode _focusNode;
  41. ValueNotifier<String> _searchValue = ValueNotifier<String>("");
  42. var userCode; // 用户自己的二维码
  43. UserInfo? _newFriendUserInfo; // 扫码出来的那个逼的userInfo(请求回来的)
  44. // int dialogType; // 弹窗类型
  45. String? newFriendCode; // 扫码出来的要加的那个逼的二维码
  46. List<UserFriend> allUserFriends = []; // 等待添加的好友队列
  47. bool isWaiting = false; // 上锁解决
  48. Timer? timerIsFriend; // 轮询是否是好友
  49. int currentIndex = 0; // 不要用栈的方法 用指针的方法...
  50. @override
  51. SimpleModel createModel() => SimpleModel((page) async {
  52. if (_searchValue.value == "") return [];
  53. return (await api.userSearch(kw: _searchValue.value, page: page)).pageResult.results ?? [];
  54. });
  55. // // 试试 写写 ViewModel
  56. // SimpleDataModel friendModel() => SimpleDataModel<NewFriend>((code) async {
  57. // return (await api.getNewFriend(code)).data;
  58. // });
  59. @override
  60. void initState() {
  61. super.initState();
  62. _focusNode = FocusNode();
  63. _controller = new TextEditingController(text: '');
  64. initFriendCode();
  65. pollGetFriendRequest();
  66. _showConfirmDialog();
  67. }
  68. @override
  69. void dispose() {
  70. super.dispose();
  71. _focusNode.dispose();
  72. _controller.dispose();
  73. timerIsFriend?.cancel();
  74. }
  75. // 初始化 好友码...
  76. initFriendCode() async {
  77. var code = await api.getNewFriendCode();
  78. setState(() {
  79. userCode = jsonEncode(code.data);
  80. });
  81. }
  82. void _showConfirmDialog() async {
  83. if (_newFriendUserInfo != null) {
  84. // _newFriendUserInfo = (await GetIt.I<RestClient>().getUserInfo("10")).data;
  85. await showDialog(
  86. context: context,
  87. barrierDismissible: false,
  88. builder: (context) =>
  89. ScanAddFriendCustomAlertDialog(
  90. userInfo: _newFriendUserInfo!,
  91. newFriendCode: newFriendCode,
  92. ));
  93. }
  94. }
  95. void pollGetFriendRequest() async {
  96. // 获取 userFriends
  97. timerIsFriend = Timer.periodic(Duration(seconds: 2), (timer) async {
  98. List<UserFriend> userFriends = (await api.getFriendRequest()).results;
  99. if (userFriends.length > 0) {
  100. if (allUserFriends.length <= 0) {
  101. allUserFriends.addAll(userFriends);
  102. } else {
  103. Iterable<int> allUserFriendsId = allUserFriends.map((e) => e.socialInfo!.id!);
  104. // 去重...
  105. for (UserFriend _userfriend in userFriends) {
  106. if (!allUserFriendsId.contains(_userfriend.socialInfo!.id!)) {
  107. allUserFriends.add(_userfriend);
  108. }
  109. }
  110. }
  111. }
  112. if (!isWaiting && allUserFriends.length > 0 && currentIndex < allUserFriends.length) {
  113. isWaiting = true; //正在等待的意思...
  114. UserFriend currentUserFriend = allUserFriends[currentIndex];
  115. if (currentUserFriend.socialInfo != null) {
  116. bool? flag = await showDialog(
  117. context: context,
  118. barrierDismissible: false,
  119. builder: (context) => ScanAddFriendCustomAlertDialog(
  120. userInfo: currentUserFriend.socialInfo!,
  121. isWaitAdd: true,
  122. ));
  123. if (flag != null) {
  124. isWaiting = false;
  125. currentIndex += 1;
  126. }
  127. }
  128. }
  129. });
  130. }
  131. @override
  132. Widget build(BuildContext context) {
  133. return Scaffold(
  134. backgroundColor: Colors.white,
  135. appBar: buildAppBar(
  136. context,
  137. title: "二维码名片",
  138. actions: <Widget>[
  139. TextButton(
  140. onPressed: () async {
  141. var p = await Permission.camera.request();
  142. if (!p.isGranted) {
  143. ToastUtil.show("没有相机的使用权限");
  144. return;
  145. }
  146. String code;
  147. // Platform messages may fail, so we use a try/catch PlatformException.
  148. try {
  149. code = await QrScanner.scan(
  150. title: "扫一扫",
  151. laserColor: Theme.of(context).accentColor,
  152. //default #ffff55ff
  153. playBeep: true,
  154. //default false
  155. promptMessage: "将二维码放入框内,即可自动扫描",
  156. errorMsg: "扫描错误",
  157. permissionDeniedText: "请在设置中打开定位权限后继续使用",
  158. messageConfirmText: "确定",
  159. messageCancelText: "取消",
  160. );
  161. } on PlatformException {
  162. code = 'Failed to get qr code.';
  163. }
  164. var data = json.decode(code);
  165. UserInfo? _userInfo = (await api.getUserInfo('${data['uid']}')).data;
  166. setState(() {
  167. _newFriendUserInfo = _userInfo;
  168. newFriendCode = data['code'];
  169. _showConfirmDialog();
  170. });
  171. // ScanConfig scanConfig = ScanConfig();
  172. // SDScan scan =
  173. // SDScan().setScanEventListener((dynamic friendData) async {
  174. // // 扫描之后就去这个逼的 主页 爱关注 不关注的...
  175. // var data = json.decode(friendData);
  176. //
  177. // UserInfo _userInfo =
  178. // (await api.getUserInfo('${data['uid']}')).data;
  179. //
  180. // setState(() {
  181. // _newFriendUserInfo = _userInfo;
  182. // newFriendCode = data['code'];
  183. // _showConfirmDialog();
  184. // });
  185. // });
  186. // scan.startScan(config: scanConfig);
  187. }, child: Row(children: [
  188. Image.asset("lib/assets/img/fiends_image_scanning.png" ,width: 22.0,
  189. height: 22.0,),
  190. Padding(
  191. padding: const EdgeInsets.only(left: 6.0),
  192. child: Text("扫一扫", style: Theme.of(context).textTheme.subtitle1,),
  193. ),
  194. ],),
  195. ),
  196. ],
  197. ),
  198. body: ProviderWidget<SimpleModel>(
  199. model: model,
  200. onModelReady: (model) => model.initData(),
  201. builder: (_, model, __) {
  202. return EasyRefresh.custom(
  203. firstRefresh: false,
  204. onRefresh: () => model.refresh(),
  205. onLoad: () => model.loadMore(),
  206. enableControlFinishRefresh: true,
  207. controller: model.refreshController,
  208. header: buildClassicalHeader(),
  209. footer: buildClassicalFooter(),
  210. slivers: <Widget>[
  211. // SliverPersistentHeader(
  212. // delegate: PersistentHeader(
  213. //// min: 50,
  214. //// max: 50,
  215. // child: Container(
  216. // color: Colors.white,
  217. // child: _searchWidget(context),
  218. // )),
  219. // pinned: true,
  220. // ),
  221. SliverToBoxAdapter(
  222. child: _searchWidget(context),
  223. ),
  224. if (model.isBusy)
  225. SliverToBoxAdapter(
  226. child: RequestLoadingWidget(),
  227. ),
  228. if (model.isEmpty || model.isError)
  229. _searchValue.value != ""
  230. ? SliverToBoxAdapter(
  231. child: RequestErrorWidget(
  232. null,
  233. msg: "暂无相关用户~",
  234. ),
  235. )
  236. : SliverToBoxAdapter(),
  237. if (model.isIdle)
  238. SliverList(
  239. delegate: SliverChildBuilderDelegate(
  240. (context, index) {
  241. return _buildItem(model.list[index]);
  242. },
  243. childCount: model.list.length,
  244. ),
  245. ),
  246. if (!model.isIdle && userCode != null && _searchValue.value == "")
  247. SliverFillRemaining(
  248. child: Container(
  249. // height: double.infinity,
  250. alignment: Alignment.center,
  251. child: Consumer<UserModel>(
  252. builder: (_, model, __) => Center(
  253. child: QrImage(
  254. data: userCode, version: QrVersions.auto, size: 150, gapless: true, embeddedImage: userAvatarProvider("${model.user.avatar}"),),),
  255. ),
  256. )),
  257. ],
  258. );
  259. }),
  260. );
  261. }
  262. Widget _buildItem(UserInfo user) {
  263. Widget child = Row(
  264. children: <Widget>[
  265. CircleAvatar(backgroundColor: Colors.black26,
  266. backgroundImage: userAvatarProvider(user.avatar),
  267. radius: 22,
  268. ),
  269. SizedBox(
  270. width: 8,
  271. ),
  272. Expanded(
  273. child: Column(
  274. crossAxisAlignment: CrossAxisAlignment.start,
  275. children: <Widget>[
  276. Text(
  277. "${user.name}",
  278. style: Theme.of(context).textTheme.headline3,
  279. ),
  280. SizedBox(
  281. height: 4,
  282. ),
  283. Text(
  284. "ID: ${user.id}",
  285. style: Theme.of(context).textTheme.bodyText2!,
  286. ),
  287. ],
  288. ),
  289. ),
  290. user.isFriend()
  291. ? Container(
  292. width: 64,
  293. height: 30,
  294. margin: EdgeInsets.only(left: 8.0),
  295. alignment: Alignment.center,
  296. child: Text(
  297. "已关注",
  298. strutStyle: fixedLine,
  299. style: Theme.of(context).textTheme.bodyText2!.copyWith(color: Theme.of(context).accentColor),
  300. ),
  301. )
  302. : GestureDetector(
  303. child: Container(
  304. width: 64,
  305. height: 30,
  306. margin: EdgeInsets.only(left: 8.0),
  307. alignment: Alignment.center,
  308. child: Text(
  309. "关注",
  310. strutStyle: fixedLine,
  311. style: Theme.of(context).textTheme.bodyText2!.copyWith(color: Theme.of(context).accentColor),
  312. ),
  313. decoration: BoxDecoration(
  314. borderRadius: BorderRadius.circular(20),
  315. border: Border.all(
  316. color: Theme.of(context).accentColor,
  317. width: .5,
  318. ),
  319. ),
  320. ),
  321. onTap: () async {
  322. if (user.isFriend()) return;
  323. await request(context, () async {
  324. var resp = await model.api.userFollow(uid: user.id!).catchError((onError) {});
  325. if (resp.code == 0) {
  326. ToastUtil.show("关注成功");
  327. setState(() {
  328. user.followStatus = "followed";
  329. });
  330. }
  331. });
  332. },
  333. )
  334. ],
  335. );
  336. return Column(
  337. children: <Widget>[
  338. Padding(
  339. padding: const EdgeInsets.all(12.0),
  340. child: InkWell(
  341. onTap: () async {
  342. List<UserFriend> friends = model.list.map((e) => UserFriend(uid: user.id, socialInfo: user, isFriends: user.isFriend() ? "1" : "0")).toList();
  343. await NavigatorUtil.goTransparentPage(
  344. context, (context,_,__) => UserDetailPage(PostUser(id: "${user.id}", name: user.name, avatar: user.avatar), userFriends: friends));
  345. user.followStatus = friends.firstWhere((element) => element.uid == user.id).isFriends == "1" ? "followed" : "none";
  346. setState(() {});
  347. },
  348. child: child),
  349. ),
  350. Divider(
  351. height: 1,
  352. )
  353. ],
  354. );
  355. }
  356. Widget _searchWidget(BuildContext context) {
  357. return Container(
  358. margin: EdgeInsets.fromLTRB(12.0, 11.0, 12.0, 6.0),
  359. height: 35,
  360. padding: EdgeInsets.fromLTRB(12.0, 0, 12.0, 0),
  361. decoration: BoxDecoration(
  362. color: Color(0xffF1F1F1),
  363. shape: BoxShape.rectangle,
  364. borderRadius: BorderRadius.all(Radius.circular(50)),
  365. ),
  366. child: Row(
  367. children: <Widget>[
  368. Image.asset("lib/assets/img/searchbar_icon_search.png"),
  369. SizedBox(
  370. width: 6,
  371. ),
  372. Expanded(
  373. child: CupertinoTextField(
  374. cursorColor: const Color(0xffFFC400),
  375. controller: _controller,
  376. maxLines: 1,
  377. focusNode: _focusNode,
  378. style: Theme.of(context).textTheme.subtitle1,
  379. placeholder: "输入帐号/用户昵称",
  380. placeholderStyle: Theme.of(context).textTheme.bodyText2,
  381. decoration: BoxDecoration( // 文本框装饰
  382. color: Colors.transparent, // 文本框颜色
  383. ),
  384. onChanged: debounceValueChanged((value) {
  385. _searchValue.value = value;
  386. model.initData();
  387. }),
  388. onSubmitted: (value) {
  389. _focusNode.unfocus();
  390. },
  391. ),
  392. ),
  393. Visibility(
  394. visible: _searchValue.value.isNotEmpty == true,
  395. child: GestureDetector(
  396. onTap: () {
  397. _searchValue.value = "";
  398. model.initData();
  399. _controller.clear();
  400. },
  401. child: Padding(
  402. padding: const EdgeInsets.all(8.0),
  403. child: Image.asset("lib/assets/img/searchbar_btn_no.png"),
  404. ),
  405. )),
  406. ],
  407. ),
  408. );
  409. }
  410. }