mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09: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: {}
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- main_disabled
|
||||
# paths:
|
||||
# - lib/**
|
||||
# - 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)
|
||||
- iOS gestures to close images
|
||||
- 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
|
||||
- Images are now stored as WebP to save storage
|
||||
- Button to report users
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ void Function({required bool isConnected}) globalCallbackConnectionState = ({
|
|||
required bool isConnected,
|
||||
}) {};
|
||||
void Function() globalCallbackAppIsOutdated = () {};
|
||||
void Function() globalCallbackNewDeviceRegistered = () {};
|
||||
void Function(String planId) globalCallbackUpdatePlan = (String planId) {};
|
||||
|
||||
bool globalIsAppInBackground = true;
|
||||
|
|
|
|||
|
|
@ -334,5 +334,6 @@
|
|||
"openChangeLog": "Changelog automatisch öffnen",
|
||||
"reportUserTitle": "Melde {username}",
|
||||
"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",
|
||||
"reportUserTitle": "Report {username}",
|
||||
"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:
|
||||
/// **'Report user'**
|
||||
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
|
||||
|
|
|
|||
|
|
@ -1087,4 +1087,8 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||
|
||||
@override
|
||||
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
|
||||
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)
|
||||
int avatarCounter = 0;
|
||||
|
||||
@JsonKey(defaultValue: 0)
|
||||
int deviceId = 0;
|
||||
|
||||
// --- SUBSCRIPTION DTA ---
|
||||
|
||||
@JsonKey(defaultValue: 'Preview')
|
||||
@JsonKey(defaultValue: 'Free')
|
||||
String subscriptionPlan;
|
||||
DateTime? lastImageSend;
|
||||
int? todaysImageCounter;
|
||||
|
|
|
|||
|
|
@ -10,12 +10,13 @@ UserData _$UserDataFromJson(Map<String, dynamic> json) => UserData(
|
|||
userId: (json['userId'] as num).toInt(),
|
||||
username: json['username'] 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,
|
||||
)
|
||||
..avatarSvg = json['avatarSvg'] as String?
|
||||
..avatarJson = json['avatarJson'] as String?
|
||||
..avatarCounter = (json['avatarCounter'] as num?)?.toInt() ?? 0
|
||||
..deviceId = (json['deviceId'] as num?)?.toInt() ?? 0
|
||||
..lastImageSend = json['lastImageSend'] == null
|
||||
? null
|
||||
: DateTime.parse(json['lastImageSend'] as String)
|
||||
|
|
@ -76,6 +77,7 @@ Map<String, dynamic> _$UserDataToJson(UserData instance) => <String, dynamic>{
|
|||
'avatarSvg': instance.avatarSvg,
|
||||
'avatarJson': instance.avatarJson,
|
||||
'avatarCounter': instance.avatarCounter,
|
||||
'deviceId': instance.deviceId,
|
||||
'subscriptionPlan': instance.subscriptionPlan,
|
||||
'lastImageSend': instance.lastImageSend?.toIso8601String(),
|
||||
'todaysImageCounter': instance.todaysImageCounter,
|
||||
|
|
|
|||
|
|
@ -445,6 +445,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
$fixnum.Int64? userId,
|
||||
$core.List<$core.int>? authToken,
|
||||
$core.String? appVersion,
|
||||
$fixnum.Int64? deviceId,
|
||||
}) {
|
||||
final $result = create();
|
||||
if (userId != null) {
|
||||
|
|
@ -456,6 +457,9 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
if (appVersion != null) {
|
||||
$result.appVersion = appVersion;
|
||||
}
|
||||
if (deviceId != null) {
|
||||
$result.deviceId = deviceId;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
Handshake_Authenticate._() : super();
|
||||
|
|
@ -466,6 +470,7 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
..aInt64(1, _omitFieldNames ? '' : 'userId')
|
||||
..a<$core.List<$core.int>>(2, _omitFieldNames ? '' : 'authToken', $pb.PbFieldType.OY)
|
||||
..aOS(3, _omitFieldNames ? '' : 'appVersion')
|
||||
..aInt64(4, _omitFieldNames ? '' : 'deviceId')
|
||||
..hasRequiredFields = false
|
||||
;
|
||||
|
||||
|
|
@ -516,6 +521,15 @@ class Handshake_Authenticate extends $pb.GeneratedMessage {
|
|||
$core.bool hasAppVersion() => $_has(2);
|
||||
@$pb.TagNumber(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 {
|
||||
|
|
|
|||
|
|
@ -106,9 +106,11 @@ const Handshake_Authenticate$json = {
|
|||
{'1': 'user_id', '3': 1, '4': 1, '5': 3, '10': 'userId'},
|
||||
{'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': 'device_id', '3': 4, '4': 1, '5': 3, '9': 1, '10': 'deviceId', '17': true},
|
||||
],
|
||||
'8': [
|
||||
{'1': '_app_version'},
|
||||
{'1': '_device_id'},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -128,9 +130,10 @@ final $typed_data.Uint8List handshakeDescriptor = $convert.base64Decode(
|
|||
'lkGAcgASgDUg5yZWdpc3RyYXRpb25JZBIaCgZpc19pb3MYCCABKAhIAVIFaXNJb3OIAQFCDgoM'
|
||||
'X2ludml0ZV9jb2RlQgkKB19pc19pb3MaEgoQR2V0QXV0aENoYWxsZW5nZRpDCgxHZXRBdXRoVG'
|
||||
'9rZW4SFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEhoKCHJlc3BvbnNlGAIgASgMUghyZXNwb25z'
|
||||
'ZRp8CgxBdXRoZW50aWNhdGUSFwoHdXNlcl9pZBgBIAEoA1IGdXNlcklkEh0KCmF1dGhfdG9rZW'
|
||||
'4YAiABKAxSCWF1dGhUb2tlbhIkCgthcHBfdmVyc2lvbhgDIAEoCUgAUgphcHBWZXJzaW9uiAEB'
|
||||
'Qg4KDF9hcHBfdmVyc2lvbkILCglIYW5kc2hha2U=');
|
||||
'ZRqsAQoMQXV0aGVudGljYXRlEhcKB3VzZXJfaWQYASABKANSBnVzZXJJZBIdCgphdXRoX3Rva2'
|
||||
'VuGAIgASgMUglhdXRoVG9rZW4SJAoLYXBwX3ZlcnNpb24YAyABKAlIAFIKYXBwVmVyc2lvbogB'
|
||||
'ARIgCglkZXZpY2VfaWQYBCABKANIAVIIZGV2aWNlSWSIAQFCDgoMX2FwcF92ZXJzaW9uQgwKCl'
|
||||
'9kZXZpY2VfaWRCCwoJSGFuZHNoYWtl');
|
||||
|
||||
@$core.Deprecated('Use applicationDataDescriptor instead')
|
||||
const ApplicationData$json = {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
|||
static const ErrorCode UserIdNotFound = ErrorCode._(1028, _omitEnumNames ? '' : 'UserIdNotFound');
|
||||
static const ErrorCode UserIdAlreadyTaken = ErrorCode._(1029, _omitEnumNames ? '' : 'UserIdAlreadyTaken');
|
||||
static const ErrorCode AppVersionOutdated = ErrorCode._(1030, _omitEnumNames ? '' : 'AppVersionOutdated');
|
||||
static const ErrorCode NewDeviceRegistered = ErrorCode._(1031, _omitEnumNames ? '' : 'NewDeviceRegistered');
|
||||
|
||||
static const $core.List<ErrorCode> values = <ErrorCode> [
|
||||
Unknown,
|
||||
|
|
@ -82,6 +83,7 @@ class ErrorCode extends $pb.ProtobufEnum {
|
|||
UserIdNotFound,
|
||||
UserIdAlreadyTaken,
|
||||
AppVersionOutdated,
|
||||
NewDeviceRegistered,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ErrorCode> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ const ErrorCode$json = {
|
|||
{'1': 'UserIdNotFound', '2': 1028},
|
||||
{'1': 'UserIdAlreadyTaken', '2': 1029},
|
||||
{'1': 'AppVersionOutdated', '2': 1030},
|
||||
{'1': 'NewDeviceRegistered', '2': 1031},
|
||||
],
|
||||
};
|
||||
|
||||
|
|
@ -69,5 +70,5 @@ final $typed_data.Uint8List errorCodeDescriptor = $convert.base64Decode(
|
|||
'dlZBD+BxIVChBQbGFuTGltaXRSZWFjaGVkEP8HEhQKD05vdEVub3VnaENyZWRpdBCACBISCg1Q'
|
||||
'bGFuRG93bmdyYWRlEIEIEhkKFFBsYW5VcGdyYWRlTm90WWVhcmx5EIIIEhgKE0ludmFsaWRTaW'
|
||||
'duZWRQcmVLZXkQgwgSEwoOVXNlcklkTm90Rm91bmQQhAgSFwoSVXNlcklkQWxyZWFkeVRha2Vu'
|
||||
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCA==');
|
||||
'EIUIEhcKEkFwcFZlcnNpb25PdXRkYXRlZBCGCBIYChNOZXdEZXZpY2VSZWdpc3RlcmVkEIcI');
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
|
|||
class CustomChangeProvider with ChangeNotifier, DiagnosticableTreeMixin {
|
||||
bool _isConnected = false;
|
||||
bool get isConnected => _isConnected;
|
||||
String plan = 'Preview';
|
||||
String plan = 'Free';
|
||||
Future<void> updateConnectionState(bool update) async {
|
||||
_isConnected = update;
|
||||
notifyListeners();
|
||||
|
|
|
|||
|
|
@ -316,6 +316,13 @@ class ApiService {
|
|||
await close(() {});
|
||||
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) {
|
||||
isAuthenticated = false;
|
||||
if (authenticated) {
|
||||
|
|
@ -346,11 +353,13 @@ class ApiService {
|
|||
const storage = FlutterSecureStorage();
|
||||
final apiAuthToken =
|
||||
await storage.read(key: SecureStorageKeys.apiAuthToken);
|
||||
final user = await getUser();
|
||||
|
||||
if (apiAuthToken != null) {
|
||||
if (apiAuthToken != null && user != null) {
|
||||
final authenticate = Handshake_Authenticate()
|
||||
..userId = Int64(userId)
|
||||
..appVersion = (await PackageInfo.fromPlatform()).version
|
||||
..deviceId = Int64(user.deviceId)
|
||||
..authToken = base64Decode(apiAuthToken);
|
||||
|
||||
final handshake = Handshake()..authenticate = authenticate;
|
||||
|
|
|
|||
|
|
@ -36,9 +36,6 @@ 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') {
|
||||
var todaysImageCounter = user.todaysImageCounter;
|
||||
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/services/twonly_safe/common.twonly_safe.dart';
|
||||
import 'package:twonly/src/utils/log.dart';
|
||||
import 'package:twonly/src/utils/storage.dart';
|
||||
|
||||
Future<void> recoverTwonlySafe(
|
||||
String username,
|
||||
|
|
@ -136,4 +137,8 @@ Future<void> handleBackupData(
|
|||
await storage.write(
|
||||
key: SecureStorageKeys.userData,
|
||||
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();
|
||||
|
||||
Future<UserData?> updateUserdata(
|
||||
UserData Function(UserData userData) updateUser) async {
|
||||
UserData Function(UserData userData) updateUser,
|
||||
) async {
|
||||
return updateProtection.protect<UserData?>(() async {
|
||||
final user = await getUser();
|
||||
if (user == null) return null;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import 'package:drift/drift.dart' hide Column;
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:twonly/globals.dart';
|
||||
import 'package:twonly/src/database/daos/contacts_dao.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/protobuf/api/websocket/server_to_client.pb.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/utils.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/headline.dart';
|
||||
import 'package:twonly/src/views/components/initialsavatar.dart';
|
||||
import 'package:twonly/src/views/settings/subscription/subscription.view.dart';
|
||||
|
||||
class AddNewUserView extends StatefulWidget {
|
||||
const AddNewUserView({super.key});
|
||||
|
|
@ -143,7 +140,6 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final isPreview = context.read<CustomChangeProvider>().plan == 'Preview';
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(context.lang.searchUsernameTitle),
|
||||
|
|
@ -154,27 +150,6 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
const EdgeInsets.only(bottom: 20, left: 10, top: 20, right: 10),
|
||||
child: Column(
|
||||
children: [
|
||||
if (isPreview) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(20),
|
||||
child: Text(
|
||||
context.lang.searchUserNamePreview,
|
||||
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),
|
||||
),
|
||||
const SizedBox(height: 30),
|
||||
],
|
||||
if (!isPreview) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
child: TextField(
|
||||
|
|
@ -196,7 +171,6 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
getInputDecoration(context.lang.searchUsernameInput),
|
||||
),
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 20),
|
||||
if (contacts.isNotEmpty)
|
||||
HeadLineComponent(
|
||||
|
|
@ -209,9 +183,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
|
|||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: isPreview
|
||||
? null
|
||||
: Padding(
|
||||
floatingActionButton: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 30),
|
||||
child: FloatingActionButton(
|
||||
foregroundColor: Colors.white,
|
||||
|
|
|
|||
|
|
@ -15,25 +15,65 @@ class AppOutdated extends StatefulWidget {
|
|||
|
||||
class _AppOutdatedState extends State<AppOutdated> {
|
||||
bool appIsOutdated = false;
|
||||
bool newDeviceRegistered = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
globalCallbackAppIsOutdated = () {};
|
||||
globalCallbackNewDeviceRegistered = () {};
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> initAsync() async {
|
||||
@override
|
||||
void initState() {
|
||||
globalCallbackAppIsOutdated = () async {
|
||||
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
||||
setState(() {
|
||||
appIsOutdated = true;
|
||||
});
|
||||
};
|
||||
globalCallbackNewDeviceRegistered = () async {
|
||||
await context.read<CustomChangeProvider>().updateConnectionState(false);
|
||||
setState(() {
|
||||
newDeviceRegistered = true;
|
||||
});
|
||||
};
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!appIsOutdated) return Container();
|
||||
if (newDeviceRegistered) {
|
||||
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.withAlpha(100),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Text(
|
||||
context.lang.newDeviceRegistered,
|
||||
textAlign: TextAlign.center,
|
||||
softWrap: true,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyMedium
|
||||
?.copyWith(color: Colors.white, fontSize: 16),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (appIsOutdated) {
|
||||
return Positioned(
|
||||
top: 60,
|
||||
left: 30,
|
||||
|
|
@ -83,4 +123,6 @@ class _AppOutdatedState extends State<AppOutdated> {
|
|||
),
|
||||
);
|
||||
}
|
||||
return Container();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue