diff --git a/lib/account/account_key_value.dart b/lib/account/account_key_value.dart
index 8c2c4b701e..088b0882f9 100644
--- a/lib/account/account_key_value.dart
+++ b/lib/account/account_key_value.dart
@@ -12,20 +12,36 @@ class AccountKeyValue extends HiveKeyValue {
   static const _refreshStickerLastTime = 'refreshStickerLastTime';
   static const _primarySessionId = 'primarySessionId';
   static const _hasNewAlbum = 'hasNewAlbum';
+  static const _checkUpdateLastTime = 'checkUpdateLastTime';
+  static const _ignoredVersion = '_ignoredVersion';
 
   bool get hasSyncCircle =>
       box.get(_hasSyncCircle, defaultValue: false) as bool;
+
   set hasSyncCircle(bool value) => box.put(_hasSyncCircle, value);
 
   int get refreshStickerLastTime =>
       box.get(_refreshStickerLastTime, defaultValue: 0) as int;
+
   set refreshStickerLastTime(int value) =>
       box.put(_refreshStickerLastTime, value);
 
   String? get primarySessionId =>
       box.get(_primarySessionId, defaultValue: null) as String?;
+
   set primarySessionId(String? value) => box.put(_primarySessionId, value);
 
   bool get hasNewAlbum => box.get(_hasNewAlbum, defaultValue: false) as bool;
+
   set hasNewAlbum(bool value) => box.put(_hasNewAlbum, value);
+
+  int get checkUpdateLastTime =>
+      box.get(_checkUpdateLastTime, defaultValue: 0) as int;
+
+  set checkUpdateLastTime(int value) => box.put(_checkUpdateLastTime, value);
+
+  String? get ignoredVersion =>
+      box.get(_ignoredVersion, defaultValue: '0.0.0') as String?;
+
+  set ignoredVersion(String? value) => box.put(_ignoredVersion, value);
 }
diff --git a/lib/app.dart b/lib/app.dart
index b0eebc49e5..8e59f9b7c1 100644
--- a/lib/app.dart
+++ b/lib/app.dart
@@ -26,6 +26,8 @@ import 'ui/home/conversation/conversation_page.dart';
 import 'ui/home/home.dart';
 import 'ui/home/route/responsive_navigator_cubit.dart';
 import 'ui/landing/landing.dart';
+import 'utils/app_lifecycle.dart';
+import 'utils/auto_update_checker.dart';
 import 'utils/extension/extension.dart';
 import 'utils/hook.dart';
 import 'utils/logger.dart';
@@ -258,6 +260,20 @@ class _Home extends HookWidget {
       }
     }, [signed]);
 
+    useEffect(() {
+      void onAppStateChanged() {
+        if (isAppActive) {
+          checkUpdate(context: context);
+        }
+      }
+
+      onAppStateChanged();
+      appActiveListener.addListener(onAppStateChanged);
+      return () {
+        appActiveListener.removeListener(onAppStateChanged);
+      };
+    }, []);
+
     if (signed) {
       BlocProvider.of<ConversationListBloc>(context)
         ..limit = MediaQuery.of(context).size.height ~/
diff --git a/lib/generated/intl/messages_en.dart b/lib/generated/intl/messages_en.dart
index b77f7999d4..cf1d1253ed 100644
--- a/lib/generated/intl/messages_en.dart
+++ b/lib/generated/intl/messages_en.dart
@@ -55,21 +55,24 @@ class MessageLookup extends MessageLookupByLibrary {
 
   static String m16(name) => "Remove ${name}";
 
-  static String m17(name) => "Do you want to delete ${name} circle?";
+  static String m17(newVersion, current) =>
+      "Mixin Messenger ${newVersion} is now available, you have ${current}. Would you like to download it now?";
 
-  static String m18(date) => "${date} join";
+  static String m18(name) => "Do you want to delete ${name} circle?";
 
-  static String m19(count) => "${count} Participants";
+  static String m19(date) => "${date} join";
 
-  static String m20(count) => "${count} Pinned Messages";
+  static String m20(count) => "${count} Participants";
 
-  static String m21(user, preview) => "${user} pinned ${preview}";
+  static String m21(count) => "${count} Pinned Messages";
 
-  static String m22(count) => "${count} related messages";
+  static String m22(user, preview) => "${user} pinned ${preview}";
 
-  static String m23(value) => "value now ${value}";
+  static String m23(count) => "${count} related messages";
 
-  static String m24(value) => "value then ${value}";
+  static String m24(value) => "value now ${value}";
+
+  static String m25(value) => "value then ${value}";
 
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -138,6 +141,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "chatWaiting": m5,
         "chatWaitingDesktop": MessageLookupByLibrary.simpleMessage("desktop"),
         "chats": MessageLookupByLibrary.simpleMessage("Chats"),
+        "checkUpdate":
+            MessageLookupByLibrary.simpleMessage("Check for updates"),
         "circleTitle": m6,
         "circles": MessageLookupByLibrary.simpleMessage("Circles"),
         "clear": MessageLookupByLibrary.simpleMessage("Clear"),
@@ -181,6 +186,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "deleteGroup": MessageLookupByLibrary.simpleMessage("Delete Group"),
         "developer": MessageLookupByLibrary.simpleMessage("Developer"),
         "done": MessageLookupByLibrary.simpleMessage("Done"),
+        "download": MessageLookupByLibrary.simpleMessage("Download"),
         "downloadLink": MessageLookupByLibrary.simpleMessage("Download Link: "),
         "editAnnouncement":
             MessageLookupByLibrary.simpleMessage("Edit group description"),
@@ -232,6 +238,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "groupsInCommon":
             MessageLookupByLibrary.simpleMessage("Groups in common"),
         "helpCenter": MessageLookupByLibrary.simpleMessage("Help center"),
+        "ignoreThisUpdate":
+            MessageLookupByLibrary.simpleMessage("Ignore this update"),
         "image": MessageLookupByLibrary.simpleMessage("Image"),
         "includeFiles": MessageLookupByLibrary.simpleMessage("Include Files"),
         "includeVideos": MessageLookupByLibrary.simpleMessage("Include Videos"),
@@ -269,6 +277,9 @@ class MessageLookup extends MessageLookupByLibrary {
         "name": MessageLookupByLibrary.simpleMessage("Name"),
         "networkConnectionFailed":
             MessageLookupByLibrary.simpleMessage("Network connection failed"),
+        "newVersionAvailable":
+            MessageLookupByLibrary.simpleMessage("New version available"),
+        "newVersionDescription": m17,
         "next": MessageLookupByLibrary.simpleMessage("Next"),
         "noAudio": MessageLookupByLibrary.simpleMessage("NO AUDIO"),
         "noData": MessageLookupByLibrary.simpleMessage("NO DATA"),
@@ -288,8 +299,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "openLogDirectory":
             MessageLookupByLibrary.simpleMessage("open log directory"),
         "originalImage": MessageLookupByLibrary.simpleMessage("Original"),
-        "pageDeleteCircle": m17,
-        "pageEditProfileJoin": m18,
+        "pageDeleteCircle": m18,
+        "pageEditProfileJoin": m19,
         "pageLandingClickToReload":
             MessageLookupByLibrary.simpleMessage("CLICK TO RELOAD QR CODE"),
         "pageLandingLoginMessage": MessageLookupByLibrary.simpleMessage(
@@ -298,12 +309,12 @@ class MessageLookup extends MessageLookupByLibrary {
             "Login to Mixin Messenger by QR Code"),
         "pageRightEmptyMessage": MessageLookupByLibrary.simpleMessage(
             "Select a conversation to start messaging"),
-        "participantsCount": m19,
+        "participantsCount": m20,
         "phoneNumber": MessageLookupByLibrary.simpleMessage("Phone number"),
         "photos": MessageLookupByLibrary.simpleMessage("Photos"),
         "pin": MessageLookupByLibrary.simpleMessage("Pin"),
-        "pinMessageCount": m20,
-        "pinned": m21,
+        "pinMessageCount": m21,
+        "pinned": m22,
         "pleaseWait":
             MessageLookupByLibrary.simpleMessage("Please wait a moment"),
         "post": MessageLookupByLibrary.simpleMessage("Post"),
@@ -334,7 +345,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "searchEmpty": MessageLookupByLibrary.simpleMessage(
             "No chats, \ncontacts or messages found."),
         "searchMessageHistory": MessageLookupByLibrary.simpleMessage("Search"),
-        "searchRelatedMessage": m22,
+        "searchRelatedMessage": m23,
         "searchUser": MessageLookupByLibrary.simpleMessage("Search contact"),
         "searchUserHint":
             MessageLookupByLibrary.simpleMessage("Mixin ID or Phone number"),
@@ -396,10 +407,10 @@ class MessageLookup extends MessageLookupByLibrary {
         "videos": MessageLookupByLibrary.simpleMessage("Videos"),
         "waitingForThisMessage":
             MessageLookupByLibrary.simpleMessage("Waiting for this message."),
-        "walletTransactionCurrentValue": m23,
+        "walletTransactionCurrentValue": m24,
         "walletTransactionThatTimeNoValue":
             MessageLookupByLibrary.simpleMessage("value then N/A"),
-        "walletTransactionThatTimeValue": m24,
+        "walletTransactionThatTimeValue": m25,
         "webView2RuntimeInstallDescription": MessageLookupByLibrary.simpleMessage(
             "The device has not installed the WebView2 Runtime component. Please download and install WebView2 Runtime first."),
         "webViewRuntimeNotAvailable": MessageLookupByLibrary.simpleMessage(
diff --git a/lib/generated/intl/messages_zh.dart b/lib/generated/intl/messages_zh.dart
index ba87a3de9b..3598e47743 100644
--- a/lib/generated/intl/messages_zh.dart
+++ b/lib/generated/intl/messages_zh.dart
@@ -52,21 +52,24 @@ class MessageLookup extends MessageLookupByLibrary {
 
   static String m16(name) => "移除 ${name}";
 
-  static String m17(name) => "确定删除${name}圈子吗?";
+  static String m17(newVersion, current) =>
+      "发现新版本 Mixin Messenger ${newVersion},当前版本为 ${current}。是否要下载最新的版本?";
 
-  static String m18(date) => "${date}加入";
+  static String m18(name) => "确定删除${name}圈子吗?";
 
-  static String m19(count) => "共 ${count} 人";
+  static String m19(date) => "${date}加入";
 
-  static String m20(count) => "${count}条置顶消息";
+  static String m20(count) => "共 ${count} 人";
 
-  static String m21(user, preview) => "${user}置顶了${preview}";
+  static String m21(count) => "${count}条置顶消息";
 
-  static String m22(count) => "${count} 条相关的消息";
+  static String m22(user, preview) => "${user}置顶了${preview}";
 
-  static String m23(value) => "价值 ${value}";
+  static String m23(count) => "${count} 条相关的消息";
 
-  static String m24(value) => "当时价值 ${value}";
+  static String m24(value) => "价值 ${value}";
+
+  static String m25(value) => "当时价值 ${value}";
 
   final messages = _notInlinedMessages(_notInlinedMessages);
   static Map<String, Function> _notInlinedMessages(_) => <String, Function>{
@@ -118,6 +121,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "chatWaiting": m5,
         "chatWaitingDesktop": MessageLookupByLibrary.simpleMessage("桌面端"),
         "chats": MessageLookupByLibrary.simpleMessage("全部聊天"),
+        "checkUpdate": MessageLookupByLibrary.simpleMessage("检查更新"),
         "circleTitle": m6,
         "circles": MessageLookupByLibrary.simpleMessage("圈子"),
         "clear": MessageLookupByLibrary.simpleMessage("清除"),
@@ -156,6 +160,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "deleteGroup": MessageLookupByLibrary.simpleMessage("删除群组"),
         "developer": MessageLookupByLibrary.simpleMessage("开发者"),
         "done": MessageLookupByLibrary.simpleMessage("完成"),
+        "download": MessageLookupByLibrary.simpleMessage("下载"),
         "downloadLink": MessageLookupByLibrary.simpleMessage("下载链接:"),
         "editAnnouncement": MessageLookupByLibrary.simpleMessage("编辑群公告"),
         "editCircle": MessageLookupByLibrary.simpleMessage("管理圈子"),
@@ -201,6 +206,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "groups": MessageLookupByLibrary.simpleMessage("群组"),
         "groupsInCommon": MessageLookupByLibrary.simpleMessage("共同群组"),
         "helpCenter": MessageLookupByLibrary.simpleMessage("帮助中心"),
+        "ignoreThisUpdate": MessageLookupByLibrary.simpleMessage("忽略这次更新"),
         "image": MessageLookupByLibrary.simpleMessage("照片"),
         "includeFiles": MessageLookupByLibrary.simpleMessage("包含文件"),
         "includeVideos": MessageLookupByLibrary.simpleMessage("包括视频"),
@@ -233,6 +239,8 @@ class MessageLookup extends MessageLookupByLibrary {
         "name": MessageLookupByLibrary.simpleMessage("名字"),
         "networkConnectionFailed":
             MessageLookupByLibrary.simpleMessage("网络连接失败"),
+        "newVersionAvailable": MessageLookupByLibrary.simpleMessage("发现新版本"),
+        "newVersionDescription": m17,
         "next": MessageLookupByLibrary.simpleMessage("下一步"),
         "noAudio": MessageLookupByLibrary.simpleMessage("没有音频"),
         "noData": MessageLookupByLibrary.simpleMessage("没有数据"),
@@ -250,8 +258,8 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("打开通知"),
         "openLogDirectory": MessageLookupByLibrary.simpleMessage("打开日志文件夹"),
         "originalImage": MessageLookupByLibrary.simpleMessage("原图"),
-        "pageDeleteCircle": m17,
-        "pageEditProfileJoin": m18,
+        "pageDeleteCircle": m18,
+        "pageEditProfileJoin": m19,
         "pageLandingClickToReload":
             MessageLookupByLibrary.simpleMessage("点击重新加载二维码"),
         "pageLandingLoginMessage": MessageLookupByLibrary.simpleMessage(
@@ -260,12 +268,12 @@ class MessageLookup extends MessageLookupByLibrary {
             MessageLookupByLibrary.simpleMessage("通过二维码登录 Mixin Messenger"),
         "pageRightEmptyMessage":
             MessageLookupByLibrary.simpleMessage("选择一个对话,开始发送信息"),
-        "participantsCount": m19,
+        "participantsCount": m20,
         "phoneNumber": MessageLookupByLibrary.simpleMessage("手机号"),
         "photos": MessageLookupByLibrary.simpleMessage("照片"),
         "pin": MessageLookupByLibrary.simpleMessage("置顶"),
-        "pinMessageCount": m20,
-        "pinned": m21,
+        "pinMessageCount": m21,
+        "pinned": m22,
         "pleaseWait": MessageLookupByLibrary.simpleMessage("请稍等一下"),
         "post": MessageLookupByLibrary.simpleMessage("文章"),
         "preview": MessageLookupByLibrary.simpleMessage("预览"),
@@ -290,7 +298,7 @@ class MessageLookup extends MessageLookupByLibrary {
         "search": MessageLookupByLibrary.simpleMessage("搜索"),
         "searchEmpty": MessageLookupByLibrary.simpleMessage("找不到联系人或消息。"),
         "searchMessageHistory": MessageLookupByLibrary.simpleMessage("搜索聊天记录"),
-        "searchRelatedMessage": m22,
+        "searchRelatedMessage": m23,
         "searchUser": MessageLookupByLibrary.simpleMessage("搜索用户"),
         "searchUserHint": MessageLookupByLibrary.simpleMessage("Mixin ID 或手机号"),
         "send": MessageLookupByLibrary.simpleMessage("发送"),
@@ -343,10 +351,10 @@ class MessageLookup extends MessageLookupByLibrary {
         "videos": MessageLookupByLibrary.simpleMessage("视频"),
         "waitingForThisMessage":
             MessageLookupByLibrary.simpleMessage("正在等待这个消息。"),
-        "walletTransactionCurrentValue": m23,
+        "walletTransactionCurrentValue": m24,
         "walletTransactionThatTimeNoValue":
             MessageLookupByLibrary.simpleMessage("当时价值 暂无"),
-        "walletTransactionThatTimeValue": m24,
+        "walletTransactionThatTimeValue": m25,
         "webView2RuntimeInstallDescription":
             MessageLookupByLibrary.simpleMessage(
                 "该设备暂未安装 WebView2 组件,请先下载并安装 WebView2 Runtime。"),
diff --git a/lib/generated/l10n.dart b/lib/generated/l10n.dart
index 715bd9c74a..86a4d2fb1c 100644
--- a/lib/generated/l10n.dart
+++ b/lib/generated/l10n.dart
@@ -2589,6 +2589,56 @@ class Localization {
       args: [],
     );
   }
+
+  /// `Check for updates`
+  String get checkUpdate {
+    return Intl.message(
+      'Check for updates',
+      name: 'checkUpdate',
+      desc: '',
+      args: [],
+    );
+  }
+
+  /// `New version available`
+  String get newVersionAvailable {
+    return Intl.message(
+      'New version available',
+      name: 'newVersionAvailable',
+      desc: '',
+      args: [],
+    );
+  }
+
+  /// `Ignore this update`
+  String get ignoreThisUpdate {
+    return Intl.message(
+      'Ignore this update',
+      name: 'ignoreThisUpdate',
+      desc: '',
+      args: [],
+    );
+  }
+
+  /// `Mixin Messenger {newVersion} is now available, you have {current}. Would you like to download it now?`
+  String newVersionDescription(Object newVersion, Object current) {
+    return Intl.message(
+      'Mixin Messenger $newVersion is now available, you have $current. Would you like to download it now?',
+      name: 'newVersionDescription',
+      desc: '',
+      args: [newVersion, current],
+    );
+  }
+
+  /// `Download`
+  String get download {
+    return Intl.message(
+      'Download',
+      name: 'download',
+      desc: '',
+      args: [],
+    );
+  }
 }
 
 class AppLocalizationDelegate extends LocalizationsDelegate<Localization> {
diff --git a/lib/l10n/intl_en.arb b/lib/l10n/intl_en.arb
index 5d3bf966e9..a1c67219ed 100644
--- a/lib/l10n/intl_en.arb
+++ b/lib/l10n/intl_en.arb
@@ -252,5 +252,10 @@
   "groupsInCommon": "Groups in common",
   "failedToOpenFile": "Failed to open file {name}",
   "openLogDirectory": "open log directory",
-  "messageTooLong": "Message content is too long"
+  "messageTooLong": "Message content is too long",
+  "checkUpdate": "Check for updates",
+  "newVersionAvailable": "New version available",
+  "ignoreThisUpdate": "Ignore this update",
+  "newVersionDescription": "Mixin Messenger {newVersion} is now available, you have {current}. Would you like to download it now?",
+  "download": "Download"
 }
diff --git a/lib/l10n/intl_zh.arb b/lib/l10n/intl_zh.arb
index 7f83d1f2a0..8dcfe61560 100644
--- a/lib/l10n/intl_zh.arb
+++ b/lib/l10n/intl_zh.arb
@@ -247,5 +247,10 @@
   "groupsInCommon": "共同群组",
   "failedToOpenFile": "无法打开文件:{name}",
   "openLogDirectory": "打开日志文件夹",
-  "messageTooLong": "消息内容过长"
+  "messageTooLong": "消息内容过长",
+  "checkUpdate": "检查更新",
+  "newVersionAvailable": "发现新版本",
+  "ignoreThisUpdate": "忽略这次更新",
+  "newVersionDescription": "发现新版本 Mixin Messenger {newVersion},当前版本为 {current}。是否要下载最新的版本?",
+  "download": "下载"
 }
diff --git a/lib/ui/setting/about_page.dart b/lib/ui/setting/about_page.dart
index 8801efec83..238073bf2d 100644
--- a/lib/ui/setting/about_page.dart
+++ b/lib/ui/setting/about_page.dart
@@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
 import 'package:flutter_hooks/flutter_hooks.dart';
 
 import '../../constants/resources.dart';
+import '../../utils/auto_update_checker.dart';
 import '../../utils/extension/extension.dart';
 import '../../utils/hook.dart';
 import '../../utils/system/package_info.dart';
@@ -82,6 +83,10 @@ class AboutPage extends HookWidget {
                       onTap: () =>
                           openUri(context, 'https://mixin.one/pages/privacy'),
                     ),
+                    CellItem(
+                      title: Text(context.l10n.checkUpdate),
+                      onTap: () => checkUpdate(context: context, force: true),
+                    ),
                   ],
                 ),
               ),
diff --git a/lib/utils/auto_update_checker.dart b/lib/utils/auto_update_checker.dart
new file mode 100644
index 0000000000..9935045952
--- /dev/null
+++ b/lib/utils/auto_update_checker.dart
@@ -0,0 +1,130 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_hooks/flutter_hooks.dart';
+import 'package:github/github.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+import '../account/account_key_value.dart';
+import '../constants/constants.dart';
+import '../widgets/dialog.dart';
+import 'extension/extension.dart';
+import 'logger.dart';
+import 'system/package_info.dart';
+
+final github = GitHub();
+
+Future<void> checkUpdate({
+  required BuildContext context,
+  bool force = false,
+}) async {
+  final lastCheckUpdate = AccountKeyValue.instance.checkUpdateLastTime;
+  final now = DateTime.now().millisecondsSinceEpoch;
+  if (!force && now - lastCheckUpdate < hours24) {
+    d('checkUpdate: skip');
+    return;
+  }
+  try {
+    final release = await github.repositories.getLatestRelease(
+      RepositorySlug('MixinNetwork', 'flutter-app'),
+    );
+    final packageInfo = await getPackageInfo();
+    final currentVersion = 'v${packageInfo.version}';
+
+    i('Latest release: ${release.tagName} ${release.publishedAt}, current version: $currentVersion');
+
+    AccountKeyValue.instance.checkUpdateLastTime = now;
+
+    if (!force && release.tagName == AccountKeyValue.instance.ignoredVersion) {
+      // ignore this version
+      return;
+    }
+    if (release.tagName == currentVersion) {
+      // current version is the latest
+      return;
+    }
+    await showMixinDialog(
+      context: context,
+      child: _UpdateDialog(
+        release: release,
+        ignored: release.tagName == AccountKeyValue.instance.ignoredVersion,
+        currentVersion: currentVersion,
+      ),
+    );
+  } catch (error, stackTrace) {
+    e('check update failed: $error, $stackTrace');
+  }
+}
+
+class _UpdateDialog extends HookWidget {
+  const _UpdateDialog({
+    Key? key,
+    required this.release,
+    required this.ignored,
+    required this.currentVersion,
+  }) : super(key: key);
+
+  final Release release;
+
+  final bool ignored;
+
+  final String currentVersion;
+
+  @override
+  Widget build(BuildContext context) {
+    final ignoreUpdate = useState(ignored);
+    return SizedBox(
+      width: 400,
+      child: AlertDialogLayout(
+        title: Text(context.l10n.newVersionAvailable),
+        titleMarginBottom: 24,
+        content: DefaultTextStyle.merge(
+          style: TextStyle(
+            fontSize: 14,
+            fontWeight: FontWeight.normal,
+            color: context.theme.text,
+          ),
+          child: Column(
+            children: [
+              Text(context.l10n.newVersionDescription(
+                  release.tagName ?? '', currentVersion)),
+              if (!ignored)
+                Padding(
+                  padding: const EdgeInsets.symmetric(vertical: 10),
+                  child: Row(
+                    children: [
+                      Checkbox(
+                        value: ignoreUpdate.value,
+                        onChanged: (checked) {
+                          final ignore = checked ?? false;
+                          ignoreUpdate.value = ignore;
+                          AccountKeyValue.instance.ignoredVersion =
+                              ignore ? release.tagName : null;
+                        },
+                      ),
+                      Text(
+                        context.l10n.ignoreThisUpdate,
+                        style: TextStyle(
+                          color: context.theme.secondaryText,
+                          fontSize: 12,
+                        ),
+                      ),
+                    ],
+                  ),
+                ),
+            ],
+          ),
+        ),
+        actions: [
+          MixinButton(
+            backgroundTransparent: true,
+            onTap: () => Navigator.pop(context),
+            child: Text(context.l10n.cancel),
+          ),
+          MixinButton(
+            onTap: () => launch('https://mixin.one/messenger'),
+            child: Text(context.l10n.download),
+          ),
+        ],
+      ),
+    );
+  }
+}
diff --git a/lib/widgets/dialog.dart b/lib/widgets/dialog.dart
index 12e5067b90..762ca5f359 100644
--- a/lib/widgets/dialog.dart
+++ b/lib/widgets/dialog.dart
@@ -108,7 +108,7 @@ class AlertDialogLayout extends StatelessWidget {
                       ),
                       child: title!,
                     ),
-                  if (title != null) const SizedBox(height: 48),
+                  if (title != null) SizedBox(height: titleMarginBottom),
                   DefaultTextStyle.merge(
                     style: TextStyle(
                       fontSize: 18,
diff --git a/pubspec.lock b/pubspec.lock
index 7aa078d522..a64ce5900c 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -627,6 +627,13 @@ packages:
       url: "https://pub.dartlang.org"
     source: hosted
     version: "2.3.2"
+  github:
+    dependency: "direct main"
+    description:
+      name: github
+      url: "https://pub.dartlang.org"
+    source: hosted
+    version: "9.0.0"
   glob:
     dependency: transitive
     description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 4180010206..972caae14d 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -53,6 +53,7 @@ dependencies:
   flutter_portal: ^0.4.0
   flutter_svg: ^1.0.3
   gallery_saver: ^2.3.2
+  github: ^9.0.0
   hive: ^2.0.4
   hive_flutter: ^1.0.0
   http: ^0.13.3