Merge pull request #743 from ente-io/user_details

Render cached user details while fetch is in progress
This commit is contained in:
Ashil 2022-12-28 11:52:16 +05:30 committed by GitHub
commit e2f1879f27
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 133 additions and 22 deletions

View file

@ -1,3 +1,5 @@
import 'dart:convert';
const freeProductID = "free"; const freeProductID = "free";
const stripe = "stripe"; const stripe = "stripe";
const appStore = "appstore"; const appStore = "appstore";
@ -43,10 +45,28 @@ class Subscription {
price: map['price'], price: map['price'],
period: map['period'], period: map['period'],
attributes: map["attributes"] != null attributes: map["attributes"] != null
? Attributes.fromJson(map["attributes"]) ? Attributes.fromMap(map["attributes"])
: null, : null,
); );
} }
Map<String, dynamic> toMap() {
return {
'productID': productID,
'storage': storage,
'originalTransactionID': originalTransactionID,
'paymentProvider': paymentProvider,
'expiryTime': expiryTime,
'price': price,
'period': period,
'attributes': attributes?.toMap(),
};
}
String toJson() => json.encode(toMap());
factory Subscription.fromJson(String source) =>
Subscription.fromMap(json.decode(source));
} }
class Attributes { class Attributes {
@ -58,8 +78,22 @@ class Attributes {
this.customerID, this.customerID,
}); });
Attributes.fromJson(dynamic json) { Map<String, dynamic> toMap() {
isCancelled = json["isCancelled"]; return {
customerID = json["customerID"]; 'isCancelled': isCancelled,
'customerID': customerID,
};
} }
factory Attributes.fromMap(Map<String, dynamic> map) {
return Attributes(
isCancelled: map['isCancelled'],
customerID: map['customerID'],
);
}
String toJson() => json.encode(toMap());
factory Attributes.fromJson(String source) =>
Attributes.fromMap(json.decode(source));
} }

View file

@ -1,3 +1,4 @@
import 'dart:convert';
import 'dart:math'; import 'dart:math';
import 'package:collection/collection.dart'; import 'package:collection/collection.dart';
@ -62,6 +63,22 @@ class UserDetails {
FamilyData.fromMap(map['familyData']), FamilyData.fromMap(map['familyData']),
); );
} }
Map<String, dynamic> toMap() {
return {
'email': email,
'usage': usage,
'fileCount': fileCount,
'sharedCollectionsCount': sharedCollectionsCount,
'subscription': subscription.toMap(),
'familyData': familyData?.toMap(),
};
}
String toJson() => json.encode(toMap());
factory UserDetails.fromJson(String source) =>
UserDetails.fromMap(json.decode(source));
} }
class FamilyMember { class FamilyMember {
@ -70,7 +87,12 @@ class FamilyMember {
final String id; final String id;
final bool isAdmin; final bool isAdmin;
FamilyMember(this.email, this.usage, this.id, this.isAdmin); FamilyMember(
this.email,
this.usage,
this.id,
this.isAdmin,
);
factory FamilyMember.fromMap(Map<String, dynamic> map) { factory FamilyMember.fromMap(Map<String, dynamic> map) {
return FamilyMember( return FamilyMember(
@ -80,6 +102,20 @@ class FamilyMember {
map['isAdmin'] as bool, map['isAdmin'] as bool,
); );
} }
Map<String, dynamic> toMap() {
return {
'email': email,
'usage': usage,
'id': id,
'isAdmin': isAdmin,
};
}
String toJson() => json.encode(toMap());
factory FamilyMember.fromJson(String source) => FamilyMember.fromMap(json.decode(source));
} }
class FamilyData { class FamilyData {
@ -89,7 +125,11 @@ class FamilyData {
final int storage; final int storage;
final int expiryTime; final int expiryTime;
FamilyData(this.members, this.storage, this.expiryTime); FamilyData(
this.members,
this.storage,
this.expiryTime,
);
int getTotalUsage() { int getTotalUsage() {
return members!.map((e) => e.usage).toList().sum; return members!.map((e) => e.usage).toList().sum;
@ -107,6 +147,18 @@ class FamilyData {
map['expiryTime'] as int, map['expiryTime'] as int,
); );
} }
Map<String, dynamic> toMap() {
return {
'members': members?.map((x) => x.toMap()).toList(),
'storage': storage,
'expiryTime': expiryTime,
};
}
String toJson() => json.encode(toMap());
factory FamilyData.fromJson(String source) => FamilyData.fromMap(json.decode(source));
} }
class FilesCount { class FilesCount {

View file

@ -39,6 +39,7 @@ import 'package:shared_preferences/shared_preferences.dart';
class UserService { class UserService {
static const keyHasEnabledTwoFactor = "has_enabled_two_factor"; static const keyHasEnabledTwoFactor = "has_enabled_two_factor";
static const keyUserDetails = "user_details";
final _dio = Network.instance.getDio(); final _dio = Network.instance.getDio();
final _enteDio = Network.instance.enteDio; final _enteDio = Network.instance.enteDio;
final _logger = Logger((UserService).toString()); final _logger = Logger((UserService).toString());
@ -134,7 +135,18 @@ class UserService {
} }
} }
Future<UserDetails> getUserDetailsV2({bool memoryCount = true}) async { UserDetails getCachedUserDetails() {
if (_preferences.containsKey(keyUserDetails)) {
return UserDetails.fromJson(_preferences.getString(keyUserDetails));
}
return null;
}
Future<UserDetails> getUserDetailsV2({
bool memoryCount = true,
bool shouldCache = false,
}) async {
_logger.info("Fetching user details");
try { try {
final response = await _enteDio.get( final response = await _enteDio.get(
"/users/details/v2", "/users/details/v2",
@ -142,7 +154,12 @@ class UserService {
"memoryCount": memoryCount, "memoryCount": memoryCount,
}, },
); );
return UserDetails.fromMap(response.data); final userDetails = UserDetails.fromMap(response.data);
if (shouldCache) {
await _preferences.setString(keyUserDetails, userDetails.toJson());
}
_logger.info("User details fetched: " + userDetails.toJson());
return userDetails;
} on DioError catch (e) { } on DioError catch (e) {
_logger.info(e); _logger.info(e);
rethrow; rethrow;

View file

@ -19,20 +19,16 @@ class UserDetailsStateWidget extends StatefulWidget {
} }
class UserDetailsStateWidgetState extends State<UserDetailsStateWidget> { class UserDetailsStateWidgetState extends State<UserDetailsStateWidget> {
late UserDetails? userDetails; late UserDetails? _userDetails;
late StreamSubscription<OpenedSettingsEvent> _openedSettingsEventSubscription; late StreamSubscription<OpenedSettingsEvent> _openedSettingsEventSubscription;
bool _isCached = true;
@override @override
void initState() { void initState() {
userDetails = null; _userDetails = UserService.instance.getCachedUserDetails();
_openedSettingsEventSubscription = _openedSettingsEventSubscription =
Bus.instance.on<OpenedSettingsEvent>().listen((event) { Bus.instance.on<OpenedSettingsEvent>().listen((event) {
Future.delayed( _fetchUserDetails();
const Duration(
milliseconds: 750,
),
_fetchUserDetails,
);
}); });
super.initState(); super.initState();
} }
@ -46,13 +42,17 @@ class UserDetailsStateWidgetState extends State<UserDetailsStateWidget> {
@override @override
Widget build(BuildContext context) => InheritedUserDetails( Widget build(BuildContext context) => InheritedUserDetails(
userDetailsState: this, userDetailsState: this,
userDetails: userDetails, userDetails: _userDetails,
isCached: _isCached,
child: widget.child, child: widget.child,
); );
void _fetchUserDetails() async { void _fetchUserDetails() async {
userDetails = _userDetails = await UserService.instance.getUserDetailsV2(
await UserService.instance.getUserDetailsV2(memoryCount: true); memoryCount: true,
shouldCache: true,
);
_isCached = false;
if (mounted) { if (mounted) {
setState(() {}); setState(() {});
} }
@ -62,11 +62,13 @@ class UserDetailsStateWidgetState extends State<UserDetailsStateWidget> {
class InheritedUserDetails extends InheritedWidget { class InheritedUserDetails extends InheritedWidget {
final UserDetailsStateWidgetState userDetailsState; final UserDetailsStateWidgetState userDetailsState;
final UserDetails? userDetails; final UserDetails? userDetails;
final bool isCached;
const InheritedUserDetails({ const InheritedUserDetails({
Key? key, Key? key,
required Widget child, required Widget child,
required this.userDetails, required this.userDetails,
required this.isCached,
required this.userDetailsState, required this.userDetailsState,
}) : super(key: key, child: child); }) : super(key: key, child: child);
@ -76,6 +78,7 @@ class InheritedUserDetails extends InheritedWidget {
@override @override
bool updateShouldNotify(covariant InheritedUserDetails oldWidget) { bool updateShouldNotify(covariant InheritedUserDetails oldWidget) {
return (userDetails?.usage != oldWidget.userDetails?.usage) || return (userDetails?.usage != oldWidget.userDetails?.usage) ||
(userDetails?.fileCount != oldWidget.userDetails?.fileCount); (userDetails?.fileCount != oldWidget.userDetails?.fileCount) ||
(isCached != oldWidget.isCached);
} }
} }

View file

@ -12,7 +12,12 @@ class SettingsTitleBarWidget extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final logger = Logger((SettingsTitleBarWidget).toString()); final logger = Logger((SettingsTitleBarWidget).toString());
final userDetails = InheritedUserDetails.of(context)?.userDetails; final inheritedDetails = InheritedUserDetails.of(context);
final userDetails = inheritedDetails?.userDetails;
bool isCached = false;
if (inheritedDetails != null) {
isCached = inheritedDetails.isCached;
}
return Container( return Container(
padding: const EdgeInsets.symmetric(vertical: 4), padding: const EdgeInsets.symmetric(vertical: 4),
child: Padding( child: Padding(
@ -27,7 +32,7 @@ class SettingsTitleBarWidget extends StatelessWidget {
}, },
icon: const Icon(Icons.keyboard_double_arrow_left_outlined), icon: const Icon(Icons.keyboard_double_arrow_left_outlined),
), ),
userDetails is UserDetails userDetails is UserDetails && !isCached
? Text( ? Text(
"${NumberFormat().format(userDetails.fileCount)} memories", "${NumberFormat().format(userDetails.fileCount)} memories",
style: getEnteTextTheme(context).largeBold, style: getEnteTextTheme(context).largeBold,