This commit is contained in:
		
							
								
								
									
										
											BIN
										
									
								
								assets/images/tags/belgium.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/images/tags/belgium.webp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 92 B  | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/tags/dutch.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/images/tags/dutch.webp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 90 B  | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/tags/german.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/images/tags/german.webp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 94 B  | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/tags/russia.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/images/tags/russia.webp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 92 B  | 
							
								
								
									
										
											BIN
										
									
								
								assets/images/tags/ukraine.webp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										
											BIN
										
									
								
								assets/images/tags/ukraine.webp
									
									
									
									
									
										Normal file
									
								
							
										
											Binary file not shown.
										
									
								
							| 
		 After Width: | Height: | Size: 108 B  | 
							
								
								
									
										98
									
								
								lib/controller/auth_controller.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								lib/controller/auth_controller.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,98 @@
 | 
				
			|||||||
 | 
					import 'dart:convert';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					import 'package:encrypt_shared_preferences/provider.dart';
 | 
				
			||||||
 | 
					import 'package:http/http.dart' as http;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/media_controller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class AuthController extends GetxController {
 | 
				
			||||||
 | 
					  final EncryptedSharedPreferencesAsync storage =
 | 
				
			||||||
 | 
					      EncryptedSharedPreferencesAsync.getInstance();
 | 
				
			||||||
 | 
					  final MediaController mediaController = Get.find<MediaController>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  RxnString token = RxnString();
 | 
				
			||||||
 | 
					  RxnInt userId = RxnInt();
 | 
				
			||||||
 | 
					  RxnString avatarUrl = RxnString();
 | 
				
			||||||
 | 
					  RxnString username = RxnString();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void onInit() {
 | 
				
			||||||
 | 
					    super.onInit();
 | 
				
			||||||
 | 
					    loadToken();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> loadToken() async {
 | 
				
			||||||
 | 
					    token.value = await storage.getString('token');
 | 
				
			||||||
 | 
					    if (token.value != null) {
 | 
				
			||||||
 | 
					      await fetchUserInfo();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> saveToken(String newToken) async {
 | 
				
			||||||
 | 
					    token.value = newToken;
 | 
				
			||||||
 | 
					    await storage.setString('token', newToken);
 | 
				
			||||||
 | 
					    await fetchUserInfo();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> logout() async {
 | 
				
			||||||
 | 
					    if (token.value != null) {
 | 
				
			||||||
 | 
					      try {
 | 
				
			||||||
 | 
					        await http.post(
 | 
				
			||||||
 | 
					          Uri.parse('https://api.f0ck.me/logout'),
 | 
				
			||||||
 | 
					          headers: {
 | 
				
			||||||
 | 
					            'Authorization': 'Bearer ${token.value}',
 | 
				
			||||||
 | 
					            'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        await mediaController.loadMediaItems();
 | 
				
			||||||
 | 
					        mediaController.mediaItems.refresh();
 | 
				
			||||||
 | 
					      } catch (e) {
 | 
				
			||||||
 | 
					        //
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    token.value = null;
 | 
				
			||||||
 | 
					    userId.value = null;
 | 
				
			||||||
 | 
					    avatarUrl.value = null;
 | 
				
			||||||
 | 
					    username.value = null;
 | 
				
			||||||
 | 
					    await storage.remove('token');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<bool> login(String username, String password) async {
 | 
				
			||||||
 | 
					    final http.Response response = await http.post(
 | 
				
			||||||
 | 
					      Uri.parse('https://api.f0ck.me/login'),
 | 
				
			||||||
 | 
					      headers: {'Content-Type': 'application/json'},
 | 
				
			||||||
 | 
					      body: json.encode({'username': username, 'password': password}),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (response.statusCode == 200) {
 | 
				
			||||||
 | 
					      final dynamic data = json.decode(response.body);
 | 
				
			||||||
 | 
					      if (data['token'] != null) {
 | 
				
			||||||
 | 
					        await saveToken(data['token']);
 | 
				
			||||||
 | 
					        await mediaController.loadMediaItems();
 | 
				
			||||||
 | 
					        mediaController.mediaItems.refresh();
 | 
				
			||||||
 | 
					        return true;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<void> fetchUserInfo() async {
 | 
				
			||||||
 | 
					    if (token.value == null) return;
 | 
				
			||||||
 | 
					    final http.Response response = await http.get(
 | 
				
			||||||
 | 
					      Uri.parse('https://api.f0ck.me/login/check'),
 | 
				
			||||||
 | 
					      headers: {'Authorization': 'Bearer ${token.value}'},
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    if (response.statusCode == 200) {
 | 
				
			||||||
 | 
					      final dynamic data = json.decode(response.body);
 | 
				
			||||||
 | 
					      userId.value = data['userid'] != null
 | 
				
			||||||
 | 
					          ? int.parse(data['userid'].toString())
 | 
				
			||||||
 | 
					          : null;
 | 
				
			||||||
 | 
					      avatarUrl.value = data['avatar'] != null
 | 
				
			||||||
 | 
					          ? 'https://f0ck.me/t/${data['avatar']}.webp'
 | 
				
			||||||
 | 
					          : null;
 | 
				
			||||||
 | 
					      username.value = data['user'];
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      await logout();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,9 +15,13 @@ class MyTranslations extends Translations {
 | 
				
			|||||||
  static Future<void> loadTranslations() async {
 | 
					  static Future<void> loadTranslations() async {
 | 
				
			||||||
    final locales = ['en_US', 'de_DE', 'fr_FR', 'nl_NL'];
 | 
					    final locales = ['en_US', 'de_DE', 'fr_FR', 'nl_NL'];
 | 
				
			||||||
    for (final locale in locales) {
 | 
					    for (final locale in locales) {
 | 
				
			||||||
      final String jsonString = await rootBundle.loadString('assets/i18n/$locale.json');
 | 
					      final String jsonString = await rootBundle.loadString(
 | 
				
			||||||
 | 
					        'assets/i18n/$locale.json',
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
      final Map<String, dynamic> jsonMap = json.decode(jsonString);
 | 
					      final Map<String, dynamic> jsonMap = json.decode(jsonString);
 | 
				
			||||||
      _translations[locale] = jsonMap.map((key, value) => MapEntry(key, value.toString()));
 | 
					      _translations[locale] = jsonMap.map(
 | 
				
			||||||
 | 
					        (key, value) => MapEntry(key, value.toString()),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -23,6 +23,8 @@ class MediaController extends GetxController {
 | 
				
			|||||||
  late RxBool drawerSwipeEnabled = true.obs;
 | 
					  late RxBool drawerSwipeEnabled = true.obs;
 | 
				
			||||||
  final Rx<PageTransition> transitionType = PageTransition.opacity.obs;
 | 
					  final Rx<PageTransition> transitionType = PageTransition.opacity.obs;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MediaItem? selectedItem;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  void onInit() async {
 | 
					  void onInit() async {
 | 
				
			||||||
    super.onInit();
 | 
					    super.onInit();
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,12 +4,14 @@ import 'package:flutter/services.dart';
 | 
				
			|||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
import 'package:encrypt_shared_preferences/provider.dart';
 | 
					import 'package:encrypt_shared_preferences/provider.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/service/media_service.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/localization_controller.dart';
 | 
					import 'package:f0ckapp/controller/localization_controller.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/appversion.dart';
 | 
					import 'package:f0ckapp/utils/appversion.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/theme_controller.dart';
 | 
					import 'package:f0ckapp/controller/theme_controller.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/media_controller.dart';
 | 
					import 'package:f0ckapp/controller/media_controller.dart';
 | 
				
			||||||
import 'package:f0ckapp/screens/detail_view.dart';
 | 
					import 'package:f0ckapp/screens/detail_view.dart';
 | 
				
			||||||
import 'package:f0ckapp/screens/media_grid.dart';
 | 
					import 'package:f0ckapp/screens/media_grid.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/auth_controller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
void main() async {
 | 
					void main() async {
 | 
				
			||||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
					  WidgetsFlutterBinding.ensureInitialized();
 | 
				
			||||||
@@ -17,7 +19,9 @@ void main() async {
 | 
				
			|||||||
  await EncryptedSharedPreferencesAsync.initialize('VokTnbAbemBUa2j9');
 | 
					  await EncryptedSharedPreferencesAsync.initialize('VokTnbAbemBUa2j9');
 | 
				
			||||||
  await MyTranslations.loadTranslations();
 | 
					  await MyTranslations.loadTranslations();
 | 
				
			||||||
  await AppVersion.init();
 | 
					  await AppVersion.init();
 | 
				
			||||||
 | 
					  Get.put(MediaService());
 | 
				
			||||||
  Get.put(MediaController());
 | 
					  Get.put(MediaController());
 | 
				
			||||||
 | 
					  Get.put(AuthController());
 | 
				
			||||||
  LocalizationController localizationController = Get.put(LocalizationController());
 | 
					  LocalizationController localizationController = Get.put(LocalizationController());
 | 
				
			||||||
  final ThemeController themeController = Get.put(ThemeController());
 | 
					  final ThemeController themeController = Get.put(ThemeController());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ class MediaItem {
 | 
				
			|||||||
  final String dest;
 | 
					  final String dest;
 | 
				
			||||||
  final int mode;
 | 
					  final int mode;
 | 
				
			||||||
  final List<Tag> tags;
 | 
					  final List<Tag> tags;
 | 
				
			||||||
 | 
					  final List<Favorite>? favorites;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  MediaItem({
 | 
					  MediaItem({
 | 
				
			||||||
    required this.id,
 | 
					    required this.id,
 | 
				
			||||||
@@ -15,8 +16,31 @@ class MediaItem {
 | 
				
			|||||||
    required this.dest,
 | 
					    required this.dest,
 | 
				
			||||||
    required this.mode,
 | 
					    required this.mode,
 | 
				
			||||||
    required this.tags,
 | 
					    required this.tags,
 | 
				
			||||||
 | 
					    required this.favorites,
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  MediaItem copyWith({
 | 
				
			||||||
 | 
					    int? id,
 | 
				
			||||||
 | 
					    String? mime,
 | 
				
			||||||
 | 
					    int? size,
 | 
				
			||||||
 | 
					    int? stamp,
 | 
				
			||||||
 | 
					    String? dest,
 | 
				
			||||||
 | 
					    int? mode,
 | 
				
			||||||
 | 
					    List<Tag>? tags,
 | 
				
			||||||
 | 
					    List<Favorite>? favorites,
 | 
				
			||||||
 | 
					  }) {
 | 
				
			||||||
 | 
					    return MediaItem(
 | 
				
			||||||
 | 
					      id: id ?? this.id,
 | 
				
			||||||
 | 
					      mime: mime ?? this.mime,
 | 
				
			||||||
 | 
					      size: size ?? this.size,
 | 
				
			||||||
 | 
					      stamp: stamp ?? this.stamp,
 | 
				
			||||||
 | 
					      dest: dest ?? this.dest,
 | 
				
			||||||
 | 
					      mode: mode ?? this.mode,
 | 
				
			||||||
 | 
					      tags: tags ?? this.tags,
 | 
				
			||||||
 | 
					      favorites: favorites ?? this.favorites,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  factory MediaItem.fromJson(Map<String, dynamic> json) {
 | 
					  factory MediaItem.fromJson(Map<String, dynamic> json) {
 | 
				
			||||||
    List<Tag> parsedTags = [];
 | 
					    List<Tag> parsedTags = [];
 | 
				
			||||||
    if (json['tags'] is List) {
 | 
					    if (json['tags'] is List) {
 | 
				
			||||||
@@ -27,6 +51,18 @@ class MediaItem {
 | 
				
			|||||||
      parsedTags = [];
 | 
					      parsedTags = [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    List<Favorite> parsedFavorites = [];
 | 
				
			||||||
 | 
					    if (json['favorites'] is List) {
 | 
				
			||||||
 | 
					      parsedFavorites = (json['favorites'] as List<dynamic>)
 | 
				
			||||||
 | 
					          .map(
 | 
				
			||||||
 | 
					            (favoritesJson) =>
 | 
				
			||||||
 | 
					                Favorite.fromJson(favoritesJson as Map<String, dynamic>),
 | 
				
			||||||
 | 
					          )
 | 
				
			||||||
 | 
					          .toList();
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      parsedFavorites = [];
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return MediaItem(
 | 
					    return MediaItem(
 | 
				
			||||||
      id: json['id'],
 | 
					      id: json['id'],
 | 
				
			||||||
      mime: json['mime'],
 | 
					      mime: json['mime'],
 | 
				
			||||||
@@ -35,6 +71,7 @@ class MediaItem {
 | 
				
			|||||||
      dest: json['dest'],
 | 
					      dest: json['dest'],
 | 
				
			||||||
      mode: json['mode'],
 | 
					      mode: json['mode'],
 | 
				
			||||||
      tags: parsedTags,
 | 
					      tags: parsedTags,
 | 
				
			||||||
 | 
					      favorites: parsedFavorites,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,3 +96,21 @@ class Tag {
 | 
				
			|||||||
    );
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Favorite {
 | 
				
			||||||
 | 
					  final int userId;
 | 
				
			||||||
 | 
					  final String user;
 | 
				
			||||||
 | 
					  final int avatar;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Favorite({required this.userId, required this.user, required this.avatar});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  factory Favorite.fromJson(Map<String, dynamic> json) {
 | 
				
			||||||
 | 
					    return Favorite(
 | 
				
			||||||
 | 
					      userId: json['user_id'],
 | 
				
			||||||
 | 
					      user: json['user'],
 | 
				
			||||||
 | 
					      avatar: json['avatar'],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  String get avatarUrl => 'https://f0ck.me/t/$avatar.webp';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,11 +3,11 @@ import 'dart:io';
 | 
				
			|||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
import 'package:flutter/services.dart';
 | 
					import 'package:flutter/services.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:cached_network_image/cached_network_image.dart';
 | 
					 | 
				
			||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
 | 
					import 'package:flutter_cache_manager/flutter_cache_manager.dart';
 | 
				
			||||||
import 'package:share_plus/share_plus.dart';
 | 
					import 'package:share_plus/share_plus.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/service/media_service.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/animatedtransition.dart';
 | 
					import 'package:f0ckapp/utils/animatedtransition.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/smartrefreshindicator.dart';
 | 
					import 'package:f0ckapp/utils/smartrefreshindicator.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/media_controller.dart';
 | 
					import 'package:f0ckapp/controller/media_controller.dart';
 | 
				
			||||||
@@ -15,7 +15,7 @@ import 'package:f0ckapp/models/media_item.dart';
 | 
				
			|||||||
import 'package:f0ckapp/screens/media_grid.dart';
 | 
					import 'package:f0ckapp/screens/media_grid.dart';
 | 
				
			||||||
import 'package:f0ckapp/screens/fullscreen_screen.dart';
 | 
					import 'package:f0ckapp/screens/fullscreen_screen.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
					import 'package:f0ckapp/widgets/end_drawer.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/video_widget.dart';
 | 
					import 'package:f0ckapp/widgets/detailmediacontent.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class DetailView extends StatefulWidget {
 | 
					class DetailView extends StatefulWidget {
 | 
				
			||||||
  final int initialId;
 | 
					  final int initialId;
 | 
				
			||||||
@@ -42,6 +42,12 @@ class _DetailViewState extends State<DetailView> {
 | 
				
			|||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    _pageController?.dispose();
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _setupInitialView() async {
 | 
					  Future<void> _setupInitialView() async {
 | 
				
			||||||
    bool itemExists = controller.mediaItems.any(
 | 
					    bool itemExists = controller.mediaItems.any(
 | 
				
			||||||
      (media) => media.id == widget.initialId,
 | 
					      (media) => media.id == widget.initialId,
 | 
				
			||||||
@@ -54,15 +60,16 @@ class _DetailViewState extends State<DetailView> {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _initializePageController() {
 | 
					  void _initializePageController() {
 | 
				
			||||||
    _currentPage = controller.mediaItems.indexWhere(
 | 
					    final page = controller.mediaItems.indexWhere(
 | 
				
			||||||
      (media) => media.id == widget.initialId,
 | 
					      (media) => media.id == widget.initialId,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
    if (_currentPage < 0) _currentPage = 0;
 | 
					    setState(() {
 | 
				
			||||||
    _pageController = PageController(initialPage: _currentPage)
 | 
					      _currentPage = page < 0 ? 0 : page;
 | 
				
			||||||
      ..addListener(() {
 | 
					      _pageController = PageController(initialPage: _currentPage)
 | 
				
			||||||
        setState(() => _currentPage = _pageController!.page!.round());
 | 
					        ..addListener(() {
 | 
				
			||||||
      });
 | 
					          setState(() => _currentPage = _pageController!.page!.round());
 | 
				
			||||||
    setState(() {});
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<void> _downloadMedia(MediaItem item) async {
 | 
					  Future<void> _downloadMedia(MediaItem item) async {
 | 
				
			||||||
@@ -106,7 +113,9 @@ class _DetailViewState extends State<DetailView> {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    if (isLoading) {
 | 
					    final MediaService mediaService = Get.find<MediaService>();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isLoading || controller.mediaItems.isEmpty || _pageController == null) {
 | 
				
			||||||
      return Scaffold(
 | 
					      return Scaffold(
 | 
				
			||||||
        appBar: AppBar(
 | 
					        appBar: AppBar(
 | 
				
			||||||
          title: const Text("f0ck"),
 | 
					          title: const Text("f0ck"),
 | 
				
			||||||
@@ -135,7 +144,7 @@ class _DetailViewState extends State<DetailView> {
 | 
				
			|||||||
                  label: Text(controller.tag.value!),
 | 
					                  label: Text(controller.tag.value!),
 | 
				
			||||||
                  onDeleted: () {
 | 
					                  onDeleted: () {
 | 
				
			||||||
                    controller.setTag(null);
 | 
					                    controller.setTag(null);
 | 
				
			||||||
                    Get.offAllNamed('/');
 | 
					                    Get.offAll(() => const MediaGrid());
 | 
				
			||||||
                  },
 | 
					                  },
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ),
 | 
					              ),
 | 
				
			||||||
@@ -152,10 +161,11 @@ class _DetailViewState extends State<DetailView> {
 | 
				
			|||||||
            leading: IconButton(
 | 
					            leading: IconButton(
 | 
				
			||||||
              icon: const Icon(Icons.arrow_back),
 | 
					              icon: const Icon(Icons.arrow_back),
 | 
				
			||||||
              onPressed: () {
 | 
					              onPressed: () {
 | 
				
			||||||
                Navigator.canPop(context)
 | 
					                Navigator.popUntil(
 | 
				
			||||||
                ? Get.back()
 | 
					                  context,
 | 
				
			||||||
                : Get.offAllNamed('/');
 | 
					                  (route) => route.settings.name == '/' || route.isFirst,
 | 
				
			||||||
              }
 | 
					                );
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
            ),
 | 
					            ),
 | 
				
			||||||
            actions: [
 | 
					            actions: [
 | 
				
			||||||
              IconButton(
 | 
					              IconButton(
 | 
				
			||||||
@@ -254,57 +264,22 @@ class _DetailViewState extends State<DetailView> {
 | 
				
			|||||||
                  },
 | 
					                  },
 | 
				
			||||||
                  child: SmartRefreshIndicator(
 | 
					                  child: SmartRefreshIndicator(
 | 
				
			||||||
                    onRefresh: () async {
 | 
					                    onRefresh: () async {
 | 
				
			||||||
                      _showMsg('not hehe');
 | 
					                      final MediaItem? refreshed = await mediaService.fetchItem(
 | 
				
			||||||
 | 
					                        pageItem.id,
 | 
				
			||||||
 | 
					                      );
 | 
				
			||||||
 | 
					                      if (refreshed != null) {
 | 
				
			||||||
 | 
					                        controller.mediaItems[index] = refreshed;
 | 
				
			||||||
 | 
					                        controller.mediaItems.refresh();
 | 
				
			||||||
 | 
					                      }
 | 
				
			||||||
                    },
 | 
					                    },
 | 
				
			||||||
                    child: SafeArea(
 | 
					                    child: DetailMediaContent(
 | 
				
			||||||
                      top: false,
 | 
					                      currentPage: _currentPage,
 | 
				
			||||||
                      child: SingleChildScrollView(
 | 
					                      index: index,
 | 
				
			||||||
                        child: Column(
 | 
					                      onTagTap: (tag) {
 | 
				
			||||||
                          children: [
 | 
					                        if (tag == 'sfw' || tag == 'nsfw') return;
 | 
				
			||||||
                            if (pageItem.mime.startsWith('image'))
 | 
					                        controller.setTag(tag);
 | 
				
			||||||
                              CachedNetworkImage(
 | 
					                        Get.offAllNamed('/');
 | 
				
			||||||
                                imageUrl: pageItem.mediaUrl,
 | 
					                      },
 | 
				
			||||||
                                fit: BoxFit.contain,
 | 
					 | 
				
			||||||
                                placeholder: (context, url) => const Center(
 | 
					 | 
				
			||||||
                                  child: CircularProgressIndicator(),
 | 
					 | 
				
			||||||
                                ),
 | 
					 | 
				
			||||||
                                errorWidget: (context, url, error) =>
 | 
					 | 
				
			||||||
                                    const Center(child: Icon(Icons.error)),
 | 
					 | 
				
			||||||
                              )
 | 
					 | 
				
			||||||
                            else
 | 
					 | 
				
			||||||
                              VideoWidget(
 | 
					 | 
				
			||||||
                                details: pageItem,
 | 
					 | 
				
			||||||
                                isActive: index == _currentPage,
 | 
					 | 
				
			||||||
                              ),
 | 
					 | 
				
			||||||
                            const SizedBox(height: 10, width: double.infinity),
 | 
					 | 
				
			||||||
                            Wrap(
 | 
					 | 
				
			||||||
                              alignment: WrapAlignment.center,
 | 
					 | 
				
			||||||
                              spacing: 5.0,
 | 
					 | 
				
			||||||
                              children: pageItem.tags.map((tag) {
 | 
					 | 
				
			||||||
                                return ActionChip(
 | 
					 | 
				
			||||||
                                  onPressed: () {
 | 
					 | 
				
			||||||
                                    if (tag.tag == 'sfw' || tag.tag == 'nsfw') {
 | 
					 | 
				
			||||||
                                      return;
 | 
					 | 
				
			||||||
                                    }
 | 
					 | 
				
			||||||
                                    controller.setTag(tag.tag);
 | 
					 | 
				
			||||||
                                    Get.offAllNamed('/');
 | 
					 | 
				
			||||||
                                  },
 | 
					 | 
				
			||||||
                                  label: Text(tag.tag),
 | 
					 | 
				
			||||||
                                  backgroundColor: switch (tag.id) {
 | 
					 | 
				
			||||||
                                    1 => Colors.green,
 | 
					 | 
				
			||||||
                                    2 => Colors.red,
 | 
					 | 
				
			||||||
                                    _ => const Color(0xFF090909),
 | 
					 | 
				
			||||||
                                  },
 | 
					 | 
				
			||||||
                                  labelStyle: const TextStyle(
 | 
					 | 
				
			||||||
                                    color: Colors.white,
 | 
					 | 
				
			||||||
                                  ),
 | 
					 | 
				
			||||||
                                );
 | 
					 | 
				
			||||||
                              }).toList(),
 | 
					 | 
				
			||||||
                            ),
 | 
					 | 
				
			||||||
                            const SizedBox(height: 20),
 | 
					 | 
				
			||||||
                          ],
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ),
 | 
					                    ),
 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                );
 | 
					                );
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										68
									
								
								lib/screens/login_screen.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								lib/screens/login_screen.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/auth_controller.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class LoginPage extends StatefulWidget {
 | 
				
			||||||
 | 
					  const LoginPage({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  State<StatefulWidget> createState() => _LoginPageState();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class _LoginPageState extends State<LoginPage> {
 | 
				
			||||||
 | 
					  final AuthController authController = Get.find();
 | 
				
			||||||
 | 
					  final TextEditingController usernameController = TextEditingController();
 | 
				
			||||||
 | 
					  final TextEditingController passwordController = TextEditingController();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  void _showMsg(String message, {String title = ''}) {
 | 
				
			||||||
 | 
					    Get
 | 
				
			||||||
 | 
					      ..closeAllSnackbars()
 | 
				
			||||||
 | 
					      ..snackbar(message, title, snackPosition: SnackPosition.BOTTOM);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  void dispose() {
 | 
				
			||||||
 | 
					    usernameController.dispose();
 | 
				
			||||||
 | 
					    passwordController.dispose();
 | 
				
			||||||
 | 
					    super.dispose();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Scaffold(
 | 
				
			||||||
 | 
					      body: CustomScrollView(
 | 
				
			||||||
 | 
					        slivers: [
 | 
				
			||||||
 | 
					          SliverAppBar(floating: false, pinned: true, title: Text('Login')),
 | 
				
			||||||
 | 
					          SliverList(
 | 
				
			||||||
 | 
					            delegate: SliverChildListDelegate([
 | 
				
			||||||
 | 
					              ListTile(
 | 
				
			||||||
 | 
					                title: Text('Benutzername'),
 | 
				
			||||||
 | 
					                subtitle: TextField(controller: usernameController),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              ListTile(
 | 
				
			||||||
 | 
					                title: Text('Passwort'),
 | 
				
			||||||
 | 
					                subtitle: TextField(
 | 
				
			||||||
 | 
					                  controller: passwordController,
 | 
				
			||||||
 | 
					                  obscureText: true,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              ElevatedButton(
 | 
				
			||||||
 | 
					                onPressed: () async {
 | 
				
			||||||
 | 
					                  final success = await authController.login(
 | 
				
			||||||
 | 
					                    usernameController.text,
 | 
				
			||||||
 | 
					                    passwordController.text,
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                  if (!success) {
 | 
				
			||||||
 | 
					                    _showMsg('Login fehlgeschlagen!');
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					                child: Text('Login'),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ]),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,3 +1,4 @@
 | 
				
			|||||||
 | 
					import 'package:encrypt_shared_preferences/provider.dart';
 | 
				
			||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/models/media_item.dart';
 | 
					import 'package:f0ckapp/models/media_item.dart';
 | 
				
			||||||
@@ -6,6 +7,9 @@ const List<String> mediaTypes = ["alles", "image", "video", "audio"];
 | 
				
			|||||||
const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
 | 
					const List<String> mediaModes = ["sfw", "nsfw", "untagged", "all"];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MediaService extends GetConnect {
 | 
					class MediaService extends GetConnect {
 | 
				
			||||||
 | 
					  final EncryptedSharedPreferencesAsync storage =
 | 
				
			||||||
 | 
					      EncryptedSharedPreferencesAsync.getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Future<List<MediaItem>> fetchMediaItems({
 | 
					  Future<List<MediaItem>> fetchMediaItems({
 | 
				
			||||||
    required int type,
 | 
					    required int type,
 | 
				
			||||||
    required int mode,
 | 
					    required int mode,
 | 
				
			||||||
@@ -13,6 +17,11 @@ class MediaService extends GetConnect {
 | 
				
			|||||||
    String? tag,
 | 
					    String? tag,
 | 
				
			||||||
    int? older,
 | 
					    int? older,
 | 
				
			||||||
  }) async {
 | 
					  }) async {
 | 
				
			||||||
 | 
					    final String? token = await storage.getString('token');
 | 
				
			||||||
 | 
					    final Map<String, String> headers = token != null
 | 
				
			||||||
 | 
					        ? {'Authorization': 'Bearer $token'}
 | 
				
			||||||
 | 
					        : {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    final queryParameters = {
 | 
					    final queryParameters = {
 | 
				
			||||||
      'type': type.toString(),
 | 
					      'type': type.toString(),
 | 
				
			||||||
      'mode': mode.toString(),
 | 
					      'mode': mode.toString(),
 | 
				
			||||||
@@ -22,9 +31,10 @@ class MediaService extends GetConnect {
 | 
				
			|||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    try {
 | 
					    try {
 | 
				
			||||||
      final response = await get(
 | 
					      final Response<dynamic> response = await get(
 | 
				
			||||||
        'https://api.f0ck.me/items/get',
 | 
					        'https://api.f0ck.me/items/get',
 | 
				
			||||||
        query: queryParameters,
 | 
					        query: queryParameters,
 | 
				
			||||||
 | 
					        headers: headers,
 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
      if (response.status.code == 200 && response.body is List) {
 | 
					      if (response.status.code == 200 && response.body is List) {
 | 
				
			||||||
        final data = response.body as List<dynamic>;
 | 
					        final data = response.body as List<dynamic>;
 | 
				
			||||||
@@ -36,4 +46,62 @@ class MediaService extends GetConnect {
 | 
				
			|||||||
      return Future.error('Netzwerkfehler: ${e.toString()}');
 | 
					      return Future.error('Netzwerkfehler: ${e.toString()}');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<List<Favorite>?> toggleFavorite(
 | 
				
			||||||
 | 
					    MediaItem item,
 | 
				
			||||||
 | 
					    bool isFavorite,
 | 
				
			||||||
 | 
					  ) async {
 | 
				
			||||||
 | 
					    final String? token = await storage.getString('token');
 | 
				
			||||||
 | 
					    if (token == null) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    final headers = {
 | 
				
			||||||
 | 
					      'Authorization': 'Bearer $token',
 | 
				
			||||||
 | 
					      'Content-Type': 'application/json',
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      Response response;
 | 
				
			||||||
 | 
					      if (!isFavorite) {
 | 
				
			||||||
 | 
					        response = await put(
 | 
				
			||||||
 | 
					          'https://api.f0ck.me/favorites/${item.id}',
 | 
				
			||||||
 | 
					          null,
 | 
				
			||||||
 | 
					          headers: headers,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        response = await delete(
 | 
				
			||||||
 | 
					          'https://api.f0ck.me/favorites/${item.id}',
 | 
				
			||||||
 | 
					          headers: headers,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (response.status.code == 200 && response.body is List) {
 | 
				
			||||||
 | 
					        return (response.body as List)
 | 
				
			||||||
 | 
					            .map((json) => Favorite.fromJson(json))
 | 
				
			||||||
 | 
					            .toList();
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        return null;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Future<MediaItem?> fetchItem(int itemId) async {
 | 
				
			||||||
 | 
					    final String? token = await storage.getString('token');
 | 
				
			||||||
 | 
					    final Map<String, String> headers = token != null
 | 
				
			||||||
 | 
					        ? {'Authorization': 'Bearer $token'}
 | 
				
			||||||
 | 
					        : {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      final Response<dynamic> response = await get(
 | 
				
			||||||
 | 
					        'https://api.f0ck.me/item/$itemId',
 | 
				
			||||||
 | 
					        headers: headers,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      if (response.status.code == 200 && response.body is Map) {
 | 
				
			||||||
 | 
					        return MediaItem.fromJson(response.body);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    } catch (e) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										80
									
								
								lib/widgets/actiontag.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										80
									
								
								lib/widgets/actiontag.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,80 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/models/media_item.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class ActionTag extends StatelessWidget {
 | 
				
			||||||
 | 
					  final Tag tag;
 | 
				
			||||||
 | 
					  final void Function(String tag) onTagTap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const ActionTag(this.tag, this.onTagTap, {super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return GestureDetector(
 | 
				
			||||||
 | 
					      onTap: () => onTagTap(tag.tag),
 | 
				
			||||||
 | 
					      child:
 | 
				
			||||||
 | 
					          [
 | 
				
			||||||
 | 
					            'german',
 | 
				
			||||||
 | 
					            'dutch',
 | 
				
			||||||
 | 
					            'ukraine',
 | 
				
			||||||
 | 
					            'russia',
 | 
				
			||||||
 | 
					            'belgium',
 | 
				
			||||||
 | 
					          ].contains(tag.tag)
 | 
				
			||||||
 | 
					          ? Stack(
 | 
				
			||||||
 | 
					              alignment: Alignment.center,
 | 
				
			||||||
 | 
					              children: [
 | 
				
			||||||
 | 
					                Container(
 | 
				
			||||||
 | 
					                  decoration: BoxDecoration(
 | 
				
			||||||
 | 
					                    borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
 | 
					                    border: Border.all(color: Colors.white, width: 1),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  child: ClipRRect(
 | 
				
			||||||
 | 
					                    borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
 | 
					                    child: Image.asset(
 | 
				
			||||||
 | 
					                      'assets/images/tags/${tag.tag}.webp',
 | 
				
			||||||
 | 
					                      height: 27,
 | 
				
			||||||
 | 
					                      width: 60,
 | 
				
			||||||
 | 
					                      repeat: ImageRepeat.repeat,
 | 
				
			||||||
 | 
					                      fit: BoxFit.fill,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                Padding(
 | 
				
			||||||
 | 
					                  padding: const EdgeInsets.symmetric(
 | 
				
			||||||
 | 
					                    horizontal: 9,
 | 
				
			||||||
 | 
					                    vertical: 6,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                  child: Text(
 | 
				
			||||||
 | 
					                    tag.tag,
 | 
				
			||||||
 | 
					                    style: const TextStyle(
 | 
				
			||||||
 | 
					                      fontSize: 12,
 | 
				
			||||||
 | 
					                      color: Colors.white,
 | 
				
			||||||
 | 
					                      fontWeight: FontWeight.w500,
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					            )
 | 
				
			||||||
 | 
					          : Container(
 | 
				
			||||||
 | 
					              padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 5),
 | 
				
			||||||
 | 
					              decoration: BoxDecoration(
 | 
				
			||||||
 | 
					                borderRadius: BorderRadius.circular(10),
 | 
				
			||||||
 | 
					                border: Border.all(color: Colors.white, width: 1),
 | 
				
			||||||
 | 
					                color: switch (tag.id) {
 | 
				
			||||||
 | 
					                  1 => Colors.green,
 | 
				
			||||||
 | 
					                  2 => Colors.red,
 | 
				
			||||||
 | 
					                  _ => const Color(0xFF090909),
 | 
				
			||||||
 | 
					                },
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					              child: Text(
 | 
				
			||||||
 | 
					                tag.tag,
 | 
				
			||||||
 | 
					                style: const TextStyle(
 | 
				
			||||||
 | 
					                  fontSize: 12,
 | 
				
			||||||
 | 
					                  color: Colors.white,
 | 
				
			||||||
 | 
					                  fontWeight: FontWeight.w500,
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										121
									
								
								lib/widgets/detailmediacontent.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										121
									
								
								lib/widgets/detailmediacontent.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,121 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:cached_network_image/cached_network_image.dart';
 | 
				
			||||||
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/media_controller.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/service/media_service.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/auth_controller.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/actiontag.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/favorite_avatars.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/widgets/video_widget.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/models/media_item.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class DetailMediaContent extends StatelessWidget {
 | 
				
			||||||
 | 
					  final int currentPage;
 | 
				
			||||||
 | 
					  final int index;
 | 
				
			||||||
 | 
					  final void Function(String tag) onTagTap;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const DetailMediaContent({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.currentPage,
 | 
				
			||||||
 | 
					    required this.index,
 | 
				
			||||||
 | 
					    required this.onTagTap,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    final MediaService mediaService = Get.find();
 | 
				
			||||||
 | 
					    final MediaController controller = Get.find();
 | 
				
			||||||
 | 
					    final AuthController authController = Get.find();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return SafeArea(
 | 
				
			||||||
 | 
					      top: false,
 | 
				
			||||||
 | 
					      child: SingleChildScrollView(
 | 
				
			||||||
 | 
					        child: Obx(() {
 | 
				
			||||||
 | 
					          final MediaItem currentItem = controller.mediaItems[index];
 | 
				
			||||||
 | 
					          final bool isFavorite =
 | 
				
			||||||
 | 
					              currentItem.favorites?.any(
 | 
				
			||||||
 | 
					                (f) => f.userId == authController.userId.value,
 | 
				
			||||||
 | 
					              ) ??
 | 
				
			||||||
 | 
					              false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          return Column(
 | 
				
			||||||
 | 
					            children: [
 | 
				
			||||||
 | 
					              _buildMedia(currentItem, index == currentPage),
 | 
				
			||||||
 | 
					              const SizedBox(height: 10, width: double.infinity),
 | 
				
			||||||
 | 
					              _buildTags(currentItem),
 | 
				
			||||||
 | 
					              if (currentItem.favorites != null &&
 | 
				
			||||||
 | 
					                  authController.token.value != null) ...[
 | 
				
			||||||
 | 
					                const SizedBox(height: 20),
 | 
				
			||||||
 | 
					                _buildFavoritesRow(context, currentItem, isFavorite, () async {
 | 
				
			||||||
 | 
					                  final List<Favorite>? newFavorites = await mediaService
 | 
				
			||||||
 | 
					                      .toggleFavorite(currentItem, isFavorite);
 | 
				
			||||||
 | 
					                  if (newFavorites != null) {
 | 
				
			||||||
 | 
					                    controller.mediaItems[index] = currentItem.copyWith(
 | 
				
			||||||
 | 
					                      favorites: newFavorites,
 | 
				
			||||||
 | 
					                    );
 | 
				
			||||||
 | 
					                    controller.mediaItems.refresh();
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					              ],
 | 
				
			||||||
 | 
					              const SizedBox(height: 20),
 | 
				
			||||||
 | 
					            ],
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildMedia(MediaItem item, bool isActive) {
 | 
				
			||||||
 | 
					    if (item.mime.startsWith('image')) {
 | 
				
			||||||
 | 
					      return CachedNetworkImage(
 | 
				
			||||||
 | 
					        imageUrl: item.mediaUrl,
 | 
				
			||||||
 | 
					        fit: BoxFit.contain,
 | 
				
			||||||
 | 
					        placeholder: (context, url) =>
 | 
				
			||||||
 | 
					            const Center(child: CircularProgressIndicator()),
 | 
				
			||||||
 | 
					        errorWidget: (context, url, error) =>
 | 
				
			||||||
 | 
					            const Center(child: Icon(Icons.error)),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return VideoWidget(details: item, isActive: isActive);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildTags(MediaItem item) {
 | 
				
			||||||
 | 
					    return Wrap(
 | 
				
			||||||
 | 
					      alignment: WrapAlignment.center,
 | 
				
			||||||
 | 
					      spacing: 5.0,
 | 
				
			||||||
 | 
					      children: item.tags
 | 
				
			||||||
 | 
					          .map<Widget>((Tag tag) => ActionTag(tag, onTagTap))
 | 
				
			||||||
 | 
					          .toList(),
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Widget _buildFavoritesRow(
 | 
				
			||||||
 | 
					    BuildContext context,
 | 
				
			||||||
 | 
					    MediaItem item,
 | 
				
			||||||
 | 
					    bool isFavorite,
 | 
				
			||||||
 | 
					    VoidCallback onFavoritePressed,
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return Row(
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        Expanded(
 | 
				
			||||||
 | 
					          child: SingleChildScrollView(
 | 
				
			||||||
 | 
					            scrollDirection: Axis.horizontal,
 | 
				
			||||||
 | 
					            child: FavoriteAvatars(
 | 
				
			||||||
 | 
					              favorites: item.favorites ?? [],
 | 
				
			||||||
 | 
					              brightness: Theme.of(context).brightness,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          ),
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					        IconButton(
 | 
				
			||||||
 | 
					          icon: isFavorite
 | 
				
			||||||
 | 
					              ? const Icon(Icons.favorite)
 | 
				
			||||||
 | 
					              : const Icon(Icons.favorite_outline),
 | 
				
			||||||
 | 
					          color: Colors.red,
 | 
				
			||||||
 | 
					          onPressed: onFavoritePressed,
 | 
				
			||||||
 | 
					        ),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import 'package:get/get.dart';
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/screens/login_screen.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/auth_controller.dart';
 | 
				
			||||||
import 'package:f0ckapp/screens/settings_screen.dart';
 | 
					import 'package:f0ckapp/screens/settings_screen.dart';
 | 
				
			||||||
import 'package:f0ckapp/controller/theme_controller.dart';
 | 
					import 'package:f0ckapp/controller/theme_controller.dart';
 | 
				
			||||||
import 'package:f0ckapp/utils/appversion.dart';
 | 
					import 'package:f0ckapp/utils/appversion.dart';
 | 
				
			||||||
@@ -9,81 +11,93 @@ import 'package:f0ckapp/utils/appversion.dart';
 | 
				
			|||||||
class EndDrawer extends StatelessWidget {
 | 
					class EndDrawer extends StatelessWidget {
 | 
				
			||||||
  const EndDrawer({super.key});
 | 
					  const EndDrawer({super.key});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  void _showMsg(String message, BuildContext context) {
 | 
					  void _showMsg(String message, {String title = ''}) {
 | 
				
			||||||
    ScaffoldMessenger.of(context)
 | 
					    Get
 | 
				
			||||||
      ..removeCurrentSnackBar()
 | 
					      ..closeAllSnackbars()
 | 
				
			||||||
      ..showSnackBar(SnackBar(content: Text(message)));
 | 
					      ..snackbar(message, title, snackPosition: SnackPosition.BOTTOM);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  Widget build(BuildContext context) {
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
    final ThemeController themeController = Get.find();
 | 
					    final ThemeController themeController = Get.find();
 | 
				
			||||||
 | 
					    final AuthController authController = Get.find();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return Drawer(
 | 
					    return Drawer(
 | 
				
			||||||
      child: ListView(
 | 
					      child: ListView(
 | 
				
			||||||
        padding: EdgeInsets.zero,
 | 
					        padding: EdgeInsets.zero,
 | 
				
			||||||
        children: [
 | 
					        children: [
 | 
				
			||||||
          DrawerHeader(
 | 
					          Obx(() {
 | 
				
			||||||
            decoration: const BoxDecoration(
 | 
					            if (authController.token.value != null &&
 | 
				
			||||||
              image: DecorationImage(
 | 
					                authController.avatarUrl.value != null) {
 | 
				
			||||||
                image: AssetImage('assets/images/menu.webp'),
 | 
					              return DrawerHeader(
 | 
				
			||||||
                fit: BoxFit.cover,
 | 
					                decoration: BoxDecoration(
 | 
				
			||||||
                alignment: Alignment.topCenter,
 | 
					                  image: DecorationImage(
 | 
				
			||||||
              ),
 | 
					                    image: NetworkImage(authController.avatarUrl.value!),
 | 
				
			||||||
            ),
 | 
					                    fit: BoxFit.cover,
 | 
				
			||||||
            child: null,
 | 
					                    alignment: Alignment.topCenter,
 | 
				
			||||||
          ),
 | 
					 | 
				
			||||||
          /*ExpansionTile(
 | 
					 | 
				
			||||||
              title: const Text('Login'),
 | 
					 | 
				
			||||||
              children: [
 | 
					 | 
				
			||||||
                Padding(
 | 
					 | 
				
			||||||
                  padding: const EdgeInsets.all(16.0),
 | 
					 | 
				
			||||||
                  child: Column(
 | 
					 | 
				
			||||||
                    children: [
 | 
					 | 
				
			||||||
                      TextField(
 | 
					 | 
				
			||||||
                        readOnly: true,
 | 
					 | 
				
			||||||
                        controller: _usernameController,
 | 
					 | 
				
			||||||
                        decoration: const InputDecoration(
 | 
					 | 
				
			||||||
                          labelText: 'Benutzername',
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      const SizedBox(height: 10),
 | 
					 | 
				
			||||||
                      TextField(
 | 
					 | 
				
			||||||
                        readOnly: true,
 | 
					 | 
				
			||||||
                        controller: _passwordController,
 | 
					 | 
				
			||||||
                        obscureText: true,
 | 
					 | 
				
			||||||
                        decoration: const InputDecoration(
 | 
					 | 
				
			||||||
                          labelText: 'Passwort',
 | 
					 | 
				
			||||||
                        ),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                      const SizedBox(height: 20),
 | 
					 | 
				
			||||||
                      ElevatedButton(
 | 
					 | 
				
			||||||
                        onPressed: () async {
 | 
					 | 
				
			||||||
                          ScaffoldMessenger.of(context).showSnackBar(
 | 
					 | 
				
			||||||
                            const SnackBar(
 | 
					 | 
				
			||||||
                              content: Text("noch nicht implementiert lol"),
 | 
					 | 
				
			||||||
                            ),
 | 
					 | 
				
			||||||
                            final success = await login(
 | 
					 | 
				
			||||||
                              _usernameController.text,
 | 
					 | 
				
			||||||
                              _passwordController.text,
 | 
					 | 
				
			||||||
                            );
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
                            if (success) {
 | 
					 | 
				
			||||||
                              Navigator.pop(context);
 | 
					 | 
				
			||||||
                            } else {
 | 
					 | 
				
			||||||
                              ScaffoldMessenger.of(context).showSnackBar(
 | 
					 | 
				
			||||||
                                SnackBar(content: Text("Login fehlgeschlagen!")),
 | 
					 | 
				
			||||||
                              );
 | 
					 | 
				
			||||||
                            }
 | 
					 | 
				
			||||||
                          );
 | 
					 | 
				
			||||||
                        },
 | 
					 | 
				
			||||||
                        child: const Text('Login'),
 | 
					 | 
				
			||||||
                      ),
 | 
					 | 
				
			||||||
                    ],
 | 
					 | 
				
			||||||
                  ),
 | 
					                  ),
 | 
				
			||||||
                ),
 | 
					                ),
 | 
				
			||||||
              ],
 | 
					                child: null,
 | 
				
			||||||
            ),*/
 | 
					              );
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              return DrawerHeader(
 | 
				
			||||||
 | 
					                decoration: const BoxDecoration(
 | 
				
			||||||
 | 
					                  image: DecorationImage(
 | 
				
			||||||
 | 
					                    image: AssetImage('assets/images/menu.webp'),
 | 
				
			||||||
 | 
					                    fit: BoxFit.cover,
 | 
				
			||||||
 | 
					                    alignment: Alignment.topCenter,
 | 
				
			||||||
 | 
					                  ),
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					                child: null,
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
 | 
					          Obx(() {
 | 
				
			||||||
 | 
					            if (authController.token.value != null) {
 | 
				
			||||||
 | 
					              return Padding(
 | 
				
			||||||
 | 
					                padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    if (authController.username.value != null)
 | 
				
			||||||
 | 
					                      Padding(
 | 
				
			||||||
 | 
					                        padding: const EdgeInsets.symmetric(vertical: 8.0),
 | 
				
			||||||
 | 
					                        child: Text(
 | 
				
			||||||
 | 
					                          'Hamlo ${authController.username.value!}',
 | 
				
			||||||
 | 
					                          style: const TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
 | 
					                        ),
 | 
				
			||||||
 | 
					                      ),
 | 
				
			||||||
 | 
					                    ElevatedButton(
 | 
				
			||||||
 | 
					                      onPressed: () async {
 | 
				
			||||||
 | 
					                        await authController.logout();
 | 
				
			||||||
 | 
					                        _showMsg('Erfolgreich ausgeloggt.');
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                      child: const Text('Logout'),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            } else {
 | 
				
			||||||
 | 
					              return Padding(
 | 
				
			||||||
 | 
					                padding: const EdgeInsets.all(16.0),
 | 
				
			||||||
 | 
					                child: Column(
 | 
				
			||||||
 | 
					                  children: [
 | 
				
			||||||
 | 
					                    const Text(
 | 
				
			||||||
 | 
					                      'Du bist nicht eingeloggt.',
 | 
				
			||||||
 | 
					                      style: TextStyle(fontWeight: FontWeight.bold),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                    const SizedBox(height: 10),
 | 
				
			||||||
 | 
					                    ElevatedButton(
 | 
				
			||||||
 | 
					                      onPressed: () {
 | 
				
			||||||
 | 
					                        Navigator.pop(context);
 | 
				
			||||||
 | 
					                        Get.bottomSheet(LoginPage(), isDismissible: false);
 | 
				
			||||||
 | 
					                      },
 | 
				
			||||||
 | 
					                      child: const Text('Login'),
 | 
				
			||||||
 | 
					                    ),
 | 
				
			||||||
 | 
					                  ],
 | 
				
			||||||
 | 
					                ),
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }),
 | 
				
			||||||
          ExpansionTile(
 | 
					          ExpansionTile(
 | 
				
			||||||
            title: const Text('Theme'),
 | 
					            title: const Text('Theme'),
 | 
				
			||||||
            children: [
 | 
					            children: [
 | 
				
			||||||
@@ -121,7 +135,7 @@ class EndDrawer extends StatelessWidget {
 | 
				
			|||||||
            title: Text('v${AppVersion.version}'),
 | 
					            title: Text('v${AppVersion.version}'),
 | 
				
			||||||
            onTap: () {
 | 
					            onTap: () {
 | 
				
			||||||
              Navigator.pop(context);
 | 
					              Navigator.pop(context);
 | 
				
			||||||
              _showMsg('jooong lass das, hier ist nichts', context);
 | 
					              _showMsg('jooong lass das, hier ist nichts');
 | 
				
			||||||
            },
 | 
					            },
 | 
				
			||||||
          ),
 | 
					          ),
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										41
									
								
								lib/widgets/favorite_avatars.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								lib/widgets/favorite_avatars.dart
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,41 @@
 | 
				
			|||||||
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					import 'package:cached_network_image/cached_network_image.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class FavoriteAvatars extends StatelessWidget {
 | 
				
			||||||
 | 
					  final List favorites;
 | 
				
			||||||
 | 
					  final Brightness brightness;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const FavoriteAvatars({
 | 
				
			||||||
 | 
					    super.key,
 | 
				
			||||||
 | 
					    required this.favorites,
 | 
				
			||||||
 | 
					    required this.brightness,
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  @override
 | 
				
			||||||
 | 
					  Widget build(BuildContext context) {
 | 
				
			||||||
 | 
					    return Row(
 | 
				
			||||||
 | 
					      crossAxisAlignment: CrossAxisAlignment.center,
 | 
				
			||||||
 | 
					      children: [
 | 
				
			||||||
 | 
					        ...favorites.map((favorite) {
 | 
				
			||||||
 | 
					          return Container(
 | 
				
			||||||
 | 
					            height: 32,
 | 
				
			||||||
 | 
					            width: 32,
 | 
				
			||||||
 | 
					            margin: const EdgeInsets.only(right: 5.0),
 | 
				
			||||||
 | 
					            decoration: BoxDecoration(
 | 
				
			||||||
 | 
					              border: Border.all(
 | 
				
			||||||
 | 
					                color: brightness == Brightness.dark
 | 
				
			||||||
 | 
					                    ? Colors.white
 | 
				
			||||||
 | 
					                    : Colors.black,
 | 
				
			||||||
 | 
					                width: 1.0,
 | 
				
			||||||
 | 
					              ),
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					            child: CachedNetworkImage(
 | 
				
			||||||
 | 
					              imageUrl: favorite.avatarUrl,
 | 
				
			||||||
 | 
					              fit: BoxFit.cover,
 | 
				
			||||||
 | 
					            ),
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
 | 
					        }),
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,14 +1,14 @@
 | 
				
			|||||||
import 'dart:async';
 | 
					import 'dart:async';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import 'package:f0ckapp/controller/media_controller.dart';
 | 
					 | 
				
			||||||
import 'package:f0ckapp/models/media_item.dart';
 | 
					 | 
				
			||||||
import 'package:flutter/material.dart';
 | 
					import 'package:flutter/material.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:get/get.dart';
 | 
				
			||||||
import 'package:cached_video_player_plus/cached_video_player_plus.dart';
 | 
					import 'package:cached_video_player_plus/cached_video_player_plus.dart';
 | 
				
			||||||
import 'package:cached_network_image/cached_network_image.dart';
 | 
					import 'package:cached_network_image/cached_network_image.dart';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import 'package:f0ckapp/controller/media_controller.dart';
 | 
				
			||||||
 | 
					import 'package:f0ckapp/models/media_item.dart';
 | 
				
			||||||
import 'package:f0ckapp/widgets/videooverlay_widget.dart';
 | 
					import 'package:f0ckapp/widgets/videooverlay_widget.dart';
 | 
				
			||||||
import 'package:get/get.dart';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class VideoWidget extends StatefulWidget {
 | 
					class VideoWidget extends StatefulWidget {
 | 
				
			||||||
  final MediaItem details;
 | 
					  final MediaItem details;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
					# 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
 | 
					# 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.
 | 
					# of the product and file versions while build-number is used as the build suffix.
 | 
				
			||||||
version: 1.3.2+58
 | 
					version: 1.3.3+59
 | 
				
			||||||
 | 
					
 | 
				
			||||||
environment:
 | 
					environment:
 | 
				
			||||||
  sdk: ^3.9.0-100.2.beta
 | 
					  sdk: ^3.9.0-100.2.beta
 | 
				
			||||||
@@ -63,6 +63,7 @@ flutter:
 | 
				
			|||||||
  # To add assets to your application, add an assets section, like this:
 | 
					  # To add assets to your application, add an assets section, like this:
 | 
				
			||||||
  assets:
 | 
					  assets:
 | 
				
			||||||
    - assets/images/
 | 
					    - assets/images/
 | 
				
			||||||
 | 
					    - assets/images/tags/
 | 
				
			||||||
    - assets/i18n/
 | 
					    - assets/i18n/
 | 
				
			||||||
    - pubspec.yaml
 | 
					    - pubspec.yaml
 | 
				
			||||||
  #   - images/a_dot_burr.jpeg
 | 
					  #   - images/a_dot_burr.jpeg
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user