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

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

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(
debugShowCheckedModeBanner: false,
theme: ref.watch(themeNotifierProvider),
home: MediaGrid(initialUri: initialUri),
);
},
final theme = ref.watch(themeNotifierProvider);
return MaterialApp.router(
debugShowCheckedModeBanner: false,
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,