aktueller Stand
This commit is contained in:
@ -2,17 +2,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'package:f0ckapp/mediagrid.dart';
|
||||
//import 'package:media_kit/media_kit.dart';
|
||||
|
||||
void main() {
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
//MediaKit.ensureInitialized();
|
||||
|
||||
SystemChrome.setPreferredOrientations([
|
||||
DeviceOrientation.portraitUp
|
||||
]).then((_) {
|
||||
runApp(const F0ckApp());
|
||||
});
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
runApp(const F0ckApp());
|
||||
}
|
||||
|
||||
class F0ckApp extends StatelessWidget {
|
||||
@ -25,13 +19,6 @@ class F0ckApp extends StatelessWidget {
|
||||
scaffoldBackgroundColor: const Color.fromARGB(255, 23, 23, 23),
|
||||
),
|
||||
home: Scaffold(
|
||||
appBar: AppBar(
|
||||
automaticallyImplyLeading: false,
|
||||
backgroundColor: const Color.fromARGB(255, 43, 43, 43),
|
||||
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
title: const Text('f0cks'),
|
||||
centerTitle: true,
|
||||
),
|
||||
body: MediaGrid(),
|
||||
),
|
||||
);
|
||||
|
@ -1,8 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:f0ckapp/services/api.dart';
|
||||
import 'package:f0ckapp/models/mediaitem.dart';
|
||||
import 'package:f0ckapp/screens/detail.dart';
|
||||
import 'dart:async';
|
||||
|
||||
class MediaGrid extends StatefulWidget {
|
||||
const MediaGrid({super.key});
|
||||
@ -15,64 +15,168 @@ class _MediaGridState extends State<MediaGrid> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
List<MediaItem> mediaItems = [];
|
||||
bool isLoading = false;
|
||||
Timer? _debounceTimer;
|
||||
Completer<void>? _navigationCompleter;
|
||||
int _crossAxisCount = 3;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadMedia();
|
||||
_scrollController.addListener(() {
|
||||
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
|
||||
_loadMedia();
|
||||
if (_scrollController.position.pixels >=
|
||||
_scrollController.position.maxScrollExtent - 100) {
|
||||
_debounceLoadMedia();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _debounceLoadMedia() {
|
||||
_debounceTimer?.cancel();
|
||||
_debounceTimer = Timer(const Duration(milliseconds: 500), _loadMedia);
|
||||
}
|
||||
|
||||
Future<void> _loadMedia() async {
|
||||
if (isLoading) return;
|
||||
setState(() => isLoading = true);
|
||||
|
||||
try {
|
||||
List<MediaItem> newMedia = await fetchMedia(older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null);
|
||||
setState(() {
|
||||
mediaItems.addAll(newMedia);
|
||||
isLoading = false;
|
||||
});
|
||||
final newMedia = await fetchMedia(
|
||||
older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null,
|
||||
);
|
||||
if (mounted) {
|
||||
setState(() => mediaItems.addAll(newMedia));
|
||||
}
|
||||
} catch (e) {
|
||||
print('Fehler: $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);
|
||||
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 {
|
||||
print('Tapped item: ${item.id}');
|
||||
|
||||
if (_navigationCompleter?.isCompleted == false) return;
|
||||
|
||||
_navigationCompleter = Completer();
|
||||
try {
|
||||
final details = await fetchMediaDetail(item.id);
|
||||
if (mounted) {
|
||||
await Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DetailView(details: details)),
|
||||
);
|
||||
}
|
||||
} 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 GridView.builder(
|
||||
controller: _scrollController,
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
crossAxisSpacing: 8.0,
|
||||
mainAxisSpacing: 8.0,
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: const Color.fromARGB(255, 43, 43, 43),
|
||||
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
title: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [
|
||||
const Text('f0cks'),
|
||||
DropdownButton<int>(
|
||||
value: _crossAxisCount,
|
||||
dropdownColor: const Color.fromARGB(255, 43, 43, 43),
|
||||
iconEnabledColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
items: [3, 4].map((int value) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: value,
|
||||
child: Text('$value Spalten', style: TextStyle(color: Colors.white)),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (int? newValue) {
|
||||
if (newValue != null) {
|
||||
setState(() {
|
||||
_crossAxisCount = newValue;
|
||||
});
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
itemCount: mediaItems.length + (isLoading ? 1 : 0),
|
||||
itemBuilder: (context, index) {
|
||||
if (index >= mediaItems.length) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
final item = mediaItems[index];
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () async {
|
||||
try {
|
||||
final details = await fetchMediaDetail(item.id);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DetailView(details: details)),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Fehler beim Laden der Details: $e');
|
||||
body: RefreshIndicator(
|
||||
onRefresh: _refreshMedia,
|
||||
child: GridView.builder(
|
||||
controller: _scrollController,
|
||||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: _crossAxisCount,
|
||||
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.tagid] ?? 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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
child: Image.network(item.thumbnailUrl, fit: BoxFit.cover),
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_scrollController.dispose();
|
||||
_debounceTimer?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ class MediaItemDetail {
|
||||
final String dest;
|
||||
final String username;
|
||||
final int stamp;
|
||||
final int next;
|
||||
final int prev;
|
||||
final int? next;
|
||||
final int? prev;
|
||||
|
||||
MediaItemDetail({
|
||||
required this.id,
|
||||
|
@ -1,82 +0,0 @@
|
||||
import 'package:f0ckapp/services/api.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'package:f0ckapp/models/mediaitem_detail.dart';
|
||||
import 'package:f0ckapp/widgets/video_widget.dart';
|
||||
|
||||
class DetailScreen extends StatelessWidget {
|
||||
final MediaItemDetail details;
|
||||
|
||||
const DetailScreen({super.key, required this.details});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (details.velocity.pixelsPerSecond.dx > 0) {
|
||||
_navigateToPrev(context);
|
||||
} else {
|
||||
_navigateToNext(context);
|
||||
}
|
||||
},
|
||||
child: VideoWidget(details: this.details)
|
||||
);
|
||||
//return VideoWidget(url: this.details.mediaUrl);
|
||||
/*return GestureDetector(
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (details.velocity.pixelsPerSecond.dx > 0) {
|
||||
_navigateToPrev(context);
|
||||
} else {
|
||||
_navigateToNext(context);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
body: Column(
|
||||
children: [
|
||||
if (this.details.mime.startsWith('image'))
|
||||
ClipRRect(
|
||||
child: Image.network(this.details.mediaUrl, fit: BoxFit.cover),
|
||||
)
|
||||
else
|
||||
VideoPost(
|
||||
url: this.details.mediaUrl,
|
||||
/*appBar: AppBar(
|
||||
backgroundColor: const Color.fromARGB(255, 43, 43, 43),
|
||||
foregroundColor: const Color.fromARGB(255, 255, 255, 255),
|
||||
title: const Text('f0ck'),
|
||||
centerTitle: true,
|
||||
)*/
|
||||
),
|
||||
|
||||
//const SizedBox(height: 10),
|
||||
//Text('f0cked by: ${this.details.username}', style: const TextStyle(fontSize: 16, color: Color.fromARGB(255, 255, 255, 255))),
|
||||
],
|
||||
),
|
||||
),
|
||||
);*/
|
||||
}
|
||||
|
||||
void _navigateToNext(BuildContext context) async {
|
||||
try {
|
||||
final nextDetails = await fetchMediaDetail(details.next);
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DetailScreen(details: nextDetails)),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Fehler beim Laden des nächsten Items: $e');
|
||||
}
|
||||
}
|
||||
|
||||
void _navigateToPrev(BuildContext context) async {
|
||||
try {
|
||||
final prevDetails = await fetchMediaDetail(details.prev);
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => DetailScreen(details: prevDetails)),
|
||||
);
|
||||
} catch (e) {
|
||||
print('Fehler beim Laden des vorherigen Items: $e');
|
||||
}
|
||||
}
|
||||
}
|
@ -84,10 +84,10 @@ class _DetailViewState extends State<DetailView> {
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onHorizontalDragEnd: (details) {
|
||||
if (details.velocity.pixelsPerSecond.dx > 0) {
|
||||
_loadNewMediaItem(widget.details.next, true);
|
||||
} else if (details.velocity.pixelsPerSecond.dx < 0) {
|
||||
_loadNewMediaItem(widget.details.prev, false);
|
||||
if (details.velocity.pixelsPerSecond.dx > 0 && widget.details.next != null) {
|
||||
_loadNewMediaItem(widget.details.next!, true);
|
||||
} else if (details.velocity.pixelsPerSecond.dx < 0 && widget.details.prev != null) {
|
||||
_loadNewMediaItem(widget.details.prev!, false);
|
||||
}
|
||||
},
|
||||
child: Scaffold(
|
||||
|
@ -21,7 +21,7 @@ Future<List<MediaItem>> fetchMedia({String? older}) async {
|
||||
}
|
||||
|
||||
Future<MediaItemDetail> fetchMediaDetail(int itemId) async {
|
||||
final Uri url = Uri.parse('https://f0ck.me/api/v2/item/$itemId');
|
||||
final Uri url = Uri.parse('https://f0ck.me/api/v2/item/${itemId.toString()}');
|
||||
|
||||
final response = await http.get(url);
|
||||
if (response.statusCode == 200) {
|
||||
|
Reference in New Issue
Block a user