123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- import 'package:flutter/material.dart';
- import 'package:logging/logging.dart';
- import 'package:photos/models/user_details.dart';
- import 'package:photos/states/user_details_state.dart';
- import 'package:photos/theme/colors.dart';
- import 'package:photos/theme/ente_theme.dart';
- import 'package:photos/ui/common/loading_widget.dart';
- // ignore: import_of_legacy_library_into_null_safe
- import 'package:photos/ui/payment/subscription.dart';
- import 'package:photos/ui/settings/storage_error_widget.dart';
- import 'package:photos/ui/settings/storage_progress_widget.dart';
- import 'package:photos/utils/data_util.dart';
- class DetailsSectionWidget extends StatefulWidget {
- const DetailsSectionWidget({Key? key}) : super(key: key);
- @override
- State<DetailsSectionWidget> createState() => _DetailsSectionWidgetState();
- }
- class _DetailsSectionWidgetState extends State<DetailsSectionWidget> {
- late Image _background;
- final _logger = Logger((_DetailsSectionWidgetState).toString());
- final ValueNotifier<bool> _isStorageCardPressed = ValueNotifier(false);
- @override
- void initState() {
- super.initState();
- _background = const Image(
- image: AssetImage("assets/storage_card_background.png"),
- fit: BoxFit.fill,
- );
- }
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- // precache background image to avoid flicker
- // https://stackoverflow.com/questions/51343735/flutter-image-preload
- precacheImage(_background.image, context);
- }
- @override
- Widget build(BuildContext context) {
- final inheritedUserDetails = InheritedUserDetails.of(context);
- if (inheritedUserDetails == null) {
- _logger.severe(
- (InheritedUserDetails).toString() +
- ' not found before ' +
- (_DetailsSectionWidgetState).toString() +
- ' on tree',
- );
- throw Error();
- } else {
- return GestureDetector(
- behavior: HitTestBehavior.translucent,
- onTap: () async {
- Navigator.of(context).push(
- MaterialPageRoute(
- builder: (BuildContext context) {
- return getSubscriptionPage();
- },
- ),
- );
- },
- onTapDown: (details) => _isStorageCardPressed.value = true,
- onTapCancel: () => _isStorageCardPressed.value = false,
- onTapUp: (details) => _isStorageCardPressed.value = false,
- child: containerForUserDetails(inheritedUserDetails),
- );
- }
- }
- Widget containerForUserDetails(
- InheritedUserDetails inheritedUserDetails,
- ) {
- return ConstrainedBox(
- constraints: const BoxConstraints(maxWidth: 350),
- child: AspectRatio(
- aspectRatio: 2 / 1,
- child: Stack(
- children: [
- _background,
- FutureBuilder(
- future: inheritedUserDetails.userDetails,
- builder: (context, snapshot) {
- if (snapshot.hasData) {
- return userDetails(snapshot.data as UserDetails);
- }
- if (snapshot.hasError) {
- _logger.severe(
- 'failed to load user details',
- snapshot.error,
- );
- return const StorageErrorWidget();
- }
- return const EnteLoadingWidget(color: strokeBaseDark);
- },
- ),
- Align(
- alignment: Alignment.centerRight,
- child: Padding(
- padding: const EdgeInsets.only(right: 4),
- child: ValueListenableBuilder<bool>(
- builder: (BuildContext context, bool value, Widget? child) {
- return Icon(
- Icons.chevron_right_outlined,
- color: value ? strokeMutedDark : strokeBaseDark,
- );
- },
- valueListenable: _isStorageCardPressed,
- ),
- ),
- ),
- ],
- ),
- ),
- );
- }
- Widget userDetails(UserDetails userDetails) {
- const hundredMBinBytes = 107374182;
- final isMobileScreenSmall = MediaQuery.of(context).size.width <= 365;
- final freeSpaceInBytes = userDetails.getFreeStorage();
- final shouldShowFreeSpaceInMBs = freeSpaceInBytes < hundredMBinBytes;
- final usedSpaceInGB = roundBytesUsedToGBs(
- userDetails.getFamilyOrPersonalUsage(),
- userDetails.getFreeStorage(),
- );
- final totalStorageInGB =
- convertBytesToGBs(userDetails.getTotalStorage()).truncate();
- return Padding(
- padding: EdgeInsets.fromLTRB(
- 16,
- 20,
- 16,
- isMobileScreenSmall ? 12 : 20,
- ),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- Align(
- alignment: Alignment.topLeft,
- child: Column(
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- isMobileScreenSmall ? "Used space" : "Storage",
- style: getEnteTextTheme(context)
- .small
- .copyWith(color: textMutedDark),
- ),
- const SizedBox(height: 2),
- RichText(
- overflow: TextOverflow.ellipsis,
- maxLines: 1,
- text: TextSpan(
- style: getEnteTextTheme(context)
- .h3Bold
- .copyWith(color: textBaseDark),
- children: [
- TextSpan(text: usedSpaceInGB.toString()),
- TextSpan(text: isMobileScreenSmall ? "/" : " GB of "),
- TextSpan(text: totalStorageInGB.toString() + " GB"),
- TextSpan(text: isMobileScreenSmall ? "" : " used"),
- ],
- ),
- ),
- ],
- ),
- ),
- Column(
- children: [
- Stack(
- children: <Widget>[
- const StorageProgressWidget(
- color:
- Color.fromRGBO(255, 255, 255, 0.2), //hardcoded in figma
- fractionOfStorage: 1,
- ),
- userDetails.isPartOfFamily()
- ? StorageProgressWidget(
- color: strokeBaseDark,
- fractionOfStorage:
- ((userDetails.getFamilyOrPersonalUsage()) /
- userDetails.getTotalStorage()),
- )
- : const SizedBox.shrink(),
- StorageProgressWidget(
- color: userDetails.isPartOfFamily()
- ? getEnteColorScheme(context).primary300
- : strokeBaseDark,
- fractionOfStorage:
- (userDetails.usage / userDetails.getTotalStorage()),
- )
- ],
- ),
- const SizedBox(height: 12),
- Row(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- userDetails.isPartOfFamily()
- ? Row(
- children: [
- Container(
- width: 8.71,
- height: 8.99,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: getEnteColorScheme(context).primary300,
- ),
- ),
- const SizedBox(width: 4),
- Text(
- "You",
- style: getEnteTextTheme(context)
- .miniBold
- .copyWith(color: textBaseDark),
- ),
- const SizedBox(width: 12),
- Container(
- width: 8.71,
- height: 8.99,
- decoration: const BoxDecoration(
- shape: BoxShape.circle,
- color: textBaseDark,
- ),
- ),
- const SizedBox(width: 4),
- Text(
- "Family",
- style: getEnteTextTheme(context)
- .miniBold
- .copyWith(color: textBaseDark),
- ),
- ],
- )
- : const SizedBox.shrink(),
- RichText(
- text: TextSpan(
- style: getEnteTextTheme(context)
- .mini
- .copyWith(color: textFaintDark),
- children: [
- TextSpan(
- text:
- "${shouldShowFreeSpaceInMBs ? convertBytesToMBs(freeSpaceInBytes) : _roundedFreeSpace(totalStorageInGB, usedSpaceInGB)}",
- ),
- TextSpan(
- text: shouldShowFreeSpaceInMBs
- ? " MB free"
- : " GB free",
- )
- ],
- ),
- ),
- ],
- ),
- ],
- )
- ],
- ),
- );
- }
- num _roundedFreeSpace(num totalStorageInGB, num usedSpaceInGB) {
- int fractionDigits;
- //subtracting usedSpace from totalStorage in GB instead of converting from bytes so that free space and used space adds up in the UI
- final freeSpace = totalStorageInGB - usedSpaceInGB;
- //show one decimal place if free space is less than 10GB
- if (freeSpace < 10) {
- fractionDigits = 1;
- } else {
- fractionDigits = 0;
- }
- //omit decimal if decimal is 0
- if (fractionDigits == 1 && freeSpace.remainder(1) == 0) {
- fractionDigits = 0;
- }
- return num.parse(freeSpace.toStringAsFixed(fractionDigits));
- }
- }
|