Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: space sort & categorization #1323

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion assets/l10n/intl_en.arb
Original file line number Diff line number Diff line change
Expand Up @@ -2761,5 +2761,8 @@
"discoverHomeservers": "Discover homeservers",
"whatIsAHomeserver": "What is a homeserver?",
"homeserverDescription": "All your data is stored on the homeserver, just like an email provider. You can choose which homeserver you want to use, while you can still communicate with everyone. Learn more at at https://matrix.org.",
"doesNotSeemToBeAValidHomeserver": "Doesn't seem to be a compatible homeserver. Wrong URL?"
"doesNotSeemToBeAValidHomeserver": "Doesn't seem to be a compatible homeserver. Wrong URL?",
"spaceViewNormal": "Normal",
"spaceViewRoomTop": "Rooms on top",
"spaceViewCategorized": "Categorize"
}
3 changes: 3 additions & 0 deletions lib/config/app_config.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import 'dart:collection';
import 'dart:ui';

import 'package:matrix/matrix.dart';
Expand Down Expand Up @@ -54,6 +55,8 @@ abstract class AppConfig {
static bool? sendOnEnter;
static bool showPresences = true;
static bool experimentalVoip = false;
static Map<String, int> spaceViewOptions = {};
static Set<String> collapsedSpace = HashSet();
static const bool hideTypingUsernames = false;
static const bool hideAllStateEvents = false;
static const String inviteLinkPrefix = 'https://matrix.to/#/';
Expand Down
2 changes: 2 additions & 0 deletions lib/config/setting_keys.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,6 @@ abstract class SettingKeys {
'chat.fluffy.display_chat_details_column';
static const String noEncryptionWarningShown =
'chat.fluffy.no_encryption_warning_shown';
static const String spaceViewOptions = 'chat.fluffy.space_view_options';
static const String collapsedSpace = 'chat.fluffy.collapsed_space';
}
202 changes: 202 additions & 0 deletions lib/pages/chat_list/chat_list_item_space.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,202 @@
import 'package:flutter/material.dart';

import 'package:adaptive_dialog/adaptive_dialog.dart';
import 'package:flutter_gen/gen_l10n/l10n.dart';
import 'package:future_loading_dialog/future_loading_dialog.dart';
import 'package:matrix/matrix.dart';

import 'package:fluffychat/config/app_config.dart';
import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart';
import 'package:fluffychat/widgets/hover_builder.dart';
import '../../config/themes.dart';
import '../../widgets/avatar.dart';

enum ArchivedRoomAction { delete, rejoin }

class ChatListSpaceItem extends StatelessWidget {
final Room room;
final Room? space;
final bool activeChat;
final void Function(BuildContext context)? onLongPress;
final void Function()? onForget;
final void Function() onTap;
final String? filter;

const ChatListSpaceItem(
this.room, {
this.activeChat = false,
required this.onTap,
this.onLongPress,
this.onForget,
this.filter,
this.space,
super.key,
});

Future<void> archiveAction(BuildContext context) async {
{
if ([Membership.leave, Membership.ban].contains(room.membership)) {
await showFutureLoadingDialog(
context: context,
future: () => room.forget(),
);
return;
}
final confirmed = await showOkCancelAlertDialog(
useRootNavigator: false,
context: context,
title: L10n.of(context)!.areYouSure,
okLabel: L10n.of(context)!.yes,
cancelLabel: L10n.of(context)!.no,
message: L10n.of(context)!.archiveRoomDescription,
);
if (confirmed == OkCancelResult.cancel) return;
await showFutureLoadingDialog(
context: context,
future: () => room.leave(),
);
return;
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);

final isMuted = room.pushRuleState != PushRuleState.notify;
final directChatMatrixId = room.directChatMatrixID;
final hasNotifications = room.notificationCount > 0;
final backgroundColor =
activeChat ? theme.colorScheme.secondaryContainer : null;
final displayname = room.getLocalizedDisplayname(
MatrixLocals(L10n.of(context)!),
);
final filter = this.filter;
if (filter != null && !displayname.toLowerCase().contains(filter)) {
return const SizedBox.shrink();
}

return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 1,
),
child: Material(
shape: Border(
top: BorderSide(
color: theme.dividerColor,
width: 1,
),
bottom: BorderSide(
color: theme.dividerColor,
width: 1,
),
),
clipBehavior: Clip.hardEdge,
color: backgroundColor,
child: FutureBuilder(
future: room.loadHeroUsers(),
builder: (context, snapshot) => HoverBuilder(
builder: (context, listTileHovered) => ListTile(
visualDensity: const VisualDensity(vertical: -0.5),
contentPadding: const EdgeInsets.symmetric(horizontal: 8),
onLongPress: () => onLongPress?.call(context),
leading: HoverBuilder(
builder: (context, hovered) => AnimatedScale(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
scale: hovered ? 1.1 : 1.0,
child: SizedBox(
width: Avatar.defaultSize * 0.5,
height: Avatar.defaultSize * 0.5,
child: Stack(
children: [
Positioned(
bottom: 0,
right: 0,
child: Avatar(
border: null,
borderRadius: null,
mxContent: room.avatar,
size: Avatar.defaultSize * 0.5,
name: displayname,
presenceUserId: directChatMatrixId,
presenceBackgroundColor: backgroundColor,
onTap: () => onLongPress?.call(context),
),
),
Positioned(
top: 0,
right: 0,
child: GestureDetector(
onTap: () => onLongPress?.call(context),
child: AnimatedScale(
duration: FluffyThemes.animationDuration,
curve: FluffyThemes.animationCurve,
scale: listTileHovered ? 1.0 : 0.0,
child: Material(
color: backgroundColor,
borderRadius: BorderRadius.circular(16),
child: const Icon(
Icons.arrow_drop_down_circle_outlined,
size: 18,
),
),
),
),
),
],
),
),
),
),
title: Row(
children: <Widget>[
Expanded(
child: Text(
displayname,
maxLines: 1,
overflow: TextOverflow.ellipsis,
softWrap: false,
style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
),
),
if (isMuted)
const Padding(
padding: EdgeInsets.only(left: 4.0),
child: Icon(
Icons.notifications_off_outlined,
size: 16,
),
),
if (room.isFavourite || room.membership == Membership.invite)
Padding(
padding: EdgeInsets.only(
right: hasNotifications ? 4.0 : 0.0,
),
child: Icon(
Icons.push_pin,
size: 16,
color: theme.colorScheme.primary,
),
),
Icon(
AppConfig.collapsedSpace.contains(room.id) ? Icons.arrow_circle_right_outlined : Icons.arrow_circle_down_outlined,
size: 18,
),
],
),
onTap: onTap,
trailing: onForget == null
? null
: IconButton(
icon: const Icon(Icons.delete_outlined),
onPressed: onForget,
),
),
),
),
),
);
}
}
Loading