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"/>
<category android:name="android.intent.category.LAUNCHER"/>
</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">
<action android:name="android.intent.action.VIEW"/>
<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/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/(?<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
Widget build(BuildContext context, WidgetRef ref) {
return Consumer(
builder: (context, ref, _) {
return MaterialApp(
final theme = ref.watch(themeNotifierProvider);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
theme: ref.watch(themeNotifierProvider),
home: MediaGrid(initialUri: initialUri),
);
},
routerConfig: _router,
theme: theme,
);
}
}

View File

@ -107,11 +107,7 @@ class MediaNotifier extends StateNotifier<MediaState> {
}
Future<void> 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 =

View File

@ -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<DetailView> {
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<DetailView> {
@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<String>(
@ -170,7 +180,7 @@ class _DetailViewState extends ConsumerState<DetailView> {
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<DetailView> {
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<DetailView> {
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),

View File

@ -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<String> mediaTypes = ["alles", "image", "video", "audio"];
const List<String> 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<MediaGrid> createState() => _MediaGridState();
@ -28,8 +26,6 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
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<MediaGrid> {
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
@ -302,15 +281,7 @@ class _MediaGridState extends ConsumerState<MediaGrid> {
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,

View File

@ -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:

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.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: