Skip to content

Commit 449bc2c

Browse files
committed
[Launcher] feat: enhance friend list dialog UI
1 parent 6023e56 commit 449bc2c

6 files changed

Lines changed: 272 additions & 138 deletions

File tree

Launcher/lib/features/maxima/dialogs/maxima_friends_dialog.dart

Lines changed: 108 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import 'package:kyber_launcher/features/maxima/widgets/maxima_avatar.dart';
1111
import 'package:kyber_launcher/features/session/providers/session_cubit.dart';
1212
import 'package:kyber_launcher/gen/fonts.gen.dart';
1313
import 'package:kyber_launcher/gen/rust/api/maxima.dart';
14-
import 'package:kyber_launcher/shared/ui/buttons/button.dart';
14+
import 'package:kyber_launcher/shared/ui/buttons/normal_button.dart';
15+
import 'package:kyber_launcher/shared/ui/ui.dart';
16+
import 'package:super_sliver_list/super_sliver_list.dart';
1517

1618
class MaximaFriendsDialog extends StatelessWidget {
1719
const MaximaFriendsDialog({super.key});
@@ -65,9 +67,10 @@ class _PartyPanel extends StatelessWidget {
6567
return Padding(
6668
padding: const .only(top: 20),
6769
child: Column(
68-
crossAxisAlignment: .start,
70+
crossAxisAlignment: .stretch,
6971
children: [
7072
const _SectionHeader(title: 'Multiplayer Group'),
73+
const SizedBox(height: 1, child: ColoredBox(color: decoColor)),
7174
Expanded(
7275
child: BlocBuilder<SessionCubit, SessionState>(
7376
builder: (context, state) {
@@ -214,14 +217,33 @@ class _PartyMemberList extends StatelessWidget {
214217
Widget build(BuildContext context) {
215218
final members = party.party.members;
216219

217-
return ListView.separated(
218-
itemCount: members.length,
219-
separatorBuilder: (_, __) => const _MemberDivider(),
220+
return SuperListView.separated(
221+
itemCount: members.length + 1,
220222
itemBuilder: (_, index) {
223+
if (index == members.length) {
224+
return const SizedBox.shrink();
225+
}
226+
221227
final player = members[index].player;
228+
final isLeader = party.party.leaderId == player.id;
229+
222230
return _MemberTile(
223231
name: player.name,
224232
avatar: MaximaAvatar(pd: player.id, height: 50, width: 50),
233+
id: player.id,
234+
status: switch (isLeader) {
235+
true => Text(
236+
'Group Leader',
237+
style: .new(color: kActiveColor),
238+
),
239+
_ => const Text('Online'),
240+
},
241+
);
242+
},
243+
separatorBuilder: (context, index) {
244+
return Container(
245+
height: 1,
246+
color: decoColor,
225247
);
226248
},
227249
);
@@ -237,6 +259,7 @@ class _CurrentUserTile extends StatelessWidget {
237259
_MemberTile(
238260
name: player.displayName,
239261
avatar: MaximaAvatar(pd: player.id, height: 50, width: 50),
262+
id: player.id,
240263
),
241264
const _MemberDivider(),
242265
],
@@ -245,57 +268,100 @@ class _CurrentUserTile extends StatelessWidget {
245268
}
246269

247270
class _MemberTile extends StatelessWidget {
248-
const _MemberTile({required this.name, required this.avatar});
271+
const _MemberTile({
272+
required this.name,
273+
required this.avatar,
274+
this.status,
275+
this.id = '',
276+
});
249277

250278
final String name;
251279
final Widget avatar;
280+
final Text? status;
281+
final String id;
252282

253283
@override
254284
Widget build(BuildContext context) {
285+
final userId = context.read<MaximaCubit>().state.servicePlayer!.id;
286+
255287
return Container(
256288
decoration: BoxDecoration(
257289
color: Colors.white.withOpacity(.05),
258-
border: const .symmetric(
259-
horizontal: .new(color: decoColor, width: 1.5),
260-
),
261290
),
262-
child: Padding(
263-
padding: const EdgeInsets.symmetric(
264-
horizontal: 10,
265-
vertical: 5,
266-
).copyWith(left: 15),
267-
child: Row(
268-
crossAxisAlignment: .start,
269-
children: [
270-
Container(
271-
clipBehavior: .antiAliasWithSaveLayer,
272-
decoration: const BoxDecoration(
273-
borderRadius: .all(.circular(6)),
274-
),
275-
child: avatar,
291+
child: HoverBuilder(
292+
builder: (context, hovered) {
293+
final partyState = context.select(
294+
(SessionCubit cubit) => cubit.state,
295+
);
296+
final isLeader =
297+
partyState is InParty && partyState.party.leaderId == userId;
298+
299+
return Padding(
300+
padding: const .symmetric(
301+
horizontal: 15,
302+
vertical: 5,
276303
),
277-
const SizedBox(width: 10),
278-
DefaultTextStyle(
279-
style: const .new(height: 1, fontFamily: FontFamily.battlefrontUI),
280-
child: Column(
281-
crossAxisAlignment: .start,
282-
children: [
283-
const SizedBox(height: 2),
284-
Text(name, style: const .new(fontSize: 18)),
285-
const SizedBox(height: 5),
286-
const Divider(
287-
size: 10,
288-
style: DividerThemeData(
289-
horizontalMargin: .zero,
290-
verticalMargin: .symmetric(vertical: 10),
291-
decoration: BoxDecoration(color: decoColor),
304+
child: Stack(
305+
children: [
306+
Row(
307+
crossAxisAlignment: .start,
308+
children: [
309+
Container(
310+
clipBehavior: .antiAliasWithSaveLayer,
311+
decoration: const BoxDecoration(
312+
borderRadius: .all(.circular(6)),
313+
),
314+
child: avatar,
315+
),
316+
const SizedBox(width: 10),
317+
DefaultTextStyle(
318+
style: const .new(
319+
height: 1,
320+
fontFamily: FontFamily.battlefrontUI,
321+
),
322+
child: Padding(
323+
padding: const .symmetric(vertical: 2),
324+
child: Column(
325+
crossAxisAlignment: .start,
326+
mainAxisAlignment: .center,
327+
spacing: 5,
328+
children: [
329+
Text(name, style: const .new(fontSize: 18)),
330+
const Divider(
331+
size: 10,
332+
style: DividerThemeData(
333+
horizontalMargin: .zero,
334+
verticalMargin: .symmetric(vertical: 10),
335+
decoration: BoxDecoration(color: decoColor),
336+
),
337+
),
338+
?status,
339+
],
340+
),
341+
),
342+
),
343+
],
344+
),
345+
if (hovered && isLeader && userId != id)
346+
Positioned(
347+
right: 0,
348+
top: 0,
349+
bottom: 0,
350+
child: Row(
351+
children: [
352+
KOutlinedButton(
353+
child: Icon(mt.Icons.close_sharp, size: 25),
354+
onPressed: () {
355+
// TODO: Implement kick from party
356+
},
357+
),
358+
],
292359
),
293360
),
294-
],
295-
),
361+
],
296362
),
297-
],
298-
),
363+
);
364+
},
299365
),
300366
);
301367
}

Launcher/lib/features/maxima/providers/maxima_rtm_cubit.dart

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import 'dart:async';
22

33
import 'package:flutter_bloc/flutter_bloc.dart';
4+
import 'package:kyber/kyber.dart';
45
import 'package:kyber_launcher/gen/rust/api/maxima.dart';
56
import 'package:logging/logging.dart';
67

@@ -68,7 +69,7 @@ class MaximaRtmState {
6869
return players;
6970
}
7071

71-
List<ServicePlayer> getSortedPlayers() {
72+
List<ServicePlayer> getSortedPlayers({PartyState? partyState}) {
7273
final players = List<ServicePlayer>.from(friends)
7374
..sort((a, b) {
7475
final presenceA = presences[a.id];
@@ -93,6 +94,13 @@ class MaximaRtmState {
9394
return a.displayName.compareTo(b.displayName);
9495
}
9596
});
97+
98+
if (partyState != null) {
99+
players.removeWhere(
100+
(element) => partyState.members.any((p) => p.player.id == element.id),
101+
);
102+
}
103+
96104
return players;
97105
}
98106
}

0 commit comments

Comments
 (0)