resolve conflicts and merge main

This commit is contained in:
ashilkn 2023-11-15 15:48:31 +05:30
commit faede193b1
24 changed files with 513 additions and 101 deletions

View file

@ -187,6 +187,8 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '', one: '1 day', other: '${count} days')}";
static String m64(endDate) => "Valid till ${endDate}";
static String m60(email) => "Verify ${email}";
static String m61(email) => "We have sent a mail to <green>${email}</green>";
@ -218,6 +220,9 @@ class MessageLookup extends MessageLookupByLibrary {
"addLocation": MessageLookupByLibrary.simpleMessage("Add location"),
"addLocationButton": MessageLookupByLibrary.simpleMessage("Add"),
"addMore": MessageLookupByLibrary.simpleMessage("Add more"),
"addOnPageSubtitle":
MessageLookupByLibrary.simpleMessage("Details of add-ons"),
"addOns": MessageLookupByLibrary.simpleMessage("Add-ons"),
"addPhotos": MessageLookupByLibrary.simpleMessage("Add photos"),
"addSelected": MessageLookupByLibrary.simpleMessage("Add selected"),
"addToAlbum": MessageLookupByLibrary.simpleMessage("Add to album"),
@ -696,8 +701,6 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Group nearby photos"),
"hearUsExplanation": MessageLookupByLibrary.simpleMessage(
"We don\'t track app installs. It\'d help if you told us where you found us!"),
"hearUsHint": MessageLookupByLibrary.simpleMessage(
"friend, reddit, ad, search, etc."),
"hearUsWhereTitle": MessageLookupByLibrary.simpleMessage(
"How did you hear about Ente? (optional)"),
"hidden": MessageLookupByLibrary.simpleMessage("Hidden"),
@ -1345,6 +1348,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSelectedPhoto":
MessageLookupByLibrary.simpleMessage("Use selected photo"),
"usedSpace": MessageLookupByLibrary.simpleMessage("Used space"),
"validTill": m64,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage(
"Verification failed, please try again"),
@ -1362,6 +1366,7 @@ class MessageLookup extends MessageLookupByLibrary {
"videoSmallCase": MessageLookupByLibrary.simpleMessage("video"),
"viewActiveSessions":
MessageLookupByLibrary.simpleMessage("View active sessions"),
"viewAddOnButton": MessageLookupByLibrary.simpleMessage("View add-ons"),
"viewAll": MessageLookupByLibrary.simpleMessage("View all"),
"viewAllExifData":
MessageLookupByLibrary.simpleMessage("View all EXIF data"),

View file

@ -187,6 +187,8 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '', one: '1 giorno', other: '${count} giorni')}";
static String m64(endDate) => "Valido fino al ${endDate}";
static String m60(email) => "Verifica ${email}";
static String m61(email) =>
@ -220,6 +222,9 @@ class MessageLookup extends MessageLookupByLibrary {
"addLocation": MessageLookupByLibrary.simpleMessage("Aggiungi luogo"),
"addLocationButton": MessageLookupByLibrary.simpleMessage("Aggiungi"),
"addMore": MessageLookupByLibrary.simpleMessage("Aggiungi altri"),
"addOnPageSubtitle": MessageLookupByLibrary.simpleMessage(
"Dettagli dei componenti aggiuntivi"),
"addOns": MessageLookupByLibrary.simpleMessage("Componenti aggiuntivi"),
"addPhotos": MessageLookupByLibrary.simpleMessage("Aggiungi foto"),
"addSelected":
MessageLookupByLibrary.simpleMessage("Aggiungi selezionate"),
@ -712,6 +717,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("Concedi il permesso"),
"groupNearbyPhotos": MessageLookupByLibrary.simpleMessage(
"Raggruppa foto nelle vicinanze"),
"hearUsExplanation": MessageLookupByLibrary.simpleMessage(
"Non teniamo traccia del numero di installazioni dell\'app. Sarebbe utile se ci dicesse dove ci ha trovato!"),
"hearUsWhereTitle": MessageLookupByLibrary.simpleMessage(
"Come hai sentito parlare di Ente? (opzionale)"),
"hidden": MessageLookupByLibrary.simpleMessage("Nascosti"),
"hide": MessageLookupByLibrary.simpleMessage("Nascondi"),
"hiding": MessageLookupByLibrary.simpleMessage("Nascondendo..."),
@ -1378,6 +1387,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useSelectedPhoto":
MessageLookupByLibrary.simpleMessage("Usa la foto selezionata"),
"usedSpace": MessageLookupByLibrary.simpleMessage("Spazio utilizzato"),
"validTill": m64,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage(
"Verifica fallita, per favore prova di nuovo"),
@ -1396,6 +1406,8 @@ class MessageLookup extends MessageLookupByLibrary {
"videoSmallCase": MessageLookupByLibrary.simpleMessage("video"),
"viewActiveSessions":
MessageLookupByLibrary.simpleMessage("Visualizza sessioni attive"),
"viewAddOnButton": MessageLookupByLibrary.simpleMessage(
"Visualizza componenti aggiuntivi"),
"viewAll": MessageLookupByLibrary.simpleMessage("Visualizza tutte"),
"viewAllExifData":
MessageLookupByLibrary.simpleMessage("Mostra tutti i dati EXIF"),

View file

@ -170,6 +170,8 @@ class MessageLookup extends MessageLookupByLibrary {
static String m59(count) =>
"${Intl.plural(count, zero: '', one: '1天', other: '${count} 天')}";
static String m64(endDate) => "有效期至 ${endDate}";
static String m60(email) => "验证 ${email}";
static String m61(email) => "我们已经发送邮件到 <green>${email}</green>";
@ -196,6 +198,8 @@ class MessageLookup extends MessageLookupByLibrary {
"addLocation": MessageLookupByLibrary.simpleMessage("添加地点"),
"addLocationButton": MessageLookupByLibrary.simpleMessage("添加"),
"addMore": MessageLookupByLibrary.simpleMessage("添加更多"),
"addOnPageSubtitle": MessageLookupByLibrary.simpleMessage("附加组件详情"),
"addOns": MessageLookupByLibrary.simpleMessage("附加组件"),
"addPhotos": MessageLookupByLibrary.simpleMessage("添加照片"),
"addSelected": MessageLookupByLibrary.simpleMessage("添加所选项"),
"addToAlbum": MessageLookupByLibrary.simpleMessage("添加到相册"),
@ -582,6 +586,10 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("请在“设置”应用中将权限更改为允许访问所有所有照片"),
"grantPermission": MessageLookupByLibrary.simpleMessage("授予权限"),
"groupNearbyPhotos": MessageLookupByLibrary.simpleMessage("将附近的照片分组"),
"hearUsExplanation": MessageLookupByLibrary.simpleMessage(
"我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!"),
"hearUsWhereTitle":
MessageLookupByLibrary.simpleMessage("您是如何知道Ente的 (可选的)"),
"hidden": MessageLookupByLibrary.simpleMessage("已隐藏"),
"hide": MessageLookupByLibrary.simpleMessage("隐藏"),
"hiding": MessageLookupByLibrary.simpleMessage("正在隐藏..."),
@ -1109,6 +1117,7 @@ class MessageLookup extends MessageLookupByLibrary {
"useRecoveryKey": MessageLookupByLibrary.simpleMessage("使用恢复密钥"),
"useSelectedPhoto": MessageLookupByLibrary.simpleMessage("使用所选照片"),
"usedSpace": MessageLookupByLibrary.simpleMessage("已用空间"),
"validTill": m64,
"verificationFailedPleaseTryAgain":
MessageLookupByLibrary.simpleMessage("验证失败,请重试"),
"verificationId": MessageLookupByLibrary.simpleMessage("验证 ID"),
@ -1122,6 +1131,7 @@ class MessageLookup extends MessageLookupByLibrary {
MessageLookupByLibrary.simpleMessage("正在验证恢复密钥..."),
"videoSmallCase": MessageLookupByLibrary.simpleMessage("视频"),
"viewActiveSessions": MessageLookupByLibrary.simpleMessage("查看活动会话"),
"viewAddOnButton": MessageLookupByLibrary.simpleMessage("查看附加组件"),
"viewAll": MessageLookupByLibrary.simpleMessage("查看全部"),
"viewAllExifData": MessageLookupByLibrary.simpleMessage("查看所有 EXIF 数据"),
"viewLogs": MessageLookupByLibrary.simpleMessage("查看日志"),

View file

@ -3914,6 +3914,16 @@ class S {
);
}
/// `Valid till {endDate}`
String validTill(Object endDate) {
return Intl.message(
'Valid till $endDate',
name: 'validTill',
desc: '',
args: [endDate],
);
}
/// `Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.`
String playStoreFreeTrialValidTill(Object endDate) {
return Intl.message(
@ -7835,16 +7845,6 @@ class S {
);
}
/// `friend, reddit, ad, search, etc.`
String get hearUsHint {
return Intl.message(
'friend, reddit, ad, search, etc.',
name: 'hearUsHint',
desc: '',
args: [],
);
}
/// `We don't track app installs. It'd help if you told us where you found us!`
String get hearUsExplanation {
return Intl.message(
@ -7854,6 +7854,36 @@ class S {
args: [],
);
}
/// `View add-ons`
String get viewAddOnButton {
return Intl.message(
'View add-ons',
name: 'viewAddOnButton',
desc: '',
args: [],
);
}
/// `Add-ons`
String get addOns {
return Intl.message(
'Add-ons',
name: 'addOns',
desc: '',
args: [],
);
}
/// `Details of add-ons`
String get addOnPageSubtitle {
return Intl.message(
'Details of add-ons',
name: 'addOnPageSubtitle',
desc: '',
args: [],
);
}
}
class AppLocalizationDelegate extends LocalizationsDelegate<S> {

View file

@ -557,6 +557,7 @@
"faqs": "FAQs",
"renewsOn": "Renews on {endDate}",
"freeTrialValidTill": "Free trial valid till {endDate}",
"validTill": "Valid till {endDate}",
"playStoreFreeTrialValidTill": "Free trial valid till {endDate}.\nYou can choose a paid plan afterwards.",
"subWillBeCancelledOn": "Your subscription will be cancelled on {endDate}",
"subscription": "Subscription",
@ -1116,5 +1117,8 @@
"fileTypes": "File types",
"deleteConfirmDialogBody": "This account is linked to other ente apps, if you use any.\\n\\nYour uploaded data, across all ente apps, will be scheduled for deletion, and your account will be permanently deleted.",
"hearUsWhereTitle": "How did you hear about Ente? (optional)",
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!"
}
"hearUsExplanation": "We don't track app installs. It'd help if you told us where you found us!",
"viewAddOnButton": "View add-ons",
"addOns": "Add-ons",
"addOnPageSubtitle": "Details of add-ons"
}

View file

@ -557,6 +557,7 @@
"faqs": "FAQ",
"renewsOn": "Si rinnova il {endDate}",
"freeTrialValidTill": "La prova gratuita termina il {endDate}",
"validTill": "Valido fino al {endDate}",
"playStoreFreeTrialValidTill": "Prova gratuita valida fino al {endDate}.\nPuoi scegliere un piano a pagamento in seguito.",
"subWillBeCancelledOn": "L'abbonamento verrà cancellato il {endDate}",
"subscription": "Abbonamento",
@ -1102,6 +1103,10 @@
"crashReporting": "Segnalazione di crash",
"addToHiddenAlbum": "Aggiungi ad album nascosto",
"moveToHiddenAlbum": "Sposta in album nascosto",
"fileTypes": "File types",
"deleteConfirmDialogBody": "Questo account è collegato ad altre app di ente, se ne utilizzi.\\n\\nI tuoi dati caricati, su tutte le app di ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente."
"deleteConfirmDialogBody": "Questo account è collegato ad altre app di ente, se ne utilizzi.\\n\\nI tuoi dati caricati, su tutte le app di ente, saranno pianificati per la cancellazione e il tuo account verrà eliminato definitivamente.",
"hearUsWhereTitle": "Come hai sentito parlare di Ente? (opzionale)",
"hearUsExplanation": "Non teniamo traccia del numero di installazioni dell'app. Sarebbe utile se ci dicesse dove ci ha trovato!",
"viewAddOnButton": "Visualizza componenti aggiuntivi",
"addOns": "Componenti aggiuntivi",
"addOnPageSubtitle": "Dettagli dei componenti aggiuntivi"
}

View file

@ -557,6 +557,7 @@
"faqs": "常见问题",
"renewsOn": "在 {endDate} 前续费",
"freeTrialValidTill": "免费试用有效期至 {endDate}",
"validTill": "有效期至 {endDate}",
"playStoreFreeTrialValidTill": "免费试用有效期至 {endDate}。\n之后您可以选择付费计划。",
"subWillBeCancelledOn": "您的订阅将于 {endDate} 取消",
"subscription": "订阅",
@ -1102,6 +1103,10 @@
"crashReporting": "崩溃报告",
"addToHiddenAlbum": "添加到隐藏相册",
"moveToHiddenAlbum": "移至隐藏相册",
"fileTypes": "File types",
"deleteConfirmDialogBody": "此账户已链接到其他 ente 旗下的应用程序(如果您使用任何 ente 旗下的应用程序)。\\n\\n您在所有 ente 旗下的应用程序中上传的数据将被安排删除,并且您的账户将被永久删除。"
"deleteConfirmDialogBody": "此账户已链接到其他 ente 旗下的应用程序(如果您使用任何 ente 旗下的应用程序)。\\n\\n您在所有 ente 旗下的应用程序中上传的数据将被安排删除,并且您的账户将被永久删除。",
"hearUsWhereTitle": "您是如何知道Ente的 (可选的)",
"hearUsExplanation": "我们不跟踪应用程序安装情况。如果您告诉我们您是在哪里找到我们的,将会有所帮助!",
"viewAddOnButton": "查看附加组件",
"addOns": "附加组件",
"addOnPageSubtitle": "附加组件详情"
}

View file

@ -0,0 +1,60 @@
class Bonus {
int storage;
String type;
int validTill;
bool isRevoked;
Bonus(this.storage, this.type, this.validTill, this.isRevoked);
factory Bonus.fromJson(Map<String, dynamic> json) {
return Bonus(
json['storage'],
json['type'],
json['validTill'],
json['isRevoked'],
);
}
Map<String, dynamic> toJson() {
return {
'storage': storage,
'type': type,
'validTill': validTill,
'isRevoked': isRevoked,
};
}
}
class BonusData {
static Set<String> signUpBonusTypes = {'SIGN_UP', 'REFERRAL'};
final List<Bonus> storageBonuses;
BonusData(this.storageBonuses);
List<Bonus> getAddOnBonuses() {
return storageBonuses
.where((b) => !signUpBonusTypes.contains(b.type))
.toList();
}
int totalAddOnBonus() {
return getAddOnBonuses().fold(0, (sum, bonus) => sum + bonus.storage);
}
factory BonusData.fromJson(Map<String, dynamic>? json) {
if (json == null || json['storageBonuses'] == null) {
return BonusData([]);
}
return BonusData(
(json['storageBonuses'] as List)
.map((bonus) => Bonus.fromJson(bonus))
.toList(),
);
}
Map<String, dynamic> toJson() {
return {
'storageBonuses': storageBonuses.map((bonus) => bonus.toJson()).toList(),
};
}
}

View file

@ -1,3 +1,5 @@
import "package:photos/models/api/storage_bonus/bonus.dart";
class ReferralView {
PlanInfo planInfo;
String code;
@ -86,34 +88,6 @@ class ReferralStat {
}
}
class Bonus {
int storage;
String type;
int validTill;
bool isRevoked;
Bonus(this.storage, this.type, this.validTill, this.isRevoked);
// fromJson
factory Bonus.fromJson(Map<String, dynamic> json) {
return Bonus(
json['storage'],
json['type'],
json['validTill'],
json['isRevoked'],
);
}
Map<String, dynamic> toJson() {
return {
'storage': storage,
'type': type,
'validTill': validTill,
'isRevoked': isRevoked,
};
}
}
class BonusDetails {
List<ReferralStat> referralStats;
List<Bonus> bonuses;

View file

@ -2,6 +2,7 @@ import 'dart:convert';
import 'dart:math';
import 'package:collection/collection.dart';
import "package:photos/models/api/storage_bonus/bonus.dart";
import 'package:photos/models/file/file_type.dart';
import 'package:photos/models/subscription.dart';
@ -14,6 +15,7 @@ class UserDetails {
final Subscription subscription;
final FamilyData? familyData;
final ProfileData? profileData;
final BonusData? bonusData;
const UserDetails(
this.email,
@ -24,6 +26,7 @@ class UserDetails {
this.subscription,
this.familyData,
this.profileData,
this.bonusData,
);
bool isPartOfFamily() {
@ -55,6 +58,12 @@ class UserDetails {
storageBonus;
}
// This is the total storage for which user has paid for.
int getPlanPlusAddonStorage() {
return (isPartOfFamily() ? familyData!.storage : subscription.storage) +
bonusData!.totalAddOnBonus();
}
factory UserDetails.fromMap(Map<String, dynamic> map) {
return UserDetails(
map['email'] as String,
@ -65,6 +74,7 @@ class UserDetails {
Subscription.fromMap(map['subscription']),
FamilyData.fromMap(map['familyData']),
ProfileData.fromJson(map['profileData']),
BonusData.fromJson(map['bonusData']),
);
}
@ -78,6 +88,7 @@ class UserDetails {
'subscription': subscription.toMap(),
'familyData': familyData?.toMap(),
'profileData': profileData?.toJson(),
'bonusData': bonusData?.toJson(),
};
}
@ -123,6 +134,7 @@ class FamilyMember {
factory FamilyMember.fromJson(String source) =>
FamilyMember.fromMap(json.decode(source));
}
class ProfileData {
bool canDisableEmailMFA;
bool isEmailMFAEnabled;
@ -135,7 +147,6 @@ class ProfileData {
this.isTwoFactorEnabled = false,
});
// Factory method to create ProfileData instance from JSON
factory ProfileData.fromJson(Map<String, dynamic>? json) {
return ProfileData(
@ -153,6 +164,7 @@ class ProfileData {
'isTwoFactorEnabled': isTwoFactorEnabled,
};
}
String toJsonString() => json.encode(toJson());
}

View file

@ -679,33 +679,23 @@ class UserService {
} on DioError catch (e, s) {
await dialog.hide();
if (e.response != null && e.response!.statusCode == 401) {
final dialogChoice = await showChoiceDialog(
await _showContactSupportDialog(
context,
title: S.of(context).incorrectPasswordTitle,
body: S.of(context).pleaseTryAgain,
firstButtonLabel: S.of(context).contactSupport,
secondButtonLabel: S.of(context).ok,
S.of(context).incorrectPasswordTitle,
S.of(context).pleaseTryAgain,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
S.of(context).contactSupport,
"support@ente.io",
postShare: () {},
);
}
} else {
_logger.fine('failed to verify password', e, s);
await showErrorDialog(
_logger.severe('failed to verify password', e, s);
await _showContactSupportDialog(
context,
S.of(context).oops,
S.of(context).verificationFailedPleaseTryAgain,
);
}
} catch (e, s) {
_logger.fine('failed to verify password', e, s);
_logger.severe('failed to verify password', e, s);
await dialog.hide();
await showErrorDialog(
await _showContactSupportDialog(
context,
S.of(context).oops,
S.of(context).verificationFailedPleaseTryAgain,
@ -1174,4 +1164,26 @@ class UserService {
rethrow;
}
}
Future<void> _showContactSupportDialog(
BuildContext context,
String title,
String message,
) async {
final dialogChoice = await showChoiceDialog(
context,
title: title,
body: message,
firstButtonLabel: S.of(context).contactSupport,
secondButtonLabel: S.of(context).ok,
);
if (dialogChoice!.action == ButtonAction.first) {
await sendLogs(
context,
S.of(context).contactSupport,
"support@ente.io",
postShare: () {},
);
}
}
}

View file

@ -122,12 +122,14 @@ class _StorageDetailsScreenState extends State<StorageDetailsScreen> {
leftValue: convertBytesToAbsoluteGBs(
min(
widget.referralView.claimedStorage,
widget.userDetails.getTotalStorage(),
widget.userDetails
.getPlanPlusAddonStorage(),
),
),
leftUnitName: "GB",
rightValue: convertBytesToAbsoluteGBs(
widget.userDetails.getTotalStorage(),
widget.userDetails
.getPlanPlusAddonStorage(),
),
rightUnitName: "GB",
),

View file

@ -0,0 +1,123 @@
import "package:flutter/material.dart";
import "package:intl/intl.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/models/api/storage_bonus/bonus.dart";
import "package:photos/theme/ente_theme.dart";
import 'package:photos/ui/components/buttons/icon_button_widget.dart';
import "package:photos/ui/components/title_bar_title_widget.dart";
import "package:photos/ui/components/title_bar_widget.dart";
import "package:photos/utils/data_util.dart";
class AddOnPage extends StatelessWidget {
final BonusData bonusData;
const AddOnPage(this.bonusData, {super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
body: CustomScrollView(
primary: false,
slivers: <Widget>[
TitleBarWidget(
flexibleSpaceTitle: TitleBarTitleWidget(
title: S.of(context).addOns,
),
flexibleSpaceCaption: S.of(context).addOnPageSubtitle,
actionIcons: [
IconButtonWidget(
icon: Icons.close_outlined,
iconButtonType: IconButtonType.secondary,
onTap: () {
Navigator.of(context).pop();
},
),
],
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 16),
sliver: SliverList(
delegate: SliverChildBuilderDelegate(
(delegateBuildContext, index) {
Bonus bonus = bonusData.getAddOnBonuses()[index];
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4),
child: AddOnViewSection(
sectionName: bonus.type == 'ADD_ON_BF_2023'
? "Black friday 2023"
: bonus.type.replaceAll('_', ' '),
bonus: bonus,
),
);
},
childCount: bonusData?.getAddOnBonuses().length ?? 0,
),
),
),
],
),
);
}
}
class AddOnViewSection extends StatelessWidget {
final String sectionName;
final Bonus bonus;
const AddOnViewSection({
super.key,
required this.sectionName,
required this.bonus,
});
@override
Widget build(BuildContext context) {
final colorScheme = getEnteColorScheme(context);
final textStyle = getEnteTextTheme(context);
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
sectionName,
style: textStyle.body.copyWith(
color: colorScheme.textMuted,
),
),
if (bonus.validTill != 0)
Text(
S.of(context).validTill(
DateFormat.yMMMd(
Localizations.localeOf(context).languageCode,
)
.format(
DateTime.fromMicrosecondsSinceEpoch(
bonus.validTill,
),
)
.toString(),
),
style: textStyle.body.copyWith(
color: colorScheme.textMuted,
),
),
],
),
const SizedBox(height: 2),
RichText(
text: TextSpan(
children: [
TextSpan(
text: convertBytesToReadableFormat(bonus.storage).toString(),
style: textStyle.h3,
),
],
),
),
const SizedBox(height: 24),
],
);
}
}

View file

@ -25,6 +25,8 @@ import 'package:photos/ui/payment/child_subscription_widget.dart';
import 'package:photos/ui/payment/skip_subscription_widget.dart';
import 'package:photos/ui/payment/subscription_common_widgets.dart';
import 'package:photos/ui/payment/subscription_plan_widget.dart';
import "package:photos/ui/payment/view_add_on_widget.dart";
import "package:photos/utils/data_util.dart";
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:url_launcher/url_launcher_string.dart';
@ -238,7 +240,12 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
}
if (_hasActiveSubscription) {
widgets.add(ValidityWidget(currentSubscription: _currentSubscription));
widgets.add(
ValidityWidget(
currentSubscription: _currentSubscription,
bonusData: _userDetails.bonusData,
),
);
}
if (_currentSubscription!.productID == freeProductID) {
@ -290,7 +297,7 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
if (!widget.isOnboarding) {
widgets.add(
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 80),
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
child: MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: _isFreePlanUser()
@ -310,6 +317,8 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
),
),
);
widgets.add(ViewAddOnButton(_userDetails.bonusData));
widgets.add(const SizedBox(height: 80));
}
return SingleChildScrollView(
child: Column(
@ -490,7 +499,16 @@ class _StoreSubscriptionPageState extends State<StoreSubscriptionPage> {
if (isActive) {
return;
}
if (_userDetails.getFamilyOrPersonalUsage() > plan.storage) {
final int addOnBonus =
_userDetails.bonusData?.totalAddOnBonus() ?? 0;
if (_userDetails.getFamilyOrPersonalUsage() >
(plan.storage + addOnBonus)) {
_logger.warning(
" familyUsage ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage())}"
" plan storage ${convertBytesToReadableFormat(plan.storage)} "
"addOnBonus ${convertBytesToReadableFormat(addOnBonus)},"
"overshooting by ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage() - (plan.storage + addOnBonus))}",
);
showErrorDialog(
context,
S.of(context).sorry,

View file

@ -25,6 +25,8 @@ import 'package:photos/ui/payment/payment_web_page.dart';
import 'package:photos/ui/payment/skip_subscription_widget.dart';
import 'package:photos/ui/payment/subscription_common_widgets.dart';
import 'package:photos/ui/payment/subscription_plan_widget.dart';
import "package:photos/ui/payment/view_add_on_widget.dart";
import "package:photos/utils/data_util.dart";
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
import 'package:step_progress_indicator/step_progress_indicator.dart';
@ -209,7 +211,10 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
widgets.add(_showSubscriptionToggle());
if (_hasActiveSubscription) {
widgets.add(ValidityWidget(currentSubscription: _currentSubscription));
widgets.add(ValidityWidget(
currentSubscription: _currentSubscription,
bonusData: _userDetails.bonusData,
));
}
if (_currentSubscription!.productID == freeProductID) {
@ -252,7 +257,7 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
if (!widget.isOnboarding) {
widgets.add(
Padding(
padding: const EdgeInsets.fromLTRB(16, 0, 16, 80),
padding: const EdgeInsets.fromLTRB(16, 0, 16, 0),
child: MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).manageFamily,
@ -270,6 +275,8 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
),
),
);
widgets.add(ViewAddOnButton(_userDetails.bonusData));
widgets.add(const SizedBox(height: 80));
}
return SingleChildScrollView(
@ -446,7 +453,16 @@ class _StripeSubscriptionPageState extends State<StripeSubscriptionPage> {
);
return;
}
if (_userDetails.getFamilyOrPersonalUsage() > plan.storage) {
final int addOnBonus =
_userDetails.bonusData?.totalAddOnBonus() ?? 0;
if (_userDetails.getFamilyOrPersonalUsage() >
(plan.storage + addOnBonus)) {
logger.warning(
" familyUsage ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage())}"
" plan storage ${convertBytesToReadableFormat(plan.storage)} "
"addOnBonus ${convertBytesToReadableFormat(addOnBonus)},"
"overshooting by ${convertBytesToReadableFormat(_userDetails.getFamilyOrPersonalUsage() - (plan.storage + addOnBonus))}",
);
showErrorDialog(
context,
S.of(context).sorry,

View file

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import "package:intl/intl.dart";
import 'package:photos/ente_theme_data.dart';
import "package:photos/generated/l10n.dart";
import "package:photos/models/api/storage_bonus/bonus.dart";
import 'package:photos/models/subscription.dart';
import "package:photos/services/update_service.dart";
import "package:photos/theme/ente_theme.dart";
@ -87,21 +88,27 @@ class _SubscriptionHeaderWidgetState extends State<SubscriptionHeaderWidget> {
class ValidityWidget extends StatelessWidget {
final Subscription? currentSubscription;
final BonusData? bonusData;
const ValidityWidget({Key? key, this.currentSubscription}) : super(key: key);
const ValidityWidget({Key? key, this.currentSubscription, this.bonusData})
: super(key: key);
@override
Widget build(BuildContext context) {
if (currentSubscription == null) {
return const SizedBox.shrink();
}
final bool isFreeTrialSub = currentSubscription!.productID == freeProductID;
if (isFreeTrialSub && (bonusData?.getAddOnBonuses().isNotEmpty ?? false)) {
return const SizedBox.shrink();
}
final endDate =
DateFormat.yMMMd(Localizations.localeOf(context).languageCode).format(
DateTime.fromMicrosecondsSinceEpoch(currentSubscription!.expiryTime),
);
var message = S.of(context).renewsOn(endDate);
if (currentSubscription!.productID == freeProductID) {
if (isFreeTrialSub) {
message = UpdateService.instance.isPlayStoreFlavor()
? S.of(context).playStoreFreeTrialValidTill(endDate)
: S.of(context).freeTrialValidTill(endDate);

View file

@ -0,0 +1,41 @@
import "package:flutter/material.dart";
import "package:photos/generated/l10n.dart";
import "package:photos/models/api/storage_bonus/bonus.dart";
import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/captioned_text_widget.dart";
import "package:photos/ui/components/menu_item_widget/menu_item_widget.dart";
import "package:photos/ui/payment/add_on_page.dart";
import "package:photos/utils/navigation_util.dart";
class ViewAddOnButton extends StatelessWidget {
final BonusData? bonusData;
const ViewAddOnButton(this.bonusData, {super.key});
@override
Widget build(BuildContext context) {
if (bonusData?.getAddOnBonuses().isEmpty ?? true) {
return const SizedBox.shrink();
}
final EnteColorScheme colorScheme = getEnteColorScheme(context);
return Padding(
padding: const EdgeInsets.fromLTRB(16, 4, 16, 0),
child: MenuItemWidget(
captionedTextWidget: CaptionedTextWidget(
title: S.of(context).viewAddOnButton,
),
menuItemColor: colorScheme.fillFaint,
trailingWidget: Icon(
Icons.chevron_right_outlined,
color: colorScheme.strokeBase,
),
singleBorderRadius: 4,
alignCaptionedTextToLeft: true,
onTap: () async {
routeToPage(context, AddOnPage(bonusData!));
},
),
);
}
}

View file

@ -8,7 +8,9 @@ import 'package:photos/services/deduplication_service.dart';
import 'package:photos/services/sync_service.dart';
import 'package:photos/services/update_service.dart';
import 'package:photos/theme/ente_theme.dart';
import "package:photos/ui/components/buttons/button_widget.dart";
import "package:photos/ui/components/captioned_text_widget.dart";
import "package:photos/ui/components/dialog_widget.dart";
import 'package:photos/ui/components/expandable_menu_item_widget.dart';
import 'package:photos/ui/components/menu_item_widget/menu_item_widget.dart';
import "package:photos/ui/components/models/button_type.dart";
@ -16,9 +18,10 @@ import 'package:photos/ui/settings/backup/backup_folder_selection_page.dart';
import 'package:photos/ui/settings/backup/backup_settings_screen.dart';
import 'package:photos/ui/settings/common_settings.dart';
import 'package:photos/ui/tools/deduplicate_page.dart';
import 'package:photos/ui/tools/free_space_page.dart';
import "package:photos/ui/tools/free_space_page.dart";
import 'package:photos/utils/data_util.dart';
import 'package:photos/utils/dialog_util.dart';
import "package:photos/utils/local_settings.dart";
import 'package:photos/utils/navigation_util.dart';
import 'package:photos/utils/toast_util.dart';
@ -153,25 +156,55 @@ class BackupSectionWidgetState extends State<BackupSectionWidget> {
}
void _showSpaceFreedDialog(BackupStatus status) {
showChoiceDialog(
context,
title: S.of(context).success,
body: S.of(context).youHaveSuccessfullyFreedUp(formatBytes(status.size)),
firstButtonLabel: S.of(context).rateUs,
firstButtonOnTap: () async {
UpdateService.instance.launchReviewUrl();
},
firstButtonType: ButtonType.primary,
secondButtonLabel: S.of(context).ok,
secondButtonOnTap: () async {
if (Platform.isIOS) {
showToast(
context,
S.of(context).remindToEmptyDeviceTrash,
);
}
},
);
if (LocalSettings.instance.shouldPromptToRateUs()) {
LocalSettings.instance.setRateUsShownCount(
LocalSettings.instance.getRateUsShownCount() + 1,
);
showChoiceDialog(
context,
title: S.of(context).success,
body:
S.of(context).youHaveSuccessfullyFreedUp(formatBytes(status.size)),
firstButtonLabel: S.of(context).rateUs,
firstButtonOnTap: () async {
UpdateService.instance.launchReviewUrl();
},
firstButtonType: ButtonType.primary,
secondButtonLabel: S.of(context).ok,
secondButtonOnTap: () async {
if (Platform.isIOS) {
showToast(
context,
S.of(context).remindToEmptyDeviceTrash,
);
}
},
);
} else {
showDialogWidget(
context: context,
title: S.of(context).success,
body:
S.of(context).youHaveSuccessfullyFreedUp(formatBytes(status.size)),
icon: Icons.download_done_rounded,
isDismissible: true,
buttons: [
ButtonWidget(
buttonType: ButtonType.neutral,
labelText: S.of(context).ok,
isInAlert: true,
onTap: () async {
if (Platform.isIOS) {
showToast(
context,
S.of(context).remindToEmptyDeviceTrash,
);
}
},
),
],
);
}
}
void _showDuplicateFilesDeletedDialog(DeduplicationResult result) {

View file

@ -164,7 +164,6 @@ class _DetailPageState extends State<DetailPage> {
}
Widget _buildPageView(BuildContext context) {
final bottomPadding = MediaQuery.of(context).padding.bottom;
return PageView.builder(
itemBuilder: (context, index) {
final file = _files![index];

View file

@ -11,6 +11,7 @@ import "package:photos/models/file/extensions/file_props.dart";
import 'package:photos/models/file/file.dart';
import "package:photos/services/feature_flag_service.dart";
import 'package:photos/services/files_service.dart';
import "package:photos/ui/actions/file/file_actions.dart";
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
import 'package:photos/ui/viewer/file/video_controls.dart';
import "package:photos/utils/dialog_util.dart";
@ -184,7 +185,14 @@ class _VideoWidgetState extends State<VideoWidget> {
final contentWithDetector = GestureDetector(
child: content,
onVerticalDragUpdate: (d) => {
if (d.delta.dy > dragSensitivity) {Navigator.of(context).pop()},
if (d.delta.dy > dragSensitivity)
{
Navigator.of(context).pop(),
}
else if (d.delta.dy < (dragSensitivity * -1))
{
showDetailsSheet(context, widget.file),
},
},
);
return VisibilityDetector(

View file

@ -12,6 +12,7 @@ import "package:photos/models/file/file.dart";
import "package:photos/services/files_service.dart";
import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/actions/file/file_actions.dart";
import "package:photos/ui/viewer/file/thumbnail_widget.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/file_util.dart";
@ -133,7 +134,14 @@ class _VideoWidgetNewState extends State<VideoWidgetNew>
fullscreen: const MaterialVideoControlsThemeData(),
child: GestureDetector(
onVerticalDragUpdate: (d) => {
if (d.delta.dy > dragSensitivity) {Navigator.of(context).pop()},
if (d.delta.dy > dragSensitivity)
{
Navigator.of(context).pop(),
}
else if (d.delta.dy < (dragSensitivity * -1))
{
showDetailsSheet(context, widget.file),
},
},
child: Center(
child: controller != null

View file

@ -16,6 +16,7 @@ import "package:photos/models/file/extensions/file_props.dart";
import 'package:photos/models/file/file.dart';
import "package:photos/models/metadata/file_magic.dart";
import "package:photos/services/file_magic_service.dart";
import "package:photos/ui/actions/file/file_actions.dart";
import 'package:photos/ui/common/loading_widget.dart';
import 'package:photos/utils/file_util.dart';
import 'package:photos/utils/image_util.dart';
@ -111,8 +112,17 @@ class _ZoomableImageState extends State<ZoomableImage>
final GestureDragUpdateCallback? verticalDragCallback = _isZooming
? null
: (d) => {
if (!_isZooming && d.delta.dy > dragSensitivity)
{Navigator.of(context).pop()},
if (!_isZooming)
{
if (d.delta.dy > dragSensitivity)
{
{Navigator.of(context).pop()},
}
else if (d.delta.dy < (dragSensitivity * -1))
{
showDetailsSheet(context, widget.photo),
},
},
};
return GestureDetector(
onVerticalDragUpdate: verticalDragCallback,

View file

@ -13,6 +13,8 @@ class LocalSettings {
static final LocalSettings instance = LocalSettings._privateConstructor();
static const kCollectionSortPref = "collection_sort_pref";
static const kPhotoGridSize = "photo_grid_size";
static const kRateUsShownCount = "rate_us_shown_count";
static const kRateUsPromptThreshold = 2;
late SharedPreferences _prefs;
@ -39,4 +41,20 @@ class LocalSettings {
Future<void> setPhotoGridSize(int value) async {
await _prefs.setInt(kPhotoGridSize, value);
}
int getRateUsShownCount() {
if (_prefs.containsKey(kRateUsShownCount)) {
return _prefs.getInt(kRateUsShownCount)!;
} else {
return 0;
}
}
Future<void> setRateUsShownCount(int value) async {
await _prefs.setInt(kRateUsShownCount, value);
}
bool shouldPromptToRateUs() {
return getRateUsShownCount() < kRateUsPromptThreshold;
}
}

View file

@ -12,7 +12,7 @@ description: ente photos application
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 0.7.105+505
version: 0.7.108+508
environment:
sdk: ">=3.0.0 <4.0.0"
@ -148,7 +148,7 @@ dependencies:
git:
url: https://github.com/ente-io/packages.git
ref: android_video_roation_fix
path: packages/video_player/video_player/p
path: packages/video_player/video_player/
video_thumbnail: ^0.5.3
visibility_detector: ^0.3.3
wakelock_plus: ^1.1.1