From ee2db04a36e04764f23c785542c74caf7ef510eb Mon Sep 17 00:00:00 2001 From: Flummi Date: Tue, 17 Jun 2025 19:03:40 +0200 Subject: [PATCH] v1.3.3+59 --- assets/images/tags/belgium.webp | Bin 0 -> 92 bytes assets/images/tags/dutch.webp | Bin 0 -> 90 bytes assets/images/tags/german.webp | Bin 0 -> 94 bytes assets/images/tags/russia.webp | Bin 0 -> 92 bytes assets/images/tags/ukraine.webp | Bin 0 -> 108 bytes lib/controller/auth_controller.dart | 98 ++++++++++++++ lib/controller/localization_controller.dart | 8 +- lib/controller/media_controller.dart | 2 + lib/main.dart | 4 + lib/models/media_item.dart | 55 ++++++++ lib/screens/detail_view.dart | 105 ++++++--------- lib/screens/login_screen.dart | 68 ++++++++++ lib/service/media_service.dart | 70 +++++++++- lib/widgets/actiontag.dart | 80 +++++++++++ lib/widgets/detailmediacontent.dart | 121 +++++++++++++++++ lib/widgets/end_drawer.dart | 142 +++++++++++--------- lib/widgets/favorite_avatars.dart | 41 ++++++ lib/widgets/video_widget.dart | 6 +- pubspec.yaml | 3 +- 19 files changed, 667 insertions(+), 136 deletions(-) create mode 100644 assets/images/tags/belgium.webp create mode 100644 assets/images/tags/dutch.webp create mode 100644 assets/images/tags/german.webp create mode 100644 assets/images/tags/russia.webp create mode 100644 assets/images/tags/ukraine.webp create mode 100644 lib/controller/auth_controller.dart create mode 100644 lib/screens/login_screen.dart create mode 100644 lib/widgets/actiontag.dart create mode 100644 lib/widgets/detailmediacontent.dart create mode 100644 lib/widgets/favorite_avatars.dart diff --git a/assets/images/tags/belgium.webp b/assets/images/tags/belgium.webp new file mode 100644 index 0000000000000000000000000000000000000000..90b95b5867c1d1a42c8a8b360730a70fc45c0b27 GIT binary patch literal 92 zcmWIYbaM+~U|rnsy|Mr&Y|Nnow{lokJ|F>?5^?xdNn+hN4*e&v^%3$y5)AuwP7#ILglO)*y literal 0 HcmV?d00001 diff --git a/assets/images/tags/dutch.webp b/assets/images/tags/dutch.webp new file mode 100644 index 0000000000000000000000000000000000000000..dcf84e09ba4b03cb8e2c8db1ea8b01c2fb7dc426 GIT binary patch literal 90 zcmWIYbaM-0U|*DB@84&h|NsB}X8IuZF2N<@{r|qF>8|VK|0Jw1I<)1ldjkUl0JMc6u&k~|9$VV{{O{{NADTmEx#laIsdrvZtnAD3=9A^Y9t5% literal 0 HcmV?d00001 diff --git a/assets/images/tags/ukraine.webp b/assets/images/tags/ukraine.webp new file mode 100644 index 0000000000000000000000000000000000000000..4163f81782ddd64633bb72a23e2ccef0e0a871f3 GIT binary patch literal 108 zcmWIYbaP8#U|wcjWGf3eu$qSEF6|G&o6ERTJD P=D@_ewk(); + + RxnString token = RxnString(); + RxnInt userId = RxnInt(); + RxnString avatarUrl = RxnString(); + RxnString username = RxnString(); + + @override + void onInit() { + super.onInit(); + loadToken(); + } + + Future loadToken() async { + token.value = await storage.getString('token'); + if (token.value != null) { + await fetchUserInfo(); + } + } + + Future saveToken(String newToken) async { + token.value = newToken; + await storage.setString('token', newToken); + await fetchUserInfo(); + } + + Future 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 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 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(); + } + } +} diff --git a/lib/controller/localization_controller.dart b/lib/controller/localization_controller.dart index 1ac40b9..7c2127a 100644 --- a/lib/controller/localization_controller.dart +++ b/lib/controller/localization_controller.dart @@ -15,9 +15,13 @@ class MyTranslations extends Translations { static Future loadTranslations() async { final locales = ['en_US', 'de_DE', 'fr_FR', 'nl_NL']; 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 jsonMap = json.decode(jsonString); - _translations[locale] = jsonMap.map((key, value) => MapEntry(key, value.toString())); + _translations[locale] = jsonMap.map( + (key, value) => MapEntry(key, value.toString()), + ); } } diff --git a/lib/controller/media_controller.dart b/lib/controller/media_controller.dart index 016cd23..c462add 100644 --- a/lib/controller/media_controller.dart +++ b/lib/controller/media_controller.dart @@ -23,6 +23,8 @@ class MediaController extends GetxController { late RxBool drawerSwipeEnabled = true.obs; final Rx transitionType = PageTransition.opacity.obs; + MediaItem? selectedItem; + @override void onInit() async { super.onInit(); diff --git a/lib/main.dart b/lib/main.dart index ebcb5df..597200e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,12 +4,14 @@ import 'package:flutter/services.dart'; import 'package:get/get.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/utils/appversion.dart'; import 'package:f0ckapp/controller/theme_controller.dart'; import 'package:f0ckapp/controller/media_controller.dart'; import 'package:f0ckapp/screens/detail_view.dart'; import 'package:f0ckapp/screens/media_grid.dart'; +import 'package:f0ckapp/controller/auth_controller.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); @@ -17,7 +19,9 @@ void main() async { await EncryptedSharedPreferencesAsync.initialize('VokTnbAbemBUa2j9'); await MyTranslations.loadTranslations(); await AppVersion.init(); + Get.put(MediaService()); Get.put(MediaController()); + Get.put(AuthController()); LocalizationController localizationController = Get.put(LocalizationController()); final ThemeController themeController = Get.put(ThemeController()); diff --git a/lib/models/media_item.dart b/lib/models/media_item.dart index 788fcb9..477f6f0 100644 --- a/lib/models/media_item.dart +++ b/lib/models/media_item.dart @@ -6,6 +6,7 @@ class MediaItem { final String dest; final int mode; final List tags; + final List? favorites; MediaItem({ required this.id, @@ -15,8 +16,31 @@ class MediaItem { required this.dest, required this.mode, required this.tags, + required this.favorites, }); + MediaItem copyWith({ + int? id, + String? mime, + int? size, + int? stamp, + String? dest, + int? mode, + List? tags, + List? 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 json) { List parsedTags = []; if (json['tags'] is List) { @@ -27,6 +51,18 @@ class MediaItem { parsedTags = []; } + List parsedFavorites = []; + if (json['favorites'] is List) { + parsedFavorites = (json['favorites'] as List) + .map( + (favoritesJson) => + Favorite.fromJson(favoritesJson as Map), + ) + .toList(); + } else { + parsedFavorites = []; + } + return MediaItem( id: json['id'], mime: json['mime'], @@ -35,6 +71,7 @@ class MediaItem { dest: json['dest'], mode: json['mode'], 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 json) { + return Favorite( + userId: json['user_id'], + user: json['user'], + avatar: json['avatar'], + ); + } + + String get avatarUrl => 'https://f0ck.me/t/$avatar.webp'; +} diff --git a/lib/screens/detail_view.dart b/lib/screens/detail_view.dart index 6a6fd56..b0c5e4b 100644 --- a/lib/screens/detail_view.dart +++ b/lib/screens/detail_view.dart @@ -3,11 +3,11 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:cached_network_image/cached_network_image.dart'; import 'package:get/get.dart'; import 'package:flutter_cache_manager/flutter_cache_manager.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/smartrefreshindicator.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/fullscreen_screen.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 { final int initialId; @@ -42,6 +42,12 @@ class _DetailViewState extends State { }); } + @override + void dispose() { + _pageController?.dispose(); + super.dispose(); + } + Future _setupInitialView() async { bool itemExists = controller.mediaItems.any( (media) => media.id == widget.initialId, @@ -54,15 +60,16 @@ class _DetailViewState extends State { } void _initializePageController() { - _currentPage = controller.mediaItems.indexWhere( + final page = controller.mediaItems.indexWhere( (media) => media.id == widget.initialId, ); - if (_currentPage < 0) _currentPage = 0; - _pageController = PageController(initialPage: _currentPage) - ..addListener(() { - setState(() => _currentPage = _pageController!.page!.round()); - }); - setState(() {}); + setState(() { + _currentPage = page < 0 ? 0 : page; + _pageController = PageController(initialPage: _currentPage) + ..addListener(() { + setState(() => _currentPage = _pageController!.page!.round()); + }); + }); } Future _downloadMedia(MediaItem item) async { @@ -106,7 +113,9 @@ class _DetailViewState extends State { @override Widget build(BuildContext context) { - if (isLoading) { + final MediaService mediaService = Get.find(); + + if (isLoading || controller.mediaItems.isEmpty || _pageController == null) { return Scaffold( appBar: AppBar( title: const Text("f0ck"), @@ -135,7 +144,7 @@ class _DetailViewState extends State { label: Text(controller.tag.value!), onDeleted: () { controller.setTag(null); - Get.offAllNamed('/'); + Get.offAll(() => const MediaGrid()); }, ), ), @@ -152,10 +161,11 @@ class _DetailViewState extends State { leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () { - Navigator.canPop(context) - ? Get.back() - : Get.offAllNamed('/'); - } + Navigator.popUntil( + context, + (route) => route.settings.name == '/' || route.isFirst, + ); + }, ), actions: [ IconButton( @@ -254,57 +264,22 @@ class _DetailViewState extends State { }, child: SmartRefreshIndicator( 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( - top: false, - child: SingleChildScrollView( - child: Column( - children: [ - if (pageItem.mime.startsWith('image')) - CachedNetworkImage( - 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), - ], - ), - ), + child: DetailMediaContent( + currentPage: _currentPage, + index: index, + onTagTap: (tag) { + if (tag == 'sfw' || tag == 'nsfw') return; + controller.setTag(tag); + Get.offAllNamed('/'); + }, ), ), ); diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart new file mode 100644 index 0000000..dac23b7 --- /dev/null +++ b/lib/screens/login_screen.dart @@ -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 createState() => _LoginPageState(); +} + +class _LoginPageState extends State { + 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'), + ), + ]), + ), + ], + ), + ); + } +} diff --git a/lib/service/media_service.dart b/lib/service/media_service.dart index 014e40f..3860d3a 100644 --- a/lib/service/media_service.dart +++ b/lib/service/media_service.dart @@ -1,3 +1,4 @@ +import 'package:encrypt_shared_preferences/provider.dart'; import 'package:get/get.dart'; import 'package:f0ckapp/models/media_item.dart'; @@ -6,6 +7,9 @@ const List mediaTypes = ["alles", "image", "video", "audio"]; const List mediaModes = ["sfw", "nsfw", "untagged", "all"]; class MediaService extends GetConnect { + final EncryptedSharedPreferencesAsync storage = + EncryptedSharedPreferencesAsync.getInstance(); + Future> fetchMediaItems({ required int type, required int mode, @@ -13,6 +17,11 @@ class MediaService extends GetConnect { String? tag, int? older, }) async { + final String? token = await storage.getString('token'); + final Map headers = token != null + ? {'Authorization': 'Bearer $token'} + : {}; + final queryParameters = { 'type': type.toString(), 'mode': mode.toString(), @@ -22,9 +31,10 @@ class MediaService extends GetConnect { }; try { - final response = await get( + final Response response = await get( 'https://api.f0ck.me/items/get', query: queryParameters, + headers: headers, ); if (response.status.code == 200 && response.body is List) { final data = response.body as List; @@ -36,4 +46,62 @@ class MediaService extends GetConnect { return Future.error('Netzwerkfehler: ${e.toString()}'); } } + + Future?> 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 fetchItem(int itemId) async { + final String? token = await storage.getString('token'); + final Map headers = token != null + ? {'Authorization': 'Bearer $token'} + : {}; + + try { + final Response 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; + } + } } diff --git a/lib/widgets/actiontag.dart b/lib/widgets/actiontag.dart new file mode 100644 index 0000000..ff746e8 --- /dev/null +++ b/lib/widgets/actiontag.dart @@ -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, + ), + ), + ), + ); + } +} diff --git a/lib/widgets/detailmediacontent.dart b/lib/widgets/detailmediacontent.dart new file mode 100644 index 0000000..fd2d9fd --- /dev/null +++ b/lib/widgets/detailmediacontent.dart @@ -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? 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((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, + ), + ], + ); + } +} diff --git a/lib/widgets/end_drawer.dart b/lib/widgets/end_drawer.dart index ea82c6c..f0580f6 100644 --- a/lib/widgets/end_drawer.dart +++ b/lib/widgets/end_drawer.dart @@ -2,6 +2,8 @@ import 'package:flutter/material.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/controller/theme_controller.dart'; import 'package:f0ckapp/utils/appversion.dart'; @@ -9,81 +11,93 @@ import 'package:f0ckapp/utils/appversion.dart'; class EndDrawer extends StatelessWidget { const EndDrawer({super.key}); - void _showMsg(String message, BuildContext context) { - ScaffoldMessenger.of(context) - ..removeCurrentSnackBar() - ..showSnackBar(SnackBar(content: Text(message))); + void _showMsg(String message, {String title = ''}) { + Get + ..closeAllSnackbars() + ..snackbar(message, title, snackPosition: SnackPosition.BOTTOM); } @override Widget build(BuildContext context) { final ThemeController themeController = Get.find(); + final AuthController authController = Get.find(); return Drawer( child: ListView( padding: EdgeInsets.zero, children: [ - DrawerHeader( - decoration: const BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/images/menu.webp'), - fit: BoxFit.cover, - alignment: Alignment.topCenter, - ), - ), - child: null, - ), - /*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'), - ), - ], + Obx(() { + if (authController.token.value != null && + authController.avatarUrl.value != null) { + return DrawerHeader( + decoration: BoxDecoration( + image: DecorationImage( + image: NetworkImage(authController.avatarUrl.value!), + fit: BoxFit.cover, + alignment: Alignment.topCenter, ), ), - ], - ),*/ + 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( title: const Text('Theme'), children: [ @@ -121,7 +135,7 @@ class EndDrawer extends StatelessWidget { title: Text('v${AppVersion.version}'), onTap: () { Navigator.pop(context); - _showMsg('jooong lass das, hier ist nichts', context); + _showMsg('jooong lass das, hier ist nichts'); }, ), ], diff --git a/lib/widgets/favorite_avatars.dart b/lib/widgets/favorite_avatars.dart new file mode 100644 index 0000000..10508a2 --- /dev/null +++ b/lib/widgets/favorite_avatars.dart @@ -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, + ), + ); + }), + ], + ); + } +} diff --git a/lib/widgets/video_widget.dart b/lib/widgets/video_widget.dart index bc471c2..696b478 100644 --- a/lib/widgets/video_widget.dart +++ b/lib/widgets/video_widget.dart @@ -1,14 +1,14 @@ import 'dart:async'; -import 'package:f0ckapp/controller/media_controller.dart'; -import 'package:f0ckapp/models/media_item.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_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:get/get.dart'; class VideoWidget extends StatefulWidget { final MediaItem details; diff --git a/pubspec.yaml b/pubspec.yaml index 5ddc61a..c8c7ccd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 # 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. -version: 1.3.2+58 +version: 1.3.3+59 environment: sdk: ^3.9.0-100.2.beta @@ -63,6 +63,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/images/ + - assets/images/tags/ - assets/i18n/ - pubspec.yaml # - images/a_dot_burr.jpeg