fApp/lib/screens/detailview_screen.dart
Flummi 9655f15927
All checks were successful
Flutter Schmutter / build (push) Successful in 3m37s
v1.2.0+54
- screaming_possum.gif
2025-06-13 13:55:05 +02:00

330 lines
11 KiB
Dart

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:get/get.dart';
import 'package:share_plus/share_plus.dart';
import 'package:f0ckapp/models/mediaitem_model.dart';
import 'package:f0ckapp/services/api_service.dart';
import 'package:f0ckapp/widgets/end_drawer.dart';
import 'package:f0ckapp/screens/fullscreen_screen.dart';
import 'package:f0ckapp/widgets/video_widget.dart';
class DetailView extends StatefulWidget {
final int initialItemId;
const DetailView({super.key, required this.initialItemId});
@override
State<DetailView> createState() => _DetailViewState();
}
class _DetailViewState extends State<DetailView> {
final ApiService apiService = Get.find<ApiService>();
PageController? _pageController;
Future<void>? _loadingFuture;
int _currentPage = 0;
@override
void initState() {
super.initState();
if (!_mediaItemExists(widget.initialItemId)) {
_loadingFuture = _fetchAndPreloadMedia(widget.initialItemId);
} else {
_initializePageController();
}
}
bool _mediaItemExists(int id) {
return apiService.mediaItems.any((media) => media.id == id);
}
Future<void> _fetchAndPreloadMedia(int targetId) async {
try {
await apiService.setTag(null);
await apiService.fetchMedia(id: targetId + 50, reset: false);
_initializePageController();
} catch (e) {
_showMsg("Medien konnten nicht geladen werden");
}
}
void _initializePageController() {
_currentPage = apiService.mediaItems.indexWhere(
(media) => media.id == widget.initialItemId,
);
if (_currentPage < 0) {
_currentPage = 0;
}
_pageController = PageController(initialPage: _currentPage)
..addListener(() {
setState(() {
_currentPage = _pageController!.page!.round();
});
});
setState(() {});
}
@override
void dispose() {
_pageController?.dispose();
super.dispose();
}
MediaItem? _findMediaItem() {
try {
return apiService.mediaItems.firstWhere(
(media) => media.id == widget.initialItemId,
);
} catch (e) {
return null;
}
}
Future<void> _downloadMedia(MediaItem item) async {
final File file = await DefaultCacheManager().getSingleFile(item.mediaUrl);
final MethodChannel methodChannel = const MethodChannel('MediaShit');
bool? success = await methodChannel.invokeMethod<bool>('saveFile', {
'filePath': file.path,
'fileName': item.dest,
});
success == true
? _showMsg('${item.dest} wurde in Downloads/fApp neigespeichert.')
: _showMsg('${item.dest} konnte nicht heruntergeladen werden.');
}
void _showMsg(String message) {
if (!mounted) return;
ScaffoldMessenger.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(message)));
}
@override
Widget build(BuildContext context) {
if (_loadingFuture != null) {
return FutureBuilder<void>(
future: _loadingFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Scaffold(
appBar: AppBar(title: const Text("Detail")),
body: const Center(child: CircularProgressIndicator()),
);
} else {
MediaItem? item = _findMediaItem();
if (item == null) {
return Scaffold(
appBar: AppBar(title: const Text("Detail")),
body: const Center(child: Text("f0ck nicht gefunden")),
);
}
return _buildDetail();
}
},
);
}
MediaItem? existingItem = _findMediaItem();
if (existingItem == null) {
return Scaffold(
appBar: AppBar(title: const Text("Detail")),
body: const Center(child: Text("f0ck nicht gefunden")),
);
}
return _buildDetail();
}
Widget _buildDetail() {
final MediaItem currentItem = apiService.mediaItems[_currentPage];
return Scaffold(
endDrawer: const EndDrawer(),
endDrawerEnableOpenDragGesture: false,
persistentFooterButtons: apiService.tag.value != null
? [
Center(
child: InputChip(
label: Text(apiService.tag.value!),
onDeleted: () {
apiService.setTag(null);
Get.offAllNamed('/');
},
),
),
]
: null,
body: CustomScrollView(
slivers: [
SliverAppBar(
floating: true,
pinned: true,
snap: true,
centerTitle: true,
title: Text('f0ck #${currentItem.id.toString()}'),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () => Get.back(),
),
actions: [
IconButton(
icon: const Icon(Icons.fullscreen),
onPressed: () {
Get.to(
FullScreenMediaView(item: currentItem),
fullscreenDialog: true,
);
},
),
IconButton(
icon: const Icon(Icons.download),
onPressed: () async {
await _downloadMedia(currentItem);
},
),
PopupMenuButton<String>(
onSelected: (value) async {
switch (value) {
case 'media':
File file = await DefaultCacheManager().getSingleFile(
currentItem.mediaUrl,
);
Uint8List bytes = await file.readAsBytes();
final params = ShareParams(
files: [
XFile.fromData(bytes, mimeType: currentItem.mime),
],
);
await SharePlus.instance.share(params);
break;
case 'direct_link':
await SharePlus.instance.share(
ShareParams(text: currentItem.mediaUrl),
);
break;
case 'post_link':
await SharePlus.instance.share(
ShareParams(text: currentItem.postUrl),
);
break;
}
},
itemBuilder: (context) => [
PopupMenuItem(
value: 'media',
child: ListTile(
leading: const Icon(Icons.image),
title: const Text('Als Datei'),
),
),
PopupMenuItem(
value: 'direct_link',
child: ListTile(
leading: const Icon(Icons.link),
title: const Text('Link zur Datei'),
),
),
PopupMenuItem(
value: 'post_link',
child: ListTile(
leading: const Icon(Icons.article),
title: const Text('Link zum f0ck'),
),
),
],
icon: const Icon(Icons.share),
),
Builder(
builder: (context) => IconButton(
icon: const Icon(Icons.menu),
onPressed: () {
Scaffold.of(context).openEndDrawer();
},
),
),
],
),
SliverFillRemaining(
child: PageView.builder(
controller: _pageController,
itemCount: apiService.mediaItems.length,
itemBuilder: (context, index) {
final MediaItem pageItem = apiService.mediaItems[index];
return AnimatedBuilder(
animation: _pageController!,
builder: (context, child) {
double value = 0;
if (_pageController!.position.haveDimensions) {
value = (_pageController!.page! - index).abs();
}
double factor = Curves.easeOut.transform(
1 - value.clamp(0.0, 1.0),
);
double scale = 0.8 + factor * 0.2;
return Transform.scale(scale: scale, child: child);
},
child: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
children: [
if (pageItem.mime.startsWith('image'))
CachedNetworkImage(
imageUrl: pageItem.mediaUrl,
fit: BoxFit.contain,
placeholder: (context, url) => const Center(
child: CircularProgressIndicator(),
),
errorWidget: (context, url, error) =>
const Center(child: Icon(Icons.error)),
)
else
VideoWidget(
details: pageItem,
isActive: index == _currentPage,
),
const SizedBox(height: 10, width: double.infinity),
Wrap(
alignment: WrapAlignment.center,
spacing: 5.0,
children: pageItem.tags.map((tag) {
return ActionChip(
onPressed: () {
if (tag.tag == 'sfw' || tag.tag == 'nsfw') {
return;
}
apiService.setTag(tag.tag);
Get.offAllNamed('/');
},
label: Text(tag.tag),
backgroundColor: switch (tag.id) {
1 => Colors.green,
2 => Colors.red,
_ => const Color(0xFF090909),
},
labelStyle: const TextStyle(
color: Colors.white,
),
);
}).toList(),
),
const SizedBox(height: 20),
],
),
),
),
);
},
),
),
],
),
);
}
}