aktueller Stand
This commit is contained in:
		@@ -2,17 +2,11 @@ import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/mediagrid.dart';
 | 
			
		||||
//import 'package:media_kit/media_kit.dart';
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
void main() async {
 | 
			
		||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
			
		||||
  //MediaKit.ensureInitialized();
 | 
			
		||||
  
 | 
			
		||||
  SystemChrome.setPreferredOrientations([
 | 
			
		||||
    DeviceOrientation.portraitUp
 | 
			
		||||
  ]).then((_) {
 | 
			
		||||
    runApp(const F0ckApp());
 | 
			
		||||
  });
 | 
			
		||||
  await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
 | 
			
		||||
  runApp(const F0ckApp());
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class F0ckApp extends StatelessWidget {
 | 
			
		||||
@@ -25,13 +19,6 @@ class F0ckApp extends StatelessWidget {
 | 
			
		||||
        scaffoldBackgroundColor: const Color.fromARGB(255, 23, 23, 23),
 | 
			
		||||
      ),
 | 
			
		||||
      home: Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          automaticallyImplyLeading: false,
 | 
			
		||||
          backgroundColor: const Color.fromARGB(255, 43, 43, 43),
 | 
			
		||||
          foregroundColor: const Color.fromARGB(255, 255, 255, 255),
 | 
			
		||||
          title: const Text('f0cks'),
 | 
			
		||||
          centerTitle: true,
 | 
			
		||||
        ),
 | 
			
		||||
        body: MediaGrid(),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,8 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/services/api.dart';
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem.dart';
 | 
			
		||||
import 'package:f0ckapp/screens/detail.dart';
 | 
			
		||||
import 'dart:async';
 | 
			
		||||
 | 
			
		||||
class MediaGrid extends StatefulWidget {
 | 
			
		||||
  const MediaGrid({super.key});
 | 
			
		||||
@@ -15,64 +15,168 @@ class _MediaGridState extends State<MediaGrid> {
 | 
			
		||||
  final ScrollController _scrollController = ScrollController();
 | 
			
		||||
  List<MediaItem> mediaItems = [];
 | 
			
		||||
  bool isLoading = false;
 | 
			
		||||
  Timer? _debounceTimer;
 | 
			
		||||
  Completer<void>? _navigationCompleter;
 | 
			
		||||
  int _crossAxisCount = 3;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _loadMedia();
 | 
			
		||||
    _scrollController.addListener(() {
 | 
			
		||||
      if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
 | 
			
		||||
        _loadMedia();
 | 
			
		||||
      if (_scrollController.position.pixels >=
 | 
			
		||||
          _scrollController.position.maxScrollExtent - 100) {
 | 
			
		||||
        _debounceLoadMedia();
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _debounceLoadMedia() {
 | 
			
		||||
    _debounceTimer?.cancel();
 | 
			
		||||
    _debounceTimer = Timer(const Duration(milliseconds: 500), _loadMedia);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _loadMedia() async {
 | 
			
		||||
    if (isLoading) return;
 | 
			
		||||
    setState(() => isLoading = true);
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      List<MediaItem> newMedia = await fetchMedia(older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null);
 | 
			
		||||
      setState(() {
 | 
			
		||||
        mediaItems.addAll(newMedia);
 | 
			
		||||
        isLoading = false;
 | 
			
		||||
      });
 | 
			
		||||
      final newMedia = await fetchMedia(
 | 
			
		||||
        older: mediaItems.isNotEmpty ? mediaItems.last.id.toString() : null,
 | 
			
		||||
      );
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        setState(() => mediaItems.addAll(newMedia));
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Fehler: $e');
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text('Fehler beim Laden der Medien: $e')),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      if (mounted) setState(() => isLoading = false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _refreshMedia() async {
 | 
			
		||||
    setState(() => isLoading = true);
 | 
			
		||||
    try {
 | 
			
		||||
      final freshMedia = await fetchMedia(older: null);
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        setState(() {
 | 
			
		||||
          mediaItems.clear();
 | 
			
		||||
          mediaItems.addAll(freshMedia);
 | 
			
		||||
        });
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text('Fehler beim Aktualisieren: $e')),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      if (mounted) setState(() => isLoading = false);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _navigateToDetail(MediaItem item) async {
 | 
			
		||||
    print('Tapped item: ${item.id}');
 | 
			
		||||
 | 
			
		||||
    if (_navigationCompleter?.isCompleted == false) return;
 | 
			
		||||
 | 
			
		||||
    _navigationCompleter = Completer();
 | 
			
		||||
    try {
 | 
			
		||||
      final details = await fetchMediaDetail(item.id);
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        await Navigator.push(
 | 
			
		||||
          context,
 | 
			
		||||
          MaterialPageRoute(builder: (context) => DetailView(details: details)),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      if (mounted) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text('Fehler beim Laden der Details: $e')),
 | 
			
		||||
        );
 | 
			
		||||
      }
 | 
			
		||||
    } finally {
 | 
			
		||||
      _navigationCompleter?.complete();
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return GridView.builder(
 | 
			
		||||
      controller: _scrollController,
 | 
			
		||||
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
 | 
			
		||||
        crossAxisCount: 3,
 | 
			
		||||
        crossAxisSpacing: 8.0,
 | 
			
		||||
        mainAxisSpacing: 8.0,
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        backgroundColor: const Color.fromARGB(255, 43, 43, 43),
 | 
			
		||||
        foregroundColor: const Color.fromARGB(255, 255, 255, 255),
 | 
			
		||||
        title: Row(
 | 
			
		||||
          mainAxisAlignment: MainAxisAlignment.spaceBetween,
 | 
			
		||||
          children: [
 | 
			
		||||
            const Text('f0cks'),
 | 
			
		||||
            DropdownButton<int>(
 | 
			
		||||
              value: _crossAxisCount,
 | 
			
		||||
              dropdownColor: const Color.fromARGB(255, 43, 43, 43),
 | 
			
		||||
              iconEnabledColor: const Color.fromARGB(255, 255, 255, 255),
 | 
			
		||||
              items: [3, 4].map((int value) {
 | 
			
		||||
                return DropdownMenuItem<int>(
 | 
			
		||||
                  value: value,
 | 
			
		||||
                  child: Text('$value Spalten', style: TextStyle(color: Colors.white)),
 | 
			
		||||
                );
 | 
			
		||||
              }).toList(),
 | 
			
		||||
              onChanged: (int? newValue) {
 | 
			
		||||
                if (newValue != null) {
 | 
			
		||||
                  setState(() {
 | 
			
		||||
                    _crossAxisCount = newValue;
 | 
			
		||||
                  });
 | 
			
		||||
                }
 | 
			
		||||
              },
 | 
			
		||||
            ),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
      itemCount: mediaItems.length + (isLoading ? 1 : 0),
 | 
			
		||||
      itemBuilder: (context, index) {
 | 
			
		||||
        if (index >= mediaItems.length) {
 | 
			
		||||
          return const Center(child: CircularProgressIndicator());
 | 
			
		||||
        }
 | 
			
		||||
        final item = mediaItems[index];
 | 
			
		||||
 | 
			
		||||
        return GestureDetector(
 | 
			
		||||
          onTap: () async {
 | 
			
		||||
            try {
 | 
			
		||||
              final details = await fetchMediaDetail(item.id);
 | 
			
		||||
              Navigator.push(
 | 
			
		||||
                context,
 | 
			
		||||
                MaterialPageRoute(builder: (context) => DetailView(details: details)),
 | 
			
		||||
              );
 | 
			
		||||
            } catch (e) {
 | 
			
		||||
              print('Fehler beim Laden der Details: $e');
 | 
			
		||||
      body: RefreshIndicator(
 | 
			
		||||
        onRefresh: _refreshMedia,
 | 
			
		||||
        child: GridView.builder(
 | 
			
		||||
          controller: _scrollController,
 | 
			
		||||
          gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
 | 
			
		||||
            crossAxisCount: _crossAxisCount,
 | 
			
		||||
            crossAxisSpacing: 5.0,
 | 
			
		||||
            mainAxisSpacing: 5.0,
 | 
			
		||||
          ),
 | 
			
		||||
          itemCount: mediaItems.length + (isLoading ? 1 : 0),
 | 
			
		||||
          itemBuilder: (context, index) {
 | 
			
		||||
            if (index >= mediaItems.length) {
 | 
			
		||||
              return const Center(child: CircularProgressIndicator());
 | 
			
		||||
            }
 | 
			
		||||
            final item = mediaItems[index];
 | 
			
		||||
 | 
			
		||||
            final mode =
 | 
			
		||||
                {1: Colors.green, 2: Colors.red}[item.tagid] ?? Colors.yellow;
 | 
			
		||||
 | 
			
		||||
            return InkWell(
 | 
			
		||||
              onTap: () => _navigateToDetail(item),
 | 
			
		||||
              child: Stack(
 | 
			
		||||
                fit: StackFit.expand,
 | 
			
		||||
                children: <Widget>[
 | 
			
		||||
                  Image.network(item.thumbnailUrl, fit: BoxFit.cover),
 | 
			
		||||
                  Align(
 | 
			
		||||
                    alignment: FractionalOffset.bottomRight,
 | 
			
		||||
                    child: Icon(Icons.square, color: mode, size: 15.0),
 | 
			
		||||
                  ),
 | 
			
		||||
                ],
 | 
			
		||||
              ),
 | 
			
		||||
            );
 | 
			
		||||
          },
 | 
			
		||||
          child: Image.network(item.thumbnailUrl, fit: BoxFit.cover),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _scrollController.dispose();
 | 
			
		||||
    _debounceTimer?.cancel();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -4,8 +4,8 @@ class MediaItemDetail {
 | 
			
		||||
  final String dest;
 | 
			
		||||
  final String username;
 | 
			
		||||
  final int stamp;
 | 
			
		||||
  final int next;
 | 
			
		||||
  final int prev;
 | 
			
		||||
  final int? next;
 | 
			
		||||
  final int? prev;
 | 
			
		||||
 | 
			
		||||
  MediaItemDetail({
 | 
			
		||||
    required this.id,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,82 +0,0 @@
 | 
			
		||||
import 'package:f0ckapp/services/api.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem_detail.dart';
 | 
			
		||||
import 'package:f0ckapp/widgets/video_widget.dart';
 | 
			
		||||
 | 
			
		||||
class DetailScreen extends StatelessWidget {
 | 
			
		||||
  final MediaItemDetail details;
 | 
			
		||||
 | 
			
		||||
  const DetailScreen({super.key, required this.details});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return GestureDetector(
 | 
			
		||||
      onHorizontalDragEnd: (details) {
 | 
			
		||||
        if (details.velocity.pixelsPerSecond.dx > 0) {
 | 
			
		||||
          _navigateToPrev(context);
 | 
			
		||||
        } else {
 | 
			
		||||
          _navigateToNext(context);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      child: VideoWidget(details: this.details)
 | 
			
		||||
    );
 | 
			
		||||
    //return VideoWidget(url: this.details.mediaUrl);
 | 
			
		||||
    /*return GestureDetector(
 | 
			
		||||
      onHorizontalDragEnd: (details) {
 | 
			
		||||
        if (details.velocity.pixelsPerSecond.dx > 0) {
 | 
			
		||||
          _navigateToPrev(context);
 | 
			
		||||
        } else {
 | 
			
		||||
          _navigateToNext(context);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        body: Column(
 | 
			
		||||
          children: [
 | 
			
		||||
            if (this.details.mime.startsWith('image'))
 | 
			
		||||
              ClipRRect(
 | 
			
		||||
                child: Image.network(this.details.mediaUrl, fit: BoxFit.cover),
 | 
			
		||||
              )
 | 
			
		||||
            else
 | 
			
		||||
              VideoPost(
 | 
			
		||||
                url: this.details.mediaUrl,
 | 
			
		||||
                /*appBar: AppBar(
 | 
			
		||||
                  backgroundColor: const Color.fromARGB(255, 43, 43, 43),
 | 
			
		||||
                  foregroundColor: const Color.fromARGB(255, 255, 255, 255),
 | 
			
		||||
                  title: const Text('f0ck'),
 | 
			
		||||
                  centerTitle: true,
 | 
			
		||||
                )*/
 | 
			
		||||
              ),
 | 
			
		||||
 | 
			
		||||
            //const SizedBox(height: 10),
 | 
			
		||||
            //Text('f0cked by: ${this.details.username}', style: const TextStyle(fontSize: 16, color: Color.fromARGB(255, 255, 255, 255))),
 | 
			
		||||
          ],
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );*/
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _navigateToNext(BuildContext context) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final nextDetails = await fetchMediaDetail(details.next);
 | 
			
		||||
      Navigator.pushReplacement(
 | 
			
		||||
        context,
 | 
			
		||||
        MaterialPageRoute(builder: (context) => DetailScreen(details: nextDetails)),
 | 
			
		||||
      );
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Fehler beim Laden des nächsten Items: $e');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  void _navigateToPrev(BuildContext context) async {
 | 
			
		||||
    try {
 | 
			
		||||
      final prevDetails = await fetchMediaDetail(details.prev);
 | 
			
		||||
      Navigator.pushReplacement(
 | 
			
		||||
        context,
 | 
			
		||||
        MaterialPageRoute(builder: (context) => DetailScreen(details: prevDetails)),
 | 
			
		||||
      );
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Fehler beim Laden des vorherigen Items: $e');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -84,10 +84,10 @@ class _DetailViewState extends State<DetailView> {
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return GestureDetector(
 | 
			
		||||
      onHorizontalDragEnd: (details) {
 | 
			
		||||
        if (details.velocity.pixelsPerSecond.dx > 0) {
 | 
			
		||||
          _loadNewMediaItem(widget.details.next, true);
 | 
			
		||||
        } else if (details.velocity.pixelsPerSecond.dx < 0) {
 | 
			
		||||
          _loadNewMediaItem(widget.details.prev, false);
 | 
			
		||||
        if (details.velocity.pixelsPerSecond.dx > 0 && widget.details.next != null) {
 | 
			
		||||
          _loadNewMediaItem(widget.details.next!, true);
 | 
			
		||||
        } else if (details.velocity.pixelsPerSecond.dx < 0 && widget.details.prev != null) {
 | 
			
		||||
          _loadNewMediaItem(widget.details.prev!, false);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ Future<List<MediaItem>> fetchMedia({String? older}) async {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Future<MediaItemDetail> fetchMediaDetail(int itemId) async {
 | 
			
		||||
  final Uri url = Uri.parse('https://f0ck.me/api/v2/item/$itemId');
 | 
			
		||||
  final Uri url = Uri.parse('https://f0ck.me/api/v2/item/${itemId.toString()}');
 | 
			
		||||
 | 
			
		||||
  final response = await http.get(url);
 | 
			
		||||
  if (response.statusCode == 200) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user