Merge pull request #743 from ente-io/user_details
Render cached user details while fetch is in progress
This commit is contained in:
commit
e2f1879f27
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue