diff --git a/assets/translations/en.json b/assets/translations/en.json index 900b94c..3d94d22 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -2,6 +2,7 @@ "nextVersionAlert": "Heavy Development Alert", "nextVersionNotice": "You are using Solian 2.0 Preview, which is the first version of Solian 2.0. The current stable branch (sn.solsynth.dev) is 1.4. This version is still under heavy development, some features may not be stable, and not all features are supported. You can roll back to 1.4.X version via TestFlight, or continue to experience the new version (sn-next.solsynth.dev).", "screen": "Screen", + "screenAbout": "About", "screenHome": "Home", "screenExplore": "Explore", "screenAccount": "Account", @@ -188,6 +189,9 @@ "settingsNetworkServerPreset": "Present HyperNet Server", "settingsNetworkServerPresetDescription": "You can choose one of our preset HyperNet server addresses from the list on the right.", "settingsNetworkServerSaved": "Server address saved.", + "settingsMisc": "Misc", + "settingsMiscAbout": "About", + "settingsMiscAboutDescription": "View the version information of Solian.", "sensitiveContent": "Sensitive Content", "sensitiveContentCollapsed": "Sensitive content has been collapsed.", "sensitiveContentDescription": "This content has been marked as sensitive, and may not be suitable for all viewers.", @@ -417,5 +421,8 @@ "termAcceptLink": "View terms", "termAcceptNextWithAgree": "By clicking the \"Next\", it means you agree to our terms and its updates.", "unauthorized": "Unauthorized", - "unauthorizedDescription": "Login to explore the entire Solar Network." + "unauthorizedDescription": "Login to explore the entire Solar Network.", + "serviceStatus": "Service Status", + "termRelated": "Related Terms", + "appDetails": "App Details" } diff --git a/assets/translations/zh.json b/assets/translations/zh.json index d35f624..31dcd91 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -2,6 +2,7 @@ "nextVersionAlert": "高强度开发提示", "nextVersionNotice": "您正在使用的是 Solian 2.0 的抢先体验版本,目前稳定分支(sn.solsynth.dev)版本为 1.4。该版本还在持续的开发中,部分功能可能不稳定,也并非所有功能都支持了。您可以通过 TestFlight 回滚到 1.4.X 或者继续体验新版本(sn-next.solsynth.dev)。", "screen": "页面", + "screenAbout": "关于", "screenHome": "首页", "screenExplore": "探索", "screenAccount": "您", @@ -188,6 +189,9 @@ "settingsNetworkServerPreset": "预设的 HyperNet 服务器", "settingsNetworkServerPresetDescription": "你可以在旁边的列表中选择我们提供的预设 HyperNet 服务器地址。", "settingsNetworkServerSaved": "服务器地址已保存。", + "settingsMisc": "杂项", + "settingsMiscAbout": "关于", + "settingsMiscAboutDescription": "查看 Solian 的版本信息。", "sensitiveContent": "敏感内容", "sensitiveContentCollapsed": "敏感内容已折叠。", "sensitiveContentDescription": "此内容已被标记,可能不适合所有人查看。", @@ -417,5 +421,8 @@ "termAcceptLink": "浏览条款", "termAcceptNextWithAgree": "点击 “下一步”,即表示你同意我们的各项条款,包括其之后的更新。", "unauthorized": "未登陆", - "unauthorizedDescription": "登陆以探索整个 Solar Network。" + "unauthorizedDescription": "登陆以探索整个 Solar Network。", + "serviceStatus": "服务状态", + "termRelated": "相关条款", + "appDetails": "应用程序详情" } diff --git a/lib/router.dart b/lib/router.dart index 79d67c4..c8e353b 100644 --- a/lib/router.dart +++ b/lib/router.dart @@ -29,6 +29,7 @@ import 'package:surface/screens/realm/manage.dart'; import 'package:surface/screens/realm/realm_detail.dart'; import 'package:surface/screens/settings.dart'; import 'package:surface/types/post.dart'; +import 'package:surface/widgets/about.dart'; import 'package:surface/widgets/navigation/app_background.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; @@ -301,6 +302,18 @@ final _appRoutes = [ ), ], ), + ShellRoute( + builder: (context, state, child) => AppPageScaffold(body: child), + routes: [ + GoRoute( + path: '/about', + name: 'about', + builder: (context, state) => const AppBackground( + child: AboutScreen(), + ), + ), + ], + ), ]; final appRouter = GoRouter( diff --git a/lib/screens/settings.dart b/lib/screens/settings.dart index 92319c2..78d6f63 100644 --- a/lib/screens/settings.dart +++ b/lib/screens/settings.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:gap/gap.dart'; +import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; import 'package:material_symbols_icons/symbols.dart'; import 'package:path_provider/path_provider.dart'; @@ -41,8 +42,7 @@ class _SettingsScreenState extends State { SharedPreferences.getInstance().then((prefs) { setState(() { _prefs = prefs; - _serverUrlController.text = - prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; + _serverUrlController.text = prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault; }); }); } @@ -65,11 +65,7 @@ class _SettingsScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('settingsAppearance') - .bold() - .fontSize(17) - .tr() - .padding(horizontal: 20, bottom: 4), + Text('settingsAppearance').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), if (!kIsWeb) ListTile( title: Text('settingsBackgroundImage').tr(), @@ -78,20 +74,17 @@ class _SettingsScreenState extends State { leading: const Icon(Symbols.image), trailing: const Icon(Symbols.chevron_right), onTap: () async { - final image = await ImagePicker() - .pickImage(source: ImageSource.gallery); + final image = await ImagePicker().pickImage(source: ImageSource.gallery); if (image == null) return; - await File(image.path) - .copy('$_docBasepath/app_background_image'); + await File(image.path).copy('$_docBasepath/app_background_image'); setState(() {}); }, ), if (!kIsWeb) FutureBuilder( - future: - File('$_docBasepath/app_background_image').exists(), + future: File('$_docBasepath/app_background_image').exists(), builder: (context, snapshot) { if (!snapshot.hasData || !snapshot.data!) { return const SizedBox.shrink(); @@ -99,16 +92,12 @@ class _SettingsScreenState extends State { return ListTile( title: Text('settingsBackgroundImageClear').tr(), - subtitle: - Text('settingsBackgroundImageClearDescription') - .tr(), - contentPadding: - const EdgeInsets.symmetric(horizontal: 24), + subtitle: Text('settingsBackgroundImageClearDescription').tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), leading: const Icon(Symbols.texture), trailing: const Icon(Symbols.chevron_right), onTap: () { - File('$_docBasepath/app_background_image') - .deleteSync(); + File('$_docBasepath/app_background_image').deleteSync(); setState(() {}); }, ); @@ -136,11 +125,7 @@ class _SettingsScreenState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text('settingsNetwork') - .bold() - .fontSize(17) - .tr() - .padding(horizontal: 20, bottom: 4), + Text('settingsNetwork').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), TextField( controller: _serverUrlController, decoration: InputDecoration( @@ -161,8 +146,7 @@ class _SettingsScreenState extends State { }, ), ), - onTapOutside: (_) => - FocusManager.instance.primaryFocus?.unfocus(), + onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), ).padding(horizontal: 16, top: 8, bottom: 4), ListTile( title: Text('settingsNetworkServerPreset').tr(), @@ -174,9 +158,7 @@ class _SettingsScreenState extends State { isExpanded: true, items: [ ...kNetworkServerDirectory, - if (!kNetworkServerDirectory - .map((ele) => ele.$2) - .contains(_serverUrlController.text)) + if (!kNetworkServerDirectory.map((ele) => ele.$2).contains(_serverUrlController.text)) ('Custom', _serverUrlController.text), ] .map( @@ -188,8 +170,7 @@ class _SettingsScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(item.$1).fontSize(14), - Text(item.$2, overflow: TextOverflow.ellipsis) - .fontSize(11) + Text(item.$2, overflow: TextOverflow.ellipsis).fontSize(11) ], ), ), @@ -232,6 +213,22 @@ class _SettingsScreenState extends State { ), ], ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('settingsMisc').bold().fontSize(17).tr().padding(horizontal: 20, bottom: 4), + ListTile( + title: Text('settingsMiscAbout').tr(), + subtitle: Text('settingsMiscAboutDescription').tr(), + contentPadding: const EdgeInsets.symmetric(horizontal: 24), + leading: const Icon(Symbols.info), + trailing: const Icon(Symbols.chevron_right), + onTap: () async { + GoRouter.of(context).pushNamed('about'); + }, + ), + ], + ), ].expand((ele) => [ele, const Gap(16)]).toList(), ).padding(vertical: 20), ), diff --git a/lib/widgets/about.dart b/lib/widgets/about.dart new file mode 100644 index 0000000..f5ea629 --- /dev/null +++ b/lib/widgets/about.dart @@ -0,0 +1,109 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:url_launcher/url_launcher_string.dart'; + +class AboutScreen extends StatelessWidget { + const AboutScreen({super.key}); + + @override + Widget build(BuildContext context) { + const denseButtonStyle = ButtonStyle(visualDensity: VisualDensity(vertical: -4)); + + return SizedBox( + width: double.infinity, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: Image.asset('assets/icon/icon-light-radius.png', width: 120, height: 120), + ), + const Gap(8), + Text( + 'Solian', + style: Theme.of(context).textTheme.titleLarge!.copyWith(fontSize: 36), + ), + const Text( + 'The Solar Network', + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + const Gap(8), + FutureBuilder( + future: PackageInfo.fromPlatform(), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const SizedBox.shrink(); + } + + return Text( + 'v${snapshot.data!.version} · ${snapshot.data!.buildNumber}', + style: const TextStyle(fontFamily: 'monospace'), + ); + }, + ), + Text('Copyright © ${DateTime.now().year} Solsynth LLC'), + const Gap(16), + Container( + constraints: const BoxConstraints(maxWidth: 280), + child: Wrap( + spacing: 4, + runSpacing: 4, + alignment: WrapAlignment.center, + children: [ + TextButton( + style: denseButtonStyle, + child: Text('appDetails').tr(), + onPressed: () async { + final info = await PackageInfo.fromPlatform(); + + if (!context.mounted) return; + showAboutDialog( + context: context, + applicationName: 'Solian', + applicationVersion: '${info.version}+${info.buildNumber}', + applicationLegalese: + 'The Solar Network App is an intuitive and open-source social network and computing platform. Experience the freedom of a user-friendly design that empowers you to create and connect with communities on your own terms. Embrace the future of social networking with a platform that prioritizes your independence and privacy.', + applicationIcon: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(16)), + child: Image.asset( + 'assets/icon/icon-light-radius.png', + width: 60, + height: 60, + ), + ), + ); + }, + ), + TextButton( + style: denseButtonStyle, + child: Text('termRelated').tr(), + onPressed: () { + launchUrlString('https://solsynth.dev/terms'); + }, + ), + TextButton( + style: denseButtonStyle, + child: Text('serviceStatus').tr(), + onPressed: () { + launchUrlString('https://status.solsynth.dev'); + }, + ), + ], + ), + ).center(), + const Gap(16), + const Text( + 'Open-sourced under AGPLv3', + style: TextStyle( + fontWeight: FontWeight.w300, + fontSize: 12, + ), + ), + ], + ), + ); + } +} diff --git a/lib/widgets/navigation/app_drawer_navigation.dart b/lib/widgets/navigation/app_drawer_navigation.dart index b17e671..2edbf74 100644 --- a/lib/widgets/navigation/app_drawer_navigation.dart +++ b/lib/widgets/navigation/app_drawer_navigation.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; +import 'package:package_info_plus/package_info_plus.dart'; import 'package:provider/provider.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'package:styled_widget/styled_widget.dart'; @@ -48,9 +49,14 @@ class _AppNavigationDrawerState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('Solar Network').bold(), - Text('Canary Preview 2.0α') - .fontSize(12) - .textColor(Theme.of(context).colorScheme.onSurface.withOpacity(0.5)), + FutureBuilder( + future: PackageInfo.fromPlatform().then((value) => 'Stable ${value.version}+${value.buildNumber}'), + builder: (context, snapshot) { + return Text(!snapshot.hasData ? 'Stable 2.0' : snapshot.data!) + .fontSize(12) + .textColor(Theme.of(context).colorScheme.onSurface.withOpacity(0.5)); + }, + ), ], ).padding( horizontal: 32, diff --git a/pubspec.lock b/pubspec.lock index af581e5..578fc09 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -955,7 +955,7 @@ packages: source: hosted version: "0.2.1+1" intl: - dependency: transitive + dependency: "direct main" description: name: intl sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf @@ -1227,7 +1227,7 @@ packages: source: hosted version: "2.1.0" package_info_plus: - dependency: transitive + dependency: "direct main" description: name: package_info_plus sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce diff --git a/pubspec.yaml b/pubspec.yaml index 7ad2318..fd3c8bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,6 +98,8 @@ dependencies: gal: ^2.3.0 share_plus: ^10.1.2 email_validator: ^3.0.0 + package_info_plus: ^8.1.1 + intl: ^0.19.0 dev_dependencies: flutter_test: @@ -130,6 +132,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - assets/icon/icon.png + - assets/icon/icon-light-radius.png - assets/translations/ # An image asset can refer to one or more resolution-specific "variants", see