325 lines
11 KiB
Dart
325 lines
11 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:badges/badges.dart' as badges;
|
|
import 'package:cached_network_image/cached_network_image.dart';
|
|
import 'package:get_it_mixin/get_it_mixin.dart';
|
|
|
|
import '/common/constants.dart';
|
|
import '/models/apply_list_model.dart';
|
|
import '/models/contact_model.dart';
|
|
import '/models/user_model.dart';
|
|
import '/request/server.dart';
|
|
import '/screens/contact/components/friend_group.dart';
|
|
import '/screens/contact/components/friend_tile.dart';
|
|
import '/screens/contact/components/group_chat_tile.dart';
|
|
|
|
class ContactScreen extends StatefulWidget with GetItStatefulWidgetMixin {
|
|
ContactScreen({super.key});
|
|
|
|
@override
|
|
State<ContactScreen> createState() => _ContactScreenState();
|
|
}
|
|
|
|
class _ContactScreenState extends State<ContactScreen> with GetItStateMixin {
|
|
final Map<String, bool> _shows = {
|
|
'friendGroups': true,
|
|
'allFriends': false,
|
|
'groupChats': false
|
|
};
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
// Seems like `watchOnly` cannot watch collecion type.
|
|
int friendCount = watchOnly(
|
|
(Contact contact) => contact.friendCount,
|
|
);
|
|
|
|
int applyCount = watchOnly(
|
|
(ApplyList applyList) => applyList.count,
|
|
);
|
|
|
|
int groupChatCount = watchOnly(
|
|
(Contact contact) => contact.groupChatCount,
|
|
);
|
|
|
|
// Create a localkey, use to generate the custom scroll view,
|
|
// or there will be a error: "Duplicate GlobalKey detected in widget tree."
|
|
// But when I wrap custom scroll view into the RefrashIndicator, the key isn't needed any more
|
|
// const Key customScrollKey = ValueKey<String>('bottom-sliver-list');
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
leading: Container(
|
|
margin: const EdgeInsets.only(left: 8),
|
|
child: CircleAvatar(
|
|
backgroundImage: CachedNetworkImageProvider(
|
|
'$userAvatarsUrl/${get<UserProfile>().avatar}',
|
|
),
|
|
),
|
|
),
|
|
title: const Text('通讯录'),
|
|
centerTitle: true,
|
|
actions: [
|
|
IconButton(
|
|
onPressed: () {},
|
|
icon: const Icon(Icons.search),
|
|
splashRadius: 20,
|
|
),
|
|
IconButton(
|
|
onPressed: () {
|
|
context.pushNamed('SearchNewContact');
|
|
},
|
|
icon: const Icon(Icons.person_add_alt_1),
|
|
splashRadius: 20,
|
|
),
|
|
],
|
|
),
|
|
body: RefreshIndicator(
|
|
onRefresh: () async {
|
|
await Future.delayed(const Duration(seconds: 2));
|
|
},
|
|
child: CustomScrollView(
|
|
physics: const BouncingScrollPhysics(),
|
|
// key: customScrollKey,
|
|
slivers: [
|
|
SliverFixedExtentList(
|
|
delegate: SliverChildListDelegate(
|
|
[
|
|
// apply list
|
|
TextButton(
|
|
onPressed: () async {
|
|
context.pushNamed('ApplyList');
|
|
},
|
|
style: TextButton.styleFrom(
|
|
iconColor: Theme.of(context).textTheme.bodyLarge?.color,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
badges.Badge(
|
|
showBadge: applyCount > 0,
|
|
badgeStyle: const badges.BadgeStyle(
|
|
badgeColor: kErrorColor,
|
|
elevation: 12,
|
|
),
|
|
badgeContent: Text(
|
|
applyCount.toString(),
|
|
style: TextStyle(
|
|
fontSize: 11,
|
|
color:
|
|
Theme.of(context).colorScheme.inversePrimary,
|
|
),
|
|
),
|
|
badgeAnimation: const badges.BadgeAnimation.scale(),
|
|
position:
|
|
badges.BadgePosition.topEnd(top: -12, end: -15),
|
|
child: Text(
|
|
'添加申请',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color:
|
|
Theme.of(context).textTheme.bodyLarge?.color,
|
|
),
|
|
),
|
|
),
|
|
const Icon(
|
|
Icons.keyboard_arrow_right,
|
|
size: 30,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
TextButton(
|
|
onPressed: () async {
|
|
context.pushNamed<int>('ManageGroup');
|
|
},
|
|
style: TextButton.styleFrom(
|
|
iconColor: Theme.of(context).textTheme.bodyLarge?.color,
|
|
),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text(
|
|
'分组管理',
|
|
style: TextStyle(
|
|
fontSize: 16,
|
|
color: Theme.of(context).textTheme.bodyLarge?.color,
|
|
),
|
|
),
|
|
const Icon(
|
|
Icons.keyboard_arrow_right,
|
|
size: 30,
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
itemExtent: 50.0,
|
|
),
|
|
SliverPersistentHeader(
|
|
delegate: _MySliverPersistentHeader(_shows, _change, _refresh),
|
|
pinned: true,
|
|
),
|
|
SliverList(
|
|
delegate: SliverChildBuilderDelegate(
|
|
(BuildContext context, int index) {
|
|
if (_shows['friendGroups']!) {
|
|
String groupName = get<Contact>().friendGroups[index];
|
|
return FriendGroup(
|
|
key: ValueKey(groupName),
|
|
groupName: groupName,
|
|
);
|
|
} else if (_shows['allFriends']!) {
|
|
String friendId =
|
|
get<Contact>().friends.keys.toList()[index];
|
|
// print(friends);
|
|
return FriendTile(
|
|
key: ValueKey(
|
|
friendId,
|
|
),
|
|
friendId: friendId,
|
|
);
|
|
} else {
|
|
String groupChatId =
|
|
get<Contact>().groupChats.keys.toList()[index];
|
|
return GroupChatTile(
|
|
key: ValueKey(groupChatId),
|
|
groupChatId: groupChatId,
|
|
);
|
|
}
|
|
},
|
|
childCount: _shows['friendGroups']!
|
|
? get<Contact>().friendGroups.length
|
|
: _shows['allFriends']!
|
|
? friendCount
|
|
: groupChatCount,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _change(String which) {
|
|
switch (which) {
|
|
case 'friendGroups':
|
|
setState(() {
|
|
_shows['friendGroups'] = true;
|
|
_shows['allFriends'] = false;
|
|
_shows['groupChats'] = false;
|
|
});
|
|
break;
|
|
case 'allFriends':
|
|
setState(() {
|
|
_shows['friendGroups'] = false;
|
|
_shows['allFriends'] = true;
|
|
_shows['groupChats'] = false;
|
|
});
|
|
break;
|
|
case 'groupChats':
|
|
setState(() {
|
|
_shows['friendGroups'] = false;
|
|
_shows['allFriends'] = false;
|
|
_shows['groupChats'] = true;
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
void _refresh() {
|
|
setState(() {});
|
|
}
|
|
}
|
|
|
|
class _MySliverPersistentHeader extends SliverPersistentHeaderDelegate {
|
|
const _MySliverPersistentHeader(this.shows, this.change, this.refresh);
|
|
|
|
final Map<String, bool> shows;
|
|
final Function(String) change;
|
|
final VoidCallback refresh;
|
|
|
|
@override
|
|
double get minExtent => 50;
|
|
|
|
@override
|
|
double get maxExtent => 50;
|
|
|
|
@override
|
|
Widget build(
|
|
BuildContext context,
|
|
double shrinkOffset,
|
|
bool overlapsContent,
|
|
) {
|
|
return Container(
|
|
height: 50,
|
|
color: Theme.of(context).colorScheme.background,
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
InkWell(
|
|
onTap: () {
|
|
change('friendGroups');
|
|
},
|
|
child: Container(
|
|
margin: const EdgeInsets.only(left: 10),
|
|
padding: const EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(4),
|
|
color: shows['friendGroups']!
|
|
? kSecondaryColor.withOpacity(0.15)
|
|
: Theme.of(context).brightness == Brightness.light
|
|
? null
|
|
: Theme.of(context).colorScheme.background,
|
|
),
|
|
child: const Text('分组'),
|
|
),
|
|
),
|
|
InkWell(
|
|
onTap: () {
|
|
change('allFriends');
|
|
},
|
|
child: Container(
|
|
padding: const EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(4),
|
|
color: shows['allFriends']!
|
|
? kSecondaryColor.withOpacity(0.15)
|
|
: Theme.of(context).brightness == Brightness.light
|
|
? null
|
|
: Theme.of(context).colorScheme.background,
|
|
),
|
|
child: const Text('全部好友'),
|
|
),
|
|
),
|
|
InkWell(
|
|
onTap: () {
|
|
change('groupChats');
|
|
},
|
|
child: Container(
|
|
margin: const EdgeInsets.only(right: 10),
|
|
padding: const EdgeInsets.all(4),
|
|
decoration: BoxDecoration(
|
|
borderRadius: BorderRadius.circular(4),
|
|
color: shows['groupChats']!
|
|
? kSecondaryColor.withOpacity(0.15)
|
|
: Theme.of(context).brightness == Brightness.light
|
|
? null
|
|
: Theme.of(context).colorScheme.background,
|
|
),
|
|
child: const Text('群聊'),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
bool shouldRebuild(covariant _MySliverPersistentHeader oldDelegate) {
|
|
return true;
|
|
}
|
|
}
|