diff --git a/assets/locales/en_us.json b/assets/locales/en_us.json index d53369b..4254b5b 100644 --- a/assets/locales/en_us.json +++ b/assets/locales/en_us.json @@ -453,5 +453,9 @@ "accountDeletionConfirm": "Confirm request account deletion", "accountDeletionConfirmDesc": "Are you sure to delete account @account? You will receive a confirmation email with a link to confirm the deletion of the account within 24 hours. Note that this action is irreversible, and all data associated with the account will be deleted, and you should be careful about it.", "accountDeletionRequested": "Account deletion requested, check your inbox to confirm the request.", - "slideToConfirm": "Slide to confirm" + "slideToConfirm": "Slide to confirm", + "serviceStatus": "Status of Service", + "firstBootTime": "First boot at @time", + "rateTheApp": "Rate the app", + "rateTheAppDesc": "Rate Solar Network on the App Store to let us serve you better!" } diff --git a/assets/locales/zh_cn.json b/assets/locales/zh_cn.json index 5860ede..4c68a02 100644 --- a/assets/locales/zh_cn.json +++ b/assets/locales/zh_cn.json @@ -449,5 +449,9 @@ "accountDeletionConfirm": "确认账号删除请求", "accountDeletionConfirmDesc": "你确定要删除账号 @account 吗?你将会在其绑定的主要邮件地址收到一封包含着确认删除账号连接的邮件,在二十四小时内使用该连接即可完成删除账号。注意,本操作不可撤销,并且账号创建或关联的所有数据都将被删除,请三思而后行。", "accountDeletionRequested": "已请求删除账号,检查你的收件箱来确认请求。", - "slideToConfirm": "滑动来确认" + "slideToConfirm": "滑动来确认", + "serviceStatus": "服务状态", + "firstBootTime": "首次启动于 @time", + "rateTheApp": "给应用评分", + "rateTheAppDesc": "在 App Store 上给 Solar Network 评分,让我们更好地为您服务吧!" } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index d9a3319..e8667fd 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -227,6 +227,8 @@ PODS: - TOCropViewController (~> 2.7.4) - image_picker_ios (0.0.1): - Flutter + - in_app_review (0.2.0): + - Flutter - livekit_client (2.2.6): - Flutter - WebRTC-SDK (= 125.6422.04) @@ -318,6 +320,7 @@ DEPENDENCIES: - gal (from `.symlinks/plugins/gal/darwin`) - image_cropper (from `.symlinks/plugins/image_cropper/ios`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - livekit_client (from `.symlinks/plugins/livekit_client/ios`) - media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) @@ -406,6 +409,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/image_cropper/ios" image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" + in_app_review: + :path: ".symlinks/plugins/in_app_review/ios" livekit_client: :path: ".symlinks/plugins/livekit_client/ios" media_kit_libs_ios_video: @@ -482,6 +487,7 @@ SPEC CHECKSUMS: GoogleUtilities: 26a3abef001b6533cf678d3eb38fd3f614b7872d image_cropper: 37d40f62177c101ff4c164906d259ea2c3aa70cf image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d livekit_client: 20e01637431bc108dad451c8a11c1d206e1dd2cd media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a diff --git a/lib/bootstrapper.dart b/lib/bootstrapper.dart index 224582b..661d7b9 100644 --- a/lib/bootstrapper.dart +++ b/lib/bootstrapper.dart @@ -1,8 +1,10 @@ import 'dart:async'; +import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:get/get.dart'; +import 'package:in_app_review/in_app_review.dart'; import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -42,6 +44,27 @@ class _BootstrapperShellState extends State { final Completer _bootCompleter = Completer(); + void _requestRating() async { + final prefs = await SharedPreferences.getInstance(); + if (prefs.containsKey('first_boot_time')) { + final rawTime = prefs.getString('first_boot_time'); + final time = DateTime.tryParse(rawTime ?? ''); + if (time != null && + time.isBefore(DateTime.now().subtract(const Duration(days: 3)))) { + final inAppReview = InAppReview.instance; + if (prefs.getBool('rating_requested') == true) return; + if (await inAppReview.isAvailable()) { + await inAppReview.requestReview(); + prefs.setBool('rating_requested', true); + } else { + log('Unable request app review, unavailable'); + } + } + } else { + prefs.setString('first_boot_time', DateTime.now().toIso8601String()); + } + } + void _updateNow(String localVersionString, String remoteVersionString) { context .showConfirmDialog( @@ -226,6 +249,9 @@ class _BootstrapperShellState extends State { super.initState(); _runPeriods(); _checkForUpdate(); + _bootCompleter.future.then((_) { + _requestRating(); + }); } @override diff --git a/lib/platform.dart b/lib/platform.dart index 6bfa083..61f2918 100644 --- a/lib/platform.dart +++ b/lib/platform.dart @@ -27,6 +27,8 @@ abstract class PlatformInfo { static bool get canCacheImage => isAndroid || isIOS || isMacOS; + static bool get canRateTheApp => isIOS || isMacOS; + static bool get canRecord => (isMobile || isMacOS); static bool get canPushNotification => isAndroid || isIOS || isMacOS; @@ -38,4 +40,4 @@ abstract class PlatformInfo { } catch (_) {} return version; } -} \ No newline at end of file +} diff --git a/lib/screens/about.dart b/lib/screens/about.dart index 5a42739..4d8775a 100644 --- a/lib/screens/about.dart +++ b/lib/screens/about.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; import 'package:get/get.dart'; +import 'package:intl/intl.dart'; import 'package:package_info_plus/package_info_plus.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:solian/widgets/sized_container.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -52,8 +54,8 @@ class AboutScreen extends StatelessWidget { CenteredContainer( maxWidth: 280, child: Wrap( - spacing: 8, - runSpacing: 8, + spacing: 4, + runSpacing: 4, alignment: WrapAlignment.center, children: [ TextButton( @@ -92,6 +94,13 @@ class AboutScreen extends StatelessWidget { launchUrlString('https://solsynth.dev/terms'); }, ), + TextButton( + style: denseButtonStyle, + child: Text('serviceStatus'.tr), + onPressed: () { + launchUrlString('https://status.solsynth.dev'); + }, + ), ], ), ), @@ -103,6 +112,34 @@ class AboutScreen extends StatelessWidget { fontSize: 12, ), ), + FutureBuilder( + future: SharedPreferences.getInstance(), + builder: (context, snapshot) { + const textStyle = TextStyle( + fontWeight: FontWeight.w300, + fontSize: 12, + ); + if (!snapshot.hasData || + !snapshot.data!.containsKey('first_boot_time')) { + return Text( + 'firstBootTime'.trParams({'time': 'unknown'.tr}), + style: textStyle, + ); + } else { + return Text( + 'firstBootTime'.trParams({ + 'time': DateFormat('yyyy-MM-dd').format( + DateTime.tryParse( + snapshot.data!.getString('first_boot_time')!, + )?.toLocal() ?? + DateTime.now(), + ), + }), + style: textStyle, + ); + } + }, + ), ], ), ), diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 36bd7c4..622c217 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:in_app_review/in_app_review.dart'; import 'package:provider/provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:solian/exceptions/request.dart'; @@ -205,6 +206,21 @@ class _SettingScreenState extends State { }); }, ), + if (PlatformInfo.canRateTheApp) + ListTile( + leading: const Icon(Icons.star), + trailing: const Icon(Icons.chevron_right), + contentPadding: const EdgeInsets.symmetric(horizontal: 22), + title: Text('rateTheApp'.tr), + subtitle: Text('rateTheAppDesc'.tr), + onTap: () { + final inAppReview = InAppReview.instance; + + inAppReview.openStoreListing( + appStoreId: '6499032345', + ); + }, + ), ListTile( leading: const Icon(Icons.info_outline), trailing: const Icon(Icons.chevron_right), diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 9c19bdd..80cb7c8 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -17,6 +17,7 @@ import flutter_local_notifications import flutter_secure_storage_macos import flutter_webrtc import gal +import in_app_review import livekit_client import macos_window_utils import media_kit_libs_macos_video @@ -46,6 +47,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) FlutterWebRTCPlugin.register(with: registry.registrar(forPlugin: "FlutterWebRTCPlugin")) GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) + InAppReviewPlugin.register(with: registry.registrar(forPlugin: "InAppReviewPlugin")) LiveKitPlugin.register(with: registry.registrar(forPlugin: "LiveKitPlugin")) MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) diff --git a/pubspec.lock b/pubspec.lock index c081f8e..abc2234 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -474,10 +474,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+1" file_selector_platform_interface: dependency: transitive description: @@ -1125,6 +1125,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.2.1+1" + in_app_review: + dependency: "direct main" + description: + name: in_app_review + sha256: "99869244d09adc76af16bf8fd731dd13cef58ecafd5917847589c49f378cbb30" + url: "https://pub.dev" + source: hosted + version: "2.0.9" + in_app_review_platform_interface: + dependency: transitive + description: + name: in_app_review_platform_interface + sha256: fed2c755f2125caa9ae10495a3c163aa7fab5af3585a9c62ef4a6920c5b45f10 + url: "https://pub.dev" + source: hosted + version: "2.0.5" infinite_scroll_pagination: dependency: "direct main" description: @@ -2078,10 +2094,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_platform_interface: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index b63fe1a..b632176 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -83,6 +83,7 @@ dependencies: flutter_app_update: ^3.1.0 version: ^3.0.2 action_slider: ^0.7.0 + in_app_review: ^2.0.9 dev_dependencies: flutter_test: