wip: Sicherungs-Commit aller Änderungen seit April + Arbeitsstruktur (CLAUDE.md, ROADMAP.md, PROGRESS.md, Autopilot)
6 Wochen uncommittete Arbeit (Voice-Channels, LiveKit-Manager, Settings-Modal u.v.m.) als ein WIP-Commit gesichert, damit nichts verloren geht und der Pi den aktuellen Stand klonen kann. Thematische Aufarbeitung: siehe ROADMAP M0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPrAGBxBT6GfPXzeWQ4AXb
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:matrix/matrix.dart';
|
||||
import 'package:pyramid/core/app_state.dart';
|
||||
import 'package:pyramid/core/matrix_client.dart';
|
||||
import 'package:pyramid/core/theme.dart';
|
||||
import 'package:pyramid/widgets/hover_region.dart';
|
||||
import 'package:pyramid/widgets/mxc_image.dart';
|
||||
import 'package:pyramid/widgets/pyramid_logo.dart';
|
||||
|
||||
final _ownProfileProvider = FutureProvider.autoDispose<Profile?>((ref) async {
|
||||
final client = await ref.watch(matrixClientProvider.future);
|
||||
final userId = client.userID;
|
||||
if (userId == null) return null;
|
||||
try {
|
||||
return await client.getProfileFromUserId(userId);
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
class UserPanel extends ConsumerWidget {
|
||||
const UserPanel({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final pt = PyramidTheme.of(context);
|
||||
final clientAsync = ref.watch(matrixClientProvider);
|
||||
final profile = ref.watch(_ownProfileProvider).valueOrNull;
|
||||
|
||||
final client = clientAsync.valueOrNull;
|
||||
final fallbackName = client?.userID?.split(':').first.replaceFirst('@', '') ?? '...';
|
||||
final displayName = profile?.displayName ?? fallbackName;
|
||||
final avatarUri = profile?.avatarUrl;
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(8, 0, 8, 8),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: pt.bg2,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
border: Border.all(color: pt.border),
|
||||
),
|
||||
child: _ProfileRow(
|
||||
pt: pt,
|
||||
client: client,
|
||||
avatarUri: avatarUri,
|
||||
displayName: displayName,
|
||||
ref: ref,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _ProfileRow extends StatelessWidget {
|
||||
final PyramidTheme pt;
|
||||
final Client? client;
|
||||
final Uri? avatarUri;
|
||||
final String displayName;
|
||||
final WidgetRef ref;
|
||||
|
||||
const _ProfileRow({
|
||||
required this.pt,
|
||||
required this.client,
|
||||
required this.avatarUri,
|
||||
required this.displayName,
|
||||
required this.ref,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
Stack(
|
||||
clipBehavior: Clip.none,
|
||||
children: [
|
||||
if (client != null && avatarUri != null)
|
||||
MxcAvatar(
|
||||
mxcUri: avatarUri!,
|
||||
client: client!,
|
||||
size: 32,
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
placeholder: (_) => _InitialAvatar(name: displayName, radius: 4),
|
||||
)
|
||||
else
|
||||
_InitialAvatar(name: displayName, radius: 4),
|
||||
Positioned(
|
||||
bottom: -2,
|
||||
right: -2,
|
||||
child: PresenceDot(status: 'online', size: 10, borderColor: pt.bg2),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
displayName,
|
||||
style: TextStyle(
|
||||
color: pt.fg,
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
Text(
|
||||
'online',
|
||||
style: TextStyle(color: pt.fgMuted, fontSize: 11),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
PyrIconBtn(
|
||||
size: 28,
|
||||
icon: const Icon(Icons.settings_rounded),
|
||||
tooltip: 'Einstellungen',
|
||||
onPressed: () => ref.read(activeModalProvider.notifier).state = ModalKind.settings,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _InitialAvatar extends StatelessWidget {
|
||||
final String name;
|
||||
final double radius;
|
||||
final double size;
|
||||
const _InitialAvatar({required this.name, required this.radius, this.size = 32});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(
|
||||
width: size,
|
||||
height: size,
|
||||
decoration: BoxDecoration(
|
||||
gradient: const LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [Color(0xFFFF6B9D), Color(0xFFC471F5)],
|
||||
),
|
||||
borderRadius: BorderRadius.circular(radius),
|
||||
),
|
||||
child: Center(
|
||||
child: Text(
|
||||
name.isNotEmpty ? name[0].toUpperCase() : '?',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: size * 0.44,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user