diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 4e1915b..252a418 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -13,6 +13,11 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - sqflite_darwin (0.0.4): + - Flutter + - FlutterMacOS + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: - connectivity_plus (from `.symlinks/plugins/connectivity_plus/darwin`) @@ -21,6 +26,8 @@ DEPENDENCIES: - flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: connectivity_plus: @@ -35,6 +42,10 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/path_provider_foundation/darwin" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite_darwin: + :path: ".symlinks/plugins/sqflite_darwin/darwin" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: connectivity_plus: 4c41c08fc6d7c91f63bc7aec70ffe3730b04f563 @@ -43,6 +54,8 @@ SPEC CHECKSUMS: flutter_native_splash: edf599c81f74d093a4daf8e17bd7a018854bc778 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sqflite_darwin: 5a7236e3b501866c1c9befc6771dfd73ffb8702d + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/lib/main.dart b/lib/main.dart index f719219..d1aec19 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization_loader/easy_localization_loader.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:relative_time/relative_time.dart'; import 'package:responsive_framework/responsive_framework.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/providers/theme.dart'; @@ -40,7 +41,10 @@ class SolianApp extends StatelessWidget { darkTheme: th.theme.dark, locale: context.locale, supportedLocales: context.supportedLocales, - localizationsDelegates: context.localizationDelegates, + localizationsDelegates: [ + RelativeTimeLocalizations.delegate, + ...context.localizationDelegates, + ], routerConfig: appRouter, ); }), diff --git a/lib/providers/sn_network.dart b/lib/providers/sn_network.dart index 9d66983..b7f042a 100644 --- a/lib/providers/sn_network.dart +++ b/lib/providers/sn_network.dart @@ -27,9 +27,14 @@ class SnNetworkProvider { ], )); - if (!kIsWeb && Platform.isAndroid || Platform.isIOS || Platform.isMacOS) { + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) { // Switch to native implementation if possible client.httpClientAdapter = NativeAdapter(); } } + + String getAttachmentUrl(String ky) { + if (ky.startsWith("http://")) return ky; + return '${client.options.baseUrl}/cgi/uc/attachments/$ky'; + } } diff --git a/lib/screens/explore.dart b/lib/screens/explore.dart index 1b3a53d..bd042a6 100644 --- a/lib/screens/explore.dart +++ b/lib/screens/explore.dart @@ -4,6 +4,7 @@ import 'package:provider/provider.dart'; import 'package:surface/providers/sn_network.dart'; import 'package:surface/types/post.dart'; import 'package:surface/widgets/navigation/app_scaffold.dart'; +import 'package:surface/widgets/post/post_item.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; class ExploreScreen extends StatefulWidget { @@ -17,16 +18,17 @@ class _ExploreScreenState extends State { bool _isBusy = true; final List _posts = List.empty(growable: true); - int _postCount = 0; + int? _postCount; void _fetchPosts() async { + if (_postCount != null && _posts.length >= _postCount!) return; + setState(() => _isBusy = true); final sn = context.read(); - final resp = await sn.client.get('/cgi/co/posts', queryParameters: { 'take': 10, - 'offset': 0, + 'offset': _posts.length, }); _postCount = resp.data['count']; @@ -51,10 +53,12 @@ class _ExploreScreenState extends State { body: InfiniteList( itemCount: _posts.length, isLoading: _isBusy, + hasReachedMax: _postCount != null && _posts.length >= _postCount!, onFetchData: _fetchPosts, itemBuilder: (context, idx) { - return Text(_posts[idx].toString()); + return PostItem(data: _posts[idx]); }, + separatorBuilder: (context, index) => const Divider(), ), ); } diff --git a/lib/widgets/account/account_image.dart b/lib/widgets/account/account_image.dart new file mode 100644 index 0000000..ccb62fe --- /dev/null +++ b/lib/widgets/account/account_image.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; +import 'package:surface/providers/sn_network.dart'; +import 'package:surface/widgets/universal_image.dart'; + +class AccountImage extends StatelessWidget { + final String content; + final Color? backgroundColor; + final Color? foregroundColor; + final double? radius; + final Widget? fallbackWidget; + + const AccountImage({ + super.key, + required this.content, + this.backgroundColor, + this.foregroundColor, + this.radius, + this.fallbackWidget, + }); + + @override + Widget build(BuildContext context) { + final sn = context.read(); + final url = sn.getAttachmentUrl(content); + + final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; + return CircleAvatar( + key: Key('attachment-${content.hashCode}'), + radius: radius, + backgroundColor: backgroundColor, + backgroundImage: content.isNotEmpty + ? ResizeImage( + UniversalImage.provider(url), + width: ((radius ?? 20) * devicePixelRatio * 2).round(), + height: ((radius ?? 20) * devicePixelRatio * 2).round(), + ) + : null, + child: content.isEmpty + ? (fallbackWidget ?? + Icon( + Icons.account_circle, + size: radius != null ? radius! * 1.2 : 24, + color: foregroundColor, + )) + : null, + ); + } +} diff --git a/lib/widgets/markdown_content.dart b/lib/widgets/markdown_content.dart new file mode 100644 index 0000000..048a370 --- /dev/null +++ b/lib/widgets/markdown_content.dart @@ -0,0 +1,183 @@ +import 'dart:ui'; + +import 'package:flutter/material.dart'; +import 'package:flutter_markdown/flutter_markdown.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:markdown/markdown.dart' as markdown; +import 'package:styled_widget/styled_widget.dart'; +import 'package:surface/widgets/universal_image.dart'; +import 'package:syntax_highlight/syntax_highlight.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:path/path.dart'; + +class MarkdownTextContent extends StatefulWidget { + final String content; + final bool isSelectable; + final bool isLargeText; + final bool isAutoWarp; + + const MarkdownTextContent({ + super.key, + required this.content, + this.isSelectable = false, + this.isLargeText = false, + this.isAutoWarp = false, + }); + + @override + State createState() => _MarkdownTextContentState(); +} + +class _MarkdownTextContentState extends State { + Widget _buildContent(BuildContext context) { + 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(), + 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('/'); + 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('/'); + return const SizedBox.shrink(); + } + return UniversalImage( + url, + width: width, + height: height, + fit: fit, + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + if (widget.isSelectable) { + return SelectionArea(child: _buildContent(context)); + } + return _buildContent(context); + } +} + +class _UserNameCardInlineSyntax extends markdown.InlineSyntax { + _UserNameCardInlineSyntax() : super(r'@[a-zA-Z0-9_]+'); + + @override + bool onMatch(markdown.InlineParser parser, Match match) { + final alias = match[0]!; + final anchor = markdown.Element.text('a', alias) + ..attributes['href'] = Uri.encodeFull( + 'solink://users/${alias.substring(1)}', + ); + parser.addNode(anchor); + + return true; + } +} + +class _MarkdownTextCodeElement extends MarkdownElementBuilder { + @override + Widget? visitElementAfter( + markdown.Element element, + TextStyle? preferredStyle, + ) { + var language = ''; + + if (element.attributes['class'] != null) { + String lg = element.attributes['class'] as String; + language = lg.substring(9).trim(); + } + return SizedBox( + child: FutureBuilder( + future: (() async { + final docPath = '../../../'; + final highlightingPath = + join(docPath, 'assets/highlighting', language); + await Highlighter.initialize([highlightingPath]); + return Highlighter( + language: highlightingPath, + theme: PlatformDispatcher.instance.platformBrightness == + Brightness.light + ? await HighlighterTheme.loadLightTheme() + : await HighlighterTheme.loadDarkTheme(), + ); + })(), + builder: (context, snapshot) { + if (snapshot.hasData) { + final highlighter = snapshot.data!; + return Text.rich( + highlighter.highlight(element.textContent.trim()), + style: GoogleFonts.robotoMono(), + ); + } + return Text( + element.textContent.trim(), + style: GoogleFonts.robotoMono(), + ); + }, + ), + ).padding(all: 8); + } +} diff --git a/lib/widgets/post/post_item.dart b/lib/widgets/post/post_item.dart index 8c21739..1a1a91d 100644 --- a/lib/widgets/post/post_item.dart +++ b/lib/widgets/post/post_item.dart @@ -1,5 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:material_symbols_icons/symbols.dart'; +import 'package:relative_time/relative_time.dart'; +import 'package:styled_widget/styled_widget.dart'; import 'package:surface/types/post.dart'; +import 'package:surface/widgets/account/account_image.dart'; +import 'package:surface/widgets/markdown_content.dart'; +import 'package:gap/gap.dart'; class PostItem extends StatelessWidget { final SnPost data; @@ -8,16 +14,61 @@ class PostItem extends StatelessWidget { @override Widget build(BuildContext context) { return Column( - children: [], + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _PostContentHeader(data: data), + _PostContentBody(data: data.body).padding(horizontal: 16, bottom: 6), + ], ); } } -class _PostContentPublisher extends StatelessWidget { - const _PostContentPublisher({super.key}); +class _PostContentHeader extends StatelessWidget { + final SnPost data; + const _PostContentHeader({required this.data}); @override Widget build(BuildContext context) { - return const Placeholder(); + return Row( + children: [ + AccountImage(content: data.publisher.avatar), + const Gap(12), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(data.publisher.nick).bold(), + Row( + children: [ + Text('@${data.publisher.name}').fontSize(13), + const Gap(4), + Text(RelativeTime(context).format(data.publishedAt)) + .fontSize(13), + ], + ).opacity(0.8), + ], + ), + ), + IconButton( + onPressed: () {}, + visualDensity: const VisualDensity(horizontal: -4, vertical: -4), + icon: const Icon( + Symbols.more_horiz, + size: 16, + ), + ), + ], + ).padding(horizontal: 12, vertical: 8); + } +} + +class _PostContentBody extends StatelessWidget { + final dynamic data; + const _PostContentBody({this.data}); + + @override + Widget build(BuildContext context) { + if (data['content'] == null) return const SizedBox.shrink(); + return MarkdownTextContent(content: data['content']); } } diff --git a/lib/widgets/universal_image.dart b/lib/widgets/universal_image.dart new file mode 100644 index 0000000..09da323 --- /dev/null +++ b/lib/widgets/universal_image.dart @@ -0,0 +1,147 @@ +import 'dart:io'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:styled_widget/styled_widget.dart'; +import 'package:flutter_animate/flutter_animate.dart'; + +class UniversalImage extends StatelessWidget { + final String url; + final double? width, height; + final BoxFit? fit; + final bool noProgressIndicator; + final bool noErrorWidget; + final double? cacheWidth, cacheHeight; + + const UniversalImage( + this.url, { + super.key, + this.width, + this.height, + this.fit, + this.noProgressIndicator = false, + this.noErrorWidget = false, + this.cacheWidth, + this.cacheHeight, + }); + + @override + Widget build(BuildContext context) { + final devicePixelRatio = MediaQuery.of(context).devicePixelRatio; + + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) { + return CachedNetworkImage( + imageUrl: url, + width: width, + height: height, + fit: fit, + memCacheHeight: cacheHeight != null + ? (cacheHeight! * devicePixelRatio).round() + : null, + memCacheWidth: cacheWidth != null + ? (cacheWidth! * devicePixelRatio).round() + : null, + progressIndicatorBuilder: noProgressIndicator + ? null + : (context, url, downloadProgress) => Center( + child: TweenAnimationBuilder( + tween: Tween( + begin: 0, + end: downloadProgress.progress ?? 0, + ), + duration: const Duration(milliseconds: 300), + builder: (context, value, _) => CircularProgressIndicator( + value: downloadProgress.progress != null + ? value.toDouble() + : null, + ), + ), + ), + errorWidget: noErrorWidget + ? null + : (context, url, error) { + return Container( + color: Theme.of(context).colorScheme.surface, + constraints: const BoxConstraints(maxWidth: 280), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimateWidgetExtensions(Icon(Icons.close, size: 24)) + .animate(onPlay: (e) => e.repeat(reverse: true)) + .fade(duration: 500.ms), + Text( + error.toString(), + textAlign: TextAlign.center, + ), + ], + ).center(), + ); + }, + ); + } + return Image.network( + url, + width: width, + height: height, + fit: fit, + cacheHeight: cacheHeight != null + ? (cacheHeight! * devicePixelRatio).round() + : null, + cacheWidth: + cacheWidth != null ? (cacheWidth! * devicePixelRatio).round() : null, + loadingBuilder: noProgressIndicator + ? null + : (BuildContext context, Widget child, + ImageChunkEvent? loadingProgress) { + if (loadingProgress == null) return child; + return Center( + child: TweenAnimationBuilder( + tween: Tween( + begin: 0, + end: loadingProgress.expectedTotalBytes != null + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : 0, + ), + duration: const Duration(milliseconds: 300), + builder: (context, value, _) => CircularProgressIndicator( + value: loadingProgress.expectedTotalBytes != null + ? value.toDouble() + : null, + ), + ), + ); + }, + errorBuilder: noErrorWidget + ? null + : (context, error, stackTrace) { + return Material( + color: Theme.of(context).colorScheme.surface, + child: Container( + constraints: const BoxConstraints(maxWidth: 280), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + AnimateWidgetExtensions(Icon(Icons.close, size: 24)) + .animate(onPlay: (e) => e.repeat(reverse: true)) + .fade(duration: 500.ms), + Text( + error.toString(), + textAlign: TextAlign.center, + ), + ], + ).center(), + ), + ); + }, + ); + } + + static ImageProvider provider(String url) { + if (!kIsWeb && (Platform.isAndroid || Platform.isIOS || Platform.isMacOS)) { + return CachedNetworkImageProvider(url); + } + return NetworkImage(url); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index e71a16d..f6f23bf 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -6,6 +6,10 @@ #include "generated_plugin_registrant.h" +#include void fl_register_plugins(FlPluginRegistry* registry) { + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index be1ee3e..df8d2f7 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 7d12a67..311f992 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,9 +8,13 @@ import Foundation import connectivity_plus import path_provider_foundation import shared_preferences_foundation +import sqflite_darwin +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index ab3902e..9636647 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -134,6 +134,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.9.2" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "7c1183e361e5c8b0a0f21a28401eecdbde252441106a9816400dd4c2b2424916" + url: "https://pub.dev" + source: hosted + version: "3.4.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" + url: "https://pub.dev" + source: hosted + version: "4.1.1" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "980842f4e8e2535b8dbd3d5ca0b1f0ba66bf61d14cc3a17a9b4788a3685ba062" + url: "https://pub.dev" + source: hosted + version: "1.3.1" characters: dependency: transitive description: @@ -347,6 +371,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_animate: + dependency: "direct main" + description: + name: flutter_animate + sha256: "7c8a6594a9252dad30cc2ef16e33270b6248c4dedc3b3d06c86c4f3f4dc05ae5" + url: "https://pub.dev" + source: hosted + version: "4.5.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "400b6592f16a4409a7f2bb929a9a7e38c72cceb8ffb99ee57bbf2cb2cecf8386" + url: "https://pub.dev" + source: hosted + version: "3.4.1" flutter_lints: dependency: "direct dev" description: @@ -360,6 +400,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown: + dependency: "direct main" + description: + name: flutter_markdown + sha256: f0e599ba89c9946c8e051780f0ec99aba4ba15895e0380a7ab68f420046fc44e + url: "https://pub.dev" + source: hosted + version: "0.7.4+1" flutter_native_splash: dependency: "direct dev" description: @@ -368,6 +416,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.4.2" + flutter_shaders: + dependency: transitive + description: + name: flutter_shaders + sha256: "34794acadd8275d971e02df03afee3dee0f98dbfb8c4837082ad0034f612a3e2" + url: "https://pub.dev" + source: hosted + version: "0.1.3" flutter_test: dependency: "direct dev" description: flutter @@ -426,6 +482,14 @@ packages: url: "https://pub.dev" source: hosted version: "14.4.1" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: b1ac0fe2832c9cc95e5e88b57d627c5e68c223b9657f4b96e1487aa9098c7b82 + url: "https://pub.dev" + source: hosted + version: "6.2.1" graphs: dependency: transitive description: @@ -594,6 +658,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3-main.0" + markdown: + dependency: "direct main" + description: + name: markdown + sha256: ef2a1298144e3f985cc736b22e0ccdaf188b5b3970648f2d9dc13efd1d9df051 + url: "https://pub.dev" + source: hosted + version: "7.2.2" matcher: dependency: transitive description: @@ -614,10 +686,10 @@ packages: dependency: "direct main" description: name: material_symbols_icons - sha256: "7626ce90395bc6dc2ecb7bdd84c04a97f3f084a4e923ff73791c3c409af02804" + sha256: "7b723abea4ad37e16fe921f1f1971cbb9b0f66d223a8c99981168a2306416b98" url: "https://pub.dev" source: hosted - version: "4.2789.0" + version: "4.2791.1" meta: dependency: transitive description: @@ -658,6 +730,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "34faa6639a78c7e3cbe79be6f9f96535867e879748ade7d17c9b1ae7536293bd" + url: "https://pub.dev" + source: hosted + version: "2.1.0" package_config: dependency: transitive description: @@ -667,7 +747,7 @@ packages: source: hosted version: "2.1.0" path: - dependency: transitive + dependency: "direct main" description: name: path sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af" @@ -778,6 +858,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.0" + relative_time: + dependency: "direct main" + description: + name: relative_time + sha256: "4e6c3b27d98ff6af5061b6dbaca178b5aa2607b7a7c2a77e6cae1d32b2759893" + url: "https://pub.dev" + source: hosted + version: "5.0.0" responsive_framework: dependency: "direct main" description: @@ -786,6 +874,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" + rxdart: + dependency: transitive + description: + name: rxdart + sha256: "5c3004a4a8dbb94bd4bf5412a4def4acdaa12e12f269737a5751369e12d1a962" + url: "https://pub.dev" + source: hosted + version: "0.28.0" shared_preferences: dependency: transitive description: @@ -887,6 +983,54 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.0" + sprintf: + dependency: transitive + description: + name: sprintf + sha256: "1fc9ffe69d4df602376b52949af107d8f5703b77cda567c4d7d86a0693120f23" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_android: + dependency: transitive + description: + name: sqflite_android + sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "4468b24876d673418a7b7147e5a08a715b4998a7ae69227acafaab762e0e5490" + url: "https://pub.dev" + source: hosted + version: "2.5.4+5" + sqflite_darwin: + dependency: transitive + description: + name: sqflite_darwin + sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + sqflite_platform_interface: + dependency: transitive + description: + name: sqflite_platform_interface + sha256: "8dd4515c7bdcae0a785b0062859336de775e8c65db81ae33dd5445f35be61920" + url: "https://pub.dev" + source: hosted + version: "2.4.0" stack_trace: dependency: transitive description: @@ -927,6 +1071,22 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + url: "https://pub.dev" + source: hosted + version: "3.3.0+3" + syntax_highlight: + dependency: "direct main" + description: + name: syntax_highlight + sha256: ee33b6aa82cc722bb9b40152a792181dee222353b486c0255fde666a3e3a4997 + url: "https://pub.dev" + source: hosted + version: "0.4.0" term_glyph: dependency: transitive description: @@ -967,6 +1127,78 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.2" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + url: "https://pub.dev" + source: hosted + version: "6.3.14" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + url: "https://pub.dev" + source: hosted + version: "3.2.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" + url: "https://pub.dev" + source: hosted + version: "3.1.3" + uuid: + dependency: transitive + description: + name: uuid + sha256: a5be9ef6618a7ac1e964353ef476418026db906c4facdedaa299b7a2e71690ff + url: "https://pub.dev" + source: hosted + version: "4.5.1" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 76e1c4f..50d011b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -49,6 +49,15 @@ dependencies: freezed_annotation: ^2.4.4 json_annotation: ^4.9.0 gap: ^3.0.1 + markdown: ^7.2.2 + flutter_markdown: ^0.7.4+1 + url_launcher: ^6.3.1 + cached_network_image: ^3.4.1 + flutter_animate: ^4.5.0 + syntax_highlight: ^0.4.0 + google_fonts: ^6.2.1 + path: ^1.9.0 + relative_time: ^5.0.0 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8777c93..5777988 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 7e0ec45..0b0bda0 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST