v1.1.7+37
All checks were successful
Flutter Schmutter / build (push) Successful in 3m44s

- worst update eu west
This commit is contained in:
2025-06-08 17:16:10 +02:00
parent f7777821fd
commit 346e447d5e
9 changed files with 183 additions and 18 deletions

View File

@ -1,3 +1,4 @@
import 'package:app_links/app_links.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -11,12 +12,14 @@ void main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
await AppVersion.init();
final Uri? initialUri = await AppLinks().getInitialLink();
runApp(ProviderScope(child: const F0ckApp()));
runApp(ProviderScope(child: F0ckApp(initialUri: initialUri)));
}
class F0ckApp extends ConsumerWidget {
const F0ckApp({super.key});
final Uri? initialUri;
const F0ckApp({super.key, this.initialUri});
@override
Widget build(BuildContext context, WidgetRef ref) {
@ -25,7 +28,7 @@ class F0ckApp extends ConsumerWidget {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ref.watch(themeNotifierProvider),
home: const MediaGrid(),
home: MediaGrid(initialUri: initialUri),
);
},
);

View File

@ -106,14 +106,16 @@ class MediaNotifier extends StateNotifier<MediaState> {
state = state.replace(mediaItems: updated);
}
Future<void> loadMedia() async {
if (state.isLoading) return;
Future<void> loadMedia({int? id}) async {
//if (state.isLoading) return;
if (id != null) {
print('requested id: ${id.toString()}');
}
state = state.replace(isLoading: true);
try {
final older = state.mediaItems.isNotEmpty
? state.mediaItems.last.id
: null;
final older =
id ?? (state.mediaItems.isNotEmpty ? state.mediaItems.last.id : null);
final newMedia = await fetchMedia(
older: older,
type: mediaTypes[state.typeIndex],

View File

@ -102,6 +102,12 @@ class _DetailViewState extends ConsumerState<DetailView> {
centerTitle: true,
title: Text('f0ck #${mediaState.mediaItems[_currentIndex].id}'),
actions: [
IconButton(
icon: Icon(Icons.fullscreen),
onPressed: () {
// wip
},
),
IconButton(
icon: Icon(Icons.download),
onPressed: () {

View File

@ -1,18 +1,21 @@
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';
const List<String> mediaTypes = ["alles", "image", "video", "audio"];
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
class MediaGrid extends ConsumerStatefulWidget {
const MediaGrid({super.key});
final Uri? initialUri = null;
const MediaGrid({super.key, required initialUri});
@override
ConsumerState<MediaGrid> createState() => _MediaGridState();
@ -25,6 +28,8 @@ 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()
@ -43,6 +48,23 @@ 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

View File

@ -0,0 +1,83 @@
import 'package:f0ckapp/providers/MediaProvider.dart';
import 'package:f0ckapp/screens/DetailView.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
Map<String, RegExp> routes = {
//'login': RegExp(r'^/login/?$'),
//'user': RegExp(r'^/user/(?<user>.*)$'),
'complex': RegExp(
r'^/?'
r'(?:tag/(?<tag>.+?))?'
r'(?:/user/(?<username>.+?)/(?<set>f0cks|favs))?'
r'(?:/(?<media>image|audio|video))?'
r'(?:/(?<id>\d+))?'
r'$',
),
//'random': RegExp(r'^/random$'),
//'search': RegExp(r'^/search/?$'),
};
Map<String, dynamic> parseDeepLink(Uri? uri) {
if (uri == null) {
return {};
}
String url = uri.toString().replaceAll("https://f0ck.me", "");
for (final MapEntry<String, RegExp> entry in routes.entries) {
final String routeName = entry.key;
final RegExp pattern = entry.value;
final RegExpMatch? match = pattern.firstMatch(url.toString());
if (match != null) {
Map<String, String> params = <String, String>{};
for (String name in match.groupNames) {
params[name] = match.namedGroup(name) ?? '';
}
return {'route': routeName, 'params': params};
}
}
return {};
}
Future<void> handleComplexDeepLink(
Map<String, String> params,
BuildContext context,
WidgetRef ref,
ScrollController scrollController,
) async {
final media = params['media'];
const validMediaTypes = {'audio', 'video', 'image'};
if (media != null && validMediaTypes.contains(media)) {
ref.read(mediaProvider.notifier).setType(media);
}
final idParam = params['id'];
if (idParam == null || idParam.isEmpty) return;
final int? id = int.tryParse(idParam);
if (id == null) return;
final mediaState = ref.read(mediaProvider);
final index = mediaState.mediaItems.indexWhere((item) => item.id == id);
if (index == -1) {
await ref.read(mediaProvider.notifier).loadMedia(id: id + 50);
final updatedState = ref.read(mediaProvider);
final updatedIndex = updatedState.mediaItems.indexWhere(
(item) => item.id == id,
);
print(updatedIndex.toString());
if (updatedIndex == -1) {
return;
}
}
bool? navigationResult = await Navigator.push(
context,
MaterialPageRoute(builder: (_) => DetailView(initialItemId: id)),
);
if (navigationResult == true) {
scrollController.jumpTo(0);
}
}

View File

@ -25,7 +25,7 @@ class VideoControlsOverlay extends ConsumerWidget {
children: [
Positioned(
right: 12,
top: 12,
bottom: 12,
child: _ControlButton(
mediaState.muted ? Icons.volume_off : Icons.volume_up,
() {
@ -35,13 +35,6 @@ class VideoControlsOverlay extends ConsumerWidget {
size: 16,
),
),
Positioned(
right: 12,
bottom: 12,
child: _ControlButton(Icons.fullscreen, () {
button();
}, size: 16),
),
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,