chore: init repository with project sources
This commit is contained in:
@@ -0,0 +1,167 @@
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import '../../../core/status.dart';
|
||||
import '../data/movie_repository.dart';
|
||||
import 'widgets/status_chip.dart';
|
||||
|
||||
class MovieListScreen extends ConsumerWidget {
|
||||
const MovieListScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final filter = ref.watch(movieFilterProvider);
|
||||
final moviesAsync = ref.watch(moviesProvider);
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(12.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
StatusChip(
|
||||
selected: filter,
|
||||
onChanged: (f) => ref.read(movieFilterProvider.notifier).state = f,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Expanded(
|
||||
child: moviesAsync.when(
|
||||
data: (items) {
|
||||
final itemsSorted = [...items]
|
||||
..sort((a, b) => a.title.toLowerCase().compareTo(b.title.toLowerCase()));
|
||||
return RefreshIndicator(
|
||||
onRefresh: () async => ref.invalidate(moviesProvider),
|
||||
child: ListView.separated(
|
||||
itemCount: itemsSorted.length,
|
||||
separatorBuilder: (_, __) => const Divider(height: 1),
|
||||
itemBuilder: (context, i) {
|
||||
final m = itemsSorted[i];
|
||||
return ListTile(
|
||||
tileColor: _statusBg(m.status, context),
|
||||
leading: m.posterPath != null
|
||||
? ClipRRect(
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
child: CachedNetworkImage(
|
||||
imageUrl:
|
||||
'https://image.tmdb.org/t/p/w154${m.posterPath}',
|
||||
width: 50,
|
||||
height: 75,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
)
|
||||
: const SizedBox(width: 50, height: 75),
|
||||
title: Text(
|
||||
m.releaseYear != null
|
||||
? '${m.title} (${m.releaseYear})'
|
||||
: m.title,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 56,
|
||||
child: Align(
|
||||
alignment: Alignment.topLeft,
|
||||
child: FittedBox(
|
||||
alignment: Alignment.topLeft,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: _resolutionBadge(m.resolution, context),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
m.overview ?? '',
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
isThreeLine: true,
|
||||
onTap: () {
|
||||
// TODO: Detailseite / Status ändern
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
loading: () => const Center(child: CircularProgressIndicator()),
|
||||
error: (e, st) => Center(
|
||||
child: Text('Fehler: ${e.toString()}'),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
IconData _statusIcon(ItemStatus s) {
|
||||
switch (s) {
|
||||
case ItemStatus.Init:
|
||||
return Icons.hourglass_empty;
|
||||
case ItemStatus.Progress:
|
||||
return Icons.downloading;
|
||||
case ItemStatus.Done:
|
||||
return Icons.check_circle;
|
||||
}
|
||||
}
|
||||
|
||||
Color? _statusBg(ItemStatus s, BuildContext context) {
|
||||
switch (s) {
|
||||
case ItemStatus.Init:
|
||||
return Colors.grey.shade200;
|
||||
case ItemStatus.Progress:
|
||||
return Colors.blue;
|
||||
case ItemStatus.Done:
|
||||
return Colors.green;
|
||||
}
|
||||
}
|
||||
|
||||
Widget _resolutionBadge(String? res, BuildContext context) {
|
||||
if (res == null || res.isEmpty) return const SizedBox.shrink();
|
||||
final m = RegExp(r"\d+").firstMatch(res);
|
||||
final v = m != null ? int.tryParse(m.group(0)!) : null;
|
||||
IconData? icon;
|
||||
Color color = Theme.of(context).colorScheme.primary;
|
||||
String label = res;
|
||||
if (v != null) {
|
||||
if (v >= 2000) {
|
||||
icon = Icons.four_k;
|
||||
color = Colors.deepOrange;
|
||||
label = '${v}p';
|
||||
} else if (v >= 1000) {
|
||||
icon = Icons.hd;
|
||||
color = Colors.blue;
|
||||
label = '${v}p';
|
||||
} else {
|
||||
icon = Icons.sd_card;
|
||||
color = Colors.grey;
|
||||
label = '${v}p';
|
||||
}
|
||||
}
|
||||
return Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surface.withOpacity(0.9),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
border: Border.all(color: color.withOpacity(0.5)),
|
||||
),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (icon != null) Icon(icon, size: 14, color: color),
|
||||
if (icon != null) const SizedBox(width: 4),
|
||||
Text(
|
||||
label,
|
||||
style: TextStyle(fontSize: 12, color: Theme.of(context).colorScheme.onSurface),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:multimediaFlutter/core/status.dart';
|
||||
|
||||
class StatusChip extends StatelessWidget {
|
||||
final ItemStatus? selected;
|
||||
final ValueChanged<ItemStatus?> onChanged;
|
||||
|
||||
const StatusChip(
|
||||
{super.key, required this.selected, required this.onChanged});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
children: [
|
||||
FilterChip(
|
||||
label: const Text('Alle'),
|
||||
selected: selected == null,
|
||||
onSelected: (_) => onChanged(null),
|
||||
),
|
||||
for (final s in ItemStatus.values)
|
||||
FilterChip(
|
||||
label: Text(s.name),
|
||||
selected: selected == s,
|
||||
onSelected: (_) => onChanged(s),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user