This commit is contained in:
otsmr 2025-07-18 23:43:22 +02:00
parent 9f45a461a2
commit eb545f84b0
21 changed files with 190 additions and 122 deletions

View file

@ -4,7 +4,7 @@ on:
workflow_dispatch: {} workflow_dispatch: {}
push: push:
branches: branches:
- main - main_disabled
# paths: # paths:
# - lib/** # - lib/**
# - pubspec.lock # - pubspec.lock

View file

@ -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

View file

@ -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;

View file

@ -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."
} }

View file

@ -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."
} }

View file

@ -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

View file

@ -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.';
} }

View file

@ -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.';
} }

View file

@ -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;

View file

@ -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,

View file

@ -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 {

View file

@ -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 = {

View file

@ -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);

View file

@ -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');

View file

@ -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();

View file

@ -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;

View file

@ -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) {

View file

@ -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;
});
} }

View file

@ -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;

View file

@ -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,27 +150,6 @@ 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: 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(
padding: const EdgeInsets.symmetric(horizontal: 10), padding: const EdgeInsets.symmetric(horizontal: 10),
child: TextField( child: TextField(
@ -196,7 +171,6 @@ class _SearchUsernameView extends State<AddNewUserView> {
getInputDecoration(context.lang.searchUsernameInput), getInputDecoration(context.lang.searchUsernameInput),
), ),
), ),
],
const SizedBox(height: 20), const SizedBox(height: 20),
if (contacts.isNotEmpty) if (contacts.isNotEmpty)
HeadLineComponent( HeadLineComponent(
@ -209,9 +183,7 @@ class _SearchUsernameView extends State<AddNewUserView> {
), ),
), ),
), ),
floatingActionButton: isPreview floatingActionButton: Padding(
? null
: Padding(
padding: const EdgeInsets.only(bottom: 30), padding: const EdgeInsets.only(bottom: 30),
child: FloatingActionButton( child: FloatingActionButton(
foregroundColor: Colors.white, foregroundColor: Colors.white,

View file

@ -15,25 +15,65 @@ 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(
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( return Positioned(
top: 60, top: 60,
left: 30, left: 30,
@ -83,4 +123,6 @@ class _AppOutdatedState extends State<AppOutdated> {
), ),
); );
} }
return Container();
}
} }