Browse Source

Referral: integrate the APIs

Neeraj Gupta 2 years ago
parent
commit
8e9eebe2b4

+ 2 - 0
lib/main.dart

@@ -28,6 +28,7 @@ import "package:photos/services/object_detection/object_detection_service.dart";
 import 'package:photos/services/push_service.dart';
 import 'package:photos/services/push_service.dart';
 import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/services/remote_sync_service.dart';
 import 'package:photos/services/search_service.dart';
 import 'package:photos/services/search_service.dart';
+import "package:photos/services/storage_bonus_service.dart";
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/sync_service.dart';
 import 'package:photos/services/trash_sync_service.dart';
 import 'package:photos/services/trash_sync_service.dart';
 import 'package:photos/services/update_service.dart';
 import 'package:photos/services/update_service.dart';
@@ -153,6 +154,7 @@ Future<void> _init(bool isBackground, {String via = ''}) async {
   LocalSettings.instance.init(preferences);
   LocalSettings.instance.init(preferences);
   LocalFileUpdateService.instance.init(preferences);
   LocalFileUpdateService.instance.init(preferences);
   SearchService.instance.init();
   SearchService.instance.init();
+  StorageBonusService.instance.init(preferences);
   if (Platform.isIOS) {
   if (Platform.isIOS) {
     PushService.instance.init().then((_) {
     PushService.instance.init().then((_) {
       FirebaseMessaging.onBackgroundMessage(
       FirebaseMessaging.onBackgroundMessage(

+ 4 - 0
lib/models/api/storage_bonus/storage_bonus.dart

@@ -4,6 +4,7 @@ class ReferralView {
   bool enableApplyCode;
   bool enableApplyCode;
   bool isFamilyMember;
   bool isFamilyMember;
   bool hasAppliedCode;
   bool hasAppliedCode;
+  int claimedStorage;
 
 
   ReferralView({
   ReferralView({
     required this.planInfo,
     required this.planInfo,
@@ -11,6 +12,7 @@ class ReferralView {
     required this.enableApplyCode,
     required this.enableApplyCode,
     required this.isFamilyMember,
     required this.isFamilyMember,
     required this.hasAppliedCode,
     required this.hasAppliedCode,
+    required this.claimedStorage,
   });
   });
 
 
   factory ReferralView.fromJson(Map<String, dynamic> json) => ReferralView(
   factory ReferralView.fromJson(Map<String, dynamic> json) => ReferralView(
@@ -19,6 +21,7 @@ class ReferralView {
         enableApplyCode: json["enableApplyCode"],
         enableApplyCode: json["enableApplyCode"],
         isFamilyMember: json["isFamilyMember"],
         isFamilyMember: json["isFamilyMember"],
         hasAppliedCode: json["hasAppliedCode"],
         hasAppliedCode: json["hasAppliedCode"],
+        claimedStorage: json["claimedStorage"],
       );
       );
 
 
   Map<String, dynamic> toJson() => {
   Map<String, dynamic> toJson() => {
@@ -27,6 +30,7 @@ class ReferralView {
         "enableApplyCode": enableApplyCode,
         "enableApplyCode": enableApplyCode,
         "isFamilyMember": isFamilyMember,
         "isFamilyMember": isFamilyMember,
         "hasAppliedCode": hasAppliedCode,
         "hasAppliedCode": hasAppliedCode,
+        "claimedStorage": claimedStorage,
       };
       };
 }
 }
 
 

+ 5 - 0
lib/services/storage_bonus_service.dart

@@ -15,4 +15,9 @@ class StorageBonusService {
 
 
   static StorageBonusService instance =
   static StorageBonusService instance =
       StorageBonusService._privateConstructor();
       StorageBonusService._privateConstructor();
+
+  // getter for gateway
+  StorageBonusGateway getGateway() {
+    return gateway;
+  }
 }
 }

+ 221 - 167
lib/ui/growth/referral_screen.dart

@@ -1,6 +1,12 @@
 import "package:dotted_border/dotted_border.dart";
 import "package:dotted_border/dotted_border.dart";
 import "package:flutter/material.dart";
 import "package:flutter/material.dart";
+import "package:photos/models/api/storage_bonus/storage_bonus.dart";
+import "package:photos/models/user_details.dart";
+import "package:photos/services/storage_bonus_service.dart";
+import "package:photos/services/user_service.dart";
 import "package:photos/theme/ente_theme.dart";
 import "package:photos/theme/ente_theme.dart";
+import "package:photos/ui/common/loading_widget.dart";
+import "package:photos/ui/common/web_page.dart";
 import "package:photos/ui/components/captioned_text_widget.dart";
 import "package:photos/ui/components/captioned_text_widget.dart";
 import "package:photos/ui/components/divider_widget.dart";
 import "package:photos/ui/components/divider_widget.dart";
 import "package:photos/ui/components/icon_button_widget.dart";
 import "package:photos/ui/components/icon_button_widget.dart";
@@ -8,7 +14,8 @@ import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
 import "package:photos/ui/components/title_bar_title_widget.dart";
 import "package:photos/ui/components/title_bar_title_widget.dart";
 import "package:photos/ui/components/title_bar_widget.dart";
 import "package:photos/ui/components/title_bar_widget.dart";
 import "package:photos/ui/growth/apply_code_screen.dart";
 import "package:photos/ui/growth/apply_code_screen.dart";
-import "package:photos/ui/tools/debug/app_storage_viewer.dart";
+import "package:photos/ui/growth/storage_details_screen.dart";
+import "package:photos/utils/data_util.dart";
 import "package:photos/utils/navigation_util.dart";
 import "package:photos/utils/navigation_util.dart";
 
 
 class ReferralScreen extends StatefulWidget {
 class ReferralScreen extends StatefulWidget {
@@ -20,6 +27,7 @@ class ReferralScreen extends StatefulWidget {
 
 
 class _ReferralScreenState extends State<ReferralScreen> {
 class _ReferralScreenState extends State<ReferralScreen> {
   bool canApplyCode = true;
   bool canApplyCode = true;
+
   @override
   @override
   void initState() {
   void initState() {
     super.initState();
     super.initState();
@@ -27,8 +35,6 @@ class _ReferralScreenState extends State<ReferralScreen> {
 
 
   @override
   @override
   Widget build(BuildContext context) {
   Widget build(BuildContext context) {
-    final colorScheme = getEnteColorScheme(context);
-    final textStyle = getEnteTextTheme(context);
     return Scaffold(
     return Scaffold(
       body: CustomScrollView(
       body: CustomScrollView(
         primary: false,
         primary: false,
@@ -37,7 +43,7 @@ class _ReferralScreenState extends State<ReferralScreen> {
             flexibleSpaceTitle: const TitleBarTitleWidget(
             flexibleSpaceTitle: const TitleBarTitleWidget(
               title: "Claim free storage",
               title: "Claim free storage",
             ),
             ),
-            flexibleSpaceCaption: "Invite friends to claim free storage",
+            flexibleSpaceCaption: "Invite your friends",
             actionIcons: [
             actionIcons: [
               IconButtonWidget(
               IconButtonWidget(
                 icon: Icons.close_outlined,
                 icon: Icons.close_outlined,
@@ -53,169 +59,22 @@ class _ReferralScreenState extends State<ReferralScreen> {
             delegate: SliverChildBuilderDelegate(
             delegate: SliverChildBuilderDelegate(
               (delegateBuildContext, index) {
               (delegateBuildContext, index) {
                 return Padding(
                 return Padding(
-                  padding: const EdgeInsets.symmetric(horizontal: 16),
-                  child: Padding(
-                    padding: const EdgeInsets.symmetric(vertical: 20),
-                    child: Column(
-                      mainAxisSize: MainAxisSize.min,
-                      children: [
-                        Column(
-                          crossAxisAlignment: CrossAxisAlignment.start,
-                          children: [
-                            // Container with 8 border radius and red color
-                            Container(
-                              width: double.infinity,
-                              decoration: BoxDecoration(
-                                border: Border.all(
-                                  color: colorScheme.strokeFaint,
-                                  width: 1,
-                                ),
-                                borderRadius: BorderRadius.circular(8),
-                              ),
-                              child: Padding(
-                                padding: const EdgeInsets.symmetric(
-                                  vertical: 12,
-                                  horizontal: 12,
-                                ),
-                                child: Column(
-                                  crossAxisAlignment: CrossAxisAlignment.start,
-                                  children: [
-                                    const Text(
-                                      "1. Give this code to your "
-                                      "friends",
-                                    ),
-                                    const SizedBox(height: 12),
-                                    Center(
-                                      child: DottedBorder(
-                                        color: colorScheme.strokeMuted,
-                                        //color of dotted/dash line
-                                        strokeWidth: 1,
-                                        //thickness of dash/dots
-                                        dashPattern: const [6, 6],
-                                        radius: const Radius.circular(8),
-                                        child: Padding(
-                                          padding: const EdgeInsets.only(
-                                            left: 26.0,
-                                            top: 14,
-                                            right: 12,
-                                            bottom: 14,
-                                          ),
-                                          child: Row(
-                                            mainAxisSize: MainAxisSize.min,
-                                            children: [
-                                              Text(
-                                                "AX17D9EB",
-                                                style:
-                                                    textStyle.bodyBold.copyWith(
-                                                  color: colorScheme.primary700,
-                                                ),
-                                              ),
-                                              const SizedBox(width: 12),
-                                              Icon(
-                                                Icons.adaptive.share,
-                                                size: 22,
-                                                color: colorScheme.strokeMuted,
-                                              )
-                                            ],
-                                          ),
-                                        ),
-                                      ),
-                                    ),
-                                    const SizedBox(height: 12),
-                                    const Text(
-                                      "2. They sign up for a paid plan",
-                                    ),
-                                    const SizedBox(height: 12),
-                                    const Text(
-                                      "3. Both of you get 10 GB* free",
-                                    ),
-                                  ],
-                                ),
-                              ),
-                            ),
-                            const SizedBox(height: 4),
-                            Text(
-                              "* You can at max double your storage",
-                              style: textStyle.mini.copyWith(
-                                color: colorScheme.textMuted,
-                              ),
-                              textAlign: TextAlign.left,
-                            ),
-                            const SizedBox(height: 24),
-                            canApplyCode
-                                ? MenuItemWidget(
-                                    captionedTextWidget:
-                                        const CaptionedTextWidget(
-                                      title: "Apply code",
-                                    ),
-                                    menuItemColor: colorScheme.fillFaint,
-                                    trailingWidget: Icon(
-                                      Icons.chevron_right_outlined,
-                                      color: colorScheme.strokeBase,
-                                    ),
-                                    singleBorderRadius: 8,
-                                    alignCaptionedTextToLeft: true,
-                                    isBottomBorderRadiusRemoved: true,
-                                    onTap: () async {
-                                      routeToPage(
-                                          context, const ApplyCodeScreen());
-                                    },
-                                  )
-                                : const SizedBox.shrink(),
-                            canApplyCode
-                                ? DividerWidget(
-                                    dividerType: DividerType.menu,
-                                    bgColor: colorScheme.fillFaint,
-                                  )
-                                : const SizedBox.shrink(),
-                            MenuItemWidget(
-                              captionedTextWidget: const CaptionedTextWidget(
-                                title: "FAQ",
-                              ),
-                              menuItemColor: colorScheme.fillFaint,
-                              trailingWidget: Icon(
-                                Icons.chevron_right_outlined,
-                                color: colorScheme.strokeBase,
-                              ),
-                              singleBorderRadius: 8,
-                              isTopBorderRadiusRemoved: canApplyCode,
-                              alignCaptionedTextToLeft: true,
-                              onTap: () async {
-                                routeToPage(context, const AppStorageViewer());
-                              },
-                            ),
-                            const SizedBox(height: 24),
-                            Padding(
-                              padding: const EdgeInsets.symmetric(
-                                horizontal: 8.0,
-                                vertical: 6.0,
-                              ),
-                              child: Text(
-                                "You have claimed 0 GB so far",
-                                style: textStyle.small.copyWith(
-                                  color: colorScheme.textMuted,
-                                ),
-                              ),
-                            ),
-                            MenuItemWidget(
-                              captionedTextWidget: const CaptionedTextWidget(
-                                title: "Details",
-                              ),
-                              menuItemColor: colorScheme.fillFaint,
-                              trailingWidget: Icon(
-                                Icons.chevron_right_outlined,
-                                color: colorScheme.strokeBase,
-                              ),
-                              singleBorderRadius: 8,
-                              alignCaptionedTextToLeft: true,
-                              onTap: () async {
-                                routeToPage(context, const AppStorageViewer());
-                              },
-                            ),
-                          ],
-                        ),
-                      ],
-                    ),
+                  padding:
+                      const EdgeInsets.symmetric(horizontal: 16, vertical: 20),
+                  child: FutureBuilder<ReferralView>(
+                    future: StorageBonusService.instance
+                        .getGateway()
+                        .getReferralView(),
+                    builder: (context, snapshot) {
+                      if (snapshot.hasData) {
+                        return ReferralWidget(
+                          snapshot.data!,
+                          UserService.instance.getCachedUserDetails()!,
+                        );
+                      } else {
+                        return const EnteLoadingWidget();
+                      }
+                    },
                   ),
                   ),
                 );
                 );
               },
               },
@@ -227,3 +86,198 @@ class _ReferralScreenState extends State<ReferralScreen> {
     );
     );
   }
   }
 }
 }
+
+class ReferralWidget extends StatelessWidget {
+  final ReferralView referralView;
+  final UserDetails userDetails;
+
+  const ReferralWidget(this.referralView, this.userDetails, {super.key});
+
+  @override
+  Widget build(BuildContext context) {
+    final colorScheme = getEnteColorScheme(context);
+    final textStyle = getEnteTextTheme(context);
+    final bool isReferralEnabled = referralView.planInfo.isEnabled;
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        // Container with 8 border radius and red color
+        isReferralEnabled
+            ? Container(
+                width: double.infinity,
+                decoration: BoxDecoration(
+                  border: Border.all(
+                    color: colorScheme.strokeFaint,
+                    width: 1,
+                  ),
+                  borderRadius: BorderRadius.circular(8),
+                ),
+                child: Padding(
+                  padding: const EdgeInsets.symmetric(
+                    vertical: 12,
+                    horizontal: 12,
+                  ),
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.start,
+                    children: [
+                      const Text(
+                        "1. Give this code to your "
+                        "friends",
+                      ),
+                      const SizedBox(height: 12),
+                      Center(
+                        child: DottedBorder(
+                          color: colorScheme.strokeMuted,
+                          //color of dotted/dash line
+                          strokeWidth: 1,
+                          //thickness of dash/dots
+                          dashPattern: const [6, 6],
+                          radius: const Radius.circular(8),
+                          child: Padding(
+                            padding: const EdgeInsets.only(
+                              left: 26.0,
+                              top: 14,
+                              right: 12,
+                              bottom: 14,
+                            ),
+                            child: Row(
+                              mainAxisSize: MainAxisSize.min,
+                              children: [
+                                Text(
+                                  referralView.code,
+                                  style: textStyle.bodyBold.copyWith(
+                                    color: colorScheme.primary700,
+                                  ),
+                                ),
+                                const SizedBox(width: 12),
+                                Icon(
+                                  Icons.adaptive.share,
+                                  size: 22,
+                                  color: colorScheme.strokeMuted,
+                                )
+                              ],
+                            ),
+                          ),
+                        ),
+                      ),
+                      const SizedBox(height: 12),
+                      const Text(
+                        "2. They sign up for a paid plan",
+                      ),
+                      const SizedBox(height: 12),
+                      Text(
+                        "3. Both of you get ${referralView.planInfo.storageInGB} "
+                        "GB* free",
+                      ),
+                    ],
+                  ),
+                ),
+              )
+            : Padding(
+                padding: const EdgeInsets.symmetric(vertical: 48),
+                child: Center(
+                  child: Column(
+                    crossAxisAlignment: CrossAxisAlignment.center,
+                    children: [
+                      Icon(
+                        Icons.error_outline,
+                        color: colorScheme.strokeMuted,
+                      ),
+                      const SizedBox(height: 12),
+                      Text("Referrals are currently paused",
+                          style: textStyle.small
+                              .copyWith(color: colorScheme.textFaint)),
+                    ],
+                  ),
+                ),
+              ),
+        const SizedBox(height: 4),
+        isReferralEnabled
+            ? Text(
+                "* You can at max double your storage",
+                style: textStyle.mini.copyWith(
+                  color: colorScheme.textMuted,
+                ),
+                textAlign: TextAlign.left,
+              )
+            : const SizedBox.shrink(),
+        const SizedBox(height: 24),
+        referralView.enableApplyCode
+            ? MenuItemWidget(
+                captionedTextWidget: const CaptionedTextWidget(
+                  title: "Apply code",
+                ),
+                menuItemColor: colorScheme.fillFaint,
+                trailingWidget: Icon(
+                  Icons.chevron_right_outlined,
+                  color: colorScheme.strokeBase,
+                ),
+                singleBorderRadius: 8,
+                alignCaptionedTextToLeft: true,
+                isBottomBorderRadiusRemoved: true,
+                onTap: () async {
+                  routeToPage(
+                    context,
+                    const ApplyCodeScreen(),
+                  );
+                },
+              )
+            : const SizedBox.shrink(),
+        referralView.enableApplyCode
+            ? DividerWidget(
+                dividerType: DividerType.menu,
+                bgColor: colorScheme.fillFaint,
+              )
+            : const SizedBox.shrink(),
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "FAQ",
+          ),
+          menuItemColor: colorScheme.fillFaint,
+          trailingWidget: Icon(
+            Icons.chevron_right_outlined,
+            color: colorScheme.strokeBase,
+          ),
+          singleBorderRadius: 8,
+          isTopBorderRadiusRemoved: referralView.enableApplyCode,
+          alignCaptionedTextToLeft: true,
+          onTap: () async {
+            routeToPage(context, const WebPage("FAQ", "https://ente.io/faq"));
+          },
+        ),
+        const SizedBox(height: 24),
+        Padding(
+          padding: const EdgeInsets.symmetric(
+            horizontal: 8.0,
+            vertical: 6.0,
+          ),
+          child: Text(
+            "${referralView.isFamilyMember ? 'Your family has' : 'You have'} claimed "
+            "${convertBytesToAbsoluteGBs(referralView.claimedStorage)} GB so far",
+            style: textStyle.small.copyWith(
+              color: colorScheme.textMuted,
+            ),
+          ),
+        ),
+        MenuItemWidget(
+          captionedTextWidget: const CaptionedTextWidget(
+            title: "Details",
+          ),
+          menuItemColor: colorScheme.fillFaint,
+          trailingWidget: Icon(
+            Icons.chevron_right_outlined,
+            color: colorScheme.strokeBase,
+          ),
+          singleBorderRadius: 8,
+          alignCaptionedTextToLeft: true,
+          onTap: () async {
+            routeToPage(
+              context,
+              StorageDetailsScreen(referralView, userDetails),
+            );
+          },
+        ),
+      ],
+    );
+  }
+}

+ 221 - 0
lib/ui/growth/storage_details_screen.dart

@@ -0,0 +1,221 @@
+import "dart:math";
+
+import "package:flutter/material.dart";
+import "package:photos/models/api/storage_bonus/storage_bonus.dart";
+import "package:photos/models/user_details.dart";
+import "package:photos/services/storage_bonus_service.dart";
+import "package:photos/theme/ente_theme.dart";
+import "package:photos/ui/common/loading_widget.dart";
+import "package:photos/ui/components/icon_button_widget.dart";
+import "package:photos/ui/components/title_bar_title_widget.dart";
+import "package:photos/ui/components/title_bar_widget.dart";
+import "package:photos/utils/data_util.dart";
+
+class StorageDetailsScreen extends StatefulWidget {
+  final ReferralView referralView;
+  final UserDetails userDetails;
+  const StorageDetailsScreen(this.referralView, this.userDetails, {super.key});
+
+  @override
+  State<StorageDetailsScreen> createState() => _StorageDetailsScreenState();
+}
+
+class _StorageDetailsScreenState extends State<StorageDetailsScreen> {
+  bool canApplyCode = true;
+  int maxClaimableStorageBonus = 2000;
+
+  @override
+  void initState() {
+    maxClaimableStorageBonus =
+        widget.referralView.planInfo.maxClaimableStorageInGB;
+    super.initState();
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final colorScheme = getEnteColorScheme(context);
+    final textStyle = getEnteTextTheme(context);
+    return Scaffold(
+      body: CustomScrollView(
+        primary: false,
+        slivers: <Widget>[
+          TitleBarWidget(
+            flexibleSpaceTitle: const TitleBarTitleWidget(
+              title: "Claim free storage",
+            ),
+            flexibleSpaceCaption: "Details",
+            actionIcons: [
+              IconButtonWidget(
+                icon: Icons.close_outlined,
+                iconButtonType: IconButtonType.secondary,
+                onTap: () {
+                  Navigator.of(context)
+                    ..pop()
+                    ..pop()
+                    ..pop();
+                },
+              ),
+            ],
+          ),
+          SliverList(
+            delegate: SliverChildBuilderDelegate(
+              (delegateBuildContext, index) {
+                return Padding(
+                  padding: const EdgeInsets.symmetric(horizontal: 12),
+                  child: Padding(
+                    padding: const EdgeInsets.symmetric(vertical: 20),
+                    // wrap the child inside a FutureBuilder to get the
+                    // current state of the TextField
+                    child: FutureBuilder<BonusDetails>(
+                      future: StorageBonusService.instance
+                          .getGateway()
+                          .getBonusDetails(),
+                      builder: (context, snapshot) {
+                        if (snapshot.connectionState ==
+                            ConnectionState.waiting) {
+                          return const Center(
+                            child: Padding(
+                              padding: EdgeInsets.only(top: 48.0),
+                              child: EnteLoadingWidget(),
+                            ),
+                          );
+                        }
+                        if (snapshot.hasError) {
+                          debugPrint(snapshot.error.toString());
+                          return const Text("Oops, something went wrong");
+                        } else {
+                          final BonusDetails data = snapshot.data!;
+                          return Padding(
+                            padding:
+                                const EdgeInsets.symmetric(horizontal: 12.0),
+                            child: Column(
+                              crossAxisAlignment: CrossAxisAlignment.start,
+                              children: [
+                                BonusInfoSection(
+                                  sectionName: "People using your code",
+                                  leftValue: data.refUpgradeCount,
+                                  leftUnitName: "eligible",
+                                  rightValue: data.refUpgradeCount >= 0
+                                      ? data.refCount
+                                      : null,
+                                  rightUnitName: "total",
+                                  showUnit: data.refCount > 0,
+                                ),
+                                data.hasAppliedCode
+                                    ? const BonusInfoSection(
+                                        sectionName: "Code used by you",
+                                        leftValue: 1,
+                                        showUnit: false,
+                                      )
+                                    : const SizedBox.shrink(),
+                                BonusInfoSection(
+                                  sectionName: "Free storage claimed",
+                                  leftValue: data.refUpgradeCount,
+                                  leftUnitName: "GB",
+                                  rightValue: maxClaimableStorageBonus,
+                                  rightUnitName: "GB",
+                                ),
+                                BonusInfoSection(
+                                  sectionName: "Free storage usable",
+                                  leftValue: min(
+                                    widget.referralView.claimedStorage,
+                                    widget.userDetails.getTotalStorage(),
+                                  ),
+                                  leftUnitName: "GB",
+                                  rightValue: convertBytesToAbsoluteGBs(
+                                      widget.userDetails.getTotalStorage()),
+                                  rightUnitName: "GB",
+                                ),
+                                const SizedBox(
+                                  height: 24,
+                                ),
+                                Text(
+                                  "Usable storage is limited by your current"
+                                  " plan, but you can claim upto "
+                                  "$maxClaimableStorageBonus GB. Excess"
+                                  " claimed storage will automatically become"
+                                  " usable when you upgrade your plan.",
+                                  style: textStyle.small
+                                      .copyWith(color: colorScheme.textMuted),
+                                )
+                              ],
+                            ),
+                          );
+                        }
+                      },
+                    ),
+                  ),
+                );
+              },
+              childCount: 1,
+            ),
+          ),
+        ],
+      ),
+    );
+  }
+}
+
+class BonusInfoSection extends StatelessWidget {
+  final String sectionName;
+  final bool showUnit;
+  final String leftUnitName;
+  final String rightUnitName;
+  final int leftValue;
+  final int? rightValue;
+
+  const BonusInfoSection({
+    super.key,
+    required this.sectionName,
+    required this.leftValue,
+    this.leftUnitName = "GB",
+    this.rightValue,
+    this.rightUnitName = "GB",
+    this.showUnit = true,
+  });
+
+  @override
+  Widget build(BuildContext context) {
+    final colorScheme = getEnteColorScheme(context);
+    final textStyle = getEnteTextTheme(context);
+    return Column(
+      crossAxisAlignment: CrossAxisAlignment.start,
+      children: [
+        Text(
+          sectionName,
+          style: textStyle.body.copyWith(
+            color: colorScheme.textMuted,
+          ),
+        ),
+        const SizedBox(height: 2),
+        RichText(
+          text: TextSpan(
+            children: [
+              TextSpan(
+                text: leftValue.toString(),
+                style: textStyle.h3,
+              ),
+              TextSpan(
+                text: showUnit ? " $leftUnitName" : "",
+                style: textStyle.large,
+              ),
+              TextSpan(
+                text: (rightValue != null && rightValue! > 0)
+                    ? " / ${rightValue.toString()}"
+                    : "",
+                style: textStyle.h3,
+              ),
+              TextSpan(
+                text: showUnit && (rightValue != null && rightValue! > 0)
+                    ? " $rightUnitName"
+                    : "",
+                style: textStyle.large,
+              ),
+            ],
+          ),
+        ),
+        const SizedBox(height: 24),
+      ],
+    );
+  }
+}

+ 1 - 0
lib/ui/settings/general_section_widget.dart

@@ -63,6 +63,7 @@ class GeneralSectionWidget extends StatelessWidget {
             routeToPage(
             routeToPage(
               context,
               context,
               const ReferralScreen(),
               const ReferralScreen(),
+              forceCustomPageRoute: true,
             );
             );
           },
           },
         ),
         ),

+ 4 - 0
lib/utils/data_util.dart

@@ -35,6 +35,10 @@ num convertBytesToGBs(int bytes) {
   return num.parse((bytes / (pow(1024, 3))).toStringAsFixed(1));
   return num.parse((bytes / (pow(1024, 3))).toStringAsFixed(1));
 }
 }
 
 
+int convertBytesToAbsoluteGBs(int bytes) {
+  return (bytes / pow(1024, 3)).round();
+}
+
 int convertBytesToMBs(int bytes) {
 int convertBytesToMBs(int bytes) {
   return (bytes / pow(1024, 2)).round();
   return (bytes / pow(1024, 2)).round();
 }
 }