aktueller Stand

This commit is contained in:
Flummi 2025-05-31 19:44:32 +02:00
parent 018581e4d2
commit 30f8f3133a
13 changed files with 375 additions and 140 deletions

View File

@ -2,17 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:f0ckapp/mediagrid.dart'; import 'package:f0ckapp/mediagrid.dart';
//import 'package:media_kit/media_kit.dart';
void main() { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
//MediaKit.ensureInitialized(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp
]).then((_) {
runApp(const F0ckApp()); runApp(const F0ckApp());
});
} }
class F0ckApp extends StatelessWidget { class F0ckApp extends StatelessWidget {
@ -25,13 +19,6 @@ class F0ckApp extends StatelessWidget {
scaffoldBackgroundColor: const Color.fromARGB(255, 23, 23, 23), scaffoldBackgroundColor: const Color.fromARGB(255, 23, 23, 23),
), ),
home: Scaffold( 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(), body: MediaGrid(),
), ),
); );

View File

@ -1,8 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:f0ckapp/services/api.dart'; import 'package:f0ckapp/services/api.dart';
import 'package:f0ckapp/models/mediaitem.dart'; import 'package:f0ckapp/models/mediaitem.dart';
import 'package:f0ckapp/screens/detail.dart'; import 'package:f0ckapp/screens/detail.dart';
import 'dart:async';
class MediaGrid extends StatefulWidget { class MediaGrid extends StatefulWidget {
const MediaGrid({super.key}); const MediaGrid({super.key});
@ -15,41 +15,134 @@ class _MediaGridState extends State<MediaGrid> {
final ScrollController _scrollController = ScrollController(); final ScrollController _scrollController = ScrollController();
List<MediaItem> mediaItems = []; List<MediaItem> mediaItems = [];
bool isLoading = false; bool isLoading = false;
Timer? _debounceTimer;
Completer<void>? _navigationCompleter;
int _crossAxisCount = 3;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_loadMedia(); _loadMedia();
_scrollController.addListener(() { _scrollController.addListener(() {
if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) { if (_scrollController.position.pixels >=
_loadMedia(); _scrollController.position.maxScrollExtent - 100) {
_debounceLoadMedia();
} }
}); });
} }
void _debounceLoadMedia() {
_debounceTimer?.cancel();
_debounceTimer = Timer(const Duration(milliseconds: 500), _loadMedia);
}
Future<void> _loadMedia() async { Future<void> _loadMedia() async {
if (isLoading) return; if (isLoading) return;
setState(() => isLoading = true); setState(() => isLoading = true);
try { try {
List<MediaItem> newMedia = await fetchMedia(older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null); final newMedia = await fetchMedia(
setState(() { older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null,
mediaItems.addAll(newMedia); );
isLoading = false; if (mounted) {
}); setState(() => mediaItems.addAll(newMedia));
}
} catch (e) { } 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GridView.builder( 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;
});
}
},
),
],
),
),
body: RefreshIndicator(
onRefresh: _refreshMedia,
child: GridView.builder(
controller: _scrollController, controller: _scrollController,
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3, crossAxisCount: _crossAxisCount,
crossAxisSpacing: 8.0, crossAxisSpacing: 5.0,
mainAxisSpacing: 8.0, mainAxisSpacing: 5.0,
), ),
itemCount: mediaItems.length + (isLoading ? 1 : 0), itemCount: mediaItems.length + (isLoading ? 1 : 0),
itemBuilder: (context, index) { itemBuilder: (context, index) {
@ -58,21 +151,32 @@ class _MediaGridState extends State<MediaGrid> {
} }
final item = mediaItems[index]; final item = mediaItems[index];
return GestureDetector( final mode =
onTap: () async { {1: Colors.green, 2: Colors.red}[item.tagid] ?? Colors.yellow;
try {
final details = await fetchMediaDetail(item.id); return InkWell(
Navigator.push( onTap: () => _navigateToDetail(item),
context, child: Stack(
MaterialPageRoute(builder: (context) => DetailView(details: details)), 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),
),
],
),
);
},
),
),
); );
} catch (e) {
print('Fehler beim Laden der Details: $e');
} }
},
child: Image.network(item.thumbnailUrl, fit: BoxFit.cover), @override
); void dispose() {
}, _scrollController.dispose();
); _debounceTimer?.cancel();
super.dispose();
} }
} }

View File

@ -4,8 +4,8 @@ class MediaItemDetail {
final String dest; final String dest;
final String username; final String username;
final int stamp; final int stamp;
final int next; final int? next;
final int prev; final int? prev;
MediaItemDetail({ MediaItemDetail({
required this.id, required this.id,

View File

@ -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');
}
}
}

View File

@ -84,10 +84,10 @@ class _DetailViewState extends State<DetailView> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return GestureDetector( return GestureDetector(
onHorizontalDragEnd: (details) { onHorizontalDragEnd: (details) {
if (details.velocity.pixelsPerSecond.dx > 0) { if (details.velocity.pixelsPerSecond.dx > 0 && widget.details.next != null) {
_loadNewMediaItem(widget.details.next, true); _loadNewMediaItem(widget.details.next!, true);
} else if (details.velocity.pixelsPerSecond.dx < 0) { } else if (details.velocity.pixelsPerSecond.dx < 0 && widget.details.prev != null) {
_loadNewMediaItem(widget.details.prev, false); _loadNewMediaItem(widget.details.prev!, false);
} }
}, },
child: Scaffold( child: Scaffold(

View File

@ -21,7 +21,7 @@ Future<List<MediaItem>> fetchMedia({String? older}) async {
} }
Future<MediaItemDetail> fetchMediaDetail(int itemId) 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); final response = await http.get(url);
if (response.statusCode == 200) { if (response.statusCode == 200) {

View File

@ -6,6 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <url_launcher_linux/url_launcher_plugin.h>
void fl_register_plugins(FlPluginRegistry* registry) { void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
} }

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_linux
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST

View File

@ -5,12 +5,20 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import device_info_plus
import package_info_plus
import path_provider_foundation import path_provider_foundation
import shared_preferences_foundation
import sqflite_darwin import sqflite_darwin
import url_launcher_macos
import video_player_avfoundation import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
} }

View File

@ -89,6 +89,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.8"
device_info_plus:
dependency: transitive
description:
name: device_info_plus
sha256: "0c6396126421b590089447154c5f98a5de423b70cfb15b1578fd018843ee6f53"
url: "https://pub.dev"
source: hosted
version: "11.4.0"
device_info_plus_platform_interface:
dependency: transitive
description:
name: device_info_plus_platform_interface
sha256: "0b04e02b30791224b31969eb1b50d723498f402971bff3630bca2ba839bd1ed2"
url: "https://pub.dev"
source: hosted
version: "7.0.2"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -240,6 +256,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "2.1.0"
os_detect:
dependency: transitive
description:
name: os_detect
sha256: "7d87c0dd98c6faf110d5aa498e9a6df02ffce4bb78cc9cfc8ad02929be9bb71f"
url: "https://pub.dev"
source: hosted
version: "2.0.3"
package_info_plus:
dependency: transitive
description:
name: package_info_plus
sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191"
url: "https://pub.dev"
source: hosted
version: "8.3.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -296,6 +336,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.0" version: "2.3.0"
petitparser:
dependency: transitive
description:
name: petitparser
sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646"
url: "https://pub.dev"
source: hosted
version: "6.1.0"
platform: platform:
dependency: transitive dependency: transitive
description: description:
@ -320,6 +368,62 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.28.0" version: "0.28.0"
shared_preferences:
dependency: transitive
description:
name: shared_preferences
sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
shared_preferences_android:
dependency: transitive
description:
name: shared_preferences_android
sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac"
url: "https://pub.dev"
source: hosted
version: "2.4.10"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:
name: shared_preferences_linux
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
url: "https://pub.dev"
source: hosted
version: "2.4.3"
shared_preferences_windows:
dependency: transitive
description:
name: shared_preferences_windows
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -437,6 +541,78 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.4.0" version: "1.4.0"
upgrader:
dependency: "direct main"
description:
name: upgrader
sha256: "60293b391f4146ce1e381ed6198b6374559a4aadf885d313e94e6c7d454d03ca"
url: "https://pub.dev"
source: hosted
version: "11.4.0"
url_launcher:
dependency: transitive
description:
name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79"
url: "https://pub.dev"
source: hosted
version: "6.3.16"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb"
url: "https://pub.dev"
source: hosted
version: "6.3.3"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2"
url: "https://pub.dev"
source: hosted
version: "3.2.2"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77"
url: "https://pub.dev"
source: hosted
version: "3.1.4"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@ -453,6 +629,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.0" version: "2.2.0"
version:
dependency: transitive
description:
name: version
sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
video_player: video_player:
dependency: "direct main" dependency: "direct main"
description: description:
@ -509,6 +693,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
url: "https://pub.dev"
source: hosted
version: "5.13.0"
win32_registry:
dependency: transitive
description:
name: win32_registry
sha256: "6f1b564492d0147b330dd794fee8f512cec4977957f310f9951b5f9d83618dae"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
xdg_directories: xdg_directories:
dependency: transitive dependency: transitive
description: description:
@ -517,6 +717,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.0" version: "1.1.0"
xml:
dependency: transitive
description:
name: xml
sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
url: "https://pub.dev"
source: hosted
version: "6.5.0"
sdks: sdks:
dart: ">=3.9.0-172.0.dev <4.0.0" dart: ">=3.9.0-172.0.dev <4.0.0"
flutter: ">=3.29.0" flutter: ">=3.29.0"

View File

@ -37,6 +37,7 @@ dependencies:
# The following adds the Cupertino Icons font to your application. # The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons. # Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.8 cupertino_icons: ^1.0.8
upgrader: ^11.4.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -6,6 +6,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
} }

View File

@ -3,6 +3,7 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
url_launcher_windows
) )
list(APPEND FLUTTER_FFI_PLUGIN_LIST list(APPEND FLUTTER_FFI_PLUGIN_LIST