// SPDX-FileCopyrightText: 2019-Present Christian Kußowski // SPDX-FileCopyrightText: 2019-Present Contributors to FluffyChat // // SPDX-License-Identifier: AGPL-3.0-or-later import 'package:go_router/go_router.dart'; import 'package:material_ui/material_ui.dart'; import 'package:matrix/matrix.dart' hide Result; import 'package:tatchat/config/themes.dart'; import 'package:tatchat/l10n/l10n.dart'; import 'package:tatchat/utils/tatu_share.dart'; import 'package:tatchat/widgets/avatar.dart'; import 'package:tatchat/widgets/matrix.dart'; import 'chat_list.dart'; /// Left navigation sidebar shown in column mode. It hosts the actions that /// used to live in the avatar popup menu plus the account switcher. class Sidebar extends StatelessWidget { final ChatListController controller; const Sidebar(this.controller, {super.key}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Material( color: theme.colorScheme.surface, child: SafeArea( bottom: false, child: SizedBox( width: FluffyThemes.sidebarWidth, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Padding( padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), child: Row( children: [ Image.asset( './assets/logo/mini/logo_mini.png', width: 28, height: 28, ), const SizedBox(width: 10), Text( 'TatuChat', style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.bold, color: theme.colorScheme.onSurface, ), ), const Spacer(), IconButton( tooltip: L10n.of(context).close, icon: const Icon(Icons.close), onPressed: () => Navigator.of(context).pop(), ), ], ), ), Divider(color: theme.colorScheme.outlineVariant, height: 1), Expanded(child: _AccountSection(controller)), ], ), ), ), ); } } class _AccountSection extends StatelessWidget { final ChatListController controller; const _AccountSection(this.controller); @override Widget build(BuildContext context) { final matrix = Matrix.of(context); final bundles = matrix.accountBundles; return ListView( padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 8), children: [ _ActionTile( icon: Icons.group_add_outlined, label: L10n.of(context).createGroup, onTap: () => context.go('/rooms/newgroup'), ), _ActionTile( icon: Icons.edit_outlined, label: L10n.of(context).setStatus, onTap: controller.setStatus, ), _ActionTile( icon: Icons.adaptive.share_outlined, label: L10n.of(context).inviteContact, onTap: () => TatuShare.shareInviteLink(context), ), _ActionTile( icon: Icons.archive_outlined, label: L10n.of(context).archive, onTap: () => context.go('/rooms/archive'), ), _ActionTile( icon: Icons.apps_outlined, label: L10n.of(context).apps, onTap: () => context.go('/rooms/apps'), ), _ActionTile( icon: Icons.settings_outlined, label: L10n.of(context).settings, onTap: () => context.go('/rooms/settings'), ), const Divider(), for (final bundle in bundles.keys) ...[ if (bundles[bundle]!.length != 1 || bundles[bundle]!.single!.userID != bundle) Padding( padding: const EdgeInsets.fromLTRB(12, 12, 12, 4), child: Text( bundle!, style: TextStyle( color: Theme.of(context).colorScheme.onSurfaceVariant, fontSize: 12, fontWeight: FontWeight.w500, ), overflow: TextOverflow.ellipsis, ), ), ...bundles[bundle]! .whereType() .where((client) => client.isLogged()) .map( (client) => _AccountTile( client: client, controller: controller, bundle: bundle, ), ), ], _AddAccountTile(onTap: () => context.go('/rooms/settings/addaccount')), ], ); } } class _ActionTile extends StatelessWidget { final IconData icon; final Color? iconColor; final String label; final VoidCallback onTap; const _ActionTile({ required this.icon, this.iconColor, required this.label, required this.onTap, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( children: [ Icon( icon, size: 22, color: iconColor ?? theme.colorScheme.onSurfaceVariant, ), const SizedBox(width: 14), Text( label, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface, ), ), ], ), ), ); } } class _AccountTile extends StatelessWidget { final Client client; final ChatListController controller; final String? bundle; const _AccountTile({ required this.client, required this.controller, required this.bundle, }); @override Widget build(BuildContext context) { final theme = Theme.of(context); final matrix = Matrix.of(context); final isActive = client == matrix.client; return FutureBuilder( future: client.fetchOwnProfile(), builder: (context, snapshot) { final displayname = snapshot.data?.displayName ?? client.userID?.localpart ?? ''; return InkWell( onTap: () => controller.setActiveClient(client), borderRadius: BorderRadius.circular(12), child: Container( margin: const EdgeInsets.symmetric(vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), decoration: BoxDecoration( color: isActive ? theme.colorScheme.primary.withValues(alpha: 0.08) : null, borderRadius: BorderRadius.circular(12), ), child: Row( children: [ Avatar( mxContent: snapshot.data?.avatarUrl, name: displayname, size: 30, ), const SizedBox(width: 10), Expanded( child: Text( displayname, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurface, fontWeight: isActive ? FontWeight.w600 : null, ), ), ), const SizedBox(width: 6), SizedBox( width: 32, height: 32, child: isActive ? Icon( Icons.check_circle, size: 20, color: theme.colorScheme.primary, ) : IconButton( icon: const Icon(Icons.edit_outlined, size: 18), tooltip: L10n.of(context).editBundlesForAccount, onPressed: () => controller.editBundlesForAccount( client.userID, bundle, ), ), ), ], ), ), ); }, ); } } class _AddAccountTile extends StatelessWidget { final VoidCallback onTap; const _AddAccountTile({required this.onTap}); @override Widget build(BuildContext context) { final theme = Theme.of(context); return InkWell( onTap: onTap, borderRadius: BorderRadius.circular(12), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( children: [ Icon( Icons.person_add_outlined, size: 22, color: theme.colorScheme.onSurfaceVariant, ), const SizedBox(width: 14), Text( L10n.of(context).addAccount, style: theme.textTheme.bodyLarge?.copyWith( color: theme.colorScheme.onSurface, ), ), ], ), ), ); } }