♻️ Add splash screen for loading data

This commit is contained in:
LittleSheep 2024-12-14 01:32:13 +08:00
parent c7d5cb48ac
commit f763c7515a
13 changed files with 163 additions and 54 deletions

View File

@ -407,6 +407,7 @@
"articleWrittenAt": "Written at {}",
"articleEditedAt": "Edited at {}",
"attachmentSaved": "Saved to album",
"attachmentSavedDesktop": "Saved to Downloads folder",
"openInAlbum": "Open in album",
"postAbuseReport": "Report Post",
"postAbuseReportDescription": "Report posts that violate our user agreement and community guidelines to help us improve the content on Solar Network. Please describe how this post violates the relevant rules. Do not include any sensitive information. We will process your report within 24 hours.",
@ -442,5 +443,6 @@
"postImageShareReadMore": "Scan the QR code to read full post",
"postImageShareAds": "Explore posts on the Solar Network",
"postShare": "Share",
"postShareImage": "Share via Image"
"postShareImage": "Share via Image",
"appInitializing": "Initializing"
}

View File

@ -405,6 +405,7 @@
"articleWrittenAt": "发表于 {}",
"articleEditedAt": "编辑于 {}",
"attachmentSaved": "已保存到相册",
"attachmentSavedDesktop": "已保存到下载目录",
"openInAlbum": "在相册中打开",
"postAbuseReport": "检举帖子",
"postAbuseReportDescription": "检举不符合我们用户协议以及社区准则的帖子,来帮助我们更好的维护 Solar Network 上的内容。请在下面描述该帖子如何违反我么的相关规定。请勿填写任何敏感信息。我们将会在 24 小时内处理您的检举。",
@ -440,5 +441,6 @@
"postImageShareReadMore": "扫描右侧 QRCode 查看全文",
"postImageShareAds": "来 Solar Network 探索更多有趣帖子",
"postShare": "分享",
"postShareImage": "分享帖图"
"postShareImage": "分享帖图",
"appInitializing": "正在初始化"
}

View File

@ -405,6 +405,7 @@
"articleWrittenAt": "發表於 {}",
"articleEditedAt": "編輯於 {}",
"attachmentSaved": "已保存到相冊",
"attachmentSavedDesktop": "已保存到下載目錄",
"openInAlbum": "在相冊中打開",
"postAbuseReport": "檢舉帖子",
"postAbuseReportDescription": "檢舉不符合我們用户協議以及社區準則的帖子,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述該帖子如何違反我麼的相關規定。請勿填寫任何敏感信息。我們將會在 24 小時內處理您的檢舉。",

View File

@ -405,6 +405,7 @@
"articleWrittenAt": "發表於 {}",
"articleEditedAt": "編輯於 {}",
"attachmentSaved": "已儲存到相簿",
"attachmentSavedDesktop": "已儲存到下載目錄",
"openInAlbum": "在相簿中開啟",
"postAbuseReport": "檢舉帖子",
"postAbuseReportDescription": "檢舉不符合我們使用者協議以及社群準則的帖子,來幫助我們更好的維護 Solar Network 上的內容。請在下面描述該帖子如何違反我麼的相關規定。請勿填寫任何敏感資訊。我們將會在 24 小時內處理您的檢舉。",

View File

@ -7,11 +7,13 @@ import 'package:easy_localization_loader/easy_localization_loader.dart';
import 'package:firebase_core/firebase_core.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:hive_flutter/hive_flutter.dart';
import 'package:provider/provider.dart';
import 'package:relative_time/relative_time.dart';
import 'package:responsive_framework/responsive_framework.dart';
import 'package:styled_widget/styled_widget.dart';
import 'package:surface/firebase_options.dart';
import 'package:surface/providers/channel.dart';
import 'package:surface/providers/chat_call.dart';
@ -29,6 +31,7 @@ import 'package:surface/router.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/types/realm.dart';
import 'package:flutter_web_plugins/url_strategy.dart' show usePathUrlStrategy;
import 'package:surface/widgets/dialog.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
@ -94,7 +97,7 @@ class SolianApp extends StatelessWidget {
ChangeNotifierProvider(create: (ctx) => ChatChannelProvider(ctx)),
ChangeNotifierProvider(create: (ctx) => ChatCallProvider(ctx)),
],
child: AppMainContent(),
child: _AppDelegate(),
),
),
breakpoints: [
@ -106,8 +109,8 @@ class SolianApp extends StatelessWidget {
}
}
class AppMainContent extends StatelessWidget {
const AppMainContent({super.key});
class _AppDelegate extends StatelessWidget {
const _AppDelegate({super.key});
@override
Widget build(BuildContext context) {
@ -129,6 +132,76 @@ class AppMainContent extends StatelessWidget {
...context.localizationDelegates,
],
routerConfig: appRouter,
builder: (context, child) {
return _AppSplashScreen(child: child!);
},
);
}
}
class _AppSplashScreen extends StatefulWidget {
final Widget child;
const _AppSplashScreen({super.key, required this.child});
@override
State<_AppSplashScreen> createState() => _AppSplashScreenState();
}
class _AppSplashScreenState extends State<_AppSplashScreen> {
bool _isReady = false;
Future<void> _initialize() async {
try {
final sn = context.read<SnNetworkProvider>();
await sn.initializeUserAgent();
if (!mounted) return;
final ua = context.read<UserProvider>();
await ua.initialize();
if (!mounted) return;
final ws = context.read<WebSocketProvider>();
await ws.tryConnect();
if (!mounted) return;
final notify = context.read<NotificationProvider>();
await notify.registerPushNotifications();
} catch (err) {
if (!mounted) return;
await context.showErrorDialog(err);
} finally {
setState(() => _isReady = true);
}
}
@override
void initState() {
super.initState();
_initialize();
}
@override
Widget build(BuildContext context) {
if (!_isReady) {
return Scaffold(
backgroundColor: Theme.of(context).colorScheme.surface,
body: Container(
constraints: const BoxConstraints(maxWidth: 180),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Image.asset("assets/icon/icon.png", width: 64, height: 64),
const Gap(6),
LinearProgressIndicator(
backgroundColor: Theme.of(context).colorScheme.surfaceContainer,
),
const Gap(20),
Text('appInitializing'.tr(), textAlign: TextAlign.center),
],
),
).center(),
);
}
return widget.child;
}
}

View File

@ -16,14 +16,6 @@ class NotificationProvider extends ChangeNotifier {
NotificationProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_ua = context.read<UserProvider>();
// Delay to wait user provider ready to use
Future.delayed(const Duration(milliseconds: 3000), () async {
if (!_ua.isAuthorized) return;
log("Registering push notifications...");
await registerPushNotifications();
log("Registered push notification subscriber successfully!");
});
}
Future<void> registerPushNotifications() async {

View File

@ -1,9 +1,13 @@
import 'dart:async';
import 'dart:convert';
import 'dart:developer';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:dio_smart_retry/dio_smart_retry.dart';
import 'package:flutter/foundation.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:synchronized/synchronized.dart';
@ -23,6 +27,8 @@ class SnNetworkProvider {
late final SharedPreferences _prefs;
String? _userAgent;
SnNetworkProvider() {
client = Dio();
@ -46,6 +52,9 @@ class SnNetworkProvider {
if (atk != null) {
options.headers['Authorization'] = 'Bearer $atk';
}
if (_userAgent != null) {
options.headers['User-Agent'] = _userAgent!;
}
return handler.next(options);
},
),
@ -53,11 +62,39 @@ class SnNetworkProvider {
SharedPreferences.getInstance().then((prefs) {
_prefs = prefs;
client.options.baseUrl =
_prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
client.options.baseUrl = _prefs.getString(kNetworkServerStoreKey) ?? kNetworkServerDefault;
});
}
Future<void> initializeUserAgent() async {
final String platformInfo;
if (kIsWeb) {
final deviceInfo = await DeviceInfoPlugin().webBrowserInfo;
platformInfo = 'Web; ${deviceInfo.vendor}';
} else if (Platform.isAndroid) {
final deviceInfo = await DeviceInfoPlugin().androidInfo;
platformInfo = 'Android; ${deviceInfo.brand} ${deviceInfo.model}; ${deviceInfo.id}';
} else if (Platform.isIOS) {
final deviceInfo = await DeviceInfoPlugin().iosInfo;
platformInfo = 'iOS; ${deviceInfo.model}; ${deviceInfo.name}';
} else if (Platform.isMacOS) {
final deviceInfo = await DeviceInfoPlugin().macOsInfo;
platformInfo = 'MacOS; ${deviceInfo.model}; ${deviceInfo.hostName}';
} else if (Platform.isWindows) {
final deviceInfo = await DeviceInfoPlugin().windowsInfo;
platformInfo = 'Windows NT; ${deviceInfo.productName}; ${deviceInfo.computerName}';
} else if (Platform.isLinux) {
final deviceInfo = await DeviceInfoPlugin().linuxInfo;
platformInfo = 'Linux; ${deviceInfo.prettyName}';
} else {
platformInfo = 'Unknown';
}
final packageInfo = await PackageInfo.fromPlatform();
_userAgent = 'Solian/${packageInfo.version}+${packageInfo.buildNumber} ($platformInfo)';
}
final tkLock = Lock();
Completer<String?>? _refreshCompleter;

View File

@ -19,16 +19,17 @@ class UserProvider extends ChangeNotifier {
UserProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
}
SharedPreferences.getInstance().then((prefs) {
final value = prefs.getString(kAtkStoreKey);
isAuthorized = value != null;
notifyListeners();
refreshUser().then((value) {
if (value != null) {
log('Logged in as @${value.name}');
}
});
Future<void> initialize() async {
final prefs = await SharedPreferences.getInstance();
final value = prefs.getString(kAtkStoreKey);
isAuthorized = value != null;
notifyListeners();
refreshUser().then((value) {
if (value != null) {
log('Logged in as @${value.name}');
}
});
}

View File

@ -23,16 +23,13 @@ class WebSocketProvider extends ChangeNotifier {
WebSocketProvider(BuildContext context) {
_sn = context.read<SnNetworkProvider>();
_ua = context.read<UserProvider>();
}
// Wait for the userinfo provide initialize authorization status
Future.delayed(const Duration(milliseconds: 250), () async {
if (_ua.isAuthorized) {
log('[WebSocket] Connecting to the server...');
await connect();
} else {
log('[WebSocket] Unable connect to the server, unauthorized.');
}
});
Future<void> tryConnect() async {
if (!_ua.isAuthorized) return;
log('[WebSocket] Connecting to the server...');
await connect();
}
Future<void> connect({noRetry = false}) async {

View File

@ -65,10 +65,6 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
return;
}
if (!await Gal.hasAccess(toAlbum: true)) {
if (!await Gal.requestAccess(toAlbum: true)) return;
}
setState(() => _isDownloading = true);
var extName = extension(item.name);
@ -85,6 +81,9 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
bool isSuccess = false;
try {
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
if (!await Gal.hasAccess(toAlbum: true)) {
if (!await Gal.requestAccess(toAlbum: true)) return;
}
await Gal.putImage(imagePath, album: 'Solar Network');
} else {
await FileSaver.instance.saveFile(
@ -104,11 +103,13 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
if (!mounted) return;
context.showSnackbar(
'attachmentSaved'.tr(),
action: SnackBarAction(
label: 'openInAlbum'.tr(),
onPressed: () async => Gal.open(),
),
(!kIsWeb && (Platform.isIOS || Platform.isAndroid)) ? 'attachmentSaved'.tr() : 'attachmentSavedDesktop'.tr(),
action: (!kIsWeb && (Platform.isIOS || Platform.isAndroid))
? SnackBarAction(
label: 'openInAlbum'.tr(),
onPressed: () async => Gal.open(),
)
: null,
);
}
@ -260,6 +261,7 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
).padding(right: 8),
),
InkWell(
borderRadius: const BorderRadius.all(Radius.circular(16)),
onTap: _isDownloading
? null
: () => _saveToAlbum(widget.data.length > 1 ? _pageController.page?.round() ?? 0 : 0),
@ -335,10 +337,11 @@ class _AttachmentZoomViewState extends State<AttachmentZoomView> {
'${item.size} Bytes',
style: metaTextStyle,
),
Text(
'${item.metadata['width']}x${item.metadata['height']}',
style: metaTextStyle,
),
if (item.metadata['width'] != null && item.metadata['height'] != null)
Text(
'${item.metadata['width']}x${item.metadata['height']}',
style: metaTextStyle,
),
if (item.metadata['ratio'] != null)
Text(
(item.metadata['ratio'] as num).toStringAsFixed(2),

View File

@ -12,13 +12,12 @@ import 'package:styled_widget/styled_widget.dart';
import 'package:surface/controllers/chat_message_controller.dart';
import 'package:surface/controllers/post_write_controller.dart';
import 'package:surface/providers/sn_attachment.dart';
import 'package:surface/providers/user_directory.dart';
import 'package:surface/types/chat.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/markdown_content.dart';
import 'package:surface/widgets/post/post_media_pending_list.dart';
import '../../providers/user_directory.dart';
class ChatMessageInput extends StatefulWidget {
final ChatMessageController controller;
final SnChannelMember? otherMember;

View File

@ -359,7 +359,7 @@ packages:
source: hosted
version: "0.7.10"
device_info_plus:
dependency: transitive
dependency: "direct main"
description:
name: device_info_plus
sha256: "4fa68e53e26ab17b70ca39f072c285562cfc1589df5bb1e9295db90f6645f431"
@ -1558,18 +1558,18 @@ packages:
dependency: transitive
description:
name: shared_preferences_android
sha256: "7f172d1b06de5da47b6264c2692ee2ead20bbbc246690427cdb4fc301cd0c549"
sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d"
url: "https://pub.dev"
source: hosted
version: "2.3.4"
version: "2.4.0"
shared_preferences_foundation:
dependency: transitive
description:
name: shared_preferences_foundation
sha256: "07e050c7cd39bad516f8d64c455f04508d09df104be326d8c02551590a0d513d"
sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03"
url: "https://pub.dev"
source: hosted
version: "2.5.3"
version: "2.5.4"
shared_preferences_linux:
dependency: transitive
description:

View File

@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.0.1+23
version: 2.0.1+24
environment:
sdk: ^3.5.4
@ -101,6 +101,7 @@ dependencies:
screenshot: ^3.0.0
qr_flutter: ^4.1.0
file_saver: ^0.2.14
device_info_plus: ^11.2.0
dev_dependencies:
flutter_test: