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:
Bernd Steckmeister
2026-07-03 05:47:18 +02:00
parent d706ace35f
commit 25ed765a03
195 changed files with 47784 additions and 1236 deletions
+165
View File
@@ -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,
),
),
),
);
}
}