first commit
This commit is contained in:
		
							
								
								
									
										39
									
								
								lib/main.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								lib/main.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/mediagrid.dart';
 | 
			
		||||
//import 'package:media_kit/media_kit.dart';
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
			
		||||
  //MediaKit.ensureInitialized();
 | 
			
		||||
  
 | 
			
		||||
  SystemChrome.setPreferredOrientations([
 | 
			
		||||
    DeviceOrientation.portraitUp
 | 
			
		||||
  ]).then((_) {
 | 
			
		||||
    runApp(const F0ckApp());
 | 
			
		||||
  });
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class F0ckApp extends StatelessWidget {
 | 
			
		||||
  const F0ckApp({super.key});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return MaterialApp(
 | 
			
		||||
      theme: ThemeData(
 | 
			
		||||
        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(),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										78
									
								
								lib/mediagrid.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								lib/mediagrid.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/services/api.dart';
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem.dart';
 | 
			
		||||
import 'package:f0ckapp/screens/detail.dart';
 | 
			
		||||
 | 
			
		||||
class MediaGrid extends StatefulWidget {
 | 
			
		||||
  const MediaGrid({super.key});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State createState() => _MediaGridState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _MediaGridState extends State<MediaGrid> {
 | 
			
		||||
  final ScrollController _scrollController = ScrollController();
 | 
			
		||||
  List<MediaItem> mediaItems = [];
 | 
			
		||||
  bool isLoading = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _loadMedia();
 | 
			
		||||
    _scrollController.addListener(() {
 | 
			
		||||
      if (_scrollController.position.pixels >= _scrollController.position.maxScrollExtent - 100) {
 | 
			
		||||
        _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;
 | 
			
		||||
      });
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      print('Fehler: $e');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return GridView.builder(
 | 
			
		||||
      controller: _scrollController,
 | 
			
		||||
      gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
 | 
			
		||||
        crossAxisCount: 3,
 | 
			
		||||
        crossAxisSpacing: 8.0,
 | 
			
		||||
        mainAxisSpacing: 8.0,
 | 
			
		||||
      ),
 | 
			
		||||
      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');
 | 
			
		||||
            }
 | 
			
		||||
          },
 | 
			
		||||
          child: Image.network(item.thumbnailUrl, fit: BoxFit.cover),
 | 
			
		||||
        );
 | 
			
		||||
      },
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										17
									
								
								lib/models/mediaitem.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								lib/models/mediaitem.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
			
		||||
class MediaItem {
 | 
			
		||||
  final int id;
 | 
			
		||||
  final String mime;
 | 
			
		||||
  final int tagid;
 | 
			
		||||
 | 
			
		||||
  MediaItem({required this.id, required this.mime, required this.tagid});
 | 
			
		||||
 | 
			
		||||
  factory MediaItem.fromJson(Map<String, dynamic> json) {
 | 
			
		||||
    return MediaItem(
 | 
			
		||||
      id: json['id'],
 | 
			
		||||
      mime: json['mime'],
 | 
			
		||||
      tagid: json['tag_id'],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String get thumbnailUrl => 'https://f0ck.me/t/$id.webp';
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								lib/models/mediaitem_detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/models/mediaitem_detail.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
class MediaItemDetail {
 | 
			
		||||
  final int id;
 | 
			
		||||
  final String mime;
 | 
			
		||||
  final String dest;
 | 
			
		||||
  final String username;
 | 
			
		||||
  final int stamp;
 | 
			
		||||
  final int next;
 | 
			
		||||
  final int prev;
 | 
			
		||||
 | 
			
		||||
  MediaItemDetail({
 | 
			
		||||
    required this.id,
 | 
			
		||||
    required this.mime,
 | 
			
		||||
    required this.dest,
 | 
			
		||||
    required this.username,
 | 
			
		||||
    required this.stamp,
 | 
			
		||||
    required this.next,
 | 
			
		||||
    required this.prev,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  factory MediaItemDetail.fromJson(Map<String, dynamic> json) {
 | 
			
		||||
    return MediaItemDetail(
 | 
			
		||||
      id: json['id'],
 | 
			
		||||
      mime: json['mime'],
 | 
			
		||||
      dest: json['dest'],
 | 
			
		||||
      username: json['username'],
 | 
			
		||||
      stamp: json['stamp'],
 | 
			
		||||
      next: json['next'],
 | 
			
		||||
      prev: json['prev'],
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  String get mediaUrl => 'https://f0ck.me/b/$dest';
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										82
									
								
								lib/screens/detail copy.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								lib/screens/detail copy.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,82 @@
 | 
			
		||||
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');
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										164
									
								
								lib/screens/detail.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								lib/screens/detail.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,164 @@
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:video_player/video_player.dart';
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem_detail.dart';
 | 
			
		||||
import 'package:f0ckapp/services/api.dart'; // API Call für neue Items
 | 
			
		||||
 | 
			
		||||
class DetailView extends StatefulWidget {
 | 
			
		||||
  final MediaItemDetail details;
 | 
			
		||||
 | 
			
		||||
  const DetailView({super.key, required this.details});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State<DetailView> createState() => _DetailViewState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _DetailViewState extends State<DetailView> {
 | 
			
		||||
  late VideoPlayerController _controller;
 | 
			
		||||
  late Future<void> _initializeVideoPlayer;
 | 
			
		||||
  bool isAudio = false;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    isAudio = widget.details.mime.startsWith("audio/");
 | 
			
		||||
    _controller = VideoPlayerController.networkUrl(Uri.parse(widget.details.mediaUrl));
 | 
			
		||||
 | 
			
		||||
    if (!widget.details.mime.startsWith("image/")) {
 | 
			
		||||
      _initializeVideoPlayer = _controller.initialize().then((_) {
 | 
			
		||||
        setState(() {
 | 
			
		||||
          _controller.play();
 | 
			
		||||
          _controller.setLooping(true);
 | 
			
		||||
        });
 | 
			
		||||
      }).catchError((error) {
 | 
			
		||||
        ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
          SnackBar(content: Text("Fehler beim Laden des Videos: $error")),
 | 
			
		||||
        );
 | 
			
		||||
      });
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _controller.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Future<void> _loadNewMediaItem(int itemId, bool swipeLeft) async {
 | 
			
		||||
    try {
 | 
			
		||||
      var newDetails = await fetchMediaDetail(itemId); // API Call
 | 
			
		||||
 | 
			
		||||
      Navigator.of(context).pushReplacement(PageRouteBuilder(
 | 
			
		||||
        pageBuilder: (context, animation, secondaryAnimation) => DetailView(details: newDetails),
 | 
			
		||||
        transitionsBuilder: (context, animation, secondaryAnimation, child) {
 | 
			
		||||
          Offset begin = swipeLeft ? const Offset(-1.0, 0.0) : const Offset(1.0, 0.0); // Richtung des Swipes
 | 
			
		||||
          const Offset end = Offset.zero;
 | 
			
		||||
          const curve = Curves.easeInOut;
 | 
			
		||||
 | 
			
		||||
          var tween = Tween(begin: begin, end: end).chain(CurveTween(curve: curve));
 | 
			
		||||
          var offsetAnimation = animation.drive(tween);
 | 
			
		||||
 | 
			
		||||
          return SlideTransition(position: offsetAnimation, child: child);
 | 
			
		||||
        },
 | 
			
		||||
      ));
 | 
			
		||||
    } catch (error) {
 | 
			
		||||
      ScaffoldMessenger.of(context).showSnackBar(
 | 
			
		||||
        SnackBar(content: Text("Fehler beim Laden des Items: $error")),
 | 
			
		||||
      );
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  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);
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      child: Scaffold(
 | 
			
		||||
        appBar: AppBar(
 | 
			
		||||
          backgroundColor: const Color(0xFF2B2B2B),
 | 
			
		||||
          foregroundColor: Colors.white,
 | 
			
		||||
          title: Text('f0ck #${widget.details.id}'),
 | 
			
		||||
          centerTitle: true,
 | 
			
		||||
        ),
 | 
			
		||||
        body: SingleChildScrollView(
 | 
			
		||||
          child: Column(
 | 
			
		||||
            children: [
 | 
			
		||||
              if (widget.details.mime.contains(RegExp(r'image/(jpeg|png|gif|webp)'))) ...[
 | 
			
		||||
                Image.network(widget.details.mediaUrl),
 | 
			
		||||
              ] else ...[
 | 
			
		||||
                Stack(
 | 
			
		||||
                  alignment: Alignment.center,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    if (isAudio)
 | 
			
		||||
                      Image.network('https://f0ck.me/ca/${widget.details.id}.webp'),
 | 
			
		||||
                    FutureBuilder(
 | 
			
		||||
                      future: _initializeVideoPlayer,
 | 
			
		||||
                      builder: (context, snapshot) {
 | 
			
		||||
                        if (snapshot.connectionState == ConnectionState.done && _controller.value.isInitialized) {
 | 
			
		||||
                          return AspectRatio(
 | 
			
		||||
                            aspectRatio: isAudio ? 1 : _controller.value.aspectRatio,
 | 
			
		||||
                            child: VideoPlayer(_controller),
 | 
			
		||||
                          );
 | 
			
		||||
                        } else {
 | 
			
		||||
                          return const CircularProgressIndicator();
 | 
			
		||||
                        }
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
                Padding(
 | 
			
		||||
                  padding: const EdgeInsets.symmetric(vertical: 8.0),
 | 
			
		||||
                  child: VideoProgressIndicator(
 | 
			
		||||
                    _controller,
 | 
			
		||||
                    allowScrubbing: true,
 | 
			
		||||
                  ),
 | 
			
		||||
                ),
 | 
			
		||||
                Row(
 | 
			
		||||
                  mainAxisAlignment: MainAxisAlignment.center,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    IconButton(
 | 
			
		||||
                      icon: const Icon(Icons.replay_10),
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        _controller.seekTo(_controller.value.position - const Duration(seconds: 10));
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
                    IconButton(
 | 
			
		||||
                      icon: Icon(_controller.value.isPlaying ? Icons.pause : Icons.play_arrow),
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        setState(() {
 | 
			
		||||
                          _controller.value.isPlaying ? _controller.pause() : _controller.play();
 | 
			
		||||
                        });
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
                    IconButton(
 | 
			
		||||
                      icon: const Icon(Icons.forward_10),
 | 
			
		||||
                      onPressed: () {
 | 
			
		||||
                        _controller.seekTo(_controller.value.position + const Duration(seconds: 10));
 | 
			
		||||
                      },
 | 
			
		||||
                    ),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ],
 | 
			
		||||
              const SizedBox(height: 16),
 | 
			
		||||
              Padding(
 | 
			
		||||
                padding: const EdgeInsets.all(16.0),
 | 
			
		||||
                child: Column(
 | 
			
		||||
                  crossAxisAlignment: CrossAxisAlignment.start,
 | 
			
		||||
                  children: [
 | 
			
		||||
                    Text('${widget.details.mime}', style: const TextStyle(fontSize: 16, color: Colors.white)),
 | 
			
		||||
                    Text("Benutzername: ${widget.details.username}", style: const TextStyle(fontSize: 16, color: Colors.white)),
 | 
			
		||||
                    Text("Zeitstempel: ${widget.details.stamp}", style: const TextStyle(fontSize: 16, color: Colors.white)),
 | 
			
		||||
                  ],
 | 
			
		||||
                ),
 | 
			
		||||
              ),
 | 
			
		||||
            ],
 | 
			
		||||
          ),
 | 
			
		||||
        ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								lib/services/api.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								lib/services/api.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,33 @@
 | 
			
		||||
import 'dart:convert';
 | 
			
		||||
import 'package:http/http.dart' as http;
 | 
			
		||||
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem.dart';
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem_detail.dart';
 | 
			
		||||
 | 
			
		||||
Future<List<MediaItem>> fetchMedia({String? older}) async {
 | 
			
		||||
  final Uri url = Uri.parse('https://f0ck.me/api/v2/items/get')
 | 
			
		||||
      .replace(queryParameters: {
 | 
			
		||||
    if (older != null) 'older': older,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  final response = await http.get(url);
 | 
			
		||||
  if (response.statusCode == 200) {
 | 
			
		||||
    final Map<String, dynamic> jsonResponse = jsonDecode(response.body);
 | 
			
		||||
    final List<dynamic> jsonList = jsonResponse['items'];
 | 
			
		||||
    return jsonList.map((item) => MediaItem.fromJson(item)).toList();
 | 
			
		||||
  } else {
 | 
			
		||||
    throw Exception('Fehler beim Abrufen der Medien: ${response.statusCode}');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
Future<MediaItemDetail> fetchMediaDetail(int itemId) async {
 | 
			
		||||
  final Uri url = Uri.parse('https://f0ck.me/api/v2/item/$itemId');
 | 
			
		||||
 | 
			
		||||
  final response = await http.get(url);
 | 
			
		||||
  if (response.statusCode == 200) {
 | 
			
		||||
    final Map<String, dynamic> jsonResponse = jsonDecode(response.body);
 | 
			
		||||
    return MediaItemDetail.fromJson(jsonResponse['rows']);
 | 
			
		||||
  } else {
 | 
			
		||||
    throw Exception('Fehler beim Abrufen der Media-Details: ${response.statusCode}');
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								lib/widgets/video_widget.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								lib/widgets/video_widget.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem_detail.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:video_player/video_player.dart';
 | 
			
		||||
 | 
			
		||||
class VideoWidget extends StatefulWidget {
 | 
			
		||||
  final MediaItemDetail details;
 | 
			
		||||
  //const VideoWidget({super.key, required this.details}): super(key: key);
 | 
			
		||||
  const VideoWidget({Key? key, required this.details}) : super(key: key);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  _VideoWidgetState createState() => _VideoWidgetState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
  late VideoPlayerController _controller;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
 | 
			
		||||
    _controller = VideoPlayerController.networkUrl(
 | 
			
		||||
      Uri.parse(widget.details.mediaUrl)
 | 
			
		||||
    )..initialize().then((_) {
 | 
			
		||||
      setState(() {});
 | 
			
		||||
    });
 | 
			
		||||
    _controller.play();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _controller.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      backgroundColor: Colors.white,
 | 
			
		||||
      body: Center(
 | 
			
		||||
        child: _controller.value.isInitialized
 | 
			
		||||
          ? AspectRatio(
 | 
			
		||||
              aspectRatio: _controller.value.aspectRatio,
 | 
			
		||||
              child: VideoPlayer(_controller),
 | 
			
		||||
            )
 | 
			
		||||
          : Center(
 | 
			
		||||
              child: CircularProgressIndicator(),
 | 
			
		||||
            ),
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										92
									
								
								lib/widgets/video_widget.dart.old
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								lib/widgets/video_widget.dart.old
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
			
		||||
import 'package:f0ckapp/models/mediaitem_detail.dart';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:media_kit/media_kit.dart';
 | 
			
		||||
import 'package:media_kit_video/media_kit_video.dart';
 | 
			
		||||
 | 
			
		||||
class VideoWidget extends StatefulWidget {
 | 
			
		||||
  final MediaItemDetail details;
 | 
			
		||||
  const VideoWidget({super.key, required this.details});
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  State createState() => _VideoWidgetState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class _VideoWidgetState extends State<VideoWidget> {
 | 
			
		||||
  late final Player _player;
 | 
			
		||||
  late final VideoController _controller;
 | 
			
		||||
  double? _aspectRatio;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void initState() {
 | 
			
		||||
    super.initState();
 | 
			
		||||
    _player = Player();
 | 
			
		||||
    _controller = VideoController(_player);
 | 
			
		||||
    _player.open(Media(widget.details.mediaUrl));
 | 
			
		||||
    _player.setPlaylistMode(PlaylistMode.loop);
 | 
			
		||||
    _player.setVolume(0);
 | 
			
		||||
 | 
			
		||||
    //_player.stream.height.first;
 | 
			
		||||
    _player.stream.playing.listen((blah) {
 | 
			
		||||
      setState(() {
 | 
			
		||||
        Future.microtask(() async {
 | 
			
		||||
          int h = await _player.stream.height.first ?? 0;
 | 
			
		||||
          int w = await _player.stream.width.first ?? 0;
 | 
			
		||||
          _aspectRatio = h / w;
 | 
			
		||||
          print(_aspectRatio);
 | 
			
		||||
        });
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  void dispose() {
 | 
			
		||||
    _player.dispose();
 | 
			
		||||
    super.dispose();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  Widget build(BuildContext context) {
 | 
			
		||||
    //_aspectRatio = _controller.player.state.height! / _controller.player.state.width!;
 | 
			
		||||
    print(_aspectRatio);
 | 
			
		||||
 | 
			
		||||
    return Scaffold(
 | 
			
		||||
      appBar: AppBar(
 | 
			
		||||
        backgroundColor: const Color.fromARGB(255, 43, 43, 43),
 | 
			
		||||
        foregroundColor: const Color.fromARGB(255, 255, 255, 255),
 | 
			
		||||
        title: Text('f0ck #${widget.details.id}'),
 | 
			
		||||
        centerTitle: true,
 | 
			
		||||
      ),
 | 
			
		||||
      body: Column(
 | 
			
		||||
        children: [
 | 
			
		||||
          if (_aspectRatio != null)
 | 
			
		||||
            AspectRatio(
 | 
			
		||||
              aspectRatio: _aspectRatio!,
 | 
			
		||||
              child: Video(
 | 
			
		||||
                controller: _controller,
 | 
			
		||||
                fit: BoxFit.cover,
 | 
			
		||||
                alignment: Alignment.topCenter,
 | 
			
		||||
              ),
 | 
			
		||||
            )
 | 
			
		||||
          else
 | 
			
		||||
            SizedBox(
 | 
			
		||||
              width: double.infinity,
 | 
			
		||||
              height: MediaQuery.of(context).size.height * 0.75,
 | 
			
		||||
              child: Video(
 | 
			
		||||
                controller: _controller,
 | 
			
		||||
                fit: BoxFit.cover,
 | 
			
		||||
                alignment: Alignment.topCenter,
 | 
			
		||||
              ),
 | 
			
		||||
            ),
 | 
			
		||||
          const SizedBox(height: 10),
 | 
			
		||||
          Text(
 | 
			
		||||
            'f0cked by: ${widget.details.username}',
 | 
			
		||||
            style: const TextStyle(
 | 
			
		||||
              fontSize: 16,
 | 
			
		||||
              color: Color.fromARGB(255, 255, 255, 255),
 | 
			
		||||
            ),
 | 
			
		||||
          ),
 | 
			
		||||
        ],
 | 
			
		||||
      ),
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user