diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index fe518f2..be66168 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -29,7 +29,7 @@
-
+
diff --git a/lib/main.dart b/lib/main.dart
index a0146ac..a58e6fc 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -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/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/DetailView.dart';
import 'package:f0ckapp/utils/AppVersion.dart';
import 'package:f0ckapp/providers/ThemeProvider.dart';
-import 'package:flutter_riverpod/flutter_riverpod.dart';
-
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await AppVersion.init();
- final Uri? initialUri = await AppLinks().getInitialLink();
- runApp(ProviderScope(child: F0ckApp(initialUri: initialUri)));
+ runApp(ProviderScope(child: F0ckApp()));
}
class F0ckApp extends ConsumerWidget {
- final Uri? initialUri;
- const F0ckApp({super.key, this.initialUri});
+ F0ckApp({super.key});
+
+ 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/(?.+?))?(?:/(?image|audio|video))?(?:/(?\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
Widget build(BuildContext context, WidgetRef ref) {
- return Consumer(
- builder: (context, ref, _) {
- return MaterialApp(
- debugShowCheckedModeBanner: false,
- theme: ref.watch(themeNotifierProvider),
- home: MediaGrid(initialUri: initialUri),
- );
- },
+ final theme = ref.watch(themeNotifierProvider);
+ return MaterialApp.router(
+ debugShowCheckedModeBanner: false,
+ routerConfig: _router,
+ theme: theme,
);
}
}
diff --git a/lib/providers/MediaProvider.dart b/lib/providers/MediaProvider.dart
index da77a88..aae4f22 100644
--- a/lib/providers/MediaProvider.dart
+++ b/lib/providers/MediaProvider.dart
@@ -107,11 +107,7 @@ class MediaNotifier extends StateNotifier {
}
Future loadMedia({int? id}) async {
- //if (state.isLoading) return;
- if (id != null) {
- print('requested id: ${id.toString()}');
- }
-
+ if (state.isLoading) return;
state = state.replace(isLoading: true);
try {
final older =
diff --git a/lib/screens/DetailView.dart b/lib/screens/DetailView.dart
index 399ea23..af8d4cd 100644
--- a/lib/screens/DetailView.dart
+++ b/lib/screens/DetailView.dart
@@ -2,10 +2,10 @@ import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
-
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_riverpod/flutter_riverpod.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:f0ckapp/models/MediaItem.dart';
@@ -24,26 +24,13 @@ class DetailView extends ConsumerStatefulWidget {
}
class _DetailViewState extends ConsumerState {
- late PageController _pageController;
+ PageController? _pageController;
bool isLoading = false;
int _currentIndex = 0;
@override
void 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 {
@@ -81,37 +68,60 @@ class _DetailViewState extends ConsumerState {
@override
void dispose() {
- _pageController.dispose();
+ _pageController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
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(
appBar: AppBar(),
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(
appBar: AppBar(
centerTitle: true,
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: [
IconButton(
- icon: Icon(Icons.fullscreen),
+ icon: const Icon(Icons.fullscreen),
onPressed: () {
- // wip
+ _showError('fullscreen ist wip');
},
),
IconButton(
- icon: Icon(Icons.download),
+ icon: const Icon(Icons.download),
onPressed: () {
- // wip
+ _showError('download ist wip');
},
),
PopupMenuButton(
@@ -170,7 +180,7 @@ class _DetailViewState extends ConsumerState {
body: Stack(
children: [
PageTransformer(
- controller: _pageController,
+ controller: _pageController!,
pages: mediaState.mediaItems.map((item) {
int itemIndex = mediaState.mediaItems.indexOf(item);
return SafeArea(
@@ -189,8 +199,8 @@ class _DetailViewState extends ConsumerState {
child: InputChip(
label: Text(mediaState.tag!),
onDeleted: () {
- mediaNotifier.setTag(null);
- Navigator.pop(context);
+ ref.read(mediaProvider.notifier).setTag(null);
+ context.go('/', extra: true);
},
),
),
@@ -224,7 +234,7 @@ class _DetailViewState extends ConsumerState {
if (tag.tag == 'sfw' || tag.tag == 'nsfw') return;
setState(() {
mediaNotifier.setTag(tag.tag);
- Navigator.pop(context, true);
+ context.go('/', extra: true);
});
},
label: Text(tag.tag),
diff --git a/lib/screens/MediaGrid.dart b/lib/screens/MediaGrid.dart
index 05a0791..6c07148 100644
--- a/lib/screens/MediaGrid.dart
+++ b/lib/screens/MediaGrid.dart
@@ -1,21 +1,19 @@
import 'package:flutter/material.dart';
-import 'package:app_links/app_links.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:f0ckapp/screens/DetailView.dart';
import 'package:f0ckapp/providers/MediaProvider.dart';
import 'package:f0ckapp/utils/AppVersion.dart';
-import 'package:f0ckapp/utils/ParseDeepLink.dart';
import 'package:f0ckapp/providers/ThemeProvider.dart';
+import 'package:go_router/go_router.dart';
const List mediaTypes = ["alles", "image", "video", "audio"];
const List mediaModes = ["sfw", "nsfw", "untagged", "all"];
class MediaGrid extends ConsumerStatefulWidget {
- final Uri? initialUri = null;
- const MediaGrid({super.key, required initialUri});
+ const MediaGrid({super.key});
@override
ConsumerState createState() => _MediaGridState();
@@ -28,8 +26,6 @@ class _MediaGridState extends ConsumerState {
final TextEditingController _usernameController = TextEditingController();
final TextEditingController _passwordController = TextEditingController();
- final appLinks = AppLinks();
-
int _calculateCrossAxisCount(BuildContext context, int defaultCount) {
return defaultCount == 0
? (MediaQuery.of(context).size.width / 110).clamp(3, 5).toInt()
@@ -48,23 +44,6 @@ class _MediaGridState extends ConsumerState {
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;
- 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;
- await handleComplexDeepLink(initparams, context, ref, _scrollController);
- });
}
@override
@@ -302,15 +281,7 @@ class _MediaGridState extends ConsumerState {
return InkWell(
onTap: () async {
- bool? ret = await Navigator.push(
- context,
- MaterialPageRoute(
- builder: (context) => DetailView(initialItemId: item.id),
- ),
- );
- if (ret != null && ret) {
- _scrollController.jumpTo(0);
- }
+ context.push('/${item.id}', extra: true);
},
child: Stack(
fit: StackFit.expand,
diff --git a/pubspec.lock b/pubspec.lock
index eda825c..d86de63 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,38 +1,6 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
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:
dependency: transitive
description:
@@ -272,14 +240,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.1"
- gtk:
- dependency: transitive
+ go_router:
+ dependency: "direct main"
description:
- name: gtk
- sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
+ name: go_router
+ sha256: b453934c36e289cef06525734d1e676c1f91da9e22e2017d9dcab6ce0f999175
url: "https://pub.dev"
source: hosted
- version: "2.1.0"
+ version: "15.1.3"
html:
dependency: transitive
description:
@@ -344,6 +312,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.0.0"
+ logging:
+ dependency: transitive
+ description:
+ name: logging
+ sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.3.0"
matcher:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 81e72fa..64d61c8 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -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.1.8+38
+version: 1.1.9+39
environment:
sdk: ^3.9.0-100.2.beta
@@ -41,7 +41,7 @@ dependencies:
share_plus: ^11.0.0
flutter_secure_storage: ^9.2.4
flutter_riverpod: ^2.6.1
- app_links: ^6.4.0
+ go_router: ^15.1.3
dev_dependencies:
flutter_test: