Compare commits

...

11 Commits

Author SHA1 Message Date
153c15e5c9 🚀 Launch 1.2.2+2 2024-09-18 13:05:08 +08:00
6a0f42cdc9 🐛 Fix realm view won't show channels 2024-09-18 13:03:40 +08:00
01aaa5455e 💄 Fix content padding mis-match 2024-09-18 00:14:16 +08:00
f3ceb5f967 🚀 Launch 1.2.2+1 2024-09-17 23:50:49 +08:00
b5e2fa4c25 🐛 Fix post editor alias overflow 2024-09-17 23:08:00 +08:00
8378024490 🚀 Launch 1.2.1+41 2024-09-17 22:31:37 +08:00
6d40d6bba3 💄 Optimize content 2024-09-17 21:48:20 +08:00
77075c8dab Optimize updater 2024-09-17 21:37:20 +08:00
dec34e297d 🐛 Bug fixes on attachments and related things 2024-09-17 20:59:01 +08:00
358677ade0 Android self-update 2024-09-17 20:40:44 +08:00
d2f37ae45d 🐛 Fix fileType render error 2024-09-17 18:28:53 +08:00
24 changed files with 314 additions and 150 deletions

View File

@ -1,4 +1,4 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="dev.solsynth.solian">
<uses-feature android:name="android.hardware.camera" />
<uses-feature android:name="android.hardware.camera.autofocus" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip

View File

@ -18,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.4.0' apply false
id "com.android.application" version '8.6.0' apply false
id "com.google.gms.google-services" version "4.3.15" apply false
id "com.google.firebase.crashlytics" version "2.8.1" apply false
id "org.jetbrains.kotlin.android" version '2.0.0' apply false

View File

@ -55,7 +55,7 @@
"edit": "Edit",
"delete": "Delete",
"settings": "Settings",
"settingsNotificationBgService": "Background Notification Service",
"settingsNotificationBgService": "Background notification service",
"settingsNotificationBgServiceDesc": "A notification service is always installed on the device, so that some devices that do not support push notifications can receive notifications in the background. When this feature is enabled, push notifications will not be registered with the server, and you will always appear to be online in the eyes of others (except for invisible). You may need to turn off power and traffic optimization in the settings.",
"search": "Search",
"post": "Post",
@ -68,6 +68,11 @@
"notificationUnreadCount": "@count unread notifications",
"errorHappened": "An error occurred",
"errorHappenedUnauthorized": "Unauthorized request, please sign in or try resign in.",
"errorHappenedRequestBad": "Request error, the server refused to process the request. Please check your request data.",
"errorHappenedRequestForbidden": "Request error, insufficient permissions.",
"errorHappenedRequestNotFound": "Request error, the requested data does not exist.",
"errorHappenedRequestConnection": "Network request failed. Please check the connection status and service status, then try again.",
"errorHappenedRequestUnknown": "Request error, unknown type. Please take a full screenshot of this message and submit feedback.",
"forgotPassword": "Forgot password",
"email": "Email",
"username": "Username",
@ -350,8 +355,7 @@
"bsCheckForUpdate": "Checking For Updates",
"bsCheckForUpdateFailed": "Unable to Check Updates",
"bsCheckForUpdateNew": "Found New Version",
"bsCheckForUpdateDescApple": "Please head to TestFlight and update your app to latest version to prevent error happens and get latest functions.",
"bsCheckForUpdateDescCommon": "Please head to our website download and install latest version of application to prevent error happens and get latest functions.",
"bsCheckForUpdateDesc": "Please head to app store and update your app to latest version to prevent error happens and get latest functions.",
"bsCheckingServer": "Checking Server Status",
"bsCheckingServerFail": "Unable connect to server, check your network connection",
"bsCheckingServerDown": "Server currently unavailable, please retry later",
@ -422,5 +426,11 @@
"notificationTopicPostFeedback": "Post feedbacks",
"notificationTopicPostSubscription": "Post subscriptions",
"preferencesApplied": "Preferences has been applied.",
"save": "Save"
"save": "Save",
"updateAvailable": "Update available",
"updateAvailableDesc": "There is an update available (@version). Do you want to download and install it now? You can still use the app normally while waiting for the download to complete.",
"update": "Update",
"updateCheckStrictly": "Strict mode",
"updateCheckStrictlyDesc": "If enabled, the app will ask for updating once the local version is different from remote one.",
"updateMayAvailable": "App version @version is available, you can update from app store or our website."
}

View File

@ -351,8 +351,7 @@
"bsCheckForUpdate": "正在检查更新",
"bsCheckForUpdateFailed": "无法检查更新",
"bsCheckForUpdateNew": "发现新版本",
"bsCheckForUpdateDescApple": "请前往 TestFlight 并将您的应用程序更新到最新版本,以防止出现错误并获取最新功能。",
"bsCheckForUpdateDescCommon": "请前往我们的网站下载并安装最新版本的应用程序,以防止出现错误并获取最新功能。",
"bsCheckForUpdateDesc": "请前往应用商店并将您的应用程序更新到最新版本,以防止出现错误并获取最新功能。",
"bsCheckingServer": "检查服务器状态中",
"bsCheckingServerFail": "无法连接至服务器,请检查你的网络连接状态",
"bsCheckingServerDown": "当前服务器不可用,请稍后重试",
@ -423,5 +422,11 @@
"notificationTopicPostFeedback": "帖子反馈",
"notificationTopicPostSubscription": "订阅源",
"preferencesApplied": "偏好设置已应用",
"save": "保存"
"save": "保存",
"updateAvailable": "有可用更新",
"updateAvailableDesc": "有可用更新 (@from 到 @to) 你想现在下载安装吗?在等待下载期间你仍可以正常使用。",
"update": "更新",
"updateCheckStrictly": "严格模式",
"updateCheckStrictlyDesc": "如果启用,应用程序将会在本地版本与远程版本不同时询问更新,而不会检查版本号大小。",
"updateMayAvailable": "版本 @version 现已可用,你可以前往应用商店或是我们的官网下载更新。"
}

View File

@ -54,22 +54,22 @@ PODS:
- Firebase/Performance (11.0.0):
- Firebase/CoreOnly
- FirebasePerformance (~> 11.0.0)
- firebase_analytics (11.3.1):
- firebase_analytics (11.3.2):
- Firebase/Analytics (= 11.0.0)
- firebase_core
- Flutter
- firebase_core (3.4.1):
- firebase_core (3.5.0):
- Firebase/CoreOnly (= 11.0.0)
- Flutter
- firebase_crashlytics (4.1.1):
- firebase_crashlytics (4.1.2):
- Firebase/Crashlytics (= 11.0.0)
- firebase_core
- Flutter
- firebase_messaging (15.1.1):
- firebase_messaging (15.1.2):
- Firebase/Messaging (= 11.0.0)
- firebase_core
- Flutter
- firebase_performance (0.10.0-6):
- firebase_performance (0.10.0-7):
- Firebase/Performance (= 11.0.0)
- firebase_core
- Flutter
@ -154,6 +154,8 @@ PODS:
- PromisesSwift (~> 2.1)
- FirebaseSharedSwift (11.2.0)
- Flutter (1.0.0)
- flutter_app_update (0.0.1):
- Flutter
- flutter_background_service_ios (0.0.3):
- Flutter
- flutter_keyboard_visibility (0.0.1):
@ -306,6 +308,7 @@ DEPENDENCIES:
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- firebase_performance (from `.symlinks/plugins/firebase_performance/ios`)
- Flutter (from `Flutter`)
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
- flutter_background_service_ios (from `.symlinks/plugins/flutter_background_service_ios/ios`)
- flutter_keyboard_visibility (from `.symlinks/plugins/flutter_keyboard_visibility/ios`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
@ -383,6 +386,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/firebase_performance/ios"
Flutter:
:path: Flutter
flutter_app_update:
:path: ".symlinks/plugins/flutter_app_update/ios"
flutter_background_service_ios:
:path: ".symlinks/plugins/flutter_background_service_ios/ios"
flutter_keyboard_visibility:
@ -445,11 +450,11 @@ SPEC CHECKSUMS:
DKPhotoGallery: b3834fecb755ee09a593d7c9e389d8b5d6deed60
file_picker: 09aa5ec1ab24135ccd7a1621c46c84134bfd6655
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9
firebase_analytics: b8ce6c2c4b245d3c3bb3a147965d09da0f455959
firebase_core: ba84e940cf5cbbc601095f86556560937419195c
firebase_crashlytics: 4111f8198b78c99471c955af488cecd8224967e6
firebase_messaging: c40f84e7a98da956d5262fada373b5c458edcf13
firebase_performance: 8b7b9ca5adf3a9b3afa12b4eb96b9cabefc2c248
firebase_analytics: 4fd10182fd08bb8358f26ac8aca8dad7b6d0f592
firebase_core: 2ec6b789859c7c24766344ec71fdf78639402d56
firebase_crashlytics: 60630a0f91ee432275fa1660fd8593079761448a
firebase_messaging: a18e1e02b2e8e69097c8173e0c851be223b21c50
firebase_performance: 12d45fdf120992fa879d990929bf73d4a5ced053
FirebaseABTesting: 2104d957ce33888a3d6f3bde298cdee376dde8f1
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
@ -464,6 +469,7 @@ SPEC CHECKSUMS:
FirebaseSessions: adcec8b72d0066a385e3affcd1bcb1ebb3908ce6
FirebaseSharedSwift: 7a0d78d155ede78407f0fdc89fbc914014c7c540
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_app_update: 65f61da626cb111d1b24674abc4b01728d7723bc
flutter_background_service_ios: e30e0d3ee69e4cee66272d0c78eacd48c2e94aac
flutter_keyboard_visibility: 0339d06371254c3eb25eeb90ba8d17dca8f9c069
flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086

View File

@ -1,8 +1,11 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:gap/gap.dart';
import 'package:get/get.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:solian/exts.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/auth.dart';
@ -13,6 +16,8 @@ import 'package:solian/providers/theme_switcher.dart';
import 'package:solian/providers/websocket.dart';
import 'package:solian/services.dart';
import 'package:solian/widgets/sized_container.dart';
import 'package:flutter_app_update/flutter_app_update.dart';
import 'package:version/version.dart';
class BootstrapperShell extends StatefulWidget {
final Widget child;
@ -34,6 +39,8 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
int _periodCursor = 0;
final Completer _bootCompleter = Completer();
late final List<({String label, Future<void> Function() action})> _periods = [
(
label: 'bsLoadingTheme',
@ -46,24 +53,60 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
action: () async {
if (PlatformInfo.isWeb) return;
try {
final prefs = await SharedPreferences.getInstance();
final info = await PackageInfo.fromPlatform();
final localVersionString = '${info.version}+${info.buildNumber}';
final resp = await GetConnect().get(
'https://git.solsynth.dev/api/v1/repos/hydrogen/solian/tags?limit=1',
'https://git.solsynth.dev/api/v1/repos/hydrogen/solian/tags?page=1&limit=1',
);
if (resp.body[0]['name'] != localVersionString) {
final remoteVersionString =
(resp.body as List).firstOrNull?['name'] ?? '0.0.0';
final remoteVersion = Version.parse(remoteVersionString ?? '0.0.0');
final localVersion =
Version.parse(localVersionString.split('+').first);
final strictUpdate = prefs.getBool('check_update_strictly') ?? false;
if (remoteVersion > localVersion ||
(remoteVersionString != localVersionString && strictUpdate)) {
setState(() {
_isErrored = true;
_subtitle = PlatformInfo.isIOS || PlatformInfo.isMacOS
? 'bsCheckForUpdateDescApple'.tr
: 'bsCheckForUpdateDescCommon'.tr;
_subtitle = 'bsCheckForUpdateDesc'.tr;
});
if (PlatformInfo.isAndroid) {
context
.showConfirmDialog(
'updateAvailable'.tr,
'updateAvailableDesc'.trParams({
'from': localVersionString,
'to': remoteVersionString,
}),
)
.then((result) {
if (result) {
final model = UpdateModel(
'https://files.solsynth.dev/d/production01/solian/app-arm64-v8a-release.apk',
'solian-app-arm64-v8a-release.apk',
'ic_launcher',
'https://testflight.apple.com/join/YJ0lmN6O',
);
AzhonAppUpdate.update(model);
}
});
} else {
setState(() {
_isErrored = true;
_subtitle = 'bsCheckForUpdateDesc'.tr;
});
}
} else if (remoteVersionString != localVersionString) {
_bootCompleter.future.then((_) {
context.showSnackbar('updateMayAvailable'.trParams({
'version': remoteVersionString,
}));
});
}
} catch (e) {
setState(() {
_isErrored = true;
_subtitle = 'bsCheckForUpdateFailed'.tr;
});
context.showErrorDialog('Unable to check update: $e');
}
},
),
@ -154,6 +197,9 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
}
} finally {
setState(() => _isBusy = false);
Future.delayed(const Duration(milliseconds: 100), () {
_bootCompleter.complete();
});
}
}
@ -251,6 +297,9 @@ class _BootstrapperShellState extends State<BootstrapperShell> {
_isBusy = false;
_isErrored = false;
});
Future.delayed(const Duration(milliseconds: 100), () {
_bootCompleter.complete();
});
} else {
setState(() {
_isBusy = true;

View File

@ -51,6 +51,28 @@ extension AppExtensions on BuildContext {
);
}
Future<bool> showConfirmDialog(String title, body) async {
return await showDialog<bool>(
useRootNavigator: true,
context: this,
builder: (ctx) => AlertDialog(
title: Text(title),
content: Text(body),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx, false),
child: Text('cancel'.tr),
),
TextButton(
onPressed: () => Navigator.pop(ctx, true),
child: Text('okay'.tr),
)
],
),
) ??
false;
}
Future<void> showErrorDialog(dynamic exception) {
Widget content = Text(exception.toString().capitalize!);
if (exception is UnauthorizedException) {

View File

@ -9,7 +9,6 @@ import 'package:go_router/go_router.dart';
import 'package:protocol_handler/protocol_handler.dart';
import 'package:provider/provider.dart';
import 'package:solian/background.dart';
import 'package:solian/bootstrapper.dart';
import 'package:solian/firebase_options.dart';
import 'package:solian/platform.dart';
import 'package:solian/providers/attachment_uploader.dart';
@ -123,9 +122,7 @@ class SolianApp extends StatelessWidget {
builder: (context, child) {
return SystemShell(
child: ScaffoldMessenger(
child: BootstrapperShell(
child: child ?? const SizedBox.shrink(),
),
child: child ?? const SizedBox.shrink(),
),
);
},

View File

@ -89,13 +89,13 @@ class ChannelProvider extends GetxController {
return resp;
}
Future<Response> listAvailableChannel({String realm = 'global'}) async {
Future<Response> listAvailableChannel({String scope = 'global'}) async {
final AuthProvider auth = Get.find();
if (auth.isAuthorized.isFalse) throw const UnauthorizedException();
final client = await auth.configureClient('messaging');
final resp = await client.get('/channels/$realm/me/available');
final resp = await client.get('/channels/$scope/me/available');
if (resp.statusCode != 200) {
throw RequestException(resp);
}

View File

@ -1,6 +1,7 @@
import 'package:animations/animations.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:solian/bootstrapper.dart';
import 'package:solian/models/realm.dart';
import 'package:solian/screens/about.dart';
import 'package:solian/screens/account.dart';
@ -32,9 +33,12 @@ abstract class AppRouter {
static GoRouter instance = GoRouter(
routes: [
ShellRoute(
builder: (context, state, child) => RootShell(
state: state,
child: child,
builder: (context, state, child) => BootstrapperShell(
key: const Key('global-bootstrapper'),
child: RootShell(
state: state,
child: child,
),
),
routes: [
GoRoute(

View File

@ -115,6 +115,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
leading: const Icon(Icons.settings),
trailing: const Icon(Icons.chevron_right),
title: Text('channelSettings'.tr),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
onTap: () async {
AppRouter.instance
.pushNamed(
@ -174,6 +175,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
ListTile(
leading: const Icon(Icons.notifications_active),
title: Text('channelNotifyLevel'.tr),
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
trailing: DropdownButtonHideUnderline(
child: DropdownButton2<int>(
isExpanded: true,
@ -206,6 +208,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
),
),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: const Icon(Icons.supervisor_account),
trailing: const Icon(Icons.chevron_right),
title: Text('channelMembers'.tr),
@ -214,6 +217,7 @@ class _ChannelDetailScreenState extends State<ChannelDetailScreen> {
...(_isOwned ? ownerActions : List.empty()),
const Divider(thickness: 0.3),
ListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 24),
leading: _isOwned
? const Icon(Icons.delete)
: const Icon(Icons.exit_to_app),

View File

@ -183,18 +183,18 @@ class _PostPublishScreenState extends State<PostPublishScreen> {
children: [
ListTile(
tileColor: Theme.of(context).colorScheme.surfaceContainerLow,
title: Row(
title: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_editorController.title ?? 'title'.tr,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
const Gap(6),
if (_editorController.aliasController.text.isNotEmpty)
Badge(
label: Text('#${_editorController.aliasController.text}'),
),
).paddingOnly(bottom: 2),
],
),
subtitle: Text(

View File

@ -34,7 +34,7 @@ class _RealmViewScreenState extends State<RealmViewScreen> {
final List<Channel> _channels = List.empty(growable: true);
getRealm({String? overrideAlias}) async {
final RealmProvider provider = Get.find();
final RealmProvider realm = Get.find();
setState(() => _isBusy = true);
@ -43,7 +43,7 @@ class _RealmViewScreenState extends State<RealmViewScreen> {
}
try {
final resp = await provider.getRealm(_overrideAlias ?? widget.alias);
final resp = await realm.getRealm(_overrideAlias ?? widget.alias);
setState(() => _realm = Realm.fromJson(resp.body));
} catch (e) {
context.showErrorDialog(e);
@ -55,14 +55,26 @@ class _RealmViewScreenState extends State<RealmViewScreen> {
getChannels() async {
setState(() => _isBusy = true);
final ChannelProvider provider = Get.find();
final resp = await provider.listChannel(scope: _realm!.alias);
final ChannelProvider channel = Get.find();
final resp = await channel.listChannel(scope: _realm!.alias);
final availableResp = await channel.listAvailableChannel(
scope: _realm!.alias,
);
final Set<int> channelIdx = {};
setState(() {
_channels.clear();
_channels.addAll(
resp.body.map((e) => Channel.fromJson(e)).toList().cast<Channel>(),
);
_channels.addAll(
availableResp.body
.map((e) => Channel.fromJson(e))
.toList()
.cast<Channel>(),
);
_channels.retainWhere((x) => channelIdx.add(x.id));
});
setState(() => _isBusy = false);

View File

@ -114,6 +114,21 @@ class _SettingScreenState extends State<SettingScreen> {
},
),
),
_buildCaptionHeader('update'.tr),
CheckboxListTile(
contentPadding: const EdgeInsets.symmetric(horizontal: 22),
secondary: const Icon(Icons.sync_alt),
title: Text('updateCheckStrictly'.tr),
subtitle: Text('updateCheckStrictlyDesc'.tr),
value: _prefs?.getBool('check_update_strictly') ?? false,
onChanged: (value) {
_prefs
?.setBool('check_update_strictly', value ?? false)
.then((_) {
setState(() {});
});
},
),
_buildCaptionHeader('more'.tr),
ListTile(
leading: const Icon(Icons.delete_sweep),

View File

@ -484,7 +484,7 @@ class _AttachmentEditorPopupState extends State<AttachmentEditorPopup> {
),
),
Text(
'${fileType[0].toUpperCase()}${fileType.substring(1)} · ${element.size.formatBytes()}',
'${fileType.isNotEmpty ? fileType.capitalize : 'unknown'.tr} · ${element.size.formatBytes()}',
style: const TextStyle(fontSize: 12),
),
],

View File

@ -233,6 +233,7 @@ class _AttachmentItemVideoState extends State<_AttachmentItemVideo> {
final ratio = widget.item.metadata?['ratio'] ?? 16 / 9;
if (!_showContent) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
child: Stack(
children: [
if (widget.item.metadata?['thumbnail'] != null)
@ -400,6 +401,7 @@ class _AttachmentItemAudioState extends State<_AttachmentItemAudio> {
const ratio = 16 / 9;
if (!_showContent) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
child: Stack(
children: [
if (widget.item.metadata?['thumbnail'] != null)

View File

@ -10,6 +10,7 @@ class AutoCacheImage extends StatelessWidget {
final BoxFit? fit;
final bool noProgressIndicator;
final bool noErrorWidget;
final bool isDense;
const AutoCacheImage(
this.url, {
@ -19,6 +20,7 @@ class AutoCacheImage extends StatelessWidget {
this.fit,
this.noProgressIndicator = false,
this.noErrorWidget = false,
this.isDense = false,
});
@override
@ -46,13 +48,14 @@ class AutoCacheImage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.close, size: 32)
Icon(Icons.close, size: isDense ? 24 : 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
Text(
error.toString(),
textAlign: TextAlign.center,
),
if (!isDense)
Text(
error.toString(),
textAlign: TextAlign.center,
),
],
),
),
@ -89,13 +92,14 @@ class AutoCacheImage extends StatelessWidget {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Icon(Icons.close, size: 32)
Icon(Icons.close, size: isDense ? 24 : 32)
.animate(onPlay: (e) => e.repeat(reverse: true))
.fade(duration: 500.ms),
Text(
error.toString(),
textAlign: TextAlign.center,
),
if (!isDense)
Text(
error.toString(),
textAlign: TextAlign.center,
),
],
),
),

View File

@ -415,6 +415,7 @@ class _ChatMessageInputState extends State<ChatMessageInput> {
x.imageUrl,
width: 28,
height: 28,
isDense: true,
),
display: x.name,
content: x.textWarpedPlaceholder,

View File

@ -14,12 +14,14 @@ class MarkdownTextContent extends StatelessWidget {
final String content;
final String parentId;
final bool isSelectable;
final bool isLargeText;
const MarkdownTextContent({
super.key,
required this.content,
required this.parentId,
this.isSelectable = false,
this.isLargeText = false,
});
Widget _buildContent(BuildContext context) {
@ -35,6 +37,14 @@ class MarkdownTextContent extends StatelessWidget {
styleSheet: MarkdownStyleSheet.fromTheme(
Theme.of(context),
).copyWith(
textScaleFactor: isLargeText ? 1.1 : 1,
blockquote: TextStyle(
color: Theme.of(context).colorScheme.onSurfaceVariant,
),
blockquoteDecoration: BoxDecoration(
color: Theme.of(context).colorScheme.surfaceContainerHigh,
borderRadius: const BorderRadius.all(Radius.circular(4)),
),
horizontalRuleDecoration: BoxDecoration(
border: Border(
top: BorderSide(

View File

@ -341,64 +341,67 @@ class _PostItemState extends State<PostItem> {
if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const SizedBox.shrink();
}
return Card(
margin: EdgeInsets.zero,
child: Column(
children: snapshot.data!
.map(
(x) => Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountAvatar(content: x.author.avatar, radius: 10),
const Gap(6),
Text(
x.author.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
const Gap(6),
Text(
format(
x.publishedAt?.toLocal() ?? DateTime.now(),
locale: 'en_short',
return Container(
constraints: const BoxConstraints(maxWidth: 480),
child: Card(
margin: EdgeInsets.zero,
child: Column(
children: snapshot.data!
.map(
(x) => Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
AccountAvatar(content: x.author.avatar, radius: 10),
const Gap(6),
Text(
x.author.nick,
style: const TextStyle(fontWeight: FontWeight.bold),
),
).paddingOnly(top: 0.5),
const Gap(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MarkdownTextContent(
content: x.body['content'],
parentId: 'p${item.id}-featured-reply${x.id}',
),
if (x.body['attachments'] is List &&
x.body['attachments'].length > 0)
Row(
children: [
Icon(
Icons.file_copy,
size: 15,
color: unFocusColor,
).paddingOnly(right: 5),
Text(
'attachmentHint'.trParams(
{
'count': x.body['attachments'].length
.toString()
},
),
style: TextStyle(color: unFocusColor),
)
],
const Gap(6),
Text(
format(
x.publishedAt?.toLocal() ?? DateTime.now(),
locale: 'en_short',
),
).paddingOnly(top: 0.5),
const Gap(8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
MarkdownTextContent(
content: x.body['content'],
parentId: 'p${item.id}-featured-reply${x.id}',
),
],
if (x.body['attachments'] is List &&
x.body['attachments'].length > 0)
Row(
children: [
Icon(
Icons.file_copy,
size: 15,
color: unFocusColor,
).paddingOnly(right: 5),
Text(
'attachmentHint'.trParams(
{
'count': x.body['attachments'].length
.toString()
},
),
style: TextStyle(color: unFocusColor),
)
],
),
],
),
),
),
],
).paddingSymmetric(horizontal: 12, vertical: 8),
)
.toList(),
],
).paddingSymmetric(horizontal: 12, vertical: 8),
)
.toList(),
),
),
)
.animate()
@ -550,6 +553,8 @@ class _PostItemState extends State<PostItem> {
parentId: 'p${item.id}-embed',
content: item.body['content'],
isSelectable: widget.isContentSelectable,
isLargeText: item.type == 'article' &&
widget.isFullContent,
).paddingOnly(left: 12, right: 8),
),
),

View File

@ -21,19 +21,19 @@ PODS:
- Firebase/Messaging (11.0.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 11.0.0)
- firebase_analytics (11.3.1):
- firebase_analytics (11.3.2):
- Firebase/Analytics (= 11.0.0)
- firebase_core
- FlutterMacOS
- firebase_core (3.4.1):
- firebase_core (3.5.0):
- Firebase/CoreOnly (~> 11.0.0)
- FlutterMacOS
- firebase_crashlytics (4.1.1):
- firebase_crashlytics (4.1.2):
- Firebase/CoreOnly (~> 11.0.0)
- Firebase/Crashlytics (~> 11.0.0)
- firebase_core
- FlutterMacOS
- firebase_messaging (15.1.1):
- firebase_messaging (15.1.2):
- Firebase/CoreOnly (~> 11.0.0)
- Firebase/Messaging (~> 11.0.0)
- firebase_core
@ -338,10 +338,10 @@ SPEC CHECKSUMS:
device_info_plus: ce1b7762849d3ec103d0e0517299f2db7ad60720
file_selector_macos: 54fdab7caa3ac3fc43c9fac4d7d8d231277f8cf2
Firebase: 9f574c08c2396885b5e7e100ed4293d956218af9
firebase_analytics: 2169e28bb3ee1f765efe0fd4f5b5f625d92fda13
firebase_core: 3f80bec72646b26618f0497e74ce8bcd608f03ca
firebase_crashlytics: 6b1e511297406a6efd4405dc6301da8843b9dfe1
firebase_messaging: ce70e6615f0cd906d80b7a651b960d76dad6de56
firebase_analytics: a2d0d907566e4a48e27745317f05b4b7db85edd9
firebase_core: c55630cdb8a01cf49eae741dd4bc8c93bdd546b8
firebase_crashlytics: a359f1f75a23e560c8c97b743ab684ba795d7688
firebase_messaging: 12726b352752420d073ff075e328cc2f0ca14c47
FirebaseAnalytics: 27eb78b97880ea4a004839b9bac0b58880f5a92a
FirebaseCore: 3cf438f431f18c12cdf2aaf64434648b63f7e383
FirebaseCoreExtension: cda74ddfb001224bd8fd1d6e74698b4ed07803de

View File

@ -13,10 +13,10 @@ packages:
dependency: transitive
description:
name: _flutterfire_internals
sha256: ddc6f775260b89176d329dee26f88b9469ef46aa3228ff6a0b91caf2b2989692
sha256: "5fdcea390499dd26c808a3c662df5f4208d6bbc0643072eee94f1476249e2818"
url: "https://pub.dev"
source: hosted
version: "1.3.42"
version: "1.3.43"
_macros:
dependency: transitive
description: dart
@ -490,114 +490,114 @@ packages:
dependency: "direct main"
description:
name: firebase_analytics
sha256: "7b5ae39d853ead76f9d030dc23389bfec4ea826d7cccb4eea4873dcb0cdd172b"
sha256: "9c52c099e9cbb852c7f1d2302c7eb34a15758834eca1877f7a779e6082f9882d"
url: "https://pub.dev"
source: hosted
version: "11.3.1"
version: "11.3.2"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
sha256: "0205e05bb37abd29d5dec5cd89aeb04f3f58bf849aad21dd938be0507d52a40c"
sha256: "4ec57aee951832fdbf10ca722bbb83fe0001d6168d6c4cfea9ccee0df6afb1e0"
url: "https://pub.dev"
source: hosted
version: "4.2.3"
version: "4.2.4"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
sha256: "434807f8b30526e21cc062410c28ee5c6680a13626c4443b5ffede29f84b0c74"
sha256: "95c594fb1e8960992a607b135459e2f9ea3683dd8d01e6b845cace7c6665ec7e"
url: "https://pub.dev"
source: hosted
version: "0.5.10"
version: "0.5.10+1"
firebase_core:
dependency: "direct main"
description:
name: firebase_core
sha256: "40921de9795fbf5887ed5c0adfdf4972d5a8d7ae7e1b2bb98dea39bc02626a88"
sha256: c7de9354eb2cd8bfe8059e1112174c9a58beda7051807207306bc48283277cfb
url: "https://pub.dev"
source: hosted
version: "3.4.1"
version: "3.5.0"
firebase_core_platform_interface:
dependency: transitive
description:
name: firebase_core_platform_interface
sha256: f7d7180c7f99babd4b4c517754d41a09a4943a0f7a69b65c894ca5c68ba66315
sha256: e30da58198a6d4b49d5bce4e852f985c32cb10db329ebef9473db2b9f09ce810
url: "https://pub.dev"
source: hosted
version: "5.2.1"
version: "5.3.0"
firebase_core_web:
dependency: transitive
description:
name: firebase_core_web
sha256: f4ee170441ca141c5f9ee5ad8737daba3ee9c8e7efb6902aee90b4fbd178ce25
sha256: f967a7138f5d2ffb1ce15950e2a382924239eaa521150a8f144af34e68b3b3e5
url: "https://pub.dev"
source: hosted
version: "2.18.0"
version: "2.18.1"
firebase_crashlytics:
dependency: "direct main"
description:
name: firebase_crashlytics
sha256: c4fdbb14ba6f36794f89dc27fb5c759c9cc67ecbaeb079edc4dba515bbf9f555
sha256: "7821f9d8373b91f2a5ca8214226891d5870e196a7376f66350f65204387e9c15"
url: "https://pub.dev"
source: hosted
version: "4.1.1"
version: "4.1.2"
firebase_crashlytics_platform_interface:
dependency: transitive
description:
name: firebase_crashlytics_platform_interface
sha256: "891d6f7ba4b93672d0e1265f27b6a9dccd56ba2cc30ce6496586b32d1d8770ac"
sha256: "8ed539fd9e9b6c07905f9f44c5f6d4785ac841a5a8195bd35586c8b1d54ec26d"
url: "https://pub.dev"
source: hosted
version: "3.6.42"
version: "3.6.43"
firebase_messaging:
dependency: "direct main"
description:
name: firebase_messaging
sha256: cc02c4afd6510cd84586020670140c4a23fbe52af16cd260ccf8ede101bb8d1b
sha256: "32ce60b747e755b48d7112d728d4f736ba82acd98ec825626558d444d385fa3a"
url: "https://pub.dev"
source: hosted
version: "15.1.1"
version: "15.1.2"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
sha256: d8a4984635f09213302243ea670fe5c42f3261d7d8c7c0a5f7dcd5d6c84be459
sha256: "69671a0f1a40c7b7c46ad0283e6f34ca2a59a0362ca14a240a4ea01c46e8a521"
url: "https://pub.dev"
source: hosted
version: "4.5.44"
version: "4.5.45"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
sha256: "258b9d637965db7855299b123533609ed95e52350746a723dfd1d8d6f3fac678"
sha256: "6890111a9d01d7b13d0f6fe74850812c334e903d2c80a2d9356a3abb8c3a9e9a"
url: "https://pub.dev"
source: hosted
version: "3.9.0"
version: "3.9.1"
firebase_performance:
dependency: "direct main"
description:
name: firebase_performance
sha256: "879ce4d83242cb7d1ec67a8b45daa351f230211778e34eeea979894839c4832a"
sha256: ed9a408b6d1f221fc0e2890dcf0733b604d1aea6cd3b897f97dd3f889f01ddfc
url: "https://pub.dev"
source: hosted
version: "0.10.0+6"
version: "0.10.0+7"
firebase_performance_platform_interface:
dependency: transitive
description:
name: firebase_performance_platform_interface
sha256: ac68eba644f593903a931ba7f26f0677b725d5a60f8f7bc0fed01d88a11d1463
sha256: bfcfbfcefeaf3853a72602675b786e13a609d49ac70fc325d302b5794b8b0c06
url: "https://pub.dev"
source: hosted
version: "0.1.4+42"
version: "0.1.4+43"
firebase_performance_web:
dependency: transitive
description:
name: firebase_performance_web
sha256: ff53b9c5d8601fc983d0173b88fdfd8abcc74948f0a3753f3c1ec276b680f23c
sha256: "2ac9e44a1be7b20f1a7a3912d84bf2e1ec76398f2dadc07b6b7c3173d590e329"
url: "https://pub.dev"
source: hosted
version: "0.1.7"
version: "0.1.7+1"
fixnum:
dependency: transitive
description:
@ -635,6 +635,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.5.0"
flutter_app_update:
dependency: "direct main"
description:
name: flutter_app_update
sha256: "2b83278d5cc807f543e623d5b466216316104335a4918d9cc4556f39985fe84a"
url: "https://pub.dev"
source: hosted
version: "3.1.0"
flutter_background_service:
dependency: "direct main"
description:
@ -2130,6 +2138,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.4"
version:
dependency: "direct main"
description:
name: version
sha256: "3d4140128e6ea10d83da32fef2fa4003fccbf6852217bb854845802f04191f94"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
very_good_infinite_list:
dependency: "direct main"
description:

View File

@ -2,7 +2,7 @@ name: solian
description: "The Solar Network App"
publish_to: "none"
version: 1.2.1+40
version: 1.2.2+2
environment:
sdk: ">=3.3.4 <4.0.0"
@ -80,6 +80,8 @@ dependencies:
path_provider: ^2.1.4
flutter_background_service: ^5.0.10
flutter_local_notifications: ^17.2.2
flutter_app_update: ^3.1.0
version: ^3.0.2
dev_dependencies:
flutter_test: