From e07da3efa5bba29f781523c1da75a37ac6eca1de Mon Sep 17 00:00:00 2001 From: LittleSheep Date: Thu, 20 Feb 2025 21:19:23 +0800 Subject: [PATCH] :sparkles: Sliding window pricing of attachment billing info displaying --- assets/translations/en-US.json | 6 +- assets/translations/zh-CN.json | 5 +- assets/translations/zh-HK.json | 6 +- assets/translations/zh-TW.json | 6 +- lib/screens/album.dart | 63 ++++++++++ lib/types/attachment.dart | 11 ++ lib/types/attachment.freezed.dart | 192 ++++++++++++++++++++++++++++++ lib/types/attachment.g.dart | 16 +++ pubspec.lock | 4 +- pubspec.yaml | 2 + 10 files changed, 305 insertions(+), 6 deletions(-) diff --git a/assets/translations/en-US.json b/assets/translations/en-US.json index a6f9463..c529402 100644 --- a/assets/translations/en-US.json +++ b/assets/translations/en-US.json @@ -666,5 +666,9 @@ "zero": "No views", "one": "{} view", "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." } diff --git a/assets/translations/zh-CN.json b/assets/translations/zh-CN.json index 74acb59..0f389f7 100644 --- a/assets/translations/zh-CN.json +++ b/assets/translations/zh-CN.json @@ -665,5 +665,8 @@ "zero": "{} 次浏览", "one": "{} 次浏览", "other": "{} 次浏览" - } + }, + "attachmentBillingUploaded": "已占用的字节数", + "attachmentBillingDiscount": "免费的字节数", + "attachmentBillingHint": "滑动窗口计价®\n在24小时内上传的文件大小超出免费空间才会适用扣费。" } diff --git a/assets/translations/zh-HK.json b/assets/translations/zh-HK.json index 660b0fd..88e49b1 100644 --- a/assets/translations/zh-HK.json +++ b/assets/translations/zh-HK.json @@ -546,6 +546,7 @@ "termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。", "unauthorized": "未登陸", "unauthorizedDescription": "登陸以探索整個 Solar Network。", + "projectDetail": "項目詳情", "serviceStatus": "服務狀態", "termRelated": "相關條款", "appDetails": "應用程序詳情", @@ -664,5 +665,8 @@ "zero": "{} 次瀏覽", "one": "{} 次瀏覽", "other": "{} 次瀏覽" - } + }, + "attachmentBillingUploaded": "已佔用的字節數", + "attachmentBillingDiscount": "免費的字節數", + "attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。" } diff --git a/assets/translations/zh-TW.json b/assets/translations/zh-TW.json index 3a6a95a..ff9c846 100644 --- a/assets/translations/zh-TW.json +++ b/assets/translations/zh-TW.json @@ -546,6 +546,7 @@ "termAcceptNextWithAgree": "點擊 “下一步”,即表示你同意我們的各項條款,包括其之後的更新。", "unauthorized": "未登陸", "unauthorizedDescription": "登陸以探索整個 Solar Network。", + "projectDetail": "項目詳情", "serviceStatus": "服務狀態", "termRelated": "相關條款", "appDetails": "應用程序詳情", @@ -664,5 +665,8 @@ "zero": "{} 次瀏覽", "one": "{} 次瀏覽", "other": "{} 次瀏覽" - } + }, + "attachmentBillingUploaded": "已佔用的字節數", + "attachmentBillingDiscount": "免費的字節數", + "attachmentBillingHint": "滑動窗口計價®\n在24小時內上傳的文件大小超出免費空間才會適用扣費。" } diff --git a/lib/screens/album.dart b/lib/screens/album.dart index 95aaa10..e7bcd36 100644 --- a/lib/screens/album.dart +++ b/lib/screens/album.dart @@ -1,7 +1,13 @@ +import 'dart:convert'; +import 'dart:developer'; + import 'package:dismissible_page/dismissible_page.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.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:styled_widget/styled_widget.dart'; import 'package:surface/providers/sn_network.dart'; @@ -27,9 +33,23 @@ class _AlbumScreenState extends State { bool _isBusy = false; int? _totalCount; + SnAttachmentBilling? _billing; + final List _attachments = List.empty(growable: true); final List _heroTags = List.empty(growable: true); + Future _fetchBillingStatus() async { + try { + final sn = context.read(); + 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 _fetchAttachments() async { setState(() => _isBusy = true); @@ -62,6 +82,7 @@ class _AlbumScreenState extends State { @override void initState() { super.initState(); + _fetchBillingStatus(); _fetchAttachments(); _scrollController.addListener(() { if (_scrollController.position.atEdge) { @@ -91,6 +112,48 @@ class _AlbumScreenState extends State { leading: AutoAppBarLeading(), 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( childCount: _attachments.length, maxCrossAxisExtent: 320, diff --git a/lib/types/attachment.dart b/lib/types/attachment.dart index c47da1f..0059337 100644 --- a/lib/types/attachment.dart +++ b/lib/types/attachment.dart @@ -177,3 +177,14 @@ class SnStickerPack with _$SnStickerPack { factory SnStickerPack.fromJson(Map 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 json) => _$SnAttachmentBillingFromJson(json); +} diff --git a/lib/types/attachment.freezed.dart b/lib/types/attachment.freezed.dart index 65db707..35f60d0 100644 --- a/lib/types/attachment.freezed.dart +++ b/lib/types/attachment.freezed.dart @@ -3007,3 +3007,195 @@ abstract class _SnStickerPack implements SnStickerPack { _$$SnStickerPackImplCopyWith<_$SnStickerPackImpl> get copyWith => throw _privateConstructorUsedError; } + +SnAttachmentBilling _$SnAttachmentBillingFromJson(Map 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 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 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 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 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 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; +} diff --git a/lib/types/attachment.g.dart b/lib/types/attachment.g.dart index 1337a46..492aa7b 100644 --- a/lib/types/attachment.g.dart +++ b/lib/types/attachment.g.dart @@ -281,3 +281,19 @@ Map _$$SnStickerPackImplToJson(_$SnStickerPackImpl instance) => 'stickers': instance.stickers?.map((e) => e.toJson()).toList(), 'account_id': instance.accountId, }; + +_$SnAttachmentBillingImpl _$$SnAttachmentBillingImplFromJson( + Map 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 _$$SnAttachmentBillingImplToJson( + _$SnAttachmentBillingImpl instance) => + { + 'current_bytes': instance.currentBytes, + 'discount_file_size': instance.discountFileSize, + 'included_ratio': instance.includedRatio, + }; diff --git a/pubspec.lock b/pubspec.lock index 0f9aefd..b3ea847 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -191,7 +191,7 @@ packages: source: hosted version: "3.4.1" cached_network_image_platform_interface: - dependency: transitive + dependency: "direct main" description: name: cached_network_image_platform_interface sha256: "35814b016e37fbdc91f7ae18c8caf49ba5c88501813f73ce8a07027a395e2829" @@ -1083,7 +1083,7 @@ packages: source: hosted version: "0.2.1+2" image_picker_platform_interface: - dependency: transitive + dependency: "direct main" description: name: image_picker_platform_interface sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" diff --git a/pubspec.yaml b/pubspec.yaml index 301f7de..313aa3d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -121,6 +121,8 @@ dependencies: tray_manager: ^0.3.2 hotkey_manager: ^0.2.3 image_picker_android: ^0.8.12+20 + cached_network_image_platform_interface: ^4.1.1 + image_picker_platform_interface: ^2.10.1 dev_dependencies: flutter_test: