275 lines
8.2 KiB
Dart
275 lines
8.2 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:f0ckapp/services/api.dart';
|
|
import 'package:f0ckapp/models/mediaitem.dart';
|
|
import 'package:f0ckapp/screens/detailview.dart';
|
|
import 'dart:async';
|
|
|
|
class MediaGrid extends StatefulWidget {
|
|
const MediaGrid({super.key});
|
|
|
|
@override
|
|
State createState() => _MediaGridState();
|
|
}
|
|
|
|
class _MediaGridState extends State<MediaGrid> {
|
|
final ScrollController _scrollController = ScrollController();
|
|
final String _version = '1.0.15';
|
|
List<MediaItem> mediaItems = [];
|
|
bool isLoading = false;
|
|
Timer? _debounceTimer;
|
|
Completer<void>? _navigationCompleter;
|
|
int _crossAxisCount = 0;
|
|
String _selectedType = 'alles';
|
|
int _selectedMode = 0;
|
|
bool _random = false;
|
|
final List<String> _modes = ["sfw", "nsfw", "untagged", "all"];
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_loadMedia();
|
|
_scrollController.addListener(() {
|
|
if (_scrollController.position.pixels >=
|
|
_scrollController.position.maxScrollExtent - 100) {
|
|
_debounceLoadMedia();
|
|
}
|
|
});
|
|
}
|
|
|
|
void _debounceLoadMedia() {
|
|
_debounceTimer?.cancel();
|
|
_debounceTimer = Timer(const Duration(milliseconds: 500), _loadMedia);
|
|
}
|
|
|
|
int _calculateCrossAxisCount(BuildContext context) {
|
|
if (_crossAxisCount != 0) {
|
|
return _crossAxisCount;
|
|
}
|
|
|
|
double screenWidth = MediaQuery.of(context).size.width;
|
|
int columnCount = (screenWidth / 110).clamp(3, 5).toInt();
|
|
|
|
return columnCount;
|
|
}
|
|
|
|
Future<void> _loadMedia() async {
|
|
if (isLoading) return;
|
|
setState(() => isLoading = true);
|
|
|
|
try {
|
|
final newMedia = await fetchMedia(
|
|
older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null,
|
|
type: _selectedType,
|
|
mode: _selectedMode,
|
|
random: _random,
|
|
);
|
|
if (mounted) {
|
|
setState(() => mediaItems.addAll(newMedia));
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Fehler beim Laden der Medien: $e')),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _refreshMedia() async {
|
|
setState(() => isLoading = true);
|
|
try {
|
|
final freshMedia = await fetchMedia(
|
|
older: null,
|
|
type: _selectedType,
|
|
mode: _selectedMode,
|
|
random: _random,
|
|
);
|
|
if (mounted) {
|
|
setState(() {
|
|
mediaItems.clear();
|
|
mediaItems.addAll(freshMedia);
|
|
});
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Fehler beim Aktualisieren: $e')),
|
|
);
|
|
}
|
|
} finally {
|
|
if (mounted) setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _navigateToDetail(MediaItem item) async {
|
|
if (_navigationCompleter?.isCompleted == false) return;
|
|
|
|
_navigationCompleter = Completer();
|
|
try {
|
|
if (mounted) {
|
|
await Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (context) => DetailView(
|
|
initialItemId: item.id,
|
|
mediaItems: mediaItems,
|
|
type: _selectedType,
|
|
mode: _selectedMode,
|
|
random: _random,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
} catch (e) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text('Fehler beim Laden der Details: $e')),
|
|
);
|
|
}
|
|
} finally {
|
|
_navigationCompleter?.complete();
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
centerTitle: true,
|
|
backgroundColor: const Color.fromARGB(255, 43, 43, 43),
|
|
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
|
|
title: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
children: [
|
|
Text('f0ck v$_version'),
|
|
Checkbox(
|
|
value: _random,
|
|
onChanged: (bool? value) {
|
|
setState(() {
|
|
_random = !_random;
|
|
_refreshMedia();
|
|
});
|
|
},
|
|
)
|
|
]
|
|
)
|
|
),
|
|
bottomNavigationBar: BottomAppBar(
|
|
color: const Color.fromARGB(255, 43, 43, 43),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: 16.0),
|
|
child: Row(
|
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
crossAxisAlignment: CrossAxisAlignment.end,
|
|
children: [
|
|
DropdownButton<String>(
|
|
value: _selectedType,
|
|
dropdownColor: const Color.fromARGB(255, 43, 43, 43),
|
|
iconEnabledColor: Colors.white,
|
|
items: ["alles", "image", "video", "audio"].map((String value) {
|
|
return DropdownMenuItem<String>(
|
|
value: value,
|
|
child: Text(value, style: TextStyle(color: Colors.white)),
|
|
);
|
|
}).toList(),
|
|
onChanged: (String? newValue) {
|
|
if (newValue != null) {
|
|
setState(() {
|
|
_selectedType = newValue;
|
|
_refreshMedia();
|
|
});
|
|
}
|
|
},
|
|
),
|
|
DropdownButton<String>(
|
|
value: _modes[_selectedMode],
|
|
dropdownColor: const Color.fromARGB(255, 43, 43, 43),
|
|
iconEnabledColor: Colors.white,
|
|
items: _modes.map((String value) {
|
|
return DropdownMenuItem<String>(
|
|
value: value,
|
|
child: Text(value, style: TextStyle(color: Colors.white)),
|
|
);
|
|
}).toList(),
|
|
onChanged: (String? newValue) {
|
|
if (newValue != null) {
|
|
setState(() {
|
|
_selectedMode = _modes.indexOf(newValue);
|
|
_refreshMedia();
|
|
});
|
|
}
|
|
},
|
|
),
|
|
DropdownButton<int>(
|
|
value: _crossAxisCount,
|
|
dropdownColor: const Color.fromARGB(255, 43, 43, 43),
|
|
iconEnabledColor: Colors.white,
|
|
items: [0, 3, 4].map((int value) {
|
|
return DropdownMenuItem<int>(
|
|
value: value,
|
|
child: Text(
|
|
value == 0 ? 'auto' : '$value Spalten',
|
|
style: TextStyle(color: Colors.white),
|
|
),
|
|
);
|
|
}).toList(),
|
|
onChanged: (int? newValue) {
|
|
if (newValue != null) {
|
|
setState(() {
|
|
_crossAxisCount = newValue;
|
|
});
|
|
}
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
body: RefreshIndicator(
|
|
onRefresh: _refreshMedia,
|
|
child: GridView.builder(
|
|
controller: _scrollController,
|
|
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
|
crossAxisCount: _calculateCrossAxisCount(context),
|
|
crossAxisSpacing: 5.0,
|
|
mainAxisSpacing: 5.0,
|
|
),
|
|
itemCount: mediaItems.length + (isLoading ? 1 : 0),
|
|
itemBuilder: (context, index) {
|
|
if (index >= mediaItems.length) {
|
|
return const Center(child: CircularProgressIndicator());
|
|
}
|
|
final item = mediaItems[index];
|
|
|
|
final mode =
|
|
{1: Colors.green, 2: Colors.red}[item.mode] ?? Colors.yellow;
|
|
|
|
return InkWell(
|
|
onTap: () => _navigateToDetail(item),
|
|
child: Stack(
|
|
fit: StackFit.expand,
|
|
children: <Widget>[
|
|
Image.network(item.thumbnailUrl, fit: BoxFit.cover),
|
|
Align(
|
|
alignment: FractionalOffset.bottomRight,
|
|
child: Icon(Icons.square, color: mode, size: 15.0),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_scrollController.dispose();
|
|
_debounceTimer?.cancel();
|
|
super.dispose();
|
|
}
|
|
}
|