From 02ffe9866d52318238c20dd62ab531841148e121 Mon Sep 17 00:00:00 2001
From: LittleSheep <littlesheep.code@hotmail.com>
Date: Sat, 15 Mar 2025 22:53:27 +0800
Subject: [PATCH] :sparkles: Unconfirmed account indicator

---
 assets/translations/en-US.json |  7 +++-
 assets/translations/zh-CN.json |  7 +++-
 assets/translations/zh-HK.json |  7 +++-
 assets/translations/zh-TW.json |  7 +++-
 lib/screens/home.dart          | 60 +++++++++++++++++++++++++++++++++-
 5 files changed, 83 insertions(+), 5 deletions(-)

diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json
index 55be92b..89fd949 100644
--- a/assets/translations/en-US.json
+++ b/assets/translations/en-US.json
@@ -814,5 +814,10 @@
   "authTicketCreatedAt": "Issued at {}",
   "authTicketExpiredAt": "Expired at {}",
   "authTicketLastGrantAt": "Last granted at {}",
-  "authTicketCurrent": "Current"
+  "authTicketCurrent": "Current",
+  "accountUnconfirmedTitle": "Unconfirmed Account",
+  "accountUnconfirmedSubtitle": "Your account is unconfirmed, which will make most features unavailable and your account will be destroyed in 24 hours. You should receive an email in your inbox with a confirmation link.",
+  "accountUnconfirmedUnreceived": "Didn't receive the email?",
+  "accountUnconfirmedResend": "Resend one",
+  "accountUnconfirmedResendSuccessful": "Email has been resent, you can resend it again in 60 minutes."
 }
diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json
index cd0353d..56cbb20 100644
--- a/assets/translations/zh-CN.json
+++ b/assets/translations/zh-CN.json
@@ -814,5 +814,10 @@
   "authTicketCreatedAt": "签发于 {}",
   "authTicketExpiredAt": "到期于 {}",
   "authTicketLastGrantAt": "上次刷新于 {}",
-  "authTicketCurrent": "当前会话"
+  "authTicketCurrent": "当前会话",
+  "accountUnconfirmedTitle": "尚未未确认账户",
+  "accountUnconfirmedSubtitle": "您的账户尚未确认,这会导致大部分功能不可用,并且您的帐户将在 24 小时后自毁。您绑定的邮箱的收件箱应该会有一封邮件指引您确认您的帐户。",
+  "accountUnconfirmedUnreceived": "未收到邮件?",
+  "accountUnconfirmedResend": "重新发送一封",
+  "accountUnconfirmedResendSuccessful": "邮件已重新发送,你可以在 60 分钟后再重发一封。"
 }
diff --git a/assets/translations/zh-HK.json b/assets/translations/zh-HK.json
index a61d579..cbe2629 100644
--- a/assets/translations/zh-HK.json
+++ b/assets/translations/zh-HK.json
@@ -814,5 +814,10 @@
   "authTicketCreatedAt": "簽發於 {}",
   "authTicketExpiredAt": "到期於 {}",
   "authTicketLastGrantAt": "上次刷新於 {}",
-  "authTicketCurrent": "當前會話"
+  "authTicketCurrent": "當前會話",
+  "accountUnconfirmedTitle": "尚未未確認賬户",
+  "accountUnconfirmedSubtitle": "您的賬户尚未確認,這會導致大部分功能不可用,並且您的帳户將在 24 小時後自毀。您綁定的郵箱的收件箱應該會有一封郵件指引您確認您的帳户。",
+  "accountUnconfirmedUnreceived": "未收到郵件?",
+  "accountUnconfirmedResend": "重新發送一封",
+  "accountUnconfirmedResendSuccessful": "郵件已重新發送,你可以在 60 分鐘後再重發一封。"
 }
diff --git a/assets/translations/zh-TW.json b/assets/translations/zh-TW.json
index eaa07d2..7c2a41e 100644
--- a/assets/translations/zh-TW.json
+++ b/assets/translations/zh-TW.json
@@ -814,5 +814,10 @@
   "authTicketCreatedAt": "簽發於 {}",
   "authTicketExpiredAt": "到期於 {}",
   "authTicketLastGrantAt": "上次刷新於 {}",
-  "authTicketCurrent": "當前會話"
+  "authTicketCurrent": "當前會話",
+  "accountUnconfirmedTitle": "尚未未確認賬戶",
+  "accountUnconfirmedSubtitle": "您的賬戶尚未確認,這會導致大部分功能不可用,並且您的帳戶將在 24 小時後自毀。您綁定的郵箱的收件箱應該會有一封郵件指引您確認您的帳戶。",
+  "accountUnconfirmedUnreceived": "未收到郵件?",
+  "accountUnconfirmedResend": "重新發送一封",
+  "accountUnconfirmedResendSuccessful": "郵件已重新發送,你可以在 60 分鐘後再重發一封。"
 }
diff --git a/lib/screens/home.dart b/lib/screens/home.dart
index 4867c40..ae24345 100644
--- a/lib/screens/home.dart
+++ b/lib/screens/home.dart
@@ -99,6 +99,7 @@ class _HomeScreenState extends State<HomeScreen> {
                         right: 8,
                       ),
                     ),
+                    _HomeDashUnconfirmedWidget().padding(horizontal: 8),
                     _HomeDashSpecialDayWidget().padding(horizontal: 8),
                     StaggeredGrid.extent(
                       maxCrossAxisExtent: 280,
@@ -123,6 +124,64 @@ class _HomeScreenState extends State<HomeScreen> {
   }
 }
 
+class _HomeDashUnconfirmedWidget extends StatelessWidget {
+  const _HomeDashUnconfirmedWidget();
+
+  Future<void> _resendConfirmationEmail(BuildContext context) async {
+    try {
+      final sn = context.read<SnNetworkProvider>();
+      await sn.client.patch('/cgi/id/users/me/confirm');
+      if (!context.mounted) return;
+      context.showSnackbar('accountUnconfirmedResendSuccessful'.tr());
+    } catch (err) {
+      if (!context.mounted) return;
+      context.showErrorDialog(err);
+    }
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    final ua = context.watch<UserProvider>();
+    if (ua.user != null && ua.user!.confirmedAt == null) {
+      return SizedBox.shrink();
+    }
+
+    return Card(
+      margin: EdgeInsets.zero,
+      child: ListTile(
+        leading: const Icon(Symbols.shield),
+        title: Text('accountUnconfirmedTitle').tr(),
+        subtitle: Column(
+          crossAxisAlignment: CrossAxisAlignment.start,
+          children: [
+            Text('accountUnconfirmedSubtitle').tr(),
+            const Gap(4),
+            Row(
+              children: [
+                Text('accountUnconfirmedUnreceived').tr(),
+                const Gap(4),
+                InkWell(
+                  child: Text(
+                    'accountUnconfirmedResend',
+                    style: TextStyle(
+                      decoration: TextDecoration.underline,
+                      color: Theme.of(context).colorScheme.onSurface,
+                    ),
+                  ).tr(),
+                  onTap: () {
+                    _resendConfirmationEmail(context);
+                  },
+                ),
+              ],
+            ),
+          ],
+        ),
+        contentPadding: const EdgeInsets.symmetric(horizontal: 24),
+      ),
+    ).padding(bottom: 8);
+  }
+}
+
 class _HomeDashUpdateWidget extends StatelessWidget {
   final EdgeInsets? padding;
 
@@ -131,7 +190,6 @@ class _HomeDashUpdateWidget extends StatelessWidget {
   @override
   Widget build(BuildContext context) {
     final config = context.watch<ConfigProvider>();
-
     return ListenableBuilder(
       listenable: config,
       builder: (context, _) {