mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 22:08:40 +00:00
fix #220
This commit is contained in:
parent
9f45a461a2
commit
eb545f84b0
21 changed files with 190 additions and 122 deletions
2
.github/workflows/release_github.yml
vendored
2
.github/workflows/release_github.yml
vendored
|
|
@ -4,7 +4,7 @@ on:
|
||||||
workflow_dispatch: {}
|
workflow_dispatch: {}
|
||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main_disabled
|
||||||
# paths:
|
# paths:
|
||||||
# - lib/**
|
# - lib/**
|
||||||
# - pubspec.lock
|
# - pubspec.lock
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
- twonly now has a free plan and is now financed by donations and an optional subscription with more features (coming soon)
|
- twonly now has a free plan and is now financed by donations and an optional subscription with more features (coming soon)
|
||||||
- iOS gestures to close images
|
- iOS gestures to close images
|
||||||
- Improved chat messages view, including better citation view and display times
|
- Improved chat messages view, including better citation view and display times
|
||||||
- onboarding screens updated and registration view simplified
|
- Onboarding screens updated and registration view simplified
|
||||||
- The sender is displayed in the top right corner when a media file is opened
|
- The sender is displayed in the top right corner when a media file is opened
|
||||||
- Images are now stored as WebP to save storage
|
- Images are now stored as WebP to save storage
|
||||||
- Button to report users
|
- Button to report users
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ void Function({required bool isConnected}) globalCallbackConnectionState = ({
|
||||||
required bool isConnected,
|
required bool isConnected,
|
||||||
}) {};
|
}) {};
|
||||||
void Function() globalCallbackAppIsOutdated = () {};
|
void Function() globalCallbackAppIsOutdated = () {};
|
||||||
|
void Function() globalCallbackNewDeviceRegistered = () {};
|
||||||
void Function(String planId) globalCallbackUpdatePlan = (String planId) {};
|
void Function(String planId) globalCallbackUpdatePlan = (String planId) {};
|
||||||
|
|
||||||
bool globalIsAppInBackground = true;
|
bool globalIsAppInBackground = true;
|
||||||
|
|
|
||||||
|
|
@ -334,5 +334,6 @@
|
||||||
"openChangeLog": "Changelog automatisch öffnen",
|
"openChangeLog": "Changelog automatisch öffnen",
|
||||||
"reportUserTitle": "Melde {username}",
|
"reportUserTitle": "Melde {username}",
|
||||||
"reportUserReason": "Meldegrund",
|
"reportUserReason": "Meldegrund",
|
||||||
"reportUser": "Benutzer melden"
|
"reportUser": "Benutzer melden",
|
||||||
|
"newDeviceRegistered": "Du hast dich auf einem anderen Gerät angemeldet. Daher wurdest du hier abgemeldet."
|
||||||
}
|
}
|
||||||
|
|
@ -490,5 +490,6 @@
|
||||||
"openChangeLog": "Open changelog automatically",
|
"openChangeLog": "Open changelog automatically",
|
||||||
"reportUserTitle": "Report {username}",
|
"reportUserTitle": "Report {username}",
|
||||||
"reportUserReason": "Reporting reason",
|
"reportUserReason": "Reporting reason",
|
||||||
"reportUser": "Report user"
|
"reportUser": "Report user",
|
||||||
|
"newDeviceRegistered": "You have logged in on another device. You have therefore been logged out here."
|
||||||
}
|
}
|
||||||
|
|
@ -2047,6 +2047,12 @@ abstract class AppLocalizations {
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Report user'**
|
/// **'Report user'**
|
||||||
String get reportUser;
|
String get reportUser;
|
||||||
|
|
||||||
|
/// No description provided for @newDeviceRegistered.
|
||||||
|
///
|
||||||
|
/// In en, this message translates to:
|
||||||
|
/// **'You have logged in on another device. You have therefore been logged out here.'**
|
||||||
|
String get newDeviceRegistered;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AppLocalizationsDelegate
|
class _AppLocalizationsDelegate
|
||||||
|
|
|
||||||
|
|
@ -1087,4 +1087,8 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get reportUser => 'Benutzer melden';
|
String get reportUser => 'Benutzer melden';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get newDeviceRegistered =>
|
||||||
|
'Du hast dich auf einem anderen Gerät angemeldet. Daher wurdest du hier abgemeldet.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1081,4 +1081,8 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get reportUser => 'Report user';
|
String get reportUser => 'Report user';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get newDeviceRegistered =>
|
||||||
|
'You have logged in on another device. You have therefore been logged out here.';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -29,9 +29,12 @@ class UserData {
|
||||||
@JsonKey(defaultValue: 0)
|
@JsonKey(defaultValue: 0)
|
||||||
int avatarCounter = 0;
|
int avatarCounter = 0;
|
||||||
|
|
||||||
|
@JsonKey(defaultValue: 0)
|
||||||
|
int deviceId = 0;
|
||||||
|
|
||||||
// --- SUBSCRIPTION DTA ---
|
// --- SUBSCRIPTION DTA ---
|
||||||
|
|
||||||
@JsonKey(defaultValue: 'Preview')
|
@JsonKey(defaultValue: 'Free')
|
||||||
String subscriptionPlan;
|
String subscriptionPlan;
|
||||||
DateTime? lastImageSend;
|
DateTime? lastImageSend;
|
||||||
int? todaysImageCounter;
|
int? todaysImageCounter;
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,13 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
||||||
userId: (json['userId'] as num).toInt(),
|
userId: (json['userId'] as num).toInt(),
|
||||||
username: json['username'] as String,
|
username: json['username'] as String,
|
||||||
displayName: json['displayName'] as String,
|
displayName: json['displayName'] as String,
|
||||||
subscriptionPlan: json['subscriptionPlan'] as String? ?? 'Preview',
|
subscriptionPlan: json['subscriptionPlan'] as String? ?? 'Free',
|
||||||
isDemoUser: json['isDemoUser'] as bool? ?? false,
|
isDemoUser: json['isDemoUser'] as bool? ?? false,
|
||||||
)
|
)
|
||||||
..avatarSvg = json['avatarSvg'] as String?
|
..avatarSvg = json['avatarSvg'] as String?
|
||||||
..avatarJson = json['avatarJson'] as String?
|
..avatarJson = json['avatarJson'] as String?
|
||||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
||||||
|
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
||||||
..lastImageSend = json['lastImageSend'] == null
|
..lastImageSend = json['lastImageSend'] == null
|
||||||
? null
|
? null
|
||||||
: DateTime.parse(json['lastImageSend'] as String)
|
: DateTime.parse(json['lastImageSend'] as String)
|
||||||
|
|
@ -76,6 +77,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
||||||
'avatarSvg': instance.avatarSvg,
|
'avatarSvg': instance.avatarSvg,
|
||||||
'avatarJson': instance.avatarJson,
|
'avatarJson': instance.avatarJson,
|
||||||
'avatarCounter': instance.avatarCounter,
|
'avatarCounter': instance.avatarCounter,
|
||||||
|
'deviceId': instance.deviceId,
|
||||||
'subscriptionPlan': instance.subscriptionPlan,
|
'subscriptionPlan': instance.subscriptionPlan,
|
||||||
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||||
'todaysImageCounter': instance.todaysImageCounter,
|
'todaysImageCounter': instance.todaysImageCounter,
|
||||||
|
|
|
||||||
|
|
@ -445,6 +445,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
||||||
$fixnum.Int64? userId,
|
$fixnum.Int64? userId,
|
||||||
$core.List<$core.int>? authToken,
|
$core.List<$core.int>? authToken,
|
||||||
$core.String? appVersion,
|
$core.String? appVersion,
|
||||||
|
$fixnum.Int64? deviceId,
|
||||||
}) {
|
}) {
|
||||||
final $result = create();
|
final $result = create();
|
||||||
if (userId != null) {
|
if (userId != null) {
|
||||||
|
|
@ -456,6 +457,9 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
||||||
if (appVersion != null) {
|
if (appVersion != null) {
|
||||||
$result.appVersion = appVersion;
|
$result.appVersion = appVersion;
|
||||||
}
|
}
|
||||||
|
if (deviceId != null) {
|
||||||
|
$result.deviceId = deviceId;
|
||||||
|
}
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
Handshake_Authenticate._() : super();
|
Handshake_Authenticate._() : super();
|
||||||
|
|
@ -466,6 +470,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
||||||
..aInt64(1, _omitFieldNames ? '' : 'userId')
|
..aInt64(1, _omitFieldNames ? '' : 'userId')
|
||||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'authToken', $pb.PbFieldType.OY)
|
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'authToken', $pb.PbFieldType.OY)
|
||||||
..aOS(3, _omitFieldNames ? '' : 'appVersion')
|
..aOS(3, _omitFieldNames ? '' : 'appVersion')
|
||||||
|
..aInt64(4, _omitFieldNames ? '' : 'deviceId')
|
||||||
..hasRequiredFields = false
|
..hasRequiredFields = false
|
||||||
;
|
;
|
||||||
|
|
||||||
|
|
@ -516,6 +521,15 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
||||||
$core.bool hasAppVersion() => $_has(2);
|
$core.bool hasAppVersion() => $_has(2);
|
||||||
@$pb.TagNumber(3)
|
@$pb.TagNumber(3)
|
||||||
void clearAppVersion() => clearField(3);
|
void clearAppVersion() => clearField(3);
|
||||||
|
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$fixnum.Int64 get deviceId => $_getI64(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
set deviceId($fixnum.Int64 v) { $_setInt64(3, v); }
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
$core.bool hasDeviceId() => $_has(3);
|
||||||
|
@$pb.TagNumber(4)
|
||||||
|
void clearDeviceId() => clearField(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Handshake_Handshake {
|
enum Handshake_Handshake {
|
||||||
|
|
|
||||||
|
|
@ -106,9 +106,11 @@ const Handshake_Authenticate$json = {
|
||||||
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||||
{'1': 'auth_token', '3': 2, '4': 1, '5': 12, '10': 'authToken'},
|
{'1': 'auth_token', '3': 2, '4': 1, '5': 12, '10': 'authToken'},
|
||||||
{'1': 'app_version', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'appVersion', '17': true},
|
{'1': 'app_version', '3': 3, '4': 1, '5': 9, '9': 0, '10': 'appVersion', '17': true},
|
||||||
|
{'1': 'device_id', '3': 4, '4': 1, '5': 3, '9': 1, '10': 'deviceId', '17': true},
|
||||||
],
|
],
|
||||||
'8': [
|
'8': [
|
||||||
{'1': '_app_version'},
|
{'1': '_app_version'},
|
||||||
|
{'1': '_device_id'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -128,9 +130,10 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
||||||
'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZBIaCgZpc19pb3MYCCABKAhIAVIFaXNJb3OIAQFCDgoM'
|
'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZBIaCgZpc19pb3MYCCABKAhIAVIFaXNJb3OIAQFCDgoM'
|
||||||
'X2ludml0ZV9jb2RlQgkKB19pc19pb3MaEgoQR2V0QXV0aENoYWxsZW5nZRpDCgxHZXRBdXRoVG'
|
'X2ludml0ZV9jb2RlQgkKB19pc19pb3MaEgoQR2V0QXV0aENoYWxsZW5nZRpDCgxHZXRBdXRoVG'
|
||||||
'9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25z'
|
'9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25z'
|
||||||
'ZRp8CgxBdXRoZW50aWNhdGUSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEh0KCmF1dGhfdG9rZW'
|
'ZRqsAQoMQXV0aGVudGljYXRlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIdCgphdXRoX3Rva2'
|
||||||
'4YAiABKAxSCWF1dGhUb2tlbhIkCgthcHBfdmVyc2lvbhgDIAEoCUgAUgphcHBWZXJzaW9uiAEB'
|
'VuGAIgASgMUglhdXRoVG9rZW4SJAoLYXBwX3ZlcnNpb24YAyABKAlIAFIKYXBwVmVyc2lvbogB'
|
||||||
'Qg4KDF9hcHBfdmVyc2lvbkILCglIYW5kc2hha2U=');
|
'ARIgCglkZXZpY2VfaWQYBCABKANIAVIIZGV2aWNlSWSIAQFCDgoMX2FwcF92ZXJzaW9uQgwKCl'
|
||||||
|
'9kZXZpY2VfaWRCCwoJSGFuZHNoYWtl');
|
||||||
|
|
||||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||||
const ApplicationData$json = {
|
const ApplicationData$json = {
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
static const ErrorCode UserIdNotFound = ErrorCode._(1028, _omitEnumNames ? '' : 'UserIdNotFound');
|
static const ErrorCode UserIdNotFound = ErrorCode._(1028, _omitEnumNames ? '' : 'UserIdNotFound');
|
||||||
static const ErrorCode UserIdAlreadyTaken = ErrorCode._(1029, _omitEnumNames ? '' : 'UserIdAlreadyTaken');
|
static const ErrorCode UserIdAlreadyTaken = ErrorCode._(1029, _omitEnumNames ? '' : 'UserIdAlreadyTaken');
|
||||||
static const ErrorCode AppVersionOutdated = ErrorCode._(1030, _omitEnumNames ? '' : 'AppVersionOutdated');
|
static const ErrorCode AppVersionOutdated = ErrorCode._(1030, _omitEnumNames ? '' : 'AppVersionOutdated');
|
||||||
|
static const ErrorCode NewDeviceRegistered = ErrorCode._(1031, _omitEnumNames ? '' : 'NewDeviceRegistered');
|
||||||
|
|
||||||
static const $core.List<ErrorCode> values = <ErrorCode> [
|
static const $core.List<ErrorCode> values = <ErrorCode> [
|
||||||
Unknown,
|
Unknown,
|
||||||
|
|
@ -82,6 +83,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
||||||
UserIdNotFound,
|
UserIdNotFound,
|
||||||
UserIdAlreadyTaken,
|
UserIdAlreadyTaken,
|
||||||
AppVersionOutdated,
|
AppVersionOutdated,
|
||||||
|
NewDeviceRegistered,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ const ErrorCode$json = {
|
||||||
{'1': 'UserIdNotFound', '2': 1028},
|
{'1': 'UserIdNotFound', '2': 1028},
|
||||||
{'1': 'UserIdAlreadyTaken', '2': 1029},
|
{'1': 'UserIdAlreadyTaken', '2': 1029},
|
||||||
{'1': 'AppVersionOutdated', '2': 1030},
|
{'1': 'AppVersionOutdated', '2': 1030},
|
||||||
|
{'1': 'NewDeviceRegistered', '2': 1031},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -69,5 +70,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
|
||||||
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
|
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
|
||||||
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
||||||
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu'
|
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu'
|
||||||
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCA==');
|
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcI');
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
|
||||||
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||||
bool _isConnected = false;
|
bool _isConnected = false;
|
||||||
bool get isConnected => _isConnected;
|
bool get isConnected => _isConnected;
|
||||||
String plan = 'Preview';
|
String plan = 'Free';
|
||||||
Future<void> updateConnectionState(bool update) async {
|
Future<void> updateConnectionState(bool update) async {
|
||||||
_isConnected = update;
|
_isConnected = update;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
|
|
|
||||||
|
|
@ -316,6 +316,13 @@ class ApiService {
|
||||||
await close(() {});
|
await close(() {});
|
||||||
return Result.error(ErrorCode.InternalError);
|
return Result.error(ErrorCode.InternalError);
|
||||||
}
|
}
|
||||||
|
if (res.error == ErrorCode.NewDeviceRegistered) {
|
||||||
|
globalCallbackNewDeviceRegistered();
|
||||||
|
Log.error('Device is disabled, as a newer device restore twonly Safe.');
|
||||||
|
appIsOutdated = true;
|
||||||
|
await close(() {});
|
||||||
|
return Result.error(ErrorCode.InternalError);
|
||||||
|
}
|
||||||
if (res.error == ErrorCode.SessionNotAuthenticated) {
|
if (res.error == ErrorCode.SessionNotAuthenticated) {
|
||||||
isAuthenticated = false;
|
isAuthenticated = false;
|
||||||
if (authenticated) {
|
if (authenticated) {
|
||||||
|
|
@ -346,11 +353,13 @@ class ApiService {
|
||||||
const storage = FlutterSecureStorage();
|
const storage = FlutterSecureStorage();
|
||||||
final apiAuthToken =
|
final apiAuthToken =
|
||||||
await storage.read(key: SecureStorageKeys.apiAuthToken);
|
await storage.read(key: SecureStorageKeys.apiAuthToken);
|
||||||
|
final user = await getUser();
|
||||||
|
|
||||||
if (apiAuthToken != null) {
|
if (apiAuthToken != null && user != null) {
|
||||||
final authenticate = Handshake_Authenticate()
|
final authenticate = Handshake_Authenticate()
|
||||||
..userId = Int64(userId)
|
..userId = Int64(userId)
|
||||||
..appVersion = (await PackageInfo.fromPlatform()).version
|
..appVersion = (await PackageInfo.fromPlatform()).version
|
||||||
|
..deviceId = Int64(user.deviceId)
|
||||||
..authToken = base64Decode(apiAuthToken);
|
..authToken = base64Decode(apiAuthToken);
|
||||||
|
|
||||||
final handshake = Handshake()..authenticate = authenticate;
|
final handshake = Handshake()..authenticate = authenticate;
|
||||||
|
|
|
||||||
|
|
@ -36,9 +36,6 @@ import 'package:video_compress/video_compress.dart';
|
||||||
Future<ErrorCode?> isAllowedToSend() async {
|
Future<ErrorCode?> isAllowedToSend() async {
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
if (user.subscriptionPlan == 'Preview') {
|
|
||||||
return ErrorCode.PlanNotAllowed;
|
|
||||||
}
|
|
||||||
if (user.subscriptionPlan == 'Free') {
|
if (user.subscriptionPlan == 'Free') {
|
||||||
var todaysImageCounter = user.todaysImageCounter;
|
var todaysImageCounter = user.todaysImageCounter;
|
||||||
if (user.lastImageSend != null && user.todaysImageCounter != null) {
|
if (user.lastImageSend != null && user.todaysImageCounter != null) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import 'package:twonly/src/model/json/userdata.dart';
|
||||||
import 'package:twonly/src/model/protobuf/backup/backup.pb.dart';
|
import 'package:twonly/src/model/protobuf/backup/backup.pb.dart';
|
||||||
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
import 'package:twonly/src/services/twonly_safe/common.twonly_safe.dart';
|
||||||
import 'package:twonly/src/utils/log.dart';
|
import 'package:twonly/src/utils/log.dart';
|
||||||
|
import 'package:twonly/src/utils/storage.dart';
|
||||||
|
|
||||||
Future<void> recoverTwonlySafe(
|
Future<void> recoverTwonlySafe(
|
||||||
String username,
|
String username,
|
||||||
|
|
@ -136,4 +137,8 @@ Future<void> handleBackupData(
|
||||||
await storage.write(
|
await storage.write(
|
||||||
key: SecureStorageKeys.userData,
|
key: SecureStorageKeys.userData,
|
||||||
value: secureStorage[SecureStorageKeys.userData] as String);
|
value: secureStorage[SecureStorageKeys.userData] as String);
|
||||||
|
await updateUserdata((u) {
|
||||||
|
u.deviceId += 1;
|
||||||
|
return u;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,8 @@ Future<void> updateUsersPlan(BuildContext context, String planId) async {
|
||||||
Mutex updateProtection = Mutex();
|
Mutex updateProtection = Mutex();
|
||||||
|
|
||||||
Future<UserData?> updateUserdata(
|
Future<UserData?> updateUserdata(
|
||||||
UserData Function(UserData userData) updateUser) async {
|
UserData Function(UserData userData) updateUser,
|
||||||
|
) async {
|
||||||
return updateProtection.protect<UserData?>(() async {
|
return updateProtection.protect<UserData?>(() async {
|
||||||
final user = await getUser();
|
final user = await getUser();
|
||||||
if (user == null) return null;
|
if (user == null) return null;
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ import 'package:drift/drift.dart' hide Column;
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:twonly/globals.dart';
|
import 'package:twonly/globals.dart';
|
||||||
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
import 'package:twonly/src/database/daos/contacts_dao.dart';
|
||||||
import 'package:twonly/src/database/tables/messages_table.dart';
|
import 'package:twonly/src/database/tables/messages_table.dart';
|
||||||
|
|
@ -14,7 +13,6 @@ import 'package:twonly/src/database/twonly_database.dart';
|
||||||
import 'package:twonly/src/model/json/message.dart';
|
import 'package:twonly/src/model/json/message.dart';
|
||||||
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
import 'package:twonly/src/model/protobuf/api/websocket/server_to_client.pb.dart';
|
||||||
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
import 'package:twonly/src/model/protobuf/push_notification/push_notification.pbserver.dart';
|
||||||
import 'package:twonly/src/providers/connection.provider.dart';
|
|
||||||
import 'package:twonly/src/services/api/messages.dart';
|
import 'package:twonly/src/services/api/messages.dart';
|
||||||
import 'package:twonly/src/services/api/utils.dart';
|
import 'package:twonly/src/services/api/utils.dart';
|
||||||
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
import 'package:twonly/src/services/notifications/pushkeys.notifications.dart';
|
||||||
|
|
@ -24,7 +22,6 @@ import 'package:twonly/src/utils/storage.dart';
|
||||||
import 'package:twonly/src/views/components/alert_dialog.dart';
|
import 'package:twonly/src/views/components/alert_dialog.dart';
|
||||||
import 'package:twonly/src/views/components/headline.dart';
|
import 'package:twonly/src/views/components/headline.dart';
|
||||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
|
||||||
|
|
||||||
class AddNewUserView extends StatefulWidget {
|
class AddNewUserView extends StatefulWidget {
|
||||||
const AddNewUserView({super.key});
|
const AddNewUserView({super.key});
|
||||||
|
|
@ -143,7 +140,6 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isPreview = context.read<CustomChangeProvider>().plan == 'Preview';
|
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: Text(context.lang.searchUsernameTitle),
|
title: Text(context.lang.searchUsernameTitle),
|
||||||
|
|
@ -154,49 +150,27 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
const EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
|
const EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
|
||||||
child: Column(
|
child: Column(
|
||||||
children: [
|
children: [
|
||||||
if (isPreview) ...[
|
Padding(
|
||||||
Padding(
|
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||||
padding: const EdgeInsets.all(20),
|
child: TextField(
|
||||||
child: Text(
|
onSubmitted: (_) {
|
||||||
context.lang.searchUserNamePreview,
|
_addNewUser(context);
|
||||||
textAlign: TextAlign.center,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
FilledButton.icon(
|
|
||||||
icon: const FaIcon(FontAwesomeIcons.shieldHeart),
|
|
||||||
onPressed: () {
|
|
||||||
Navigator.push(context,
|
|
||||||
MaterialPageRoute(builder: (context) {
|
|
||||||
return const SubscriptionView();
|
|
||||||
}));
|
|
||||||
},
|
},
|
||||||
label: Text(context.lang.selectSubscription),
|
onChanged: (value) {
|
||||||
|
searchUserName.text = value.toLowerCase();
|
||||||
|
searchUserName.selection = TextSelection.fromPosition(
|
||||||
|
TextPosition(offset: searchUserName.text.length),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
inputFormatters: [
|
||||||
|
LengthLimitingTextInputFormatter(12),
|
||||||
|
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z]')),
|
||||||
|
],
|
||||||
|
controller: searchUserName,
|
||||||
|
decoration:
|
||||||
|
getInputDecoration(context.lang.searchUsernameInput),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 30),
|
),
|
||||||
],
|
|
||||||
if (!isPreview) ...[
|
|
||||||
Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
|
||||||
child: TextField(
|
|
||||||
onSubmitted: (_) {
|
|
||||||
_addNewUser(context);
|
|
||||||
},
|
|
||||||
onChanged: (value) {
|
|
||||||
searchUserName.text = value.toLowerCase();
|
|
||||||
searchUserName.selection = TextSelection.fromPosition(
|
|
||||||
TextPosition(offset: searchUserName.text.length),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
inputFormatters: [
|
|
||||||
LengthLimitingTextInputFormatter(12),
|
|
||||||
FilteringTextInputFormatter.allow(RegExp('[a-z0-9A-Z]')),
|
|
||||||
],
|
|
||||||
controller: searchUserName,
|
|
||||||
decoration:
|
|
||||||
getInputDecoration(context.lang.searchUsernameInput),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (contacts.isNotEmpty)
|
if (contacts.isNotEmpty)
|
||||||
HeadLineComponent(
|
HeadLineComponent(
|
||||||
|
|
@ -209,20 +183,18 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
floatingActionButton: isPreview
|
floatingActionButton: Padding(
|
||||||
? null
|
padding: const EdgeInsets.only(bottom: 30),
|
||||||
: Padding(
|
child: FloatingActionButton(
|
||||||
padding: const EdgeInsets.only(bottom: 30),
|
foregroundColor: Colors.white,
|
||||||
child: FloatingActionButton(
|
onPressed: () {
|
||||||
foregroundColor: Colors.white,
|
if (!_isLoading) _addNewUser(context);
|
||||||
onPressed: () {
|
},
|
||||||
if (!_isLoading) _addNewUser(context);
|
child: _isLoading
|
||||||
},
|
? const Center(child: CircularProgressIndicator())
|
||||||
child: _isLoading
|
: const FaIcon(FontAwesomeIcons.magnifyingGlassPlus),
|
||||||
? const Center(child: CircularProgressIndicator())
|
),
|
||||||
: const FaIcon(FontAwesomeIcons.magnifyingGlassPlus),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,72 +15,114 @@ class AppOutdated extends StatefulWidget {
|
||||||
|
|
||||||
class _AppOutdatedState extends State<AppOutdated> {
|
class _AppOutdatedState extends State<AppOutdated> {
|
||||||
bool appIsOutdated = false;
|
bool appIsOutdated = false;
|
||||||
|
bool newDeviceRegistered = false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
globalCallbackAppIsOutdated = () {};
|
globalCallbackAppIsOutdated = () {};
|
||||||
|
globalCallbackNewDeviceRegistered = () {};
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> initAsync() async {
|
@override
|
||||||
|
void initState() {
|
||||||
globalCallbackAppIsOutdated = () async {
|
globalCallbackAppIsOutdated = () async {
|
||||||
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
||||||
setState(() {
|
setState(() {
|
||||||
appIsOutdated = true;
|
appIsOutdated = true;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
globalCallbackNewDeviceRegistered = () async {
|
||||||
|
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
||||||
|
setState(() {
|
||||||
|
newDeviceRegistered = true;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (!appIsOutdated) return Container();
|
if (newDeviceRegistered) {
|
||||||
return Positioned(
|
return Positioned(
|
||||||
top: 60,
|
top: 60,
|
||||||
left: 30,
|
left: 30,
|
||||||
right: 30,
|
right: 30,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: Colors.red,
|
color: Colors.red.withAlpha(100),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
context.lang.appOutdated,
|
context.lang.newDeviceRegistered,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
softWrap: true,
|
softWrap: true,
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
.textTheme
|
.textTheme
|
||||||
.bodyMedium
|
.bodyMedium
|
||||||
?.copyWith(color: Colors.white, fontSize: 16),
|
?.copyWith(color: Colors.white, fontSize: 16),
|
||||||
),
|
|
||||||
if (Platform.isAndroid) const SizedBox(height: 5),
|
|
||||||
if (Platform.isAndroid)
|
|
||||||
ElevatedButton(
|
|
||||||
onPressed: () {
|
|
||||||
launchUrl(Uri.parse(
|
|
||||||
'https://play.google.com/store/apps/details?id=eu.twonly'));
|
|
||||||
},
|
|
||||||
style: ElevatedButton.styleFrom(
|
|
||||||
shape: RoundedRectangleBorder(
|
|
||||||
borderRadius: BorderRadius.circular(10),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
child: Text(
|
|
||||||
context.lang.appOutdatedBtn,
|
|
||||||
style: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodyMedium
|
|
||||||
?.copyWith(color: Colors.white, fontSize: 16),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
);
|
||||||
);
|
}
|
||||||
|
if (appIsOutdated) {
|
||||||
|
return Positioned(
|
||||||
|
top: 60,
|
||||||
|
left: 30,
|
||||||
|
right: 30,
|
||||||
|
child: SafeArea(
|
||||||
|
child: Container(
|
||||||
|
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Colors.red,
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
context.lang.appOutdated,
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
softWrap: true,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
if (Platform.isAndroid) const SizedBox(height: 5),
|
||||||
|
if (Platform.isAndroid)
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () {
|
||||||
|
launchUrl(Uri.parse(
|
||||||
|
'https://play.google.com/store/apps/details?id=eu.twonly'));
|
||||||
|
},
|
||||||
|
style: ElevatedButton.styleFrom(
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(10),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
context.lang.appOutdatedBtn,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(color: Colors.white, fontSize: 16),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Container();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue