Compare commits

...

4 Commits

Author SHA1 Message Date
c7d996a402 v1.0.29+29
All checks were successful
Flutter Schmutter / build (push) Successful in 3m28s
2025-06-06 11:29:01 +02:00
ee93ef576b xd 2025-06-06 10:10:40 +02:00
78ff1953ad v1.0.28+28
All checks were successful
Flutter Schmutter / build (push) Successful in 3m30s
2025-06-06 08:43:50 +02:00
6fb4775043 v1.0.27+27 -.-
All checks were successful
Flutter Schmutter / build (push) Successful in 3m20s
2025-06-05 21:59:02 +02:00
9 changed files with 154 additions and 107 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -4,12 +4,14 @@ import 'package:flutter/services.dart';
import 'package:f0ckapp/providers/MediaProvider.dart';
import 'package:f0ckapp/providers/ThemeProvider.dart';
import 'package:f0ckapp/screens/MediaGrid.dart';
import 'package:f0ckapp/utils/AppVersion.dart';
import 'package:provider/provider.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await AppVersion.init();
runApp(
MultiProvider(

View File

@ -8,7 +8,7 @@ class MediaProvider extends ChangeNotifier {
bool _random = false;
String? _tag;
int _crossAxisCount = 0;
final List<MediaItem> _mediaItems = [];
List<MediaItem> _mediaItems = [];
bool _isLoading = false;
List<String> types = ["alles", "image", "video", "audio"];
@ -19,28 +19,29 @@ class MediaProvider extends ChangeNotifier {
int get mode => _mode;
bool get random => _random;
String? get tag => _tag;
int get crossAxisCount => _crossAxisCount;
int get crossAxisCount => _crossAxisCount;
List<MediaItem> get mediaItems => _mediaItems;
bool get isLoading => _isLoading;
Function get resetMedia => _resetMedia;
void setType(String type) {
_typeid = types.indexOf(type);
loadMedia(reload: true);
_resetMedia();
}
void setMode(int mode) {
_mode = mode;
loadMedia(reload: true);
_resetMedia();
}
void toggleRandom() {
_random = !_random;
loadMedia(reload: true);
_resetMedia();
}
void setTag(String? tag) {
_tag = tag;
loadMedia(reload: true);
_resetMedia();
}
void setCrossAxisCount(int crossAxisCount) {
@ -49,9 +50,11 @@ class MediaProvider extends ChangeNotifier {
}
void setMediaItems(List<MediaItem> mediaItems) {
_mediaItems.clear();
addMediaItems(mediaItems);
notifyListeners();
if (_mediaItems != mediaItems) {
_mediaItems.clear();
_mediaItems.addAll(mediaItems);
notifyListeners();
}
}
void addMediaItems(List<MediaItem> newItems) {
@ -59,30 +62,32 @@ class MediaProvider extends ChangeNotifier {
notifyListeners();
}
Future<void> loadMedia({bool reload = false}) async {
void _resetMedia() {
_mediaItems.clear();
notifyListeners();
loadMedia();
}
Future<void> loadMedia({bool notify = true}) async {
if (_isLoading) return;
_isLoading = true;
notifyListeners();
if (notify) notifyListeners();
try {
final newMedia = await fetchMedia(
older: reload
? null
: _mediaItems.isNotEmpty
? _mediaItems.last.id
: null,
older: _mediaItems.isNotEmpty ? _mediaItems.last.id : null,
type: type,
mode: mode,
random: random,
tag: tag,
);
reload ? setMediaItems(newMedia) : addMediaItems(newMedia);
addMediaItems(newMedia);
} catch (e) {
debugPrint('Fehler beim Laden der Medien: $e');
} finally {
_isLoading = false;
notifyListeners();
if (notify) notifyListeners();
}
}
}

View File

@ -21,6 +21,7 @@ class DetailView extends StatefulWidget {
class _DetailViewState extends State<DetailView> {
late PageController _pageController;
bool isLoading = false;
int _currentIndex = 0;
@override
void initState() {
@ -30,8 +31,14 @@ class _DetailViewState extends State<DetailView> {
final initialIndex = provider.mediaItems.indexWhere(
(item) => item.id == widget.initialItemId,
);
_pageController = PageController(initialPage: initialIndex);
_currentIndex = initialIndex;
_pageController.addListener(() {
setState(() => _currentIndex = _pageController.page?.round() ?? 0);
});
_preloadAdjacentMedia(initialIndex);
}
@ -88,19 +95,18 @@ class _DetailViewState extends State<DetailView> {
final provider = Provider.of<MediaProvider>(context);
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('f0ck #${widget.initialItemId} (${provider.type})'),
),
appBar: AppBar(centerTitle: true, title: const Text('f0ck')),
body: Stack(
children: [
PageTransformer(
controller: _pageController,
pages: provider.mediaItems.map((item) {
int itemIndex = provider.mediaItems.indexOf(item);
return SafeArea(
child: SmartRefreshIndicator(
onRefresh: _loadMoreMedia,
child: _buildMediaItem(item),
child: _buildMediaItem(item, _currentIndex == itemIndex),
),
);
}).toList(),
@ -129,7 +135,7 @@ class _DetailViewState extends State<DetailView> {
);
}
Widget _buildMediaItem(MediaItem item) {
Widget _buildMediaItem(MediaItem item, bool isActive) {
final provider = Provider.of<MediaProvider>(context);
return SingleChildScrollView(
@ -143,10 +149,10 @@ class _DetailViewState extends State<DetailView> {
errorWidget: (context, url, error) => Icon(Icons.error),
)
else
VideoWidget(details: item, isActive: true),
VideoWidget(details: item, isActive: isActive),
const SizedBox(height: 20),
Text(
item.mime,
'f0ck #${item.id.toString()}',
style: const TextStyle(color: Colors.white, fontSize: 18),
),
const SizedBox(height: 10, width: double.infinity),
@ -159,7 +165,7 @@ class _DetailViewState extends State<DetailView> {
if (tag.tag == 'sfw' || tag.tag == 'nsfw') return;
setState(() {
provider.setTag(tag.tag);
Navigator.pop(context);
Navigator.pop(context, true);
});
},
label: Text(tag.tag),

View File

@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:provider/provider.dart';
import 'package:f0ckapp/screens/DetailView.dart';
import 'package:f0ckapp/services/Api.dart';
import 'package:f0ckapp/providers/MediaProvider.dart';
import 'package:f0ckapp/utils/AppVersion.dart';
class MediaGrid extends StatefulWidget {
const MediaGrid({super.key});
@ -27,12 +27,16 @@ class _MediaGridState extends State<MediaGrid> {
_scrollController.addListener(() {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 100) {
provider.loadMedia();
_scrollController.position.maxScrollExtent - 200) {
provider.loadMedia(notify: false);
}
});
}
void scrollToTop() {
_scrollController.jumpTo(0);
}
@override
Widget build(BuildContext context) {
final provider = Provider.of<MediaProvider>(context);
@ -42,20 +46,15 @@ class _MediaGridState extends State<MediaGrid> {
key: scaffoldKey,
appBar: AppBar(
//centerTitle: true,
title: Text('f0ck v1.0.26+26'),
title: Text('fApp v${AppVersion.version}'),
actions: [
DropdownButton<String>(
// mode
value: provider.modes[provider.mode],
isDense: true,
icon: SizedBox.shrink(),
items: provider.modes.map((String value) {
return DropdownMenuItem<String>(value: value, child: Text(value));
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
provider.setMode(provider.modes.indexOf(newValue));
}
IconButton(
icon: Icon(
provider.random ? Icons.shuffle_on_outlined : Icons.shuffle,
),
onPressed: () {
provider.toggleRandom();
_scrollController.jumpTo(0);
},
),
IconButton(
@ -66,6 +65,52 @@ class _MediaGridState extends State<MediaGrid> {
),
],
),
bottomNavigationBar: BottomAppBar(
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
Text('type: '),
DropdownButton<String>(
// type
value: provider.type,
isDense: true,
//icon: SizedBox.shrink(),
items: provider.types.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value, style: TextStyle(color: Colors.white)),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
provider.setType(newValue);
_scrollController.jumpTo(0);
}
},
),
Text('mode: '),
DropdownButton<String>(
// mode
value: provider.modes[provider.mode],
isDense: true,
//icon: SizedBox.shrink(),
items: provider.modes.map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (String? newValue) {
if (newValue != null) {
provider.setMode(provider.modes.indexOf(newValue));
_scrollController.jumpTo(0);
}
},
),
],
),
),
endDrawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
@ -74,62 +119,6 @@ class _MediaGridState extends State<MediaGrid> {
padding: EdgeInsets.all(0),
child: Image.asset('assets/images/menu.webp', fit: BoxFit.cover),
),
ListTile(
title: Text(
'All',
style: TextStyle(
fontWeight: provider.type == 'alles'
? FontWeight.bold
: FontWeight.normal,
color: provider.type == 'alles' ? Colors.blue : Colors.white,
),
),
onTap: () {
provider.setType('all');
},
),
ListTile(
title: Text(
'Images',
style: TextStyle(
fontWeight: provider.type == 'image'
? FontWeight.bold
: FontWeight.normal,
color: provider.type == 'image' ? Colors.blue : Colors.white,
),
),
onTap: () {
provider.setType('image');
},
),
ListTile(
title: Text(
'Videos',
style: TextStyle(
fontWeight: provider.type == 'video'
? FontWeight.bold
: FontWeight.normal,
color: provider.type == 'video' ? Colors.blue : Colors.white,
),
),
onTap: () {
provider.setType('video');
},
),
ListTile(
title: Text(
'Audio',
style: TextStyle(
fontWeight: provider.type == 'audio'
? FontWeight.bold
: FontWeight.normal,
color: provider.type == 'audio' ? Colors.blue : Colors.white,
),
),
onTap: () {
provider.setType('audio');
},
),
],
),
),
@ -145,6 +134,7 @@ class _MediaGridState extends State<MediaGrid> {
labelStyle: const TextStyle(color: Colors.white),
onDeleted: () {
provider.setTag(null);
_scrollController.jumpTo(0);
},
),
],
@ -153,11 +143,13 @@ class _MediaGridState extends State<MediaGrid> {
: null,
body: RefreshIndicator(
onRefresh: () async {
await provider.loadMedia(reload: true);
await provider.resetMedia();
_scrollController.jumpTo(0);
},
child: Consumer<MediaProvider>(
builder: (context, mediaProvider, child) {
return GridView.builder(
key: PageStorageKey('mediaGrid'),
controller: _scrollController,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: mediaProvider.crossAxisCount == 0
@ -177,12 +169,17 @@ class _MediaGridState extends State<MediaGrid> {
final item = provider.mediaItems[index];
return InkWell(
onTap: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailView(initialItemId: item.id),
),
),
onTap: () async {
bool test = await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailView(initialItemId: item.id),
),
);
if (test) {
scrollToTop();
}
},
child: Stack(
fit: StackFit.expand,
children: <Widget>[

10
lib/utils/AppVersion.dart Normal file
View File

@ -0,0 +1,10 @@
import 'package:package_info_plus/package_info_plus.dart';
class AppVersion {
static String version = "";
static Future<void> init() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
version = '${packageInfo.version}+${packageInfo.buildNumber}';
}
}

View File

@ -5,11 +5,13 @@
import FlutterMacOS
import Foundation
import package_info_plus
import path_provider_foundation
import sqflite_darwin
import video_player_avfoundation
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))

View File

@ -272,6 +272,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.0"
package_info_plus:
dependency: "direct main"
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:
dependency: transitive
description:
@ -541,6 +557,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.1"
win32:
dependency: transitive
description:
name: win32
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
url: "https://pub.dev"
source: hosted
version: "5.13.0"
xdg_directories:
dependency: transitive
description:

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.26+26
version: 1.0.29+29
environment:
sdk: ^3.9.0-100.2.beta
@ -38,6 +38,7 @@ dependencies:
cached_network_image: ^3.4.1
cached_video_player_plus: ^3.0.3
provider: ^6.1.5
package_info_plus: ^8.3.0
dev_dependencies:
flutter_test: