🐛 Fixes on post compose

This commit is contained in:
LittleSheep 2025-06-23 23:40:29 +08:00
parent 9482594117
commit ffbe399614
9 changed files with 222 additions and 172 deletions

View File

@ -8,6 +8,10 @@ class AppRouter extends RootStackRouter {
@override @override
List<AutoRoute> get routes => [ List<AutoRoute> get routes => [
AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
AutoRoute(page: CallRoute.page, path: '/chat/:id/call'),
AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'),
AutoRoute( AutoRoute(
page: TabsRoute.page, page: TabsRoute.page,
path: '/', path: '/',
@ -52,10 +56,6 @@ class AppRouter extends RootStackRouter {
), ),
], ],
), ),
AutoRoute(page: PostComposeRoute.page, path: '/posts/compose'),
AutoRoute(page: PostEditRoute.page, path: '/posts/:id/edit'),
AutoRoute(page: CallRoute.page, path: '/chat/:id/call'),
AutoRoute(page: EventCalanderRoute.page, path: '/account/:name/calendar'),
AutoRoute( AutoRoute(
page: CreatorHubShellRoute.page, page: CreatorHubShellRoute.page,
path: '/creators', path: '/creators',

View File

@ -32,11 +32,13 @@ class PostEditScreen extends HookConsumerWidget {
data: (post) => PostComposeScreen(originalPost: post), data: (post) => PostComposeScreen(originalPost: post),
loading: loading:
() => AppScaffold( () => AppScaffold(
noBackground: false,
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: const Center(child: CircularProgressIndicator()), body: const Center(child: CircularProgressIndicator()),
), ),
error: error:
(e, _) => AppScaffold( (e, _) => AppScaffold(
noBackground: false,
appBar: AppBar(leading: const PageBackButton()), appBar: AppBar(leading: const PageBackButton()),
body: Text('Error: $e', textAlign: TextAlign.center), body: Text('Error: $e', textAlign: TextAlign.center),
), ),
@ -117,32 +119,6 @@ class PostComposeScreen extends HookConsumerWidget {
); );
} }
void showKeyboardShortcutsDialog() {
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: Text('keyboardShortcuts'.tr()),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Ctrl/Cmd + Enter: ${'submit'.tr()}'),
Text('Ctrl/Cmd + V: ${'paste'.tr()}'),
Text('Ctrl/Cmd + I: ${'add_image'.tr()}'),
Text('Ctrl/Cmd + Shift + V: ${'add_video'.tr()}'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('close'.tr()),
),
],
),
);
}
Widget buildWideAttachmentGrid() { Widget buildWideAttachmentGrid() {
return GridView.builder( return GridView.builder(
shrinkWrap: true, shrinkWrap: true,
@ -219,14 +195,6 @@ class PostComposeScreen extends HookConsumerWidget {
onPressed: showSettingsSheet, onPressed: showSettingsSheet,
tooltip: 'postSettings'.tr(), tooltip: 'postSettings'.tr(),
), ),
if (isWideScreen(context))
Tooltip(
message: 'keyboardShortcuts'.tr(),
child: IconButton(
icon: const Icon(Symbols.keyboard),
onPressed: showKeyboardShortcutsDialog,
),
),
ValueListenableBuilder<bool>( ValueListenableBuilder<bool>(
valueListenable: state.submitting, valueListenable: state.submitting,
builder: (context, submitting, _) { builder: (context, submitting, _) {

View File

@ -1,19 +1,17 @@
import 'package:auto_route/auto_route.dart'; import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/models/file.dart'; import 'package:island/models/file.dart';
import 'package:island/models/post.dart'; import 'package:island/models/post.dart';
import 'package:island/screens/creators/publishers.dart'; import 'package:island/screens/creators/publishers.dart';
import 'package:island/services/responsive.dart'; import 'package:island/services/responsive.dart';
import 'package:island/widgets/app_scaffold.dart'; import 'package:island/widgets/app_scaffold.dart';
import 'package:island/screens/posts/detail.dart'; import 'package:island/screens/posts/detail.dart';
import 'package:island/widgets/content/attachment_preview.dart'; import 'package:island/widgets/content/attachment_preview.dart';
import 'package:island/widgets/content/markdown.dart';
import 'package:island/widgets/post/compose_shared.dart'; import 'package:island/widgets/post/compose_shared.dart';
import 'package:island/widgets/post/publishers_modal.dart'; import 'package:island/widgets/post/publishers_modal.dart';
import 'package:island/widgets/content/cloud_files.dart'; import 'package:island/widgets/content/cloud_files.dart';
@ -94,33 +92,6 @@ class ArticleComposeScreen extends HookConsumerWidget {
); );
} }
void showKeyboardShortcutsDialog() {
showDialog(
context: context,
builder:
(context) => AlertDialog(
title: Text('keyboardShortcuts'.tr()),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Ctrl/Cmd + Enter: ${'submit'.tr()}'),
Text('Ctrl/Cmd + V: ${'paste'.tr()}'),
Text('Ctrl/Cmd + I: ${'add_image'.tr()}'),
Text('Ctrl/Cmd + Shift + V: ${'add_video'.tr()}'),
Text('Ctrl/Cmd + P: ${'toggle_preview'.tr()}'),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('close'.tr()),
),
],
),
);
}
Widget buildPreviewPane() { Widget buildPreviewPane() {
return Container( return Container(
decoration: BoxDecoration( decoration: BoxDecoration(
@ -150,33 +121,49 @@ class ArticleComposeScreen extends HookConsumerWidget {
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.all(16),
child: Column( child: ValueListenableBuilder<TextEditingValue>(
crossAxisAlignment: CrossAxisAlignment.start, valueListenable: state.titleController,
children: [ builder: (context, titleValue, _) {
if (state.titleController.text.isNotEmpty) ...[ return ValueListenableBuilder<TextEditingValue>(
Text( valueListenable: state.descriptionController,
state.titleController.text, builder: (context, descriptionValue, _) {
style: theme.textTheme.headlineSmall?.copyWith( return ValueListenableBuilder<TextEditingValue>(
fontWeight: FontWeight.bold, valueListenable: state.contentController,
), builder: (context, contentValue, _) {
), return Column(
const Gap(16), crossAxisAlignment: CrossAxisAlignment.start,
], children: [
if (state.descriptionController.text.isNotEmpty) ...[ if (titleValue.text.isNotEmpty) ...[
Text( Text(
state.descriptionController.text, titleValue.text,
style: theme.textTheme.bodyLarge?.copyWith( style: theme.textTheme.headlineSmall
color: colorScheme.onSurface.withOpacity(0.7), ?.copyWith(fontWeight: FontWeight.bold),
), ),
), const Gap(16),
const Gap(16), ],
], if (descriptionValue.text.isNotEmpty) ...[
if (state.contentController.text.isNotEmpty) Text(
Text( descriptionValue.text,
state.contentController.text, style: theme.textTheme.bodyLarge?.copyWith(
style: theme.textTheme.bodyMedium, color: colorScheme.onSurface.withOpacity(
), 0.7,
], ),
),
),
const Gap(16),
],
if (contentValue.text.isNotEmpty)
MarkdownTextContent(
content: contentValue.text,
textStyle: theme.textTheme.bodyMedium,
),
],
);
},
);
},
);
},
), ),
), ),
), ),
@ -191,6 +178,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
children: [ children: [
// Publisher row // Publisher row
Card( Card(
margin: EdgeInsets.only(bottom: 8),
elevation: 1, elevation: 1,
child: Padding( child: Padding(
padding: const EdgeInsets.all(12), padding: const EdgeInsets.all(12),
@ -316,8 +304,17 @@ class ArticleComposeScreen extends HookConsumerWidget {
} }
return AppScaffold( return AppScaffold(
noBackground: false,
appBar: AppBar( appBar: AppBar(
leading: const PageBackButton(), leading: const PageBackButton(),
title: ValueListenableBuilder<TextEditingValue>(
valueListenable: state.titleController,
builder: (context, titleValue, _) {
return Text(
titleValue.text.isEmpty ? 'postTitle'.tr() : titleValue.text,
);
},
),
actions: [ actions: [
// Info banner for article compose // Info banner for article compose
const SizedBox.shrink(), const SizedBox.shrink(),
@ -333,14 +330,6 @@ class ArticleComposeScreen extends HookConsumerWidget {
onPressed: () => showPreview.value = !showPreview.value, onPressed: () => showPreview.value = !showPreview.value,
), ),
), ),
if (isWideScreen(context))
Tooltip(
message: 'keyboardShortcuts'.tr(),
child: IconButton(
icon: const Icon(Symbols.keyboard),
onPressed: showKeyboardShortcutsDialog,
),
),
ValueListenableBuilder<bool>( ValueListenableBuilder<bool>(
valueListenable: state.submitting, valueListenable: state.submitting,
builder: (context, submitting, _) { builder: (context, submitting, _) {
@ -378,7 +367,7 @@ class ArticleComposeScreen extends HookConsumerWidget {
children: [ children: [
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.all(16), padding: const EdgeInsets.only(left: 16, right: 16),
child: child:
isWideScreen(context) isWideScreen(context)
? Row( ? Row(

View File

@ -2,10 +2,13 @@ import 'package:auto_route/auto_route.dart';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_highlight/themes/a11y-dark.dart';
import 'package:flutter_highlight/themes/a11y-light.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:island/pods/config.dart'; import 'package:island/pods/config.dart';
import 'package:island/widgets/alert.dart'; import 'package:island/widgets/alert.dart';
import 'package:island/widgets/content/markdown_latex.dart';
import 'package:markdown/markdown.dart' as markdown; import 'package:markdown/markdown.dart' as markdown;
import 'package:markdown_widget/markdown_widget.dart'; import 'package:markdown_widget/markdown_widget.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
@ -18,6 +21,7 @@ class MarkdownTextContent extends HookConsumerWidget {
final TextScaler? textScaler; final TextScaler? textScaler;
final TextStyle? textStyle; final TextStyle? textStyle;
final TextStyle? linkStyle; final TextStyle? linkStyle;
final EdgeInsets? linesMargin;
final bool isSelectable; final bool isSelectable;
const MarkdownTextContent({ const MarkdownTextContent({
@ -28,6 +32,7 @@ class MarkdownTextContent extends HookConsumerWidget {
this.textStyle, this.textStyle,
this.linkStyle, this.linkStyle,
this.isSelectable = false, this.isSelectable = false,
this.linesMargin,
}); });
@override @override
@ -54,19 +59,13 @@ class MarkdownTextContent extends HookConsumerWidget {
config: config.copy( config: config.copy(
configs: [ configs: [
isDark isDark
? PreConfig.darkConfig.copy( ? PreConfig.darkConfig.copy(textStyle: textStyle)
textStyle: textStyle, : PreConfig().copy(textStyle: textStyle),
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
)
: PreConfig().copy(
textStyle: textStyle,
padding: EdgeInsets.zero,
margin: EdgeInsets.zero,
),
PConfig( PConfig(
textStyle: textStyle ?? Theme.of(context).textTheme.bodyMedium!, textStyle: textStyle ?? Theme.of(context).textTheme.bodyMedium!,
), ),
HrConfig(height: 1, color: Theme.of(context).dividerColor),
PreConfig(theme: isDark ? a11yDarkTheme : a11yLightTheme),
LinkConfig( LinkConfig(
style: style:
linkStyle ?? linkStyle ??
@ -146,8 +145,13 @@ class MarkdownTextContent extends HookConsumerWidget {
], ],
), ),
generator: MarkdownGenerator( generator: MarkdownGenerator(
inlineSyntaxList: [_UserNameCardInlineSyntax(), _StickerInlineSyntax()], generators: [latexGenerator],
linesMargin: EdgeInsets.zero, inlineSyntaxList: [
_UserNameCardInlineSyntax(),
_StickerInlineSyntax(),
LatexSyntax(isDark),
],
linesMargin: linesMargin ?? EdgeInsets.symmetric(vertical: 4),
), ),
); );
} }

View File

@ -0,0 +1,80 @@
import 'package:flutter/material.dart';
import 'package:markdown_widget/markdown_widget.dart';
import 'package:flutter_math_fork/flutter_math.dart';
import 'package:markdown/markdown.dart' as m;
SpanNodeGeneratorWithTag latexGenerator = SpanNodeGeneratorWithTag(
tag: _latexTag,
generator:
(e, config, visitor) => LatexNode(e.attributes, e.textContent, config),
);
const _latexTag = 'latex';
class LatexSyntax extends m.InlineSyntax {
final bool isDark;
LatexSyntax(this.isDark) : super(r'(\$\$[\s\S]+\$\$)|(\$.+?\$)');
@override
bool onMatch(m.InlineParser parser, Match match) {
final input = match.input;
final matchValue = input.substring(match.start, match.end);
String content = '';
bool isInline = true;
const blockSyntax = '\$\$';
const inlineSyntax = '\$';
if (matchValue.startsWith(blockSyntax) &&
matchValue.endsWith(blockSyntax) &&
(matchValue != blockSyntax)) {
content = matchValue.substring(2, matchValue.length - 2);
isInline = false;
} else if (matchValue.startsWith(inlineSyntax) &&
matchValue.endsWith(inlineSyntax) &&
matchValue != inlineSyntax) {
content = matchValue.substring(1, matchValue.length - 1);
}
m.Element el = m.Element.text(_latexTag, matchValue);
el.attributes['content'] = content;
el.attributes['isInline'] = '$isInline';
el.attributes['isDark'] = isDark.toString();
parser.addNode(el);
return true;
}
}
class LatexNode extends SpanNode {
final Map<String, String> attributes;
final String textContent;
final MarkdownConfig config;
LatexNode(this.attributes, this.textContent, this.config);
@override
InlineSpan build() {
final content = attributes['content'] ?? '';
final isInline = attributes['isInline'] == 'true';
final isDark = attributes['isDark'] == 'true';
final style = parentStyle ?? config.p.textStyle;
if (content.isEmpty) return TextSpan(style: style, text: textContent);
final latex = Math.tex(
content,
mathStyle: MathStyle.text,
textStyle: style.copyWith(color: isDark ? Colors.white : Colors.black),
textScaleFactor: 1,
onErrorFallback: (error) {
return Text(textContent, style: style.copyWith(color: Colors.red));
},
);
return WidgetSpan(
alignment: PlaceholderAlignment.middle,
child:
!isInline
? Container(
width: double.infinity,
margin: EdgeInsets.symmetric(vertical: 16),
child: Center(child: latex),
)
: latex,
);
}
}

View File

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:gap/gap.dart';
import 'package:island/widgets/content/sheet.dart'; import 'package:island/widgets/content/sheet.dart';
import 'package:material_symbols_icons/symbols.dart'; import 'package:material_symbols_icons/symbols.dart';
@ -23,6 +24,9 @@ class ComposeSettingsSheet extends HookWidget {
final theme = Theme.of(context); final theme = Theme.of(context);
final colorScheme = theme.colorScheme; final colorScheme = theme.colorScheme;
// Listen to visibility changes to trigger rebuilds
final currentVisibility = useValueListenable(visibility);
IconData getVisibilityIcon(int visibilityValue) { IconData getVisibilityIcon(int visibilityValue) {
switch (visibilityValue) { switch (visibilityValue) {
case 1: case 1:
@ -71,38 +75,39 @@ class ComposeSettingsSheet extends HookWidget {
void showVisibilitySheet() { void showVisibilitySheet() {
showModalBottomSheet( showModalBottomSheet(
context: context, context: context,
builder: (context) => SheetScaffold( builder:
titleText: 'postVisibility'.tr(), (context) => SheetScaffold(
child: Column( titleText: 'postVisibility'.tr(),
mainAxisSize: MainAxisSize.min, child: Column(
children: [ mainAxisSize: MainAxisSize.min,
buildVisibilityOption( children: [
context, buildVisibilityOption(
0, context,
Symbols.public, 0,
'postVisibilityPublic', Symbols.public,
'postVisibilityPublic',
),
buildVisibilityOption(
context,
1,
Symbols.group,
'postVisibilityFriends',
),
buildVisibilityOption(
context,
2,
Symbols.link_off,
'postVisibilityUnlisted',
),
buildVisibilityOption(
context,
3,
Symbols.lock,
'postVisibilityPrivate',
),
],
), ),
buildVisibilityOption( ),
context,
1,
Symbols.group,
'postVisibilityFriends',
),
buildVisibilityOption(
context,
2,
Symbols.link_off,
'postVisibilityUnlisted',
),
buildVisibilityOption(
context,
3,
Symbols.lock,
'postVisibilityPrivate',
),
],
),
),
); );
} }
@ -124,10 +129,11 @@ class ComposeSettingsSheet extends HookWidget {
), ),
contentPadding: const EdgeInsets.all(16), contentPadding: const EdgeInsets.all(16),
), ),
style: theme.textTheme.titleLarge, style: theme.textTheme.titleMedium,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 16), const Gap(16),
// Description field // Description field
TextField( TextField(
@ -140,25 +146,23 @@ class ComposeSettingsSheet extends HookWidget {
), ),
contentPadding: const EdgeInsets.all(16), contentPadding: const EdgeInsets.all(16),
), ),
style: theme.textTheme.bodyLarge, style: theme.textTheme.bodyMedium,
maxLines: 3, maxLines: 3,
onTapOutside: (_) => FocusManager.instance.primaryFocus?.unfocus(), onTapOutside:
(_) => FocusManager.instance.primaryFocus?.unfocus(),
), ),
const SizedBox(height: 24), const Gap(16),
// Visibility setting // Visibility setting
Container( Container(
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(color: colorScheme.outline, width: 1),
color: colorScheme.outline,
width: 1,
),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
), ),
child: ListTile( child: ListTile(
leading: Icon(getVisibilityIcon(visibility.value)), leading: Icon(getVisibilityIcon(currentVisibility)),
title: Text('postVisibility'.tr()), title: Text('postVisibility'.tr()),
subtitle: Text(getVisibilityText(visibility.value).tr()), subtitle: Text(getVisibilityText(currentVisibility).tr()),
trailing: const Icon(Symbols.chevron_right), trailing: const Icon(Symbols.chevron_right),
onTap: showVisibilitySheet, onTap: showVisibilitySheet,
shape: RoundedRectangleBorder( shape: RoundedRectangleBorder(

View File

@ -209,7 +209,11 @@ class PostItem extends HookConsumerWidget {
), ),
).padding(bottom: 8), ).padding(bottom: 8),
if (item.content?.isNotEmpty ?? false) if (item.content?.isNotEmpty ?? false)
MarkdownTextContent(content: item.content!), MarkdownTextContent(
content: item.content!,
linesMargin:
item.type == 0 ? EdgeInsets.zero : null,
),
// Show truncation hint if post is truncated // Show truncation hint if post is truncated
if (item.isTruncated && !isFullPost) if (item.isTruncated && !isFullPost)
_PostTruncateHint(), _PostTruncateHint(),

View File

@ -149,10 +149,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build name: build
sha256: "74273591bd8b7f82eeb1f191c1b65a6576535bbfd5ca3722778b07d5702d33cc" sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.3" version: "2.5.4"
build_config: build_config:
dependency: transitive dependency: transitive
description: description:
@ -173,26 +173,26 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: build_resolvers name: build_resolvers
sha256: badce70566085f2e87434531c4a6bc8e833672f755fc51146d612245947e91c9 sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.3" version: "2.5.4"
build_runner: build_runner:
dependency: "direct dev" dependency: "direct dev"
description: description:
name: build_runner name: build_runner
sha256: b9070a4127033777c0e63195f6f117ed16a351ed676f6313b095cf4f328c0b82 sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.5.3" version: "2.5.4"
build_runner_core: build_runner_core:
dependency: transitive dependency: transitive
description: description:
name: build_runner_core name: build_runner_core
sha256: "1cdfece3eeb3f1263f7dbf5bcc0cba697bd0c22d2c866cb4b578c954dbb09bcf" sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.1.1" version: "9.1.2"
built_collection: built_collection:
dependency: transitive dependency: transitive
description: description:
@ -836,7 +836,7 @@ packages:
source: hosted source: hosted
version: "0.3.4" version: "0.3.4"
flutter_math_fork: flutter_math_fork:
dependency: transitive dependency: "direct main"
description: description:
name: flutter_math_fork name: flutter_math_fork
sha256: "6d5f2f1aa57ae539ffb0a04bb39d2da67af74601d685a161aff7ce5bda5fa407" sha256: "6d5f2f1aa57ae539ffb0a04bb39d2da67af74601d685a161aff7ce5bda5fa407"
@ -1745,10 +1745,10 @@ packages:
dependency: transitive dependency: transitive
description: description:
name: record_platform_interface name: record_platform_interface
sha256: "8a575828733d4c3cb5983c914696f40db8667eab3538d4c41c50cbb79e722ef4" sha256: c1ad38f51e4af88a085b3e792a22c685cb3e7c23fc37aa7ce44c4cf18f25fe89
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.3.0"
record_web: record_web:
dependency: transitive dependency: transitive
description: description:

View File

@ -118,6 +118,7 @@ dependencies:
native_exif: ^0.6.2 native_exif: ^0.6.2
local_auth: ^2.3.0 local_auth: ^2.3.0
flutter_secure_storage: ^4.2.1 flutter_secure_storage: ^4.2.1
flutter_math_fork: ^0.7.4
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: