✨ Sliding window pricing of attachment billing info displaying
This commit is contained in:
parent
4f7f015250
commit
e07da3efa5
@ -666,5 +666,9 @@
|
|||||||
"zero": "No views",
|
"zero": "No views",
|
||||||
"one": "{} view",
|
"one": "{} view",
|
||||||
"other": "{} views"
|
"other": "{} views"
|
||||||
}
|
},
|
||||||
|
"attachmentBillingUploaded": "Used space",
|
||||||
|
"attachmentBillingDiscount": "Free space",
|
||||||
|
"attachmentBillingRatio": "Usage",
|
||||||
|
"attachmentBillingHint": "Sliding Window Pricing®\nFees will only apply if the size of the file uploaded within 24 hours exceeds the free space."
|
||||||
}
|
}
|
||||||
|
@ -665,5 +665,8 @@
|
|||||||
"zero": "{} 次浏览",
|
"zero": "{} 次浏览",
|
||||||
"one": "{} 次浏览",
|
"one": "{} 次浏览",
|
||||||
"other": "{} 次浏览"
|
"other": "{} 次浏览"
|
||||||
}
|
},
|
||||||
|
"attachmentBillingUploaded": "已占用的字节数",
|
||||||
|
"attachmentBillingDiscount": "免费的字节数",
|
||||||
|
"attachmentBillingHint": "滑动窗口计价®\n在24小时内上传的文件大小超出免费空间才会适用扣费。"
|
||||||
}
|
}
|
||||||
|
@ -546,6 +546,7 @@
|
|||||||
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||||
"unauthorized": "未登陸",
|
"unauthorized": "未登陸",
|
||||||
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
||||||
|
"projectDetail": "項目詳情",
|
||||||
"serviceStatus": "服務狀態",
|
"serviceStatus": "服務狀態",
|
||||||
"termRelated": "相關條款",
|
"termRelated": "相關條款",
|
||||||
"appDetails": "應用程序詳情",
|
"appDetails": "應用程序詳情",
|
||||||
@ -664,5 +665,8 @@
|
|||||||
"zero": "{} 次瀏覽",
|
"zero": "{} 次瀏覽",
|
||||||
"one": "{} 次瀏覽",
|
"one": "{} 次瀏覽",
|
||||||
"other": "{} 次瀏覽"
|
"other": "{} 次瀏覽"
|
||||||
}
|
},
|
||||||
|
"attachmentBillingUploaded": "已佔用的字節數",
|
||||||
|
"attachmentBillingDiscount": "免費的字節數",
|
||||||
|
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。"
|
||||||
}
|
}
|
||||||
|
@ -546,6 +546,7 @@
|
|||||||
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
"termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。",
|
||||||
"unauthorized": "未登陸",
|
"unauthorized": "未登陸",
|
||||||
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
"unauthorizedDescription": "登陸以探索整個 Solar Network。",
|
||||||
|
"projectDetail": "項目詳情",
|
||||||
"serviceStatus": "服務狀態",
|
"serviceStatus": "服務狀態",
|
||||||
"termRelated": "相關條款",
|
"termRelated": "相關條款",
|
||||||
"appDetails": "應用程序詳情",
|
"appDetails": "應用程序詳情",
|
||||||
@ -664,5 +665,8 @@
|
|||||||
"zero": "{} 次瀏覽",
|
"zero": "{} 次瀏覽",
|
||||||
"one": "{} 次瀏覽",
|
"one": "{} 次瀏覽",
|
||||||
"other": "{} 次瀏覽"
|
"other": "{} 次瀏覽"
|
||||||
}
|
},
|
||||||
|
"attachmentBillingUploaded": "已佔用的字節數",
|
||||||
|
"attachmentBillingDiscount": "免費的字節數",
|
||||||
|
"attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。"
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:dismissible_page/dismissible_page.dart';
|
import 'package:dismissible_page/dismissible_page.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_staggered_grid_view/flutter_staggered_grid_view.dart';
|
import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:google_fonts/google_fonts.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
import 'package:surface/providers/sn_network.dart';
|
import 'package:surface/providers/sn_network.dart';
|
||||||
@ -27,9 +33,23 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
bool _isBusy = false;
|
bool _isBusy = false;
|
||||||
int? _totalCount;
|
int? _totalCount;
|
||||||
|
|
||||||
|
SnAttachmentBilling? _billing;
|
||||||
|
|
||||||
final List<SnAttachment> _attachments = List.empty(growable: true);
|
final List<SnAttachment> _attachments = List.empty(growable: true);
|
||||||
final List<String> _heroTags = List.empty(growable: true);
|
final List<String> _heroTags = List.empty(growable: true);
|
||||||
|
|
||||||
|
Future<void> _fetchBillingStatus() async {
|
||||||
|
try {
|
||||||
|
final sn = context.read<SnNetworkProvider>();
|
||||||
|
final resp = await sn.client.get('/cgi/uc/billing');
|
||||||
|
final out = SnAttachmentBilling.fromJson(resp.data);
|
||||||
|
setState(() => _billing = out);
|
||||||
|
} catch (err) {
|
||||||
|
if (!mounted) return;
|
||||||
|
context.showErrorDialog(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _fetchAttachments() async {
|
Future<void> _fetchAttachments() async {
|
||||||
setState(() => _isBusy = true);
|
setState(() => _isBusy = true);
|
||||||
|
|
||||||
@ -62,6 +82,7 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
_fetchBillingStatus();
|
||||||
_fetchAttachments();
|
_fetchAttachments();
|
||||||
_scrollController.addListener(() {
|
_scrollController.addListener(() {
|
||||||
if (_scrollController.position.atEdge) {
|
if (_scrollController.position.atEdge) {
|
||||||
@ -91,6 +112,48 @@ class _AlbumScreenState extends State<AlbumScreen> {
|
|||||||
leading: AutoAppBarLeading(),
|
leading: AutoAppBarLeading(),
|
||||||
title: Text('screenAlbum').tr(),
|
title: Text('screenAlbum').tr(),
|
||||||
),
|
),
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Card(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 80,
|
||||||
|
height: 80,
|
||||||
|
child: CircularProgressIndicator(
|
||||||
|
value: _billing?.includedRatio ?? 0,
|
||||||
|
strokeWidth: 8,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surfaceContainerHigh,
|
||||||
|
),
|
||||||
|
).padding(all: 12),
|
||||||
|
const Gap(24),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text('attachmentBillingUploaded').tr().bold(),
|
||||||
|
Text(
|
||||||
|
(_billing?.currentBytes ?? 0).formatBytes(decimals: 4),
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
),
|
||||||
|
Text('attachmentBillingDiscount').tr().bold(),
|
||||||
|
Text(
|
||||||
|
'${(_billing?.discountFileSize ?? 0).formatBytes(decimals: 2)} · ${((_billing?.includedRatio ?? 0) * 100).toStringAsFixed(2)}%',
|
||||||
|
style: GoogleFonts.robotoMono(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Tooltip(
|
||||||
|
message: 'attachmentBillingHint'.tr(),
|
||||||
|
child: IconButton(
|
||||||
|
icon: const Icon(Symbols.info),
|
||||||
|
onPressed: () {},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
).padding(horizontal: 24, vertical: 8),
|
||||||
|
),
|
||||||
|
),
|
||||||
SliverMasonryGrid.extent(
|
SliverMasonryGrid.extent(
|
||||||
childCount: _attachments.length,
|
childCount: _attachments.length,
|
||||||
maxCrossAxisExtent: 320,
|
maxCrossAxisExtent: 320,
|
||||||
|
@ -177,3 +177,14 @@ class SnStickerPack with _$SnStickerPack {
|
|||||||
|
|
||||||
factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json);
|
factory SnStickerPack.fromJson(Map<String, Object?> json) => _$SnStickerPackFromJson(json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
class SnAttachmentBilling with _$SnAttachmentBilling {
|
||||||
|
const factory SnAttachmentBilling({
|
||||||
|
required int currentBytes,
|
||||||
|
required int discountFileSize,
|
||||||
|
required double includedRatio,
|
||||||
|
}) = _SnAttachmentBilling;
|
||||||
|
|
||||||
|
factory SnAttachmentBilling.fromJson(Map<String, Object?> json) => _$SnAttachmentBillingFromJson(json);
|
||||||
|
}
|
||||||
|
@ -3007,3 +3007,195 @@ abstract class _SnStickerPack implements SnStickerPack {
|
|||||||
_$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith =>
|
_$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith =>
|
||||||
throw _privateConstructorUsedError;
|
throw _privateConstructorUsedError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SnAttachmentBilling _$SnAttachmentBillingFromJson(Map<String, dynamic> json) {
|
||||||
|
return _SnAttachmentBilling.fromJson(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnAttachmentBilling {
|
||||||
|
int get currentBytes => throw _privateConstructorUsedError;
|
||||||
|
int get discountFileSize => throw _privateConstructorUsedError;
|
||||||
|
double get includedRatio => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Serializes this SnAttachmentBilling to a JSON map.
|
||||||
|
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
|
/// Create a copy of SnAttachmentBilling
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
$SnAttachmentBillingCopyWith<SnAttachmentBilling> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class $SnAttachmentBillingCopyWith<$Res> {
|
||||||
|
factory $SnAttachmentBillingCopyWith(
|
||||||
|
SnAttachmentBilling value, $Res Function(SnAttachmentBilling) then) =
|
||||||
|
_$SnAttachmentBillingCopyWithImpl<$Res, SnAttachmentBilling>;
|
||||||
|
@useResult
|
||||||
|
$Res call({int currentBytes, int discountFileSize, double includedRatio});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnAttachmentBillingCopyWithImpl<$Res, $Val extends SnAttachmentBilling>
|
||||||
|
implements $SnAttachmentBillingCopyWith<$Res> {
|
||||||
|
_$SnAttachmentBillingCopyWithImpl(this._value, this._then);
|
||||||
|
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Val _value;
|
||||||
|
// ignore: unused_field
|
||||||
|
final $Res Function($Val) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnAttachmentBilling
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? currentBytes = null,
|
||||||
|
Object? discountFileSize = null,
|
||||||
|
Object? includedRatio = null,
|
||||||
|
}) {
|
||||||
|
return _then(_value.copyWith(
|
||||||
|
currentBytes: null == currentBytes
|
||||||
|
? _value.currentBytes
|
||||||
|
: currentBytes // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
discountFileSize: null == discountFileSize
|
||||||
|
? _value.discountFileSize
|
||||||
|
: discountFileSize // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
includedRatio: null == includedRatio
|
||||||
|
? _value.includedRatio
|
||||||
|
: includedRatio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,
|
||||||
|
) as $Val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract class _$$SnAttachmentBillingImplCopyWith<$Res>
|
||||||
|
implements $SnAttachmentBillingCopyWith<$Res> {
|
||||||
|
factory _$$SnAttachmentBillingImplCopyWith(_$SnAttachmentBillingImpl value,
|
||||||
|
$Res Function(_$SnAttachmentBillingImpl) then) =
|
||||||
|
__$$SnAttachmentBillingImplCopyWithImpl<$Res>;
|
||||||
|
@override
|
||||||
|
@useResult
|
||||||
|
$Res call({int currentBytes, int discountFileSize, double includedRatio});
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
class __$$SnAttachmentBillingImplCopyWithImpl<$Res>
|
||||||
|
extends _$SnAttachmentBillingCopyWithImpl<$Res, _$SnAttachmentBillingImpl>
|
||||||
|
implements _$$SnAttachmentBillingImplCopyWith<$Res> {
|
||||||
|
__$$SnAttachmentBillingImplCopyWithImpl(_$SnAttachmentBillingImpl _value,
|
||||||
|
$Res Function(_$SnAttachmentBillingImpl) _then)
|
||||||
|
: super(_value, _then);
|
||||||
|
|
||||||
|
/// Create a copy of SnAttachmentBilling
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
@override
|
||||||
|
$Res call({
|
||||||
|
Object? currentBytes = null,
|
||||||
|
Object? discountFileSize = null,
|
||||||
|
Object? includedRatio = null,
|
||||||
|
}) {
|
||||||
|
return _then(_$SnAttachmentBillingImpl(
|
||||||
|
currentBytes: null == currentBytes
|
||||||
|
? _value.currentBytes
|
||||||
|
: currentBytes // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
discountFileSize: null == discountFileSize
|
||||||
|
? _value.discountFileSize
|
||||||
|
: discountFileSize // ignore: cast_nullable_to_non_nullable
|
||||||
|
as int,
|
||||||
|
includedRatio: null == includedRatio
|
||||||
|
? _value.includedRatio
|
||||||
|
: includedRatio // ignore: cast_nullable_to_non_nullable
|
||||||
|
as double,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
class _$SnAttachmentBillingImpl implements _SnAttachmentBilling {
|
||||||
|
const _$SnAttachmentBillingImpl(
|
||||||
|
{required this.currentBytes,
|
||||||
|
required this.discountFileSize,
|
||||||
|
required this.includedRatio});
|
||||||
|
|
||||||
|
factory _$SnAttachmentBillingImpl.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$$SnAttachmentBillingImplFromJson(json);
|
||||||
|
|
||||||
|
@override
|
||||||
|
final int currentBytes;
|
||||||
|
@override
|
||||||
|
final int discountFileSize;
|
||||||
|
@override
|
||||||
|
final double includedRatio;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnAttachmentBilling(currentBytes: $currentBytes, discountFileSize: $discountFileSize, includedRatio: $includedRatio)';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) ||
|
||||||
|
(other.runtimeType == runtimeType &&
|
||||||
|
other is _$SnAttachmentBillingImpl &&
|
||||||
|
(identical(other.currentBytes, currentBytes) ||
|
||||||
|
other.currentBytes == currentBytes) &&
|
||||||
|
(identical(other.discountFileSize, discountFileSize) ||
|
||||||
|
other.discountFileSize == discountFileSize) &&
|
||||||
|
(identical(other.includedRatio, includedRatio) ||
|
||||||
|
other.includedRatio == includedRatio));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
Object.hash(runtimeType, currentBytes, discountFileSize, includedRatio);
|
||||||
|
|
||||||
|
/// Create a copy of SnAttachmentBilling
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$$SnAttachmentBillingImplCopyWith<_$SnAttachmentBillingImpl> get copyWith =>
|
||||||
|
__$$SnAttachmentBillingImplCopyWithImpl<_$SnAttachmentBillingImpl>(
|
||||||
|
this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$$SnAttachmentBillingImplToJson(
|
||||||
|
this,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class _SnAttachmentBilling implements SnAttachmentBilling {
|
||||||
|
const factory _SnAttachmentBilling(
|
||||||
|
{required final int currentBytes,
|
||||||
|
required final int discountFileSize,
|
||||||
|
required final double includedRatio}) = _$SnAttachmentBillingImpl;
|
||||||
|
|
||||||
|
factory _SnAttachmentBilling.fromJson(Map<String, dynamic> json) =
|
||||||
|
_$SnAttachmentBillingImpl.fromJson;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get currentBytes;
|
||||||
|
@override
|
||||||
|
int get discountFileSize;
|
||||||
|
@override
|
||||||
|
double get includedRatio;
|
||||||
|
|
||||||
|
/// Create a copy of SnAttachmentBilling
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
_$$SnAttachmentBillingImplCopyWith<_$SnAttachmentBillingImpl> get copyWith =>
|
||||||
|
throw _privateConstructorUsedError;
|
||||||
|
}
|
||||||
|
@ -281,3 +281,19 @@ Map<String, dynamic> _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) =>
|
|||||||
'stickers': instance.stickers?.map((e) => e.toJson()).toList(),
|
'stickers': instance.stickers?.map((e) => e.toJson()).toList(),
|
||||||
'account_id': instance.accountId,
|
'account_id': instance.accountId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
_$SnAttachmentBillingImpl _$$SnAttachmentBillingImplFromJson(
|
||||||
|
Map<String, dynamic> json) =>
|
||||||
|
_$SnAttachmentBillingImpl(
|
||||||
|
currentBytes: (json['current_bytes'] as num).toInt(),
|
||||||
|
discountFileSize: (json['discount_file_size'] as num).toInt(),
|
||||||
|
includedRatio: (json['included_ratio'] as num).toDouble(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$$SnAttachmentBillingImplToJson(
|
||||||
|
_$SnAttachmentBillingImpl instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'current_bytes': instance.currentBytes,
|
||||||
|
'discount_file_size': instance.discountFileSize,
|
||||||
|
'included_ratio': instance.includedRatio,
|
||||||
|
};
|
||||||
|
@ -191,7 +191,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.4.1"
|
version: "3.4.1"
|
||||||
cached_network_image_platform_interface:
|
cached_network_image_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: cached_network_image_platform_interface
|
name: cached_network_image_platform_interface
|
||||||
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829"
|
||||||
@ -1083,7 +1083,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "0.2.1+2"
|
version: "0.2.1+2"
|
||||||
image_picker_platform_interface:
|
image_picker_platform_interface:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: image_picker_platform_interface
|
name: image_picker_platform_interface
|
||||||
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0"
|
||||||
|
@ -121,6 +121,8 @@ dependencies:
|
|||||||
tray_manager: ^0.3.2
|
tray_manager: ^0.3.2
|
||||||
hotkey_manager: ^0.2.3
|
hotkey_manager: ^0.2.3
|
||||||
image_picker_android: ^0.8.12+20
|
image_picker_android: ^0.8.12+20
|
||||||
|
cached_network_image_platform_interface: ^4.1.1
|
||||||
|
image_picker_platform_interface: ^2.10.1
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user