diff --git a/lib/screens/account/profile_page.dart b/lib/screens/account/profile_page.dart index f060d38..bffb4e6 100644 --- a/lib/screens/account/profile_page.dart +++ b/lib/screens/account/profile_page.dart @@ -348,7 +348,7 @@ class _AccountProfilePageState extends State { detail: _userinfo, profile: _userinfo!.profile, extraWidgets: [ - if (_dailySignRecords.isNotEmpty) + if (_dailySignRecords.length > 1) Card( child: SizedBox( height: 180, diff --git a/lib/screens/channel/call/call.dart b/lib/screens/channel/call/call.dart index bbb5330..b86763d 100644 --- a/lib/screens/channel/call/call.dart +++ b/lib/screens/channel/call/call.dart @@ -198,7 +198,7 @@ class _CallScreenState extends State with TickerProviderStateMixin { Widget build(BuildContext context) { final ChatCallProvider ctrl = Get.find(); - return RootContainer( + return ResponsiveRootContainer( child: Scaffold( appBar: widget.hideAppBar ? null diff --git a/lib/widgets/attachments/attachment_editor.dart b/lib/widgets/attachments/attachment_editor.dart index c21f696..6c043e5 100644 --- a/lib/widgets/attachments/attachment_editor.dart +++ b/lib/widgets/attachments/attachment_editor.dart @@ -229,7 +229,10 @@ class _AttachmentEditorPopupState extends State { .listMetadata(widget.initialAttachments ?? List.empty()) .then((result) { setState(() { - _attachments = List.from(result, growable: true); + _attachments = List.from( + result.where((x) => x != null), + growable: true, + ); _isBusy = false; _isFirstTimeBusy = false; }); diff --git a/lib/widgets/attachments/attachment_fullscreen.dart b/lib/widgets/attachments/attachment_fullscreen.dart index d5b266e..5b23c5e 100644 --- a/lib/widgets/attachments/attachment_fullscreen.dart +++ b/lib/widgets/attachments/attachment_fullscreen.dart @@ -265,8 +265,15 @@ class _AttachmentFullScreenState extends State { 'ISO${widget.item.metadata?['exif']?['ISO']}', style: metaTextStyle, ).paddingOnly(right: 2), - if (widget.item.metadata?['exif']?['Megapixels'] != + if (widget.item.metadata?['exif']?['Aperture'] != null) + Text( + 'f/${widget.item.metadata?['exif']?['Aperture']}', + style: metaTextStyle, + ).paddingOnly(right: 2), + if (widget.item.metadata?['exif']?['Megapixels'] != + null && + widget.item.metadata?['exif']?['Model'] != null) Text( '${widget.item.metadata?['exif']?['Megapixels']}MP', style: metaTextStyle, diff --git a/lib/widgets/markdown_text_content.dart b/lib/widgets/markdown_text_content.dart index fec48e1..e669863 100644 --- a/lib/widgets/markdown_text_content.dart +++ b/lib/widgets/markdown_text_content.dart @@ -1,8 +1,8 @@ +import 'dart:developer'; import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter_markdown/flutter_markdown.dart'; -import 'package:gap/gap.dart'; import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:markdown/markdown.dart' as markdown; @@ -15,7 +15,7 @@ import 'package:url_launcher/url_launcher_string.dart'; import 'account/account_profile_popup.dart'; -class MarkdownTextContent extends StatelessWidget { +class MarkdownTextContent extends StatefulWidget { final String content; final String parentId; final bool isSelectable; @@ -31,190 +31,191 @@ class MarkdownTextContent extends StatelessWidget { this.isAutoWarp = false, }); - Widget _buildContent(BuildContext context) { + @override + State createState() => _MarkdownTextContentState(); +} + +class _MarkdownTextContentState extends State { + final List _stickerSizes = []; + + @override + initState() { + super.initState(); final stickerRegex = RegExp(r':([-\w]+):'); // Split the content into paragraphs - final paragraphs = content.split(RegExp(r'\n\s*\n')); + final paragraphs = widget.content.split(RegExp(r'\n\s*\n')); // Iterate over each paragraph to process stickers individually - List contentWidgets = []; + for (var idx = 0; idx < paragraphs.length; idx++) { // Getting paragraph var paragraph = paragraphs[idx]; // Matching stickers final stickerMatch = stickerRegex.allMatches(paragraph); - final isOnlySticker = - paragraph.replaceAll(stickerRegex, '').trim().isEmpty; - - contentWidgets.add( - Markdown( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - data: paragraph, - padding: EdgeInsets.zero, - styleSheet: MarkdownStyleSheet.fromTheme( - Theme.of(context), - ).copyWith( - textScaler: TextScaler.linear(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( - width: 1.0, - color: Theme.of(context).dividerColor, - ), - ), - ), - codeblockDecoration: BoxDecoration( - border: Border.all( - color: Theme.of(context).dividerColor, - width: 0.3, - ), - borderRadius: const BorderRadius.all(Radius.circular(4)), - color: Theme.of(context).colorScheme.surface.withOpacity(0.5), - )), - builders: { - 'code': _MarkdownTextCodeElement(), - }, - softLineBreak: true, - extensionSet: markdown.ExtensionSet( - [ - markdown.CodeBlockSyntax(), - ...markdown.ExtensionSet.commonMark.blockSyntaxes, - ...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes, - ], - [ - if (isAutoWarp) markdown.LineBreakSyntax(), - _UserNameCardInlineSyntax(), - _CustomEmoteInlineSyntax(), - markdown.AutolinkSyntax(), - markdown.AutolinkExtensionSyntax(), - markdown.CodeSyntax(), - ...markdown.ExtensionSet.commonMark.inlineSyntaxes, - ...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes - ], - ), - onTapLink: (text, href, title) async { - if (href == null) return; - if (href.startsWith('solink://')) { - final segments = href.replaceFirst('solink://', '').split('/'); - switch (segments[0]) { - case 'users': - showModalBottomSheet( - useRootNavigator: true, - isScrollControlled: true, - backgroundColor: Theme.of(context).colorScheme.surface, - context: context, - builder: (context) => AccountProfilePopup( - name: segments[1], - ), - ); - } - return; - } - - await launchUrlString( - href, - mode: LaunchMode.externalApplication, - ); - }, - imageBuilder: (uri, title, alt) { - var url = uri.toString(); - double? width, height; - BoxFit? fit; - if (url.startsWith('solink://')) { - final segments = url.replaceFirst('solink://', '').split('/'); - switch (segments[0]) { - case 'stickers': - double radius = 8; - final StickerProvider sticker = Get.find(); - - // Adjust sticker size based on the sticker count in this paragraph - if (stickerMatch.length <= 1 && isOnlySticker) { - width = 128; - height = 128; - } else if (stickerMatch.length <= 3 && isOnlySticker) { - width = 32; - height = 32; - } else { - radius = 4; - width = 16; - height = 16; - } - fit = BoxFit.contain; - return ClipRRect( - borderRadius: BorderRadius.all(Radius.circular(radius)), - child: Container( - width: width, - height: height, - color: Theme.of(context).colorScheme.surfaceContainer, - child: FutureBuilder( - future: sticker.getStickerByAlias(segments[1]), - builder: (context, snapshot) { - if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator()); - } - return AutoCacheImage( - snapshot.data!.imageUrl, - width: width, - height: height, - fit: fit, - noErrorWidget: true, - ); - }, - ), - ), - ).paddingSymmetric(vertical: 4); - case 'attachments': - const radius = BorderRadius.all(Radius.circular(8)); - return LimitedBox( - maxHeight: MediaQuery.of(context).size.width, - child: ClipRRect( - borderRadius: radius, - child: AttachmentSelfContainedEntry( - isDense: true, - parentId: parentId, - rid: segments[1], - ), - ), - ).paddingSymmetric(vertical: 4); - } - } - return AutoCacheImage( - url, - width: width, - height: height, - fit: fit, - ); - }, - ), - ); - - if (idx < paragraphs.length - 1) { - contentWidgets.add(isAutoWarp ? const Gap(4) : const Gap(8)); + if (stickerMatch.length > 3) { + _stickerSizes.addAll(List.filled(stickerMatch.length, 16)); + } else if (stickerMatch.length > 1) { + _stickerSizes.addAll(List.filled(stickerMatch.length, 32)); + } else { + _stickerSizes.addAll(List.filled(stickerMatch.length, 128)); } } + } - // Return the list of widgets for the paragraphs - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: contentWidgets, + Widget _buildContent(BuildContext context) { + var stickerIdx = 0; + + return Markdown( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + data: widget.content, + padding: EdgeInsets.zero, + styleSheet: MarkdownStyleSheet.fromTheme( + Theme.of(context), + ).copyWith( + textScaler: TextScaler.linear(widget.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( + width: 1.0, + color: Theme.of(context).dividerColor, + ), + ), + ), + codeblockDecoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).dividerColor, + width: 0.3, + ), + borderRadius: const BorderRadius.all(Radius.circular(4)), + color: Theme.of(context).colorScheme.surface.withOpacity(0.5), + )), + builders: { + 'code': _MarkdownTextCodeElement(), + }, + softLineBreak: true, + extensionSet: markdown.ExtensionSet( + [ + markdown.CodeBlockSyntax(), + ...markdown.ExtensionSet.commonMark.blockSyntaxes, + ...markdown.ExtensionSet.gitHubFlavored.blockSyntaxes, + ], + [ + if (widget.isAutoWarp) markdown.LineBreakSyntax(), + _UserNameCardInlineSyntax(), + _CustomEmoteInlineSyntax(), + markdown.AutolinkSyntax(), + markdown.AutolinkExtensionSyntax(), + markdown.CodeSyntax(), + ...markdown.ExtensionSet.commonMark.inlineSyntaxes, + ...markdown.ExtensionSet.gitHubFlavored.inlineSyntaxes + ], + ), + onTapLink: (text, href, title) async { + if (href == null) return; + if (href.startsWith('solink://')) { + final segments = href.replaceFirst('solink://', '').split('/'); + switch (segments[0]) { + case 'users': + showModalBottomSheet( + useRootNavigator: true, + isScrollControlled: true, + backgroundColor: Theme.of(context).colorScheme.surface, + context: context, + builder: (context) => AccountProfilePopup( + name: segments[1], + ), + ); + } + return; + } + + await launchUrlString( + href, + mode: LaunchMode.externalApplication, + ); + }, + imageBuilder: (uri, title, alt) { + var url = uri.toString(); + double? width, height; + BoxFit? fit; + if (url.startsWith('solink://')) { + final segments = url.replaceFirst('solink://', '').split('/'); + switch (segments[0]) { + case 'stickers': + double radius = 4; + final StickerProvider sticker = Get.find(); + + // Adjust sticker size based on the sticker count in this paragraph + width = + _stickerSizes.elementAtOrNull(stickerIdx)?.toDouble() ?? 16; + height = + _stickerSizes.elementAtOrNull(stickerIdx)?.toDouble() ?? 16; + if (width > 16) { + radius = 8; + } + stickerIdx++; + fit = BoxFit.contain; + return ClipRRect( + borderRadius: BorderRadius.all(Radius.circular(radius)), + child: Container( + width: width, + height: height, + color: Theme.of(context).colorScheme.surfaceContainer, + child: FutureBuilder( + future: sticker.getStickerByAlias(segments[1]), + builder: (context, snapshot) { + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + return AutoCacheImage( + snapshot.data!.imageUrl, + width: width, + height: height, + fit: fit, + noErrorWidget: true, + ); + }, + ), + ), + ).paddingSymmetric(vertical: 4); + case 'attachments': + const radius = BorderRadius.all(Radius.circular(8)); + return LimitedBox( + maxHeight: MediaQuery.of(context).size.width, + child: ClipRRect( + borderRadius: radius, + child: AttachmentSelfContainedEntry( + isDense: true, + parentId: widget.parentId, + rid: segments[1], + ), + ), + ).paddingSymmetric(vertical: 4); + } + } + return AutoCacheImage( + url, + width: width, + height: height, + fit: fit, + ); + }, ); } @override Widget build(BuildContext context) { - if (isSelectable) { + if (widget.isSelectable) { return SelectionArea(child: _buildContent(context)); } return _buildContent(context); diff --git a/lib/widgets/posts/post_item.dart b/lib/widgets/posts/post_item.dart index 25fe291..662e66e 100644 --- a/lib/widgets/posts/post_item.dart +++ b/lib/widgets/posts/post_item.dart @@ -526,7 +526,7 @@ class _PostHeaderDividerWidget extends StatelessWidget { @override Widget build(BuildContext context) { if (item.body['description'] != null || item.body['title'] != null) { - return const Gap(8); + return const SizedBox(height: 8); } return const SizedBox.shrink(); } diff --git a/pubspec.lock b/pubspec.lock index 7709d69..f5f8353 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2214,10 +2214,10 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" uuid: dependency: "direct main" description: @@ -2350,10 +2350,10 @@ packages: dependency: transitive description: name: win32 - sha256: e5c39a90447e7c81cfec14b041cdbd0d0916bd9ebbc7fe02ab69568be703b9bd + sha256: "2294c64768987ea280b43a3d8357d42d5679f3e2b5b69b602be45b2abbd165b0" url: "https://pub.dev" source: hosted - version: "5.6.0" + version: "5.6.1" win32_registry: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index dc0baa5..0f79707 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,7 +2,7 @@ name: solian description: "The Solar Network App" publish_to: "none" -version: 1.4.0+16 +version: 1.4.0+17 environment: sdk: ">=3.3.4 <4.0.0"