diff --git a/lib/apis/user_api.dart b/lib/apis/user_api.dart index 1055b61..5f29968 100644 --- a/lib/apis/user_api.dart +++ b/lib/apis/user_api.dart @@ -39,4 +39,11 @@ class UserApi { final data = jsonDecode(response.body); return PersonalProfile.fromMap(data); } + + static Future getStorageQuota(ApiClient client) async { + final response = await client.get("/users/${client.userId}/storage"); + client.checkResponse(response); + final data = jsonDecode(response.body); + return StorageQuota.fromMap(data); + } } \ No newline at end of file diff --git a/lib/models/personal_profile.dart b/lib/models/personal_profile.dart index 959e334..7443875 100644 --- a/lib/models/personal_profile.dart +++ b/lib/models/personal_profile.dart @@ -1,3 +1,5 @@ +import 'package:recon/auxiliary.dart'; +import 'package:recon/models/users/entitlement.dart'; import 'package:recon/models/users/user_profile.dart'; class PersonalProfile { @@ -6,18 +8,21 @@ class PersonalProfile { final String email; final DateTime? publicBanExpiration; final String? publicBanType; - final List storageQuotas; - final Map quotaBytesSource; - final int quotaBytes; - final int usedBytes; final bool twoFactor; - final bool isPatreonSupporter; final UserProfile userProfile; + final List entitlements; + final List supporterMetadata; PersonalProfile({ - required this.id, required this.username, required this.email, required this.publicBanExpiration, - required this.publicBanType, required this.storageQuotas, required this.quotaBytesSource, required this.quotaBytes, - required this.usedBytes, required this.twoFactor, required this.isPatreonSupporter, required this.userProfile, + required this.id, + required this.username, + required this.email, + required this.publicBanExpiration, + required this.publicBanType, + required this.twoFactor, + required this.userProfile, + required this.entitlements, + required this.supporterMetadata, }); factory PersonalProfile.fromMap(Map map) { @@ -27,34 +32,83 @@ class PersonalProfile { email: map["email"] ?? "", publicBanExpiration: DateTime.tryParse(map["publicBanExpiration"] ?? ""), publicBanType: map["publicBanType"], - storageQuotas: (map["storageQuotas"] as List? ?? []).map((e) => StorageQuotas.fromMap(e)).toList(), - quotaBytesSource: (map["quotaBytesSources"] as Map? ?? {}).map((key, value) => MapEntry(key, value as int)), - quotaBytes: map["quotaBytes"] ?? 0, - usedBytes: map["usedBytes"] ?? 0, twoFactor: map["2fa_login"] ?? false, - isPatreonSupporter: map["patreonData"]?["isPatreonSupporter"] ?? false, userProfile: UserProfile.fromMap(map["profile"]), + entitlements: ((map["entitlements"] ?? []) as List).map((e) => Entitlement.fromMap(e)).toList(), + supporterMetadata: ((map["supporterMetadata"] ?? []) as List).map((e) => SupporterMetadata.fromMap(e)).toList(), + ); + } + + bool get isPatreonSupporter => + supporterMetadata.whereType().any((element) => element.isActiveSupporter); +} + +class StorageQuota { + final String id; + final int usedBytes; + final int quotaBytes; + final int fullQuotaBytes; + + StorageQuota({ + required this.id, + required this.usedBytes, + required this.quotaBytes, + required this.fullQuotaBytes, + }); + + factory StorageQuota.fromMap(Map map) { + return StorageQuota( + id: map["id"] ?? "", + usedBytes: map["usedBytes"] ?? 0, + quotaBytes: map["quotaBytes"] ?? 0, + fullQuotaBytes: map["fullQuotaBytes"] ?? 0, ); } } -class StorageQuotas { - final String id; - final int bytes; - final DateTime addedOn; - final DateTime expiresOn; - final String giftedByUserId; +class SupporterMetadata { + SupporterMetadata(); - StorageQuotas({required this.id, required this.bytes, required this.addedOn, required this.expiresOn, - required this.giftedByUserId}); + factory SupporterMetadata.fromMap(Map map) { + final type = map["\$type"]; + return switch (type) { + "patreon" => PatreonSupporter.fromMap(map), + _ => SupporterMetadata(), + }; + } +} - factory StorageQuotas.fromMap(Map map) { - return StorageQuotas( - id: map["id"] ?? "", - bytes: map["bytes"] ?? 0, - addedOn: DateTime.tryParse(map["addedOn"]) ?? DateTime.fromMillisecondsSinceEpoch(0), - expiresOn: DateTime.tryParse(map["expiresOn"]) ?? DateTime.fromMillisecondsSinceEpoch(0), - giftedByUserId: map["giftedByUserId"] ?? "", +class PatreonSupporter extends SupporterMetadata { + final bool isActiveSupporter; + final int totalSupportMonths; + final int totalSupportCents; + final int lastTierCents; + final int highestTierCents; + final int lowestTierCents; + final DateTime firstSupportTimestamp; + final DateTime lastSupportTimestamp; + + PatreonSupporter({ + required this.isActiveSupporter, + required this.totalSupportMonths, + required this.totalSupportCents, + required this.lastTierCents, + required this.highestTierCents, + required this.lowestTierCents, + required this.firstSupportTimestamp, + required this.lastSupportTimestamp, + }); + + factory PatreonSupporter.fromMap(Map map) { + return PatreonSupporter( + isActiveSupporter: map["isActiveSupporter"], + totalSupportMonths: map["totalSupportMonths"], + totalSupportCents: map["totalSupportCents"], + lastTierCents: map["lastTierCents"], + highestTierCents: map["highestTierCents"], + lowestTierCents: map["lowestTierCents"], + firstSupportTimestamp: DateTime.tryParse(map["firstSupportTimestamp"] ?? "") ?? DateTimeX.epoch, + lastSupportTimestamp: DateTime.tryParse(map["lastSupportTimestamp"] ?? "") ?? DateTimeX.epoch, ); } -} \ No newline at end of file +} diff --git a/lib/models/users/entitlement.dart b/lib/models/users/entitlement.dart new file mode 100644 index 0000000..75a573e --- /dev/null +++ b/lib/models/users/entitlement.dart @@ -0,0 +1,49 @@ +import 'package:recon/auxiliary.dart'; + +class Entitlement { + Entitlement(); + + factory Entitlement.fromMap(Map map) { + final type = map["\$type"]; + + return switch (type) { + "storageSpace" => StorageSpace.fromMap(map), + _ => Entitlement(), + }; + } +} + +class StorageSpace extends Entitlement { + final int bytes; + final int maximumShareLevel; + final String storageId; + final String group; + final DateTime startsOn; + final DateTime expiresOn; + final String name; + final String description; + + StorageSpace({ + required this.bytes, + required this.maximumShareLevel, + required this.storageId, + required this.group, + required this.startsOn, + required this.expiresOn, + required this.name, + required this.description, + }); + + factory StorageSpace.fromMap(Map map) { + return StorageSpace( + bytes: map["bytes"], + maximumShareLevel: map["maximumShareLevel"], + storageId: map["storageId"], + group: map["group"], + startsOn: DateTime.tryParse(map["startsOn"] ?? "") ?? DateTimeX.epoch, + expiresOn: DateTime.tryParse(map["expiresOn"] ?? "") ?? DateTimeX.epoch, + name: map["name"], + description: map["description"], + ); + } +} diff --git a/lib/widgets/my_profile_dialog.dart b/lib/widgets/my_profile_dialog.dart index 06677a5..b3f4efc 100644 --- a/lib/widgets/my_profile_dialog.dart +++ b/lib/widgets/my_profile_dialog.dart @@ -17,6 +17,7 @@ class MyProfileDialog extends StatefulWidget { class _MyProfileDialogState extends State { ClientHolder? _clientHolder; Future? _personalProfileFuture; + Future? _storageQuotaFuture; @override @@ -27,6 +28,7 @@ class _MyProfileDialogState extends State { _clientHolder = clientHolder; final apiClient = _clientHolder!.apiClient; _personalProfileFuture = UserApi.getPersonalProfile(apiClient); + _storageQuotaFuture = UserApi.getStorageQuota(apiClient); } } @@ -87,7 +89,13 @@ class _MyProfileDialogState extends State { children: [Text("Ban Expiration: ", style: tt.labelLarge,), Text(dateFormat.format(profile.publicBanExpiration!))], ), - StorageIndicator(usedBytes: profile.usedBytes, maxBytes: profile.quotaBytes,), + FutureBuilder( + future: _storageQuotaFuture, + builder: (context, snapshot) { + final storage = snapshot.data; + return StorageIndicator(usedBytes: storage?.usedBytes ?? 0, maxBytes: storage?.fullQuotaBytes ?? 1,); + } + ), const SizedBox(height: 12,), ], ),