v1.1.9+39
All checks were successful
Flutter Schmutter / build (push) Successful in 3m35s

This commit is contained in:
Flummi 2025-06-09 14:02:59 +02:00
parent 93fb3536ee
commit 671b3cfbe0
7 changed files with 122 additions and 118 deletions

View File

@ -29,7 +29,7 @@
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<meta-data android:name="flutter_deeplinking_enabled" android:value="false"/> <meta-data android:name="flutter_deeplinking_enabled" android:value="true"/>
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW"/> <action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/> <category android:name="android.intent.category.DEFAULT"/>

View File

@ -1,36 +1,87 @@
import 'package:app_links/app_links.dart'; import 'package:f0ckapp/providers/MediaProvider.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:go_router/go_router.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:f0ckapp/screens/MediaGrid.dart'; import 'package:f0ckapp/screens/MediaGrid.dart';
import 'package:f0ckapp/screens/DetailView.dart';
import 'package:f0ckapp/utils/AppVersion.dart'; import 'package:f0ckapp/utils/AppVersion.dart';
import 'package:f0ckapp/providers/ThemeProvider.dart'; import 'package:f0ckapp/providers/ThemeProvider.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await AppVersion.init(); await AppVersion.init();
final Uri? initialUri = await AppLinks().getInitialLink();
runApp(ProviderScope(child: F0ckApp(initialUri: initialUri))); runApp(ProviderScope(child: F0ckApp()));
} }
class F0ckApp extends ConsumerWidget { class F0ckApp extends ConsumerWidget {
final Uri? initialUri; F0ckApp({super.key});
const F0ckApp({super.key, this.initialUri});
final GoRouter _router = GoRouter(
initialLocation: '/',
routes: [
GoRoute(
path: '/',
builder: (context, state) {
return const MediaGrid();
},
),
GoRoute(
path: '/:rest(.*)',
builder: (context, state) {
final isInternalLink = (state.extra is bool && state.extra == true);
final fullPath = state.matchedLocation;
final regExp = RegExp(
r'^(?:/tag/(?<tag>.+?))?(?:/(?<mime>image|audio|video))?(?:/(?<itemid>\d+))?$',
);
final match = regExp.firstMatch(fullPath);
if (match == null) {
return const Scaffold(body: Center(child: Text('Ungültiger Link')));
}
final String? tag = match.namedGroup('tag');
final String? mime = match.namedGroup('mime');
final String? idStr = match.namedGroup('itemid');
final int? itemId = idStr != null ? int.tryParse(idStr) : null;
const preloadOffset = 50;
return Consumer(
builder: (context, ref, child) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
final mediaNotifier = ref.read(mediaProvider.notifier);
if (!isInternalLink) {
mediaNotifier.setType(mime ?? "alles");
mediaNotifier.setTag(tag);
}
if (itemId != null) {
await mediaNotifier.loadMedia(id: itemId + preloadOffset);
}
});
if (itemId != null) {
return DetailView(initialItemId: itemId);
} else {
return MediaGrid();
}
},
);
},
),
],
);
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
return Consumer( final theme = ref.watch(themeNotifierProvider);
builder: (context, ref, _) { return MaterialApp.router(
return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
theme: ref.watch(themeNotifierProvider), routerConfig: _router,
home: MediaGrid(initialUri: initialUri), theme: theme,
);
},
); );
} }
} }

View File

@ -107,11 +107,7 @@ class MediaNotifier extends StateNotifier<MediaState> {
} }
Future<void> loadMedia({int? id}) async { Future<void> loadMedia({int? id}) async {
//if (state.isLoading) return; if (state.isLoading) return;
if (id != null) {
print('requested id: ${id.toString()}');
}
state = state.replace(isLoading: true); state = state.replace(isLoading: true);
try { try {
final older = final older =

View File

@ -2,10 +2,10 @@ import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:go_router/go_router.dart';
import 'package:share_plus/share_plus.dart'; import 'package:share_plus/share_plus.dart';
import 'package:f0ckapp/models/MediaItem.dart'; import 'package:f0ckapp/models/MediaItem.dart';
@ -24,26 +24,13 @@ class DetailView extends ConsumerStatefulWidget {
} }
class _DetailViewState extends ConsumerState<DetailView> { class _DetailViewState extends ConsumerState<DetailView> {
late PageController _pageController; PageController? _pageController;
bool isLoading = false; bool isLoading = false;
int _currentIndex = 0; int _currentIndex = 0;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
final mediaState = ref.read(mediaProvider);
final initialIndex = mediaState.mediaItems.indexWhere(
(item) => item.id == widget.initialItemId,
);
_pageController = PageController(initialPage: initialIndex);
_currentIndex = initialIndex;
_pageController.addListener(() {
setState(() => _currentIndex = _pageController.page?.round() ?? 0);
});
_preloadAdjacentMedia(initialIndex);
} }
void _preloadAdjacentMedia(int index) async { void _preloadAdjacentMedia(int index) async {
@ -81,37 +68,60 @@ class _DetailViewState extends ConsumerState<DetailView> {
@override @override
void dispose() { void dispose() {
_pageController.dispose(); _pageController?.dispose();
super.dispose(); super.dispose();
} }
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final mediaState = ref.watch(mediaProvider); final mediaState = ref.watch(mediaProvider);
final mediaNotifier = ref.read(mediaProvider.notifier); final int itemIndex = mediaState.mediaItems.indexWhere(
(item) => item.id == widget.initialItemId,
);
if (mediaState.mediaItems.isEmpty) { if (itemIndex == -1) {
Future.microtask(() {
ref
.read(mediaProvider.notifier)
.loadMedia(id: widget.initialItemId + 50);
});
return Scaffold( return Scaffold(
appBar: AppBar(), appBar: AppBar(),
body: const Center(child: CircularProgressIndicator()), body: const Center(child: CircularProgressIndicator()),
); );
} }
if (_pageController == null) {
_pageController = PageController(initialPage: itemIndex);
_currentIndex = itemIndex;
_pageController!.addListener(() {
setState(() => _currentIndex = _pageController!.page?.round() ?? 0);
});
_preloadAdjacentMedia(itemIndex);
}
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
centerTitle: true, centerTitle: true,
title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'), title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'),
automaticallyImplyLeading: false,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
context.canPop() ? context.pop() : context.go('/', extra: true);
},
),
actions: [ actions: [
IconButton( IconButton(
icon: Icon(Icons.fullscreen), icon: const Icon(Icons.fullscreen),
onPressed: () { onPressed: () {
// wip _showError('fullscreen ist wip');
}, },
), ),
IconButton( IconButton(
icon: Icon(Icons.download), icon: const Icon(Icons.download),
onPressed: () { onPressed: () {
// wip _showError('download ist wip');
}, },
), ),
PopupMenuButton<String>( PopupMenuButton<String>(
@ -170,7 +180,7 @@ class _DetailViewState extends ConsumerState<DetailView> {
body: Stack( body: Stack(
children: [ children: [
PageTransformer( PageTransformer(
controller: _pageController, controller: _pageController!,
pages: mediaState.mediaItems.map((item) { pages: mediaState.mediaItems.map((item) {
int itemIndex = mediaState.mediaItems.indexOf(item); int itemIndex = mediaState.mediaItems.indexOf(item);
return SafeArea( return SafeArea(
@ -189,8 +199,8 @@ class _DetailViewState extends ConsumerState<DetailView> {
child: InputChip( child: InputChip(
label: Text(mediaState.tag!), label: Text(mediaState.tag!),
onDeleted: () { onDeleted: () {
mediaNotifier.setTag(null); ref.read(mediaProvider.notifier).setTag(null);
Navigator.pop(context); context.go('/', extra: true);
}, },
), ),
), ),
@ -224,7 +234,7 @@ class _DetailViewState extends ConsumerState<DetailView> {
if (tag.tag == 'sfw' || tag.tag == 'nsfw') return; if (tag.tag == 'sfw' || tag.tag == 'nsfw') return;
setState(() { setState(() {
mediaNotifier.setTag(tag.tag); mediaNotifier.setTag(tag.tag);
Navigator.pop(context, true); context.go('/', extra: true);
}); });
}, },
label: Text(tag.tag), label: Text(tag.tag),

View File

@ -1,21 +1,19 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:app_links/app_links.dart';
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:f0ckapp/screens/DetailView.dart'; import 'package:f0ckapp/screens/DetailView.dart';
import 'package:f0ckapp/providers/MediaProvider.dart'; import 'package:f0ckapp/providers/MediaProvider.dart';
import 'package:f0ckapp/utils/AppVersion.dart'; import 'package:f0ckapp/utils/AppVersion.dart';
import 'package:f0ckapp/utils/ParseDeepLink.dart';
import 'package:f0ckapp/providers/ThemeProvider.dart'; import 'package:f0ckapp/providers/ThemeProvider.dart';
import 'package:go_router/go_router.dart';
const List<String> mediaTypes = ["alles", "image", "video", "audio"]; const List<String> mediaTypes = ["alles", "image", "video", "audio"];
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"]; const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
class MediaGrid extends ConsumerStatefulWidget { class MediaGrid extends ConsumerStatefulWidget {
final Uri? initialUri = null; const MediaGrid({super.key});
const MediaGrid({super.key, required initialUri});
@override @override
ConsumerState<MediaGrid> createState() => _MediaGridState(); ConsumerState<MediaGrid> createState() => _MediaGridState();
@ -28,8 +26,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
final TextEditingController _usernameController = TextEditingController(); final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController(); final TextEditingController _passwordController = TextEditingController();
final appLinks = AppLinks();
int _calculateCrossAxisCount(BuildContext context, int defaultCount) { int _calculateCrossAxisCount(BuildContext context, int defaultCount) {
return defaultCount == 0 return defaultCount == 0
? (MediaQuery.of(context).size.width / 110).clamp(3, 5).toInt() ? (MediaQuery.of(context).size.width / 110).clamp(3, 5).toInt()
@ -48,23 +44,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
ref.read(mediaProvider.notifier).loadMedia(); ref.read(mediaProvider.notifier).loadMedia();
} }
}); });
appLinks.uriLinkStream.listen((Uri uri) async {
final parsedResult = parseDeepLink(uri);
if (parsedResult == null) return;
if (parsedResult['route'] != 'complex') return;
final params = parsedResult['params'] as Map<String, String>;
await handleComplexDeepLink(params, context, ref, _scrollController);
});
//print('initial: ${parseDeepLink(widget.initialUri)}');
Future.microtask(() async {
final initparsedResult = parseDeepLink(widget.initialUri);
if (initparsedResult == null) return;
if (initparsedResult['route'] != 'complex') return;
final initparams = initparsedResult['params'] as Map<String, String>;
await handleComplexDeepLink(initparams, context, ref, _scrollController);
});
} }
@override @override
@ -302,15 +281,7 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
return InkWell( return InkWell(
onTap: () async { onTap: () async {
bool? ret = await Navigator.push( context.push('/${item.id}', extra: true);
context,
MaterialPageRoute(
builder: (context) => DetailView(initialItemId: item.id),
),
);
if (ret != null && ret) {
_scrollController.jumpTo(0);
}
}, },
child: Stack( child: Stack(
fit: StackFit.expand, fit: StackFit.expand,

View File

@ -1,38 +1,6 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
app_links:
dependency: "direct main"
description:
name: app_links
sha256: "85ed8fc1d25a76475914fff28cc994653bd900bc2c26e4b57a49e097febb54ba"
url: "https://pub.dev"
source: hosted
version: "6.4.0"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -272,14 +240,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
gtk: go_router:
dependency: transitive dependency: "direct main"
description: description:
name: gtk name: go_router
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c sha256: b453934c36e289cef06525734d1e676c1f91da9e22e2017d9dcab6ce0f999175
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" version: "15.1.3"
html: html:
dependency: transitive dependency: transitive
description: description:
@ -344,6 +312,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.0.0" version: "6.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
url: "https://pub.dev"
source: hosted
version: "1.3.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: 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 # 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 # 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. # of the product and file versions while build-number is used as the build suffix.
version: 1.1.8+38 version: 1.1.9+39
environment: environment:
sdk: ^3.9.0-100.2.beta sdk: ^3.9.0-100.2.beta
@ -41,7 +41,7 @@ dependencies:
share_plus: ^11.0.0 share_plus: ^11.0.0
flutter_secure_storage: ^9.2.4 flutter_secure_storage: ^9.2.4
flutter_riverpod: ^2.6.1 flutter_riverpod: ^2.6.1
app_links: ^6.4.0 go_router: ^15.1.3
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: