From 3135da5eb2f7a7105c2aa7e6df0380431bce2543 Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 9 Oct 2024 22:10:53 +0200 Subject: [PATCH 01/10] faster image transitions and fix zoom out issue --- lib/ui/widgets/cached_image.dart | 6 ++++-- lib/ui/widgets/gallery.dart | 13 +++++++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/ui/widgets/cached_image.dart b/lib/ui/widgets/cached_image.dart index eaae7176c..0efaa486c 100644 --- a/lib/ui/widgets/cached_image.dart +++ b/lib/ui/widgets/cached_image.dart @@ -10,12 +10,14 @@ class CachedImage extends CachedNetworkImage { BoxFit super.fit = BoxFit.cover, Duration super.fadeOutDuration = const Duration(milliseconds: 200), super.fadeInDuration = const Duration(milliseconds: 200), - required String placeholder, + String? placeholder, }) : super( key: ValueKey(imageUrl), cacheManager: cache.ThaliaCacheManager(), cacheKey: _getCacheKey(imageUrl), - placeholder: (_, __) => Image.asset(placeholder, fit: fit), + placeholder: placeholder == null + ? null + : (_, __) => Image.asset(placeholder, fit: fit), ); } diff --git a/lib/ui/widgets/gallery.dart b/lib/ui/widgets/gallery.dart index b47fd65b1..5907497c2 100644 --- a/lib/ui/widgets/gallery.dart +++ b/lib/ui/widgets/gallery.dart @@ -12,6 +12,7 @@ import 'package:reaxit/models.dart'; import 'package:reaxit/ui/theme.dart'; import 'package:share_plus/share_plus.dart'; import 'package:gal/gal.dart'; +import 'package:reaxit/ui/widgets/cached_image.dart'; abstract class GalleryCubit extends StateStreamableSource { Future updateLike({required bool liked, required int index}); @@ -169,9 +170,13 @@ class _GalleryState extends State child = GestureDetector( onDoubleTap: () => _likePhoto(photos, i), child: RotatedBox( - quarterTurns: photos[i].rotation ~/ 90, - child: Image.network(photos[i].full), - ), + quarterTurns: photos[i].rotation ~/ 90, + child: CachedImage( + imageUrl: photos[i].full, + fit: BoxFit.contain, + // placeholder: + // 'assets/img/photo_placeholder_${(360 - photos[i].rotation) % 360}.png'), + )), ); } else { child = const Center( @@ -181,7 +186,7 @@ class _GalleryState extends State return PhotoViewGalleryPageOptions.customChild( child: child, - minScale: PhotoViewComputedScale.contained * 0.8, + minScale: PhotoViewComputedScale.contained * 1, maxScale: PhotoViewComputedScale.covered * 2, ); }, From 9d9524b8f4b15f621520af59d4d29e1e9be8961b Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 6 Nov 2024 19:15:24 +0100 Subject: [PATCH 02/10] remove unnecessary comment --- lib/ui/widgets/gallery.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/ui/widgets/gallery.dart b/lib/ui/widgets/gallery.dart index 5907497c2..4b7232f51 100644 --- a/lib/ui/widgets/gallery.dart +++ b/lib/ui/widgets/gallery.dart @@ -174,8 +174,6 @@ class _GalleryState extends State child: CachedImage( imageUrl: photos[i].full, fit: BoxFit.contain, - // placeholder: - // 'assets/img/photo_placeholder_${(360 - photos[i].rotation) % 360}.png'), )), ); } else { From fb7ce63399c54efe6bac0f8b14de052a7db9d919 Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 13 Nov 2024 22:15:41 +0100 Subject: [PATCH 03/10] Add class for announcement --- lib/ui/screens/welcome_screen.dart | 119 ++++++++++++++++++----------- 1 file changed, 76 insertions(+), 43 deletions(-) diff --git a/lib/ui/screens/welcome_screen.dart b/lib/ui/screens/welcome_screen.dart index b5a179542..6da785229 100644 --- a/lib/ui/screens/welcome_screen.dart +++ b/lib/ui/screens/welcome_screen.dart @@ -31,49 +31,13 @@ class _WelcomeScreenState extends State { } Widget _makeAnnouncement() { - return BlocBuilder( - builder: (context, state) { - if (state.result != null) { - final me = state.result!; - return InkWell( - child: Container( - color: Theme.of(context).colorScheme.primary, - padding: EdgeInsets.all(20), - child: Row( - children: [ - Icon(Icons.campaign), - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB(20, 0, 0, 0), - child: Text( - 'You don\'t have a profile picture yet! Upload one on your profile page by clicking this banner, so that the other members know who you are. :)'))), - ], - ), - ), - onTap: () => context.pushNamed( - 'member', - pathParameters: {'memberPk': me.pk.toString()}, - extra: me, - ), - ); - } else { - return InkWell( - child: Padding( - padding: EdgeInsets.all(20), - child: Row( - children: [ - Icon(Icons.campaign), - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB(20, 0, 0, 0), - child: Text( - 'You don\'t have a profile picture yet! Upload one on your profile page by clicking this banner, so that the other members know who you are. :)'))), - ], - ), - ), - onTap: () => {}); - } - }); + return Column(children: [ + Announcement( + 'You don\'t have a profile picture yet! Upload one on your profile page by clicking this banner, so that the other members know who you are. :)', + false, + 'member'), + Announcement('This is the second announcement', true, 'calendar'), + ]); } Widget _makeSlides(List slides) { @@ -320,3 +284,72 @@ class _SlidesCarouselState extends State { ); } } + +class Announcement extends StatefulWidget { + String announcement = ''; + bool closable = false; + String location = ''; + Announcement(this.announcement, this.closable, this.location); + + @override + State createState() => _AnnouncementState(); +} + +class _AnnouncementState extends State { + void navigate(BuildContext context, String location, FullMember me) { + if (location == 'member') { + if (me != null) { + context.pushNamed( + 'member', + pathParameters: {'memberPk': me.pk.toString()}, + extra: me, + ); + } + } else { + context.goNamed(location); + // Pop the menu drawer + Navigator.of(context).pop(); + } + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + if (state.result != null) { + final me = state.result!; + return InkWell( + child: Container( + color: Theme.of(context).colorScheme.primary, + padding: EdgeInsets.all(20), + child: Row( + children: [ + Icon(Icons.campaign), + Expanded( + child: Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Text(this.widget.announcement))), + ], + ), + ), + onTap: () => this.navigate(context, this.widget.location, me), + ); + } else { + return InkWell( + child: Padding( + padding: EdgeInsets.all(20), + child: Row( + children: [ + Icon(Icons.campaign), + Expanded( + child: Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Text(this.widget.announcement))), + ], + ), + ), + onTap: () => {}); + } + }); + } +} From 7afddf2a84cbf52b7e6298f8d8454f2344553c84 Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 11 Dec 2024 20:54:26 +0100 Subject: [PATCH 04/10] Show announcements with id that is not null --- lib/api/api_repository.dart | 3 + lib/api/concrexit_api_repository.dart | 25 ++++++ lib/blocs/welcome_cubit.dart | 21 ++++- lib/models/announcement.dart | 15 ++++ lib/models/announcement.g.dart | 20 +++++ lib/ui/screens/welcome_screen.dart | 109 ++++++++++---------------- lib/ui/widgets/gallery.dart | 1 - test/mocks.mocks.dart | 11 +++ 8 files changed, 134 insertions(+), 71 deletions(-) create mode 100644 lib/models/announcement.dart create mode 100644 lib/models/announcement.g.dart diff --git a/lib/api/api_repository.dart b/lib/api/api_repository.dart index d0d180670..1e678a7fa 100644 --- a/lib/api/api_repository.dart +++ b/lib/api/api_repository.dart @@ -1,5 +1,6 @@ import 'package:reaxit/config.dart'; import 'package:reaxit/models.dart'; +import 'package:reaxit/models/announcement.dart'; import 'package:reaxit/models/thabliod.dart'; import 'package:reaxit/api/exceptions.dart'; @@ -301,6 +302,8 @@ abstract class ApiRepository { int? offset, }); + Future> getAnnouncements(); + /// Get a list of [FrontpageArticle]s. /// /// Use `limit` and `offset` for pagination. [ListResponse.count] is the diff --git a/lib/api/concrexit_api_repository.dart b/lib/api/concrexit_api_repository.dart index 3a5afcbc5..f95e1dd9a 100644 --- a/lib/api/concrexit_api_repository.dart +++ b/lib/api/concrexit_api_repository.dart @@ -10,6 +10,7 @@ import 'package:reaxit/api/exceptions.dart'; import 'package:reaxit/config.dart'; import 'package:reaxit/models.dart'; import 'package:reaxit/models/thabliod.dart'; +import 'package:reaxit/models/announcement.dart'; import 'package:sentry_flutter/sentry_flutter.dart'; class LoggingClient extends oauth2.Client { @@ -116,6 +117,10 @@ class ConcrexitApiRepository implements ApiRepository { static Map _jsonDecode(Response response) => jsonDecode(utf8.decode(response.bodyBytes)) as Map; + /// Wrapper that utf-8 decodes the body of a response to json. + static List _jsonDecodeList(Response response) => + jsonDecode(utf8.decode(response.bodyBytes)) as List; + /// A wrapper for requests that throws only [ApiException]s. /// /// Translates exceptions that can be thrown by [oauth2.Client.send()], @@ -1128,6 +1133,26 @@ class ConcrexitApiRepository implements ApiRepository { ); } + @override + Future> getAnnouncements() async { + return sandbox(() async { + final uri = _uri( + path: '/announcements/announcements/', + ); + + final response = await _handleExceptions(() => _client.get(uri)); + return await compute(_parseAnnouncements, response); + }); + } + + static List _parseAnnouncements( + Response response, + ) { + return (_jsonDecodeList(response)) + .map((json) => Announcement.fromJson(json as Map)) + .toList(); + } + @override Future registerDevice({ required String token, diff --git a/lib/blocs/welcome_cubit.dart b/lib/blocs/welcome_cubit.dart index 6f1802af5..cfa8058af 100644 --- a/lib/blocs/welcome_cubit.dart +++ b/lib/blocs/welcome_cubit.dart @@ -5,6 +5,7 @@ import 'package:meta/meta.dart'; import 'package:reaxit/api/api_repository.dart'; import 'package:reaxit/api/exceptions.dart'; import 'package:reaxit/models.dart'; +import 'package:reaxit/models/announcement.dart'; class WelcomeState extends Equatable { /// This can only be null when [isLoading] or [hasException] is true. @@ -16,6 +17,9 @@ class WelcomeState extends Equatable { /// This can only be null when [isLoading] or [hasException] is true. final List? upcomingEvents; + /// This can only be null when [isLoading] or [hasException] is true. + final List? announcements; + /// A message describing why there are no results. final String? message; @@ -24,25 +28,30 @@ class WelcomeState extends Equatable { bool get hasException => message != null; bool get hasResults => - slides != null && articles != null && upcomingEvents != null; + slides != null && + articles != null && + upcomingEvents != null && + announcements != null; @protected const WelcomeState({ required this.slides, required this.articles, required this.upcomingEvents, + required this.announcements, required this.isLoading, required this.message, }); @override List get props => - [slides, articles, upcomingEvents, message, isLoading]; + [slides, articles, upcomingEvents, announcements, message, isLoading]; WelcomeState copyWith({ List? slides, List? articles, List? upcomingEvents, + List? announcements, bool? isLoading, String? message, }) => @@ -50,6 +59,7 @@ class WelcomeState extends Equatable { slides: slides ?? this.slides, articles: articles ?? this.articles, upcomingEvents: upcomingEvents ?? this.upcomingEvents, + announcements: announcements ?? this.announcements, isLoading: isLoading ?? this.isLoading, message: message ?? this.message, ); @@ -58,10 +68,12 @@ class WelcomeState extends Equatable { required List this.slides, required List this.articles, required List this.upcomingEvents, + required List this.announcements, }) : message = null, isLoading = false; - const WelcomeState.loading({this.slides, this.articles, this.upcomingEvents}) + const WelcomeState.loading( + {this.slides, this.articles, this.upcomingEvents, this.announcements}) : message = null, isLoading = true; @@ -69,6 +81,7 @@ class WelcomeState extends Equatable { : slides = null, articles = null, upcomingEvents = null, + announcements = null, isLoading = false; } @@ -92,6 +105,7 @@ class WelcomeCubit extends Cubit { ordering: 'start', limit: 3, ); + final announcementsResponse = await api.getAnnouncements(); List events = eventsResponse.results .map((e) => e) @@ -109,6 +123,7 @@ class WelcomeCubit extends Cubit { slides: slides, articles: articlesResponse.results, upcomingEvents: events, + announcements: announcementsResponse, )); } on ApiException catch (exception) { emit(WelcomeState.failure(message: exception.message)); diff --git a/lib/models/announcement.dart b/lib/models/announcement.dart new file mode 100644 index 000000000..fb3c8cbee --- /dev/null +++ b/lib/models/announcement.dart @@ -0,0 +1,15 @@ +import 'package:json_annotation/json_annotation.dart'; + +part 'announcement.g.dart'; + +@JsonSerializable(fieldRename: FieldRename.snake) +class Announcement { + final String content; + final bool closeable; + final String icon; + + const Announcement(this.content, this.closeable, this.icon); + + factory Announcement.fromJson(Map json) => + _$AnnouncementFromJson(json); +} diff --git a/lib/models/announcement.g.dart b/lib/models/announcement.g.dart new file mode 100644 index 000000000..1b67ff95e --- /dev/null +++ b/lib/models/announcement.g.dart @@ -0,0 +1,20 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'announcement.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +Announcement _$AnnouncementFromJson(Map json) => Announcement( + json['content'] as String, + json['closeable'] as bool, + json['icon'] as String, + ); + +Map _$AnnouncementToJson(Announcement instance) => + { + 'content': instance.content, + 'closeable': instance.closeable, + 'icon': instance.icon, + }; diff --git a/lib/ui/screens/welcome_screen.dart b/lib/ui/screens/welcome_screen.dart index 6da785229..08e278f4d 100644 --- a/lib/ui/screens/welcome_screen.dart +++ b/lib/ui/screens/welcome_screen.dart @@ -10,6 +10,7 @@ import 'package:reaxit/routes.dart'; import 'package:reaxit/ui/widgets.dart'; import 'package:collection/collection.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:reaxit/models/announcement.dart'; class WelcomeScreen extends StatefulWidget { @override @@ -30,14 +31,13 @@ class _WelcomeScreenState extends State { ); } - Widget _makeAnnouncement() { - return Column(children: [ - Announcement( - 'You don\'t have a profile picture yet! Upload one on your profile page by clicking this banner, so that the other members know who you are. :)', - false, - 'member'), - Announcement('This is the second announcement', true, 'calendar'), - ]); + Widget _makeAnnouncements(List announcements) { + return AnimatedSize( + curve: Curves.ease, + duration: const Duration(milliseconds: 300), + child: announcements.isNotEmpty + ? Announcements(announcements) + : const SizedBox.shrink()); } Widget _makeSlides(List slides) { @@ -189,7 +189,8 @@ class _WelcomeScreenState extends State { key: const PageStorageKey('welcome'), physics: const AlwaysScrollableScrollPhysics(), children: [ - _makeAnnouncement(), + _makeAnnouncements(state.announcements!), + if (state.announcements!.isNotEmpty) const Divider(height: 0), _makeSlides(state.slides!), if (state.slides!.isNotEmpty) const Divider(height: 0), _makeArticles(state.articles!), @@ -285,71 +286,45 @@ class _SlidesCarouselState extends State { } } -class Announcement extends StatefulWidget { - String announcement = ''; - bool closable = false; - String location = ''; - Announcement(this.announcement, this.closable, this.location); +class Announcements extends StatefulWidget { + List announcements; + Announcements(this.announcements); @override - State createState() => _AnnouncementState(); + State createState() => _AnnouncementState(); } -class _AnnouncementState extends State { - void navigate(BuildContext context, String location, FullMember me) { - if (location == 'member') { - if (me != null) { - context.pushNamed( - 'member', - pathParameters: {'memberPk': me.pk.toString()}, - extra: me, - ); - } - } else { - context.goNamed(location); - // Pop the menu drawer - Navigator.of(context).pop(); - } +class _AnnouncementState extends State { + Widget _makeAnnouncement(Announcement announcement) { + return Container( + color: Theme.of(context).colorScheme.primary, + padding: EdgeInsets.all(20), + child: Row( + children: [ + Icon(Icons.campaign), + Expanded( + child: Padding( + padding: EdgeInsets.fromLTRB(20, 0, 0, 0), + child: Text(announcement.content))), + if (announcement.closeable) + CloseButton( + onPressed: () => setState( + () => this.widget.announcements.remove(announcement))) + ], + ), + ); } @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - if (state.result != null) { - final me = state.result!; - return InkWell( - child: Container( - color: Theme.of(context).colorScheme.primary, - padding: EdgeInsets.all(20), - child: Row( - children: [ - Icon(Icons.campaign), - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB(20, 0, 0, 0), - child: Text(this.widget.announcement))), - ], - ), - ), - onTap: () => this.navigate(context, this.widget.location, me), - ); - } else { - return InkWell( - child: Padding( - padding: EdgeInsets.all(20), - child: Row( - children: [ - Icon(Icons.campaign), - Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB(20, 0, 0, 0), - child: Text(this.widget.announcement))), - ], - ), - ), - onTap: () => {}); - } - }); + return Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + for (final article in widget.announcements) ...[ + const Divider(height: 8), + this._makeAnnouncement(article), + ] + ], + ); } } diff --git a/lib/ui/widgets/gallery.dart b/lib/ui/widgets/gallery.dart index 369da1ee5..4b7232f51 100644 --- a/lib/ui/widgets/gallery.dart +++ b/lib/ui/widgets/gallery.dart @@ -13,7 +13,6 @@ import 'package:reaxit/ui/theme.dart'; import 'package:share_plus/share_plus.dart'; import 'package:gal/gal.dart'; import 'package:reaxit/ui/widgets/cached_image.dart'; -import 'package:reaxit/ui/widgets/cached_image.dart'; abstract class GalleryCubit extends StateStreamableSource { Future updateLike({required bool liked, required int index}); diff --git a/test/mocks.mocks.dart b/test/mocks.mocks.dart index eff1d2687..7111fe7e1 100644 --- a/test/mocks.mocks.dart +++ b/test/mocks.mocks.dart @@ -14,6 +14,7 @@ import 'package:reaxit/blocs/payment_user_cubit.dart' as _i6; import 'package:reaxit/blocs/welcome_cubit.dart' as _i7; import 'package:reaxit/config.dart' as _i3; import 'package:reaxit/models.dart' as _i4; +import 'package:reaxit/models/announcement.dart' as _i12; import 'package:reaxit/models/thabliod.dart' as _i11; // ignore_for_file: type=lint @@ -1361,6 +1362,16 @@ class MockApiRepository extends _i1.Mock implements _i5.ApiRepository { )), ) as _i8.Future<_i4.ListResponse<_i4.Slide>>); + @override + _i8.Future> getAnnouncements() => (super.noSuchMethod( + Invocation.method( + #getAnnouncements, + [], + ), + returnValue: + _i8.Future>.value(<_i12.Announcement>[]), + ) as _i8.Future>); + @override _i8.Future<_i4.ListResponse<_i4.FrontpageArticle>> getFrontpageArticles({ int? limit, From 7daad242468894fb3d896d571f7429ab1d17ceb8 Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 29 Jan 2025 21:08:28 +0100 Subject: [PATCH 05/10] Add link to profile picture in welcome screen --- lib/routes.dart | 14 +++++++ lib/ui/screens/welcome_screen.dart | 62 +++++++++++++++++++++++++----- 2 files changed, 66 insertions(+), 10 deletions(-) diff --git a/lib/routes.dart b/lib/routes.dart index 36298ad62..6e5f6ee2d 100644 --- a/lib/routes.dart +++ b/lib/routes.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:go_router/go_router.dart'; import 'package:reaxit/config.dart' as config; import 'package:reaxit/models.dart'; @@ -9,6 +10,7 @@ import 'package:reaxit/ui/screens.dart'; import 'package:reaxit/ui/screens/liked_photos_screen.dart'; import 'package:reaxit/ui/screens/thabloids_screen.dart'; import 'package:reaxit/ui/widgets.dart'; +import 'package:reaxit/blocs.dart'; /// Returns true if [uri] is a deep link that can be handled by the app. bool isDeepLink(Uri uri) { @@ -39,6 +41,7 @@ final List _deepLinkRegExps = [ RegExp('^/association/societies(/[0-9]+)?/?\$'), RegExp('^/association/committees(/[0-9]+)?/?\$'), RegExp('^/association/boards/([0-9]{4}-[0-9]{4})/?\$'), + RegExp('^/user/edit-profile/') ]; final List routes = [ @@ -498,4 +501,15 @@ final List routes = [ name: 'pay', pageBuilder: (context, state) => MaterialPage(key: state.pageKey, child: PayScreen())), + GoRoute( + path: '/user/edit-profile', + name: 'profile', + pageBuilder: (context, state) => MaterialPage( + key: state.pageKey, + child: ProfileScreen( + pk: BlocProvider.of(context).state.result!.pk, + member: state.extra as ListMember?, + ), + ), + ), ]; diff --git a/lib/ui/screens/welcome_screen.dart b/lib/ui/screens/welcome_screen.dart index 08e278f4d..2cf37f9e6 100644 --- a/lib/ui/screens/welcome_screen.dart +++ b/lib/ui/screens/welcome_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; import 'package:go_router/go_router.dart'; import 'package:intl/intl.dart'; +import 'package:reaxit/api/api_repository.dart'; import 'package:reaxit/blocs.dart'; import 'package:reaxit/models.dart'; import 'package:reaxit/routes.dart'; @@ -287,8 +288,9 @@ class _SlidesCarouselState extends State { } class Announcements extends StatefulWidget { - List announcements; - Announcements(this.announcements); + final List announcements; + + const Announcements(this.announcements); @override State createState() => _AnnouncementState(); @@ -301,11 +303,51 @@ class _AnnouncementState extends State { padding: EdgeInsets.all(20), child: Row( children: [ - Icon(Icons.campaign), + Padding( + padding: EdgeInsets.fromLTRB(0, 0, 20, 0), + child: Container(child: Icon(Icons.campaign)), + ), Expanded( - child: Padding( - padding: EdgeInsets.fromLTRB(20, 0, 0, 0), - child: Text(announcement.content))), + child: HtmlWidget( + announcement.content, + customStylesBuilder: (element) { + if (element.localName == 'a') { + return { + 'color': 'white', // Change link color to red + 'text-decoration': 'none', // Remove default underline + 'font-weight': 'bold', + 'border-bottom': '2px solid white', + }; + } + return null; + }, + onTapUrl: (String url) async { + Uri uri = Uri(path: url); + String host = + RepositoryProvider.of(context).config.host; + if (uri.scheme.isEmpty) uri = uri.replace(scheme: 'https'); + if (uri.host.isEmpty) uri = uri.replace(host: host); + if (isDeepLink(uri)) { + context.push(Uri( + path: uri.path, + query: uri.query, + ).toString()); + return true; + } else { + final messenger = ScaffoldMessenger.of(context); + try { + await launchUrl(uri, mode: LaunchMode.externalApplication); + } catch (_) { + messenger.showSnackBar(SnackBar( + behavior: SnackBarBehavior.floating, + content: Text('Could not open "$url".'), + )); + } + return true; + } + }, + ), + ), if (announcement.closeable) CloseButton( onPressed: () => setState( @@ -320,10 +362,10 @@ class _AnnouncementState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ - for (final article in widget.announcements) ...[ - const Divider(height: 8), - this._makeAnnouncement(article), - ] + for (final announcement in widget.announcements) ...[ + //const Divider(height: 8), + this._makeAnnouncement(announcement), + ], ], ); } From ee1f93051e931a5809758786d13f4213882f1947 Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 12 Feb 2025 21:00:46 +0100 Subject: [PATCH 06/10] Working announcements --- android/app/build.gradle | 2 +- android/gradle/wrapper/gradle-wrapper.properties | 4 +++- lib/models/announcement.dart | 5 ++++- lib/models/announcement.g.dart | 2 ++ test/mocks.mocks.dart | 10 ++++++++++ 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 31755be99..5089588e4 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -53,7 +53,7 @@ android { } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '11' } sourceSets { diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index ceccc3a85..09523c0e5 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-all.zip diff --git a/lib/models/announcement.dart b/lib/models/announcement.dart index fb3c8cbee..39577c550 100644 --- a/lib/models/announcement.dart +++ b/lib/models/announcement.dart @@ -1,4 +1,6 @@ import 'package:json_annotation/json_annotation.dart'; +import 'package:reaxit/api/api_repository.dart'; +import 'package:reaxit/api/exceptions.dart'; part 'announcement.g.dart'; @@ -7,8 +9,9 @@ class Announcement { final String content; final bool closeable; final String icon; + final int? id; - const Announcement(this.content, this.closeable, this.icon); + const Announcement(this.content, this.closeable, this.icon, this.id); factory Announcement.fromJson(Map json) => _$AnnouncementFromJson(json); diff --git a/lib/models/announcement.g.dart b/lib/models/announcement.g.dart index 1b67ff95e..50e1bfe87 100644 --- a/lib/models/announcement.g.dart +++ b/lib/models/announcement.g.dart @@ -10,6 +10,7 @@ Announcement _$AnnouncementFromJson(Map json) => Announcement( json['content'] as String, json['closeable'] as bool, json['icon'] as String, + (json['id'] as num?)?.toInt(), ); Map _$AnnouncementToJson(Announcement instance) => @@ -17,4 +18,5 @@ Map _$AnnouncementToJson(Announcement instance) => 'content': instance.content, 'closeable': instance.closeable, 'icon': instance.icon, + 'id': instance.id, }; diff --git a/test/mocks.mocks.dart b/test/mocks.mocks.dart index 7111fe7e1..fbc922dd9 100644 --- a/test/mocks.mocks.dart +++ b/test/mocks.mocks.dart @@ -1372,6 +1372,16 @@ class MockApiRepository extends _i1.Mock implements _i5.ApiRepository { _i8.Future>.value(<_i12.Announcement>[]), ) as _i8.Future>); + @override + _i8.Future deleteAnnouncement(int? id) => (super.noSuchMethod( + Invocation.method( + #deleteAnnouncement, + [id], + ), + returnValue: _i8.Future.value(), + returnValueForMissingStub: _i8.Future.value(), + ) as _i8.Future); + @override _i8.Future<_i4.ListResponse<_i4.FrontpageArticle>> getFrontpageArticles({ int? limit, From fdb57d59161e294e5e74aa6cbae60ec595b5d766 Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 12 Feb 2025 21:18:20 +0100 Subject: [PATCH 07/10] Fix testing --- test/widget/welcome_test.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/widget/welcome_test.dart b/test/widget/welcome_test.dart index cc43f2bf5..e5cdac9c6 100644 --- a/test/widget/welcome_test.dart +++ b/test/widget/welcome_test.dart @@ -57,7 +57,8 @@ void main() { final state = WelcomeState.result( slides: const [], articles: const [], - upcomingEvents: [normalEvent, partnerEvent]); + upcomingEvents: [normalEvent, partnerEvent], + announcements: const []); final cubit = MockWelcomeCubit(); final streamController = StreamController.broadcast() From fd182185c139dc71bd0f59e7b8265c0ec277cb8a Mon Sep 17 00:00:00 2001 From: Floris Reuvers <102288378+rodepanda7@users.noreply.github.com> Date: Wed, 12 Feb 2025 21:36:02 +0100 Subject: [PATCH 08/10] Remove unused code --- test/mocks.mocks.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/test/mocks.mocks.dart b/test/mocks.mocks.dart index fbc922dd9..7111fe7e1 100644 --- a/test/mocks.mocks.dart +++ b/test/mocks.mocks.dart @@ -1372,16 +1372,6 @@ class MockApiRepository extends _i1.Mock implements _i5.ApiRepository { _i8.Future>.value(<_i12.Announcement>[]), ) as _i8.Future>); - @override - _i8.Future deleteAnnouncement(int? id) => (super.noSuchMethod( - Invocation.method( - #deleteAnnouncement, - [id], - ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); - @override _i8.Future<_i4.ListResponse<_i4.FrontpageArticle>> getFrontpageArticles({ int? limit, From d03e962824d5c319b1174555733e70b8d76588d6 Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 12 Feb 2025 22:00:49 +0100 Subject: [PATCH 09/10] Fix linting --- lib/ui/screens/welcome_screen.dart | 12 ++++++------ test/mocks.mocks.dart | 10 ---------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/lib/ui/screens/welcome_screen.dart b/lib/ui/screens/welcome_screen.dart index 2cf37f9e6..c4a584faf 100644 --- a/lib/ui/screens/welcome_screen.dart +++ b/lib/ui/screens/welcome_screen.dart @@ -300,12 +300,12 @@ class _AnnouncementState extends State { Widget _makeAnnouncement(Announcement announcement) { return Container( color: Theme.of(context).colorScheme.primary, - padding: EdgeInsets.all(20), + padding: const EdgeInsets.all(20), child: Row( children: [ - Padding( + const Padding( padding: EdgeInsets.fromLTRB(0, 0, 20, 0), - child: Container(child: Icon(Icons.campaign)), + child: Icon(Icons.campaign), ), Expanded( child: HtmlWidget( @@ -350,8 +350,8 @@ class _AnnouncementState extends State { ), if (announcement.closeable) CloseButton( - onPressed: () => setState( - () => this.widget.announcements.remove(announcement))) + onPressed: () => + setState(() => widget.announcements.remove(announcement))) ], ), ); @@ -364,7 +364,7 @@ class _AnnouncementState extends State { children: [ for (final announcement in widget.announcements) ...[ //const Divider(height: 8), - this._makeAnnouncement(announcement), + _makeAnnouncement(announcement), ], ], ); diff --git a/test/mocks.mocks.dart b/test/mocks.mocks.dart index fbc922dd9..7111fe7e1 100644 --- a/test/mocks.mocks.dart +++ b/test/mocks.mocks.dart @@ -1372,16 +1372,6 @@ class MockApiRepository extends _i1.Mock implements _i5.ApiRepository { _i8.Future>.value(<_i12.Announcement>[]), ) as _i8.Future>); - @override - _i8.Future deleteAnnouncement(int? id) => (super.noSuchMethod( - Invocation.method( - #deleteAnnouncement, - [id], - ), - returnValue: _i8.Future.value(), - returnValueForMissingStub: _i8.Future.value(), - ) as _i8.Future); - @override _i8.Future<_i4.ListResponse<_i4.FrontpageArticle>> getFrontpageArticles({ int? limit, From 838392aef7856f5831dbfbf1b47bdfa00c5342ee Mon Sep 17 00:00:00 2001 From: rodepanda7 Date: Wed, 12 Feb 2025 22:02:59 +0100 Subject: [PATCH 10/10] Fix linting --- lib/models/announcement.dart | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/models/announcement.dart b/lib/models/announcement.dart index 39577c550..05cc1a7c5 100644 --- a/lib/models/announcement.dart +++ b/lib/models/announcement.dart @@ -1,6 +1,4 @@ import 'package:json_annotation/json_annotation.dart'; -import 'package:reaxit/api/api_repository.dart'; -import 'package:reaxit/api/exceptions.dart'; part 'announcement.g.dart';