diff --git a/mobile-app/lib/l10n/app_en.arb b/mobile-app/lib/l10n/app_en.arb index 6328fa969..55f78904e 100644 --- a/mobile-app/lib/l10n/app_en.arb +++ b/mobile-app/lib/l10n/app_en.arb @@ -729,6 +729,9 @@ "daily_challenge_start": "Start the challenge", "daily_challenge_completed_semantics": "Daily challenge completed. View past challenges.", "daily_challenge_card_semantics": "Daily challenge card", + "daily_challenge_archive_card_semantics": "A year of coding challenges. View the archive.", + "daily_challenge_archive_title": "A year of coding challenges", + "daily_challenge_view_archive": "View challenge archive", "daily_challenge_month_description": "Explore the daily coding challenges for {monthYear}. Stay motivated and keep your learning streak alive!", "@daily_challenge_month_description": { "description": "daily challenges view description for a month group", @@ -781,11 +784,11 @@ } } }, - "new_daily_challenge_available": "New Daily Challenge Available! 🧩", + "new_daily_challenge_available": "Daily Coding Challenge", "@new_daily_challenge_available": { "description": "daily challenge notification title" }, - "daily_challenge_notification_body": "A fresh coding challenge is waiting for you. Ready to solve it?", + "daily_challenge_notification_body": "Today's coding challenge is ready. Can you solve it?", "@daily_challenge_notification_body": { "description": "daily challenge notification body" }, diff --git a/mobile-app/lib/l10n/app_es.arb b/mobile-app/lib/l10n/app_es.arb index 1304402c2..b58cc6763 100644 --- a/mobile-app/lib/l10n/app_es.arb +++ b/mobile-app/lib/l10n/app_es.arb @@ -650,6 +650,9 @@ "daily_challenge_start": "Start the challenge", "daily_challenge_completed_semantics": "Daily challenge completed. View past challenges.", "daily_challenge_card_semantics": "Daily challenge card", + "daily_challenge_archive_card_semantics": "A year of coding challenges. View the archive.", + "daily_challenge_archive_title": "A year of coding challenges", + "daily_challenge_view_archive": "View challenge archive", "daily_challenge_month_description": "Explore the daily coding challenges for {monthYear}. Stay motivated and keep your learning streak alive!", "@daily_challenge_month_description": { "placeholders": { @@ -691,8 +694,8 @@ } } }, - "new_daily_challenge_available": "New Daily Challenge Available! 🧩", - "daily_challenge_notification_body": "A fresh coding challenge is waiting for you. Ready to solve it?", + "new_daily_challenge_available": "Daily Coding Challenge", + "daily_challenge_notification_body": "Today's coding challenge is ready. Can you solve it?", "daily_challenge_notifications": "Daily Challenge Notifications", "daily_challenge_notifications_description": "Notifications for new daily coding challenges", "download_complete": "Download complete", diff --git a/mobile-app/lib/l10n/app_localizations.dart b/mobile-app/lib/l10n/app_localizations.dart index e9324cea2..49c5745b2 100644 --- a/mobile-app/lib/l10n/app_localizations.dart +++ b/mobile-app/lib/l10n/app_localizations.dart @@ -1205,6 +1205,24 @@ abstract class AppLocalizations { /// **'Daily challenge card'** String get daily_challenge_card_semantics; + /// No description provided for @daily_challenge_archive_card_semantics. + /// + /// In en, this message translates to: + /// **'A year of coding challenges. View the archive.'** + String get daily_challenge_archive_card_semantics; + + /// No description provided for @daily_challenge_archive_title. + /// + /// In en, this message translates to: + /// **'A year of coding challenges'** + String get daily_challenge_archive_title; + + /// No description provided for @daily_challenge_view_archive. + /// + /// In en, this message translates to: + /// **'View challenge archive'** + String get daily_challenge_view_archive; + /// daily challenges view description for a month group /// /// In en, this message translates to: @@ -1234,13 +1252,13 @@ abstract class AppLocalizations { /// daily challenge notification title /// /// In en, this message translates to: - /// **'New Daily Challenge Available! 🧩'** + /// **'Daily Coding Challenge'** String get new_daily_challenge_available; /// daily challenge notification body /// /// In en, this message translates to: - /// **'A fresh coding challenge is waiting for you. Ready to solve it?'** + /// **'Today\'s coding challenge is ready. Can you solve it?'** String get daily_challenge_notification_body; /// Android notification channel name for daily challenge notifications diff --git a/mobile-app/lib/l10n/app_localizations_en.dart b/mobile-app/lib/l10n/app_localizations_en.dart index eb54e4a79..86795fe27 100644 --- a/mobile-app/lib/l10n/app_localizations_en.dart +++ b/mobile-app/lib/l10n/app_localizations_en.dart @@ -651,6 +651,16 @@ class AppLocalizationsEn extends AppLocalizations { @override String get daily_challenge_card_semantics => 'Daily challenge card'; + @override + String get daily_challenge_archive_card_semantics => + 'A year of coding challenges. View the archive.'; + + @override + String get daily_challenge_archive_title => 'A year of coding challenges'; + + @override + String get daily_challenge_view_archive => 'View challenge archive'; + @override String daily_challenge_month_description(String monthYear) { return 'Explore the daily coding challenges for $monthYear. Stay motivated and keep your learning streak alive!'; @@ -675,12 +685,11 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get new_daily_challenge_available => - 'New Daily Challenge Available! 🧩'; + String get new_daily_challenge_available => 'Daily Coding Challenge'; @override String get daily_challenge_notification_body => - 'A fresh coding challenge is waiting for you. Ready to solve it?'; + 'Today\'s coding challenge is ready. Can you solve it?'; @override String get daily_challenge_notifications => 'Daily Challenge Notifications'; diff --git a/mobile-app/lib/l10n/app_localizations_es.dart b/mobile-app/lib/l10n/app_localizations_es.dart index 78d3ef746..039aad243 100644 --- a/mobile-app/lib/l10n/app_localizations_es.dart +++ b/mobile-app/lib/l10n/app_localizations_es.dart @@ -651,6 +651,16 @@ class AppLocalizationsEs extends AppLocalizations { @override String get daily_challenge_card_semantics => 'Daily challenge card'; + @override + String get daily_challenge_archive_card_semantics => + 'A year of coding challenges. View the archive.'; + + @override + String get daily_challenge_archive_title => 'A year of coding challenges'; + + @override + String get daily_challenge_view_archive => 'View challenge archive'; + @override String daily_challenge_month_description(String monthYear) { return 'Explore the daily coding challenges for $monthYear. Stay motivated and keep your learning streak alive!'; @@ -675,12 +685,11 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get new_daily_challenge_available => - 'New Daily Challenge Available! 🧩'; + String get new_daily_challenge_available => 'Daily Coding Challenge'; @override String get daily_challenge_notification_body => - 'A fresh coding challenge is waiting for you. Ready to solve it?'; + 'Today\'s coding challenge is ready. Can you solve it?'; @override String get daily_challenge_notifications => 'Daily Challenge Notifications'; diff --git a/mobile-app/lib/l10n/app_localizations_pt.dart b/mobile-app/lib/l10n/app_localizations_pt.dart index 8370dd554..34349f67f 100644 --- a/mobile-app/lib/l10n/app_localizations_pt.dart +++ b/mobile-app/lib/l10n/app_localizations_pt.dart @@ -651,6 +651,16 @@ class AppLocalizationsPt extends AppLocalizations { @override String get daily_challenge_card_semantics => 'Daily challenge card'; + @override + String get daily_challenge_archive_card_semantics => + 'A year of coding challenges. View the archive.'; + + @override + String get daily_challenge_archive_title => 'A year of coding challenges'; + + @override + String get daily_challenge_view_archive => 'View challenge archive'; + @override String daily_challenge_month_description(String monthYear) { return 'Explore the daily coding challenges for $monthYear. Stay motivated and keep your learning streak alive!'; @@ -675,12 +685,11 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get new_daily_challenge_available => - 'New Daily Challenge Available! 🧩'; + String get new_daily_challenge_available => 'Daily Coding Challenge'; @override String get daily_challenge_notification_body => - 'A fresh coding challenge is waiting for you. Ready to solve it?'; + 'Today\'s coding challenge is ready. Can you solve it?'; @override String get daily_challenge_notifications => 'Daily Challenge Notifications'; diff --git a/mobile-app/lib/l10n/app_pt.arb b/mobile-app/lib/l10n/app_pt.arb index 54e00c790..c919afdbb 100644 --- a/mobile-app/lib/l10n/app_pt.arb +++ b/mobile-app/lib/l10n/app_pt.arb @@ -650,6 +650,9 @@ "daily_challenge_start": "Start the challenge", "daily_challenge_completed_semantics": "Daily challenge completed. View past challenges.", "daily_challenge_card_semantics": "Daily challenge card", + "daily_challenge_archive_card_semantics": "A year of coding challenges. View the archive.", + "daily_challenge_archive_title": "A year of coding challenges", + "daily_challenge_view_archive": "View challenge archive", "daily_challenge_month_description": "Explore the daily coding challenges for {monthYear}. Stay motivated and keep your learning streak alive!", "@daily_challenge_month_description": { "placeholders": { @@ -691,8 +694,8 @@ } } }, - "new_daily_challenge_available": "New Daily Challenge Available! 🧩", - "daily_challenge_notification_body": "A fresh coding challenge is waiting for you. Ready to solve it?", + "new_daily_challenge_available": "Daily Coding Challenge", + "daily_challenge_notification_body": "Today's coding challenge is ready. Can you solve it?", "daily_challenge_notifications": "Daily Challenge Notifications", "daily_challenge_notifications_description": "Notifications for new daily coding challenges", "download_complete": "Download complete", diff --git a/mobile-app/lib/models/learn/daily_challenge_model.dart b/mobile-app/lib/models/learn/daily_challenge_model.dart index e42fff33f..d20894c46 100644 --- a/mobile-app/lib/models/learn/daily_challenge_model.dart +++ b/mobile-app/lib/models/learn/daily_challenge_model.dart @@ -90,7 +90,7 @@ class DailyChallengeLanguageData { /// Model representing a block of daily challenges for a specific month class DailyChallengeBlock { - final String monthYear; // e.g., "January 2025" + final String monthYear; // e.g., "January 2025" — used as an internal key final List challenges; final String description; @@ -100,11 +100,14 @@ class DailyChallengeBlock { required this.description, }); + // Year-agnostic display name, e.g. "January" + String get displayMonth => monthYear.split(' ').first; + // Daily challenges don't have a block or a super block. // Return a `Block` here to allow daily challenges // to reuse the code for standard challenges. Block toCurriculumBlock() { - final blockName = 'Daily Challenges $monthYear'; + final blockName = 'Daily Challenges $displayMonth'; final blockDashedName = 'daily-challenges-${monthYear.toLowerCase().replaceAll(' ', '-')}'; return Block( diff --git a/mobile-app/lib/service/learn/daily_challenge_service.dart b/mobile-app/lib/service/learn/daily_challenge_service.dart index 3ac80ec4f..f29d69940 100644 --- a/mobile-app/lib/service/learn/daily_challenge_service.dart +++ b/mobile-app/lib/service/learn/daily_challenge_service.dart @@ -40,8 +40,12 @@ class DailyChallengeService { } Future fetchChallengeByDate(String date) async { + // Convert YYYY-MM-DD to MM-DD for the year-agnostic /day/:day endpoint. + // Also accepts an already-converted MM-DD string. + final monthDay = + date.length == 10 ? date.substring(5) : date; final response = await _dio.get( - '${AuthenticationService.baseApiURL}/daily-coding-challenge/date/$date'); + '${AuthenticationService.baseApiURL}/daily-coding-challenge/day/$monthDay'); if (response.statusCode == 200) { return DailyChallenge.fromJson(response.data); diff --git a/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_view.dart b/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_view.dart index 6fd087d8f..86dffb71e 100644 --- a/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_view.dart +++ b/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_view.dart @@ -97,7 +97,7 @@ class DailyChallengeView extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(8.0), child: Text( - block.monthYear, + block.displayMonth, style: TextStyle( wordSpacing: 0, letterSpacing: 0, diff --git a/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_viewmodel.dart b/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_viewmodel.dart index b389702cd..4219a10ab 100644 --- a/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_viewmodel.dart +++ b/mobile-app/lib/ui/views/learn/daily_challenge/daily_challenge_viewmodel.dart @@ -91,7 +91,8 @@ class DailyChallengeViewModel extends BaseViewModel { final block = DailyChallengeBlock( monthYear: monthYear, challenges: monthChallenges, - description: t.daily_challenge_month_description(monthYear), + description: t.daily_challenge_month_description( + monthYear.split(' ').first), ); _blocks.add(block); @@ -132,7 +133,8 @@ class DailyChallengeViewModel extends BaseViewModel { Block block = DailyChallengeBlock( monthYear: monthYear, challenges: [challenge], - description: t.daily_challenge_month_description(monthYear), + description: t.daily_challenge_month_description( + monthYear.split(' ').first), ).toCurriculumBlock(); _navigationService.navigateTo( diff --git a/mobile-app/lib/ui/views/learn/landing/landing_view.dart b/mobile-app/lib/ui/views/learn/landing/landing_view.dart index b7813fe14..07812f391 100644 --- a/mobile-app/lib/ui/views/learn/landing/landing_view.dart +++ b/mobile-app/lib/ui/views/learn/landing/landing_view.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:freecodecamp/app/app.locator.dart'; +import 'package:freecodecamp/app/app.router.dart'; import 'package:freecodecamp/extensions/i18n_extension.dart'; import 'package:freecodecamp/models/learn/curriculum_model.dart'; import 'package:freecodecamp/models/learn/motivational_quote_model.dart'; @@ -9,6 +11,7 @@ import 'package:freecodecamp/ui/views/learn/landing/landing_viewmodel.dart'; import 'package:freecodecamp/ui/views/learn/widgets/daily_challenge_card.dart'; import 'package:freecodecamp/ui/widgets/drawer_widget/drawer_widget_view.dart'; import 'package:stacked/stacked.dart'; +import 'package:stacked_services/stacked_services.dart'; class LearnLandingView extends StatelessWidget { const LearnLandingView({super.key}); @@ -324,6 +327,79 @@ class SuperBlockButton extends StatelessWidget { } } +class DailyChallengeButton extends StatelessWidget { + const DailyChallengeButton({super.key}); + + @override + Widget build(BuildContext context) { + final navigationService = locator(); + + return Container( + padding: const EdgeInsets.symmetric( + vertical: 6, + horizontal: 4, + ), + constraints: BoxConstraints( + minHeight: 80, + ), + child: ElevatedButton( + style: ElevatedButton.styleFrom( + padding: const EdgeInsets.all(5), + backgroundColor: FccColors.gray80, + side: const BorderSide( + width: 2, + color: FccColors.gray75, + ), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(5)), + side: const BorderSide( + color: Colors.teal, + width: 2.0, + ), + ), + ), + onPressed: () { + navigationService.navigateTo(Routes.dailyChallengeView); + }, + child: Row( + children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: SizedBox( + width: 36, + height: 36, + child: Icon( + Icons.calendar_today, + color: FccColors.gray00, + size: 36, + ), + ), + ), + Expanded( + flex: 8, + child: Text( + context.t.daily_challenge_view_archive, + textAlign: TextAlign.left, + style: const TextStyle(fontSize: 20), + ), + ), + const Expanded( + flex: 2, + child: Align( + alignment: Alignment.centerRight, + child: Padding( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 12), + child: Icon(Icons.arrow_forward_ios), + ), + ), + ) + ], + ), + ), + ); + } +} + class QuoteWidget extends StatelessWidget { const QuoteWidget({ super.key, diff --git a/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart b/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart index ad4d74b18..a26570e01 100644 --- a/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart +++ b/mobile-app/lib/ui/views/learn/landing/landing_viewmodel.dart @@ -248,6 +248,19 @@ class LearnLandingViewModel extends BaseViewModel { widgetOrder.add(button); } + + // Insert daily challenge archive after core curriculum + if (stage == 'core') { + widgetOrder.add( + Text( + t.daily_challenge_archive_title, + style: headerStyle, + ), + ); + widgetOrder.add( + const DailyChallengeButton(), + ); + } } }