mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 14:28:40 +00:00
error msg when limit is reached
This commit is contained in:
parent
c5734bbcc6
commit
db96157ff5
12 changed files with 177 additions and 43 deletions
|
|
@ -223,5 +223,7 @@
|
||||||
"checkoutSubmit": "Kostenpflichtig bestellen",
|
"checkoutSubmit": "Kostenpflichtig bestellen",
|
||||||
"additionalUsersList": "Ihre zusätzlichen Benutzer",
|
"additionalUsersList": "Ihre zusätzlichen Benutzer",
|
||||||
"additionalUsersPlusTokens": "twonly-Codes für \"Plus\"-Benutzer",
|
"additionalUsersPlusTokens": "twonly-Codes für \"Plus\"-Benutzer",
|
||||||
"additionalUsersFreeTokens": "twonly-Codes für \"Free\"-Benutzer"
|
"additionalUsersFreeTokens": "twonly-Codes für \"Free\"-Benutzer",
|
||||||
|
"planNotAllowed": "In deinem aktuellen Plan kannst du keine Mediendateien versenden. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.",
|
||||||
|
"planLimitReached": "Du hast dein Planlimit für heute erreicht. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden."
|
||||||
}
|
}
|
||||||
|
|
@ -382,5 +382,7 @@
|
||||||
"checkoutSubmit": "Order with a fee.",
|
"checkoutSubmit": "Order with a fee.",
|
||||||
"additionalUsersList": "Your additional users",
|
"additionalUsersList": "Your additional users",
|
||||||
"additionalUsersPlusTokens": "twonly-Codes für \"Plus\" user",
|
"additionalUsersPlusTokens": "twonly-Codes für \"Plus\" user",
|
||||||
"additionalUsersFreeTokens": "twonly-Codes für \"Free\" user"
|
"additionalUsersFreeTokens": "twonly-Codes für \"Free\" user",
|
||||||
|
"planLimitReached": "You have reached your plan limit for today. Upgrade your plan now to send the media file.",
|
||||||
|
"planNotAllowed": "You cannot send media files with your current tariff. Upgrade your plan now to send the media file."
|
||||||
}
|
}
|
||||||
|
|
@ -1354,6 +1354,18 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'twonly-Codes für \"Free\" user'**
|
/// **'twonly-Codes für \"Free\" user'**
|
||||||
String get additionalUsersFreeTokens;
|
String get additionalUsersFreeTokens;
|
||||||
|
|
||||||
|
/// No description provided for @planLimitReached.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You have reached your plan limit for today. Upgrade your plan now to send the media file.'**
|
||||||
|
String get planLimitReached;
|
||||||
|
|
||||||
|
/// No description provided for @planNotAllowed.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You cannot send media files with your current tariff. Upgrade your plan now to send the media file.'**
|
||||||
|
String get planNotAllowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
class _AppLocalizationsDelegate extends LocalizationsDelegate<AppLocalizations> {
|
||||||
|
|
|
||||||
|
|
@ -651,4 +651,10 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get additionalUsersFreeTokens => 'twonly-Codes für \"Free\"-Benutzer';
|
String get additionalUsersFreeTokens => 'twonly-Codes für \"Free\"-Benutzer';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get planLimitReached => 'Du hast dein Planlimit für heute erreicht. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get planNotAllowed => 'In deinem aktuellen Plan kannst du keine Mediendateien versenden. Aktualisiere deinen Plan jetzt, um die Mediendatei zu senden.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -651,4 +651,10 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get additionalUsersFreeTokens => 'twonly-Codes für \"Free\" user';
|
String get additionalUsersFreeTokens => 'twonly-Codes für \"Free\" user';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get planLimitReached => 'You have reached your plan limit for today. Upgrade your plan now to send the media file.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get planNotAllowed => 'You cannot send media files with your current tariff. Upgrade your plan now to send the media file.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ class UserData {
|
||||||
String? lastPlanBallance;
|
String? lastPlanBallance;
|
||||||
String? additionalUserInvites;
|
String? additionalUserInvites;
|
||||||
|
|
||||||
|
DateTime? lastImageSend;
|
||||||
|
int? todaysImageCounter;
|
||||||
|
|
||||||
final int userId;
|
final int userId;
|
||||||
|
|
||||||
factory UserData.fromJson(Map<String, dynamic> json) =>
|
factory UserData.fromJson(Map<String, dynamic> json) =>
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,11 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
?.map((e) => e as String)
|
?.map((e) => e as String)
|
||||||
.toList()
|
.toList()
|
||||||
..lastPlanBallance = json['lastPlanBallance'] as String?
|
..lastPlanBallance = json['lastPlanBallance'] as String?
|
||||||
..additionalUserInvites = json['additionalUserInvites'] as String?;
|
..additionalUserInvites = json['additionalUserInvites'] as String?
|
||||||
|
..lastImageSend = json['lastImageSend'] == null
|
||||||
|
? null
|
||||||
|
: DateTime.parse(json['lastImageSend'] as String)
|
||||||
|
..todaysImageCounter = (json['todaysImageCounter'] as num?)?.toInt();
|
||||||
|
|
||||||
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'username': instance.username,
|
'username': instance.username,
|
||||||
|
|
@ -49,6 +53,8 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'lastUsedEditorEmojis': instance.lastUsedEditorEmojis,
|
'lastUsedEditorEmojis': instance.lastUsedEditorEmojis,
|
||||||
'lastPlanBallance': instance.lastPlanBallance,
|
'lastPlanBallance': instance.lastPlanBallance,
|
||||||
'additionalUserInvites': instance.additionalUserInvites,
|
'additionalUserInvites': instance.additionalUserInvites,
|
||||||
|
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||||
|
'todaysImageCounter': instance.todaysImageCounter,
|
||||||
'userId': instance.userId,
|
'userId': instance.userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:cryptography_plus/cryptography_plus.dart';
|
import 'package:cryptography_plus/cryptography_plus.dart';
|
||||||
import 'package:drift/drift.dart';
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
import 'package:flutter_image_compress/flutter_image_compress.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:mutex/mutex.dart';
|
import 'package:mutex/mutex.dart';
|
||||||
|
|
@ -19,16 +20,44 @@ import 'package:twonly/src/providers/api/api.dart';
|
||||||
import 'package:twonly/src/providers/api/api_utils.dart';
|
import 'package:twonly/src/providers/api/api_utils.dart';
|
||||||
import 'package:twonly/src/providers/api/media_received.dart';
|
import 'package:twonly/src/providers/api/media_received.dart';
|
||||||
import 'package:twonly/src/services/notification_service.dart';
|
import 'package:twonly/src/services/notification_service.dart';
|
||||||
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:video_compress/video_compress.dart';
|
import 'package:video_compress/video_compress.dart';
|
||||||
|
|
||||||
|
Future<ErrorCode?> isAllowedToSend() async {
|
||||||
|
final user = await getUser();
|
||||||
|
if (user == null) return null;
|
||||||
|
if (user.subscriptionPlan == "Preview") {
|
||||||
|
return ErrorCode.PlanNotAllowed;
|
||||||
|
}
|
||||||
|
if (user.subscriptionPlan == "Free") {
|
||||||
|
if (user.lastImageSend != null && user.todaysImageCounter != null) {
|
||||||
|
if (isToday(user.lastImageSend!)) {
|
||||||
|
if (user.todaysImageCounter == 3) {
|
||||||
|
return ErrorCode.PlanLimitReached;
|
||||||
|
}
|
||||||
|
user.todaysImageCounter = user.todaysImageCounter! + 1;
|
||||||
|
} else {
|
||||||
|
user.todaysImageCounter = 1;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
user.todaysImageCounter = 1;
|
||||||
|
}
|
||||||
|
user.lastImageSend = DateTime.now();
|
||||||
|
await updateUser(user);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
Future sendMediaFile(
|
Future sendMediaFile(
|
||||||
List<int> userIds,
|
List<int> userIds,
|
||||||
Uint8List imageBytes,
|
Uint8List imageBytes,
|
||||||
bool isRealTwonly,
|
bool isRealTwonly,
|
||||||
int maxShowTime,
|
int maxShowTime,
|
||||||
XFile? videoFilePath,
|
XFile? videoFilePath,
|
||||||
bool? enableVideoAudio,
|
bool? enableVideoAudio,
|
||||||
bool mirrorVideo) async {
|
bool mirrorVideo,
|
||||||
|
) async {
|
||||||
MediaUploadMetadata metadata = MediaUploadMetadata();
|
MediaUploadMetadata metadata = MediaUploadMetadata();
|
||||||
metadata.contactIds = userIds;
|
metadata.contactIds = userIds;
|
||||||
metadata.isRealTwonly = isRealTwonly;
|
metadata.isRealTwonly = isRealTwonly;
|
||||||
|
|
@ -314,6 +343,14 @@ Future<bool> handleGetUploadToken(MediaUpload media) async {
|
||||||
await apiProvider.getUploadToken(media.metadata.contactIds.length);
|
await apiProvider.getUploadToken(media.metadata.contactIds.length);
|
||||||
|
|
||||||
if (res.isError || !res.value.hasUploadtoken()) {
|
if (res.isError || !res.value.hasUploadtoken()) {
|
||||||
|
if (res.isError) {
|
||||||
|
if (res.error == ErrorCode.PlanNotAllowed) {
|
||||||
|
throw Exception("PlanNotAllowed");
|
||||||
|
}
|
||||||
|
if (res.error == ErrorCode.PlanLimitReached) {
|
||||||
|
throw Exception("PlanLimitReached");
|
||||||
|
}
|
||||||
|
}
|
||||||
Logger("media_send.dart")
|
Logger("media_send.dart")
|
||||||
.shout("Will be tried again when reconnected to server!");
|
.shout("Will be tried again when reconnected to server!");
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -194,3 +194,10 @@ bool isDarkMode(BuildContext context) {
|
||||||
return selectedTheme == ThemeMode.dark ||
|
return selectedTheme == ThemeMode.dark ||
|
||||||
(selectedTheme == ThemeMode.system && isDarkMode);
|
(selectedTheme == ThemeMode.system && isDarkMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool isToday(DateTime lastImageSend) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
return lastImageSend.year == now.year &&
|
||||||
|
lastImageSend.month == now.month &&
|
||||||
|
lastImageSend.day == now.day;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/api/error.pb.dart' show ErrorCode;
|
||||||
import 'package:twonly/src/providers/api/media_send.dart';
|
import 'package:twonly/src/providers/api/media_send.dart';
|
||||||
import 'package:twonly/src/views/camera/components/save_to_gallery.dart';
|
import 'package:twonly/src/views/camera/components/save_to_gallery.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
import 'package:twonly/src/views/camera/image_editor/action_button.dart';
|
||||||
|
|
@ -21,6 +22,7 @@ import 'package:twonly/src/views/camera/image_editor/data/layer.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart';
|
import 'package:twonly/src/views/camera/image_editor/layers_viewer.dart';
|
||||||
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
import 'package:twonly/src/views/camera/image_editor/modules/all_emojis.dart';
|
||||||
import 'package:screenshot/screenshot.dart';
|
import 'package:screenshot/screenshot.dart';
|
||||||
|
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
|
||||||
import 'package:video_player/video_player.dart';
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
List<Layer> layers = [];
|
List<Layer> layers = [];
|
||||||
|
|
@ -365,18 +367,32 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
|
||||||
Navigator.pop(context, true);
|
Navigator.pop(context, true);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sendMediaFile(
|
ErrorCode? err = await isAllowedToSend();
|
||||||
[widget.sendTo!.userId],
|
if (!context.mounted) return;
|
||||||
imageBytes,
|
|
||||||
_isRealTwonly,
|
if (err != null) {
|
||||||
maxShowTime,
|
setState(() {
|
||||||
widget.videoFilePath,
|
sendingOrLoadingImage = false;
|
||||||
videoWithAudio,
|
});
|
||||||
widget.mirrorVideo,
|
await Navigator.push(context, MaterialPageRoute(builder: (context) {
|
||||||
);
|
return SubscriptionView(
|
||||||
if (context.mounted) {
|
redirectError: err,
|
||||||
// ignore: use_build_context_synchronously
|
);
|
||||||
Navigator.pop(context, true);
|
}));
|
||||||
|
} else {
|
||||||
|
sendMediaFile(
|
||||||
|
[widget.sendTo!.userId],
|
||||||
|
imageBytes,
|
||||||
|
_isRealTwonly,
|
||||||
|
maxShowTime,
|
||||||
|
widget.videoFilePath,
|
||||||
|
videoWithAudio,
|
||||||
|
widget.mirrorVideo,
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
// ignore: use_build_context_synchronously
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:camera/camera.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
|
||||||
import 'package:twonly/src/providers/api/media_send.dart';
|
import 'package:twonly/src/providers/api/media_send.dart';
|
||||||
import 'package:twonly/src/views/camera/components/best_friends_selector.dart';
|
import 'package:twonly/src/views/camera/components/best_friends_selector.dart';
|
||||||
import 'package:twonly/src/views/components/flame.dart';
|
import 'package:twonly/src/views/components/flame.dart';
|
||||||
|
|
@ -14,6 +15,7 @@ import 'package:twonly/src/views/components/verified_shield.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/twonly_database.dart';
|
import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
import 'package:twonly/src/views/settings/subscription/subscription_view.dart';
|
||||||
|
|
||||||
class ShareImageView extends StatefulWidget {
|
class ShareImageView extends StatefulWidget {
|
||||||
const ShareImageView(
|
const ShareImageView(
|
||||||
|
|
@ -243,26 +245,40 @@ class _ShareImageView extends State<ShareImageView> {
|
||||||
if (imageBytes == null || _selectedUserIds.isEmpty) {
|
if (imageBytes == null || _selectedUserIds.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
setState(() {
|
|
||||||
sendingImage = true;
|
ErrorCode? err = await isAllowedToSend();
|
||||||
});
|
if (!context.mounted) return;
|
||||||
sendMediaFile(
|
|
||||||
_selectedUserIds.toList(),
|
if (err != null) {
|
||||||
imageBytes!,
|
await Navigator.push(context,
|
||||||
widget.isRealTwonly,
|
MaterialPageRoute(builder: (context) {
|
||||||
widget.maxShowTime,
|
return SubscriptionView(
|
||||||
widget.videoFilePath,
|
redirectError: err,
|
||||||
widget.enableVideoAudio,
|
);
|
||||||
widget.mirrorVideo,
|
}));
|
||||||
);
|
} else {
|
||||||
if (context.mounted) {
|
setState(() {
|
||||||
Navigator.pop(context, true);
|
sendingImage = true;
|
||||||
// if (widget.preselectedUser != null) {
|
});
|
||||||
// Navigator.pop(context, true);
|
|
||||||
// } else {
|
sendMediaFile(
|
||||||
// Navigator.popUntil(context, (route) => route.isFirst, true);
|
_selectedUserIds.toList(),
|
||||||
// globalUpdateOfHomeViewPageIndex(1);
|
imageBytes!,
|
||||||
// }
|
widget.isRealTwonly,
|
||||||
|
widget.maxShowTime,
|
||||||
|
widget.videoFilePath,
|
||||||
|
widget.enableVideoAudio,
|
||||||
|
widget.mirrorVideo,
|
||||||
|
);
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context, true);
|
||||||
|
// if (widget.preselectedUser != null) {
|
||||||
|
// Navigator.pop(context, true);
|
||||||
|
// } else {
|
||||||
|
// Navigator.popUntil(context, (route) => route.isFirst, true);
|
||||||
|
// globalUpdateOfHomeViewPageIndex(1);
|
||||||
|
// }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import 'package:intl/intl.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
|
import 'package:twonly/src/model/protobuf/api/error.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/server_to_client.pb.dart';
|
||||||
import 'package:twonly/src/providers/connection_provider.dart';
|
import 'package:twonly/src/providers/connection_provider.dart';
|
||||||
import 'package:twonly/src/utils/misc.dart';
|
import 'package:twonly/src/utils/misc.dart';
|
||||||
|
|
@ -82,7 +83,9 @@ int calculateRefund(Response_PlanBallance current) {
|
||||||
}
|
}
|
||||||
|
|
||||||
class SubscriptionView extends StatefulWidget {
|
class SubscriptionView extends StatefulWidget {
|
||||||
const SubscriptionView({super.key});
|
const SubscriptionView({super.key, this.redirectError});
|
||||||
|
|
||||||
|
final ErrorCode? redirectError;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<SubscriptionView> createState() => _SubscriptionViewState();
|
State<SubscriptionView> createState() => _SubscriptionViewState();
|
||||||
|
|
@ -135,6 +138,24 @@ class _SubscriptionViewState extends State<SubscriptionView> {
|
||||||
),
|
),
|
||||||
body: ListView(
|
body: ListView(
|
||||||
children: [
|
children: [
|
||||||
|
if (widget.redirectError != null)
|
||||||
|
Center(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
margin: EdgeInsets.all(16),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.orangeAccent,
|
||||||
|
borderRadius: BorderRadius.circular(15),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
(widget.redirectError == ErrorCode.PlanLimitReached)
|
||||||
|
? context.lang.planLimitReached
|
||||||
|
: context.lang.planNotAllowed,
|
||||||
|
style: TextStyle(color: Colors.black),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
Padding(
|
Padding(
|
||||||
padding: const EdgeInsets.all(32.0),
|
padding: const EdgeInsets.all(32.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue