Files
KC-APP/client/app/lib/screens/files_screen.dart
linusandClaude Sonnet 5 0886424526 feat(client): Flutter app (web target) — Phase 7 start
Single Flutter codebase under client/app/ with web enabled (mobile/desktop
can be added later; lib/ is platform-agnostic). Talks to the NestJS backend
via a thin REST wrapper; API_BASE is a --dart-define (defaults to the local
backend).

Screens:
- Login: Konfi/guest (invite code), local Teamer password login, Teamer
  invite redemption. Token persisted in shared_preferences, restored on
  start; GET /auth/me drives a role-aware home.
- Workshop-Wahl (guests): loads /wahl/guest/overview, ordered pick of up to
  3 workshops, submits to /wahl/:id/teilnehmer.
- Dateien: /files/:kcId list.
- Chat: channel + message list (read-only; WS send is a follow-up).

State: AppState (ChangeNotifier) exposed via an InheritedNotifier
(AppScope) — no third-party state package. flutter analyze clean,
flutter build web --release passes, one widget smoke test.

Also: interim client/web/ HTML placeholder stays as-is (per plan it is
superseded by this Flutter web build).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-10 08:52:36 +02:00

78 lines
2.3 KiB
Dart

import 'package:flutter/material.dart';
import '../api.dart';
import '../main.dart';
class FilesScreen extends StatefulWidget {
const FilesScreen({super.key, required this.kcId});
final String kcId;
@override
State<FilesScreen> createState() => _FilesScreenState();
}
class _FilesScreenState extends State<FilesScreen> {
Future<List<FileEntry>>? _future;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_future ??= AppScope.of(context).api.files(widget.kcId);
}
static const _visibilityLabels = {
'ALLE': 'Alle',
'ALLE_AUSSER_KONFIS': 'Team (ohne Konfis)',
'NUR_LT': 'Nur Leitungsteam',
};
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dateien')),
body: FutureBuilder<List<FileEntry>>(
future: _future,
builder: (context, snap) {
if (snap.connectionState != ConnectionState.done) {
return const Center(child: CircularProgressIndicator());
}
if (snap.hasError) {
return Center(
child: Padding(
padding: const EdgeInsets.all(24),
child: Text('${snap.error}', textAlign: TextAlign.center),
),
);
}
final files = snap.data!;
if (files.isEmpty) {
return const Center(child: Text('Keine Dateien freigegeben.'));
}
return ListView.separated(
itemCount: files.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, i) {
final f = files[i];
return ListTile(
leading: const Icon(Icons.insert_drive_file_outlined),
title: Text(f.filename),
subtitle: Text(_visibilityLabels[f.visibility] ?? f.visibility),
trailing: const Icon(Icons.download),
onTap: () {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'Download-URL: ${AppScope.of(context).api.fileDownloadUrl(f.id)}',
),
),
);
},
);
},
);
},
),
);
}
}