diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart index e683466d484cfaaa37130d87e62ec215279d9547..ef3b802a95eb7fd06725cacc83fe8a61f479e318 100644 --- a/lib/generated/intl/messages_en.dart +++ b/lib/generated/intl/messages_en.dart @@ -347,6 +347,8 @@ class MessageLookup extends MessageLookupByLibrary { "backupSettings": MessageLookupByLibrary.simpleMessage("Backup settings"), "backupVideos": MessageLookupByLibrary.simpleMessage("Backup videos"), + "blackFridaySale": + MessageLookupByLibrary.simpleMessage("Black Friday Sale"), "blog": MessageLookupByLibrary.simpleMessage("Blog"), "cachedData": MessageLookupByLibrary.simpleMessage("Cached data"), "calculating": MessageLookupByLibrary.simpleMessage("Calculating..."), @@ -1338,6 +1340,8 @@ class MessageLookup extends MessageLookupByLibrary { "upgrade": MessageLookupByLibrary.simpleMessage("Upgrade"), "uploadingFilesToAlbum": MessageLookupByLibrary.simpleMessage("Uploading files to album..."), + "upto50OffUntil4thDec": MessageLookupByLibrary.simpleMessage( + "Upto 50% off, until 4th Dec."), "usableReferralStorageInfo": MessageLookupByLibrary.simpleMessage( "Usable storage is limited by your current plan. Excess claimed storage will automatically become usable when you upgrade your plan."), "usePublicLinksForPeopleNotOnEnte": diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart index ee0a68d7d9f37968c4f11a93bab09389babb8b69..bae3b877e62efcdb3a3394d9dec83c809035391e 100644 --- a/lib/generated/l10n.dart +++ b/lib/generated/l10n.dart @@ -7895,6 +7895,16 @@ class S { ); } + /// `Black Friday Sale` + String get blackFridaySale { + return Intl.message( + 'Black Friday Sale', + name: 'blackFridaySale', + desc: '', + args: [], + ); + } + /// `Modify your query, or try searching for` String get modifyYourQueryOrTrySearchingFor { return Intl.message( @@ -7904,6 +7914,16 @@ class S { args: [], ); } + + /// `Upto 50% off, until 4th Dec.` + String get upto50OffUntil4thDec { + return Intl.message( + 'Upto 50% off, until 4th Dec.', + name: 'upto50OffUntil4thDec', + desc: '', + args: [], + ); + } } class AppLocalizationDelegate extends LocalizationsDelegate { diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb index 4c00753b67357f27c632738ec02758ce48f612c9..cc538a1fa61610077801e0696f8a3f9d2339e243 100644 --- a/lib/l10n/intl_en.arb +++ b/lib/l10n/intl_en.arb @@ -1122,5 +1122,7 @@ "addOns": "Add-ons", "addOnPageSubtitle": "Details of add-ons", "yourMap": "Your map", - "modifyYourQueryOrTrySearchingFor": "Modify your query, or try searching for" -} \ No newline at end of file + "modifyYourQueryOrTrySearchingFor": "Modify your query, or try searching for", + "blackFridaySale": "Black Friday Sale", + "upto50OffUntil4thDec": "Upto 50% off, until 4th Dec." +} diff --git a/lib/models/user_details.dart b/lib/models/user_details.dart index ef1fdbbd7607ff5a41ba5c979ec6cc207800968c..f107a2e6b053050c5fb64573c54973fd597fb432 100644 --- a/lib/models/user_details.dart +++ b/lib/models/user_details.dart @@ -33,6 +33,10 @@ class UserDetails { return familyData?.members?.isNotEmpty ?? false; } + bool hasPaidAddon() { + return bonusData?.getAddOnBonuses().isNotEmpty ?? false; + } + bool isFamilyAdmin() { assert(isPartOfFamily(), "verify user is part of family before calling"); final FamilyMember currentUserMember = familyData!.members! diff --git a/lib/services/billing_service.dart b/lib/services/billing_service.dart index 24ec308fbd507928c31c61534c35c66d1d7b0060..6891089e39a347d0c7441dc1b4db3c257ccc4e38 100644 --- a/lib/services/billing_service.dart +++ b/lib/services/billing_service.dart @@ -166,7 +166,8 @@ class BillingService { BuildContext context, UserDetails userDetails, ) async { - if (userDetails.subscription.productID == freeProductID) { + if (userDetails.subscription.productID == freeProductID && + !userDetails.hasPaidAddon()) { await showErrorDialog( context, S.of(context).familyPlans, diff --git a/lib/ui/components/notification_widget.dart b/lib/ui/components/notification_widget.dart index 6b069123829d2669098d832360652252d175371c..6779a58fae013145f8d293280eaf2b8b45201349 100644 --- a/lib/ui/components/notification_widget.dart +++ b/lib/ui/components/notification_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import "package:flutter_animate/flutter_animate.dart"; import "package:photos/ente_theme_data.dart"; import 'package:photos/theme/colors.dart'; import "package:photos/theme/ente_theme.dart"; @@ -20,6 +21,7 @@ class NotificationWidget extends StatelessWidget { final String? subText; final GestureTapCallback onTap; final NotificationType type; + final bool isBlackFriday; const NotificationWidget({ Key? key, @@ -27,6 +29,7 @@ class NotificationWidget extends StatelessWidget { required this.actionIcon, required this.text, required this.onTap, + this.isBlackFriday = false, this.subText, this.type = NotificationType.warning, }) : super(key: key); @@ -89,11 +92,34 @@ class NotificationWidget extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Icon( - startIcon, - size: 36, - color: strokeColorScheme.strokeBase, - ), + isBlackFriday + ? Icon( + startIcon, + size: 36, + color: strokeColorScheme.strokeBase, + ) + .animate( + onPlay: (controller) => + controller.repeat(reverse: true), + delay: 2000.ms, + ) + .shake( + duration: 500.ms, + hz: 6, + delay: 1600.ms, + ) + .scale( + duration: 500.ms, + begin: const Offset(0.9, 0.9), + end: const Offset(1.1, 1.1), + delay: 1600.ms, + // curve: Curves.easeInOut, + ) + : Icon( + startIcon, + size: 36, + color: strokeColorScheme.strokeBase, + ), const SizedBox(width: 12), Expanded( child: Column( diff --git a/lib/ui/payment/stripe_subscription_page.dart b/lib/ui/payment/stripe_subscription_page.dart index 506e597dabab52fc5df9e39206b73aea6b01fd83..54a551b7d7e4380ce8f5fe33466b813ec01ec836 100644 --- a/lib/ui/payment/stripe_subscription_page.dart +++ b/lib/ui/payment/stripe_subscription_page.dart @@ -211,10 +211,12 @@ class _StripeSubscriptionPageState extends State { widgets.add(_showSubscriptionToggle()); if (_hasActiveSubscription) { - widgets.add(ValidityWidget( - currentSubscription: _currentSubscription, - bonusData: _userDetails.bonusData, - )); + widgets.add( + ValidityWidget( + currentSubscription: _currentSubscription, + bonusData: _userDetails.bonusData, + ), + ); } if (_currentSubscription!.productID == freeProductID) { diff --git a/lib/ui/settings_page.dart b/lib/ui/settings_page.dart index da99ad32036788887babf875c6d249e313ff7404..044a70975a0e46b8f5bc14918e56c53a0b3ca30b 100644 --- a/lib/ui/settings_page.dart +++ b/lib/ui/settings_page.dart @@ -9,6 +9,7 @@ import 'package:photos/events/opened_settings_event.dart'; import "package:photos/generated/l10n.dart"; import 'package:photos/services/feature_flag_service.dart'; import "package:photos/services/storage_bonus_service.dart"; +import "package:photos/services/user_service.dart"; import 'package:photos/theme/colors.dart'; import 'package:photos/theme/ente_theme.dart'; import "package:photos/ui/components/notification_widget.dart"; @@ -28,6 +29,7 @@ import 'package:photos/ui/settings/support_section_widget.dart'; import 'package:photos/ui/settings/theme_switch_widget.dart'; import "package:photos/ui/sharing/verify_identity_dialog.dart"; import "package:photos/utils/navigation_util.dart"; +import "package:url_launcher/url_launcher_string.dart"; class SettingsPage extends StatelessWidget { final ValueNotifier emailNotifier; @@ -84,23 +86,42 @@ class SettingsPage extends StatelessWidget { const sectionSpacing = SizedBox(height: 8); contents.add(const SizedBox(height: 8)); if (hasLoggedIn) { + final shouldShowBFBanner = shouldShowBfBanner(); + final showStorageBonusBanner = + StorageBonusService.instance.shouldShowStorageBonus(); contents.addAll([ const StorageCardWidget(), - StorageBonusService.instance.shouldShowStorageBonus() + (shouldShowBFBanner || showStorageBonusBanner) ? RepaintBoundary( child: Padding( padding: const EdgeInsets.symmetric(vertical: 8.0), - child: NotificationWidget( - startIcon: Icons.auto_awesome, - actionIcon: Icons.arrow_forward_outlined, - text: S.of(context).doubleYourStorage, - subText: S.of(context).referFriendsAnd2xYourPlan, - type: NotificationType.goldenBanner, - onTap: () async { - StorageBonusService.instance.markStorageBonusAsDone(); - routeToPage(context, const ReferralScreen()); - }, - ), + child: shouldShowBFBanner + ? NotificationWidget( + isBlackFriday: true, + startIcon: Icons.celebration, + actionIcon: Icons.arrow_forward_outlined, + text: S.of(context).blackFridaySale, + subText: S.of(context).upto50OffUntil4thDec, + type: NotificationType.goldenBanner, + onTap: () async { + launchUrlString( + "https://ente.io/blackfriday", + mode: LaunchMode.platformDefault, + ); + }, + ) + : NotificationWidget( + startIcon: Icons.auto_awesome, + actionIcon: Icons.arrow_forward_outlined, + text: S.of(context).doubleYourStorage, + subText: S.of(context).referFriendsAnd2xYourPlan, + type: NotificationType.goldenBanner, + onTap: () async { + StorageBonusService.instance + .markStorageBonusAsDone(); + routeToPage(context, const ReferralScreen()); + }, + ), ).animate(onPlay: (controller) => controller.repeat()).shimmer( duration: 1000.ms, delay: 3200.ms, @@ -167,6 +188,23 @@ class SettingsPage extends StatelessWidget { ); } + bool shouldShowBfBanner() { + if (!Platform.isAndroid && !kDebugMode) { + return false; + } + // if date is after 5th of December 2023, 00:00:00, hide banner + if (DateTime.now().isAfter(DateTime(2023, 12, 5))) { + return false; + } + // if coupon is already applied, can hide the banner + return (UserService.instance + .getCachedUserDetails() + ?.bonusData + ?.getAddOnBonuses() + .isEmpty ?? + true); + } + Future _showVerifyIdentityDialog(BuildContext context) async { await showDialog( context: context, diff --git a/pubspec.yaml b/pubspec.yaml index 315b70db657de434c23fb3ddb6b347f61488210d..9d2349252df8cf1839ffddbd6ccb6519f6b7b49b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ description: ente photos application # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.7.108+508 +version: 0.7.116+516 environment: sdk: ">=3.0.0 <4.0.0"