This commit is contained in:
otsmr 2025-06-22 15:52:04 +02:00
parent ff96a80373
commit 434945cb54
10 changed files with 117 additions and 30 deletions

View file

@ -1,3 +1,4 @@
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import 'package:twonly/globals.dart';
@ -5,6 +6,7 @@ import 'package:twonly/src/localization/generated/app_localizations.dart';
import 'package:twonly/src/providers/connection.provider.dart';
import 'package:twonly/src/providers/settings.provider.dart';
import 'package:twonly/src/services/api/media_upload.dart';
import 'package:twonly/src/utils/misc.dart';
import 'package:twonly/src/utils/storage.dart';
import 'package:twonly/src/views/onboarding/onboarding.view.dart';
import 'package:twonly/src/views/home.view.dart';
@ -12,6 +14,7 @@ import 'package:twonly/src/views/onboarding/register.view.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'dart:async';
import 'package:url_launcher/url_launcher.dart';
// these two callbacks are called on updated to the corresponding database
@ -24,6 +27,7 @@ class App extends StatefulWidget {
class _AppState extends State<App> with WidgetsBindingObserver {
bool wasPaused = false;
bool appIsOutdated = false;
@override
void initState() {
@ -62,6 +66,12 @@ class _AppState extends State<App> with WidgetsBindingObserver {
Future initAsync() async {
setUserPlan();
globalCallbackAppIsOutdated = () async {
context.read<CustomChangeProvider>().updateConnectionState(false);
setState(() {
appIsOutdated = true;
});
};
await apiService.connect(force: true);
apiService.listenToNetworkChanges();
// call this function so invalid media files are get purged
@ -87,6 +97,7 @@ class _AppState extends State<App> with WidgetsBindingObserver {
void dispose() {
WidgetsBinding.instance.removeObserver(this);
globalCallbackConnectionState = (a) {};
globalCallbackAppIsOutdated = () {};
super.dispose();
}
@ -133,8 +144,10 @@ class _AppState extends State<App> with WidgetsBindingObserver {
themeMode: context.watch<SettingsChangeProvider>().themeMode,
initialRoute: '/',
routes: {
"/": (context) => AppMainWidget(initialPage: 1),
"/chats": (context) => AppMainWidget(initialPage: 0)
"/": (context) =>
AppMainWidget(initialPage: 1, appIsOutdated: appIsOutdated),
"/chats": (context) =>
AppMainWidget(initialPage: 0, appIsOutdated: appIsOutdated)
},
);
},
@ -143,8 +156,10 @@ class _AppState extends State<App> with WidgetsBindingObserver {
}
class AppMainWidget extends StatefulWidget {
const AppMainWidget({super.key, required this.initialPage});
const AppMainWidget(
{super.key, required this.initialPage, required this.appIsOutdated});
final int initialPage;
final bool appIsOutdated;
@override
State<AppMainWidget> createState() => _AppMainWidgetState();
}
@ -187,6 +202,55 @@ class _AppMainWidgetState extends State<AppMainWidget> {
);
},
),
if (widget.appIsOutdated)
Positioned(
top: 60,
left: 30,
right: 30,
child: SafeArea(
child: Container(
padding: 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) 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),
),
),
],
),
),
),
),
],
);
}

View file

@ -16,6 +16,7 @@ bool gIsDemoUser = false;
// This callback called by the apiProvider
Function(bool) globalCallbackConnectionState = (a) {};
Function() globalCallbackAppIsOutdated = () {};
bool globalIsAppInBackground = true;
int globalBestFriendUserId = -1;

View file

@ -298,5 +298,7 @@
"backupTwonlySaveNow": "Jetzt speichern",
"inviteFriends": "Freunde einladen",
"inviteFriendsShareBtn": "Teilen",
"inviteFriendsShareText": "Wechseln wir zu twonly: {url}"
"inviteFriendsShareText": "Wechseln wir zu twonly: {url}",
"appOutdated": "Deine Version von twonly ist veraltet.",
"appOutdatedBtn": "Jetzt aktualisieren."
}

View file

@ -455,5 +455,7 @@
"twonlySafeRecoverBtn": "Restore backup",
"inviteFriends": "Invite your friends",
"inviteFriendsShareBtn": "Share",
"inviteFriendsShareText": "Let's switch to twonly: {url}"
"inviteFriendsShareText": "Let's switch to twonly: {url}",
"appOutdated": "Your version of twonly is out of date.",
"appOutdatedBtn": "Update Now"
}

View file

@ -1831,6 +1831,18 @@ abstract class AppLocalizations {
/// In en, this message translates to:
/// **'Let\'s switch to twonly: {url}'**
String inviteFriendsShareText(Object url);
/// No description provided for @appOutdated.
///
/// In en, this message translates to:
/// **'Your version of twonly is out of date.'**
String get appOutdated;
/// No description provided for @appOutdatedBtn.
///
/// In en, this message translates to:
/// **'Update Now'**
String get appOutdatedBtn;
}
class _AppLocalizationsDelegate

View file

@ -974,4 +974,10 @@ class AppLocalizationsDe extends AppLocalizations {
String inviteFriendsShareText(Object url) {
return 'Wechseln wir zu twonly: $url';
}
@override
String get appOutdated => 'Deine Version von twonly ist veraltet.';
@override
String get appOutdatedBtn => 'Jetzt aktualisieren.';
}

View file

@ -968,4 +968,10 @@ class AppLocalizationsEn extends AppLocalizations {
String inviteFriendsShareText(Object url) {
return 'Let\'s switch to twonly: $url';
}
@override
String get appOutdated => 'Your version of twonly is out of date.';
@override
String get appOutdatedBtn => 'Update Now';
}

View file

@ -48,6 +48,7 @@ class ApiService {
final String apiHost = (kDebugMode) ? "10.99.0.140:3030" : "api.twonly.eu";
final String apiSecure = (kDebugMode) ? "" : "s";
bool appIsOutdated = false;
bool isAuthenticated = false;
ApiService();
@ -60,6 +61,7 @@ class ApiService {
StreamSubscription<List<ConnectivityResult>>? connectivitySubscription;
Future<bool> _connectTo(String apiUrl) async {
if (appIsOutdated) return false;
try {
var channel = IOWebSocketChannel.connect(
Uri.parse(apiUrl),
@ -303,6 +305,13 @@ class ApiService {
Result res = asResult(await _waitForResponse(seq));
if (res.isError) {
Log.error("got error from server: ${res.error}");
if (res.error == ErrorCode.AppVersionOutdated) {
globalCallbackAppIsOutdated();
Log.error("App Version is OUTDATED.");
appIsOutdated = true;
await close(() {});
return Result.error(ErrorCode.InternalError);
}
if (res.error == ErrorCode.SessionNotAuthenticated) {
isAuthenticated = false;
if (authenticated) {

View file

@ -11,7 +11,7 @@ import 'package:twonly/src/utils/storage.dart';
class SaveToGalleryButton extends StatefulWidget {
final Future<Uint8List?> Function() getMergedImage;
final String? sendNextMediaToUserName;
final bool displayButtonLabel;
final File? videoFilePath;
final int? mediaUploadId;
final bool isLoading;
@ -20,7 +20,7 @@ class SaveToGalleryButton extends StatefulWidget {
super.key,
required this.getMergedImage,
required this.isLoading,
this.sendNextMediaToUserName,
required this.displayButtonLabel,
this.mediaUploadId,
this.videoFilePath,
});
@ -107,8 +107,8 @@ class SaveToGalleryButtonState extends State<SaveToGalleryButton> {
: _imageSaved
? Icon(Icons.check)
: FaIcon(FontAwesomeIcons.floppyDisk),
if (widget.sendNextMediaToUserName == null) SizedBox(width: 10),
if (widget.sendNextMediaToUserName == null)
if (widget.displayButtonLabel) SizedBox(width: 10),
if (widget.displayButtonLabel)
Text(_imageSaved
? context.lang.shareImagedEditorSavedImage
: context.lang.shareImagedEditorSaveImage)

View file

@ -3,7 +3,6 @@ import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:twonly/globals.dart';
import 'package:twonly/src/model/protobuf/api/websocket/error.pb.dart'
show ErrorCode;
import 'package:twonly/src/services/api/media_upload.dart';
@ -56,7 +55,6 @@ class ShareImageEditorView extends StatefulWidget {
class _ShareImageEditorView extends State<ShareImageEditorView> {
bool _isRealTwonly = false;
int maxShowTime = gMediaShowInfinite;
String? sendNextMediaToUserName;
double tabDownPosition = 0;
bool sendingOrLoadingImage = true;
bool loadingImage = true;
@ -138,15 +136,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
setState(() {});
}
Future updateAsync(int userId) async {
if (sendNextMediaToUserName != null) return;
Contact? contact =
await twonlyDB.contactsDao.getContactByUserId(userId).getSingleOrNull();
if (contact != null) {
sendNextMediaToUserName = getContactDisplayName(contact);
}
}
List<Widget> get actionsAtTheRight {
if (layers.isNotEmpty &&
layers.last.isEditing &&
@ -449,10 +438,6 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
Widget build(BuildContext context) {
pixelRatio = MediaQuery.of(context).devicePixelRatio;
if (widget.sendTo != null) {
sendNextMediaToUserName = getContactDisplayName(widget.sendTo!);
}
return Scaffold(
backgroundColor:
widget.sharedFromGallery ? null : Colors.white.withAlpha(0),
@ -553,11 +538,11 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
getMergedImage: getMergedImage,
mediaUploadId: mediaUploadId,
videoFilePath: widget.videoFilePath,
sendNextMediaToUserName: sendNextMediaToUserName,
displayButtonLabel: widget.sendTo == null,
isLoading: loadingImage,
),
if (sendNextMediaToUserName != null) SizedBox(width: 10),
if (sendNextMediaToUserName != null)
if (widget.sendTo != null) SizedBox(width: 10),
if (widget.sendTo != null)
OutlinedButton(
style: OutlinedButton.styleFrom(
iconColor: Theme.of(context).colorScheme.primary,
@ -566,7 +551,7 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
onPressed: pushShareImageView,
child: FaIcon(FontAwesomeIcons.userPlus),
),
SizedBox(width: sendNextMediaToUserName == null ? 20 : 10),
SizedBox(width: widget.sendTo == null ? 20 : 10),
FilledButton.icon(
icon: sendingOrLoadingImage
? SizedBox(
@ -589,9 +574,9 @@ class _ShareImageEditorView extends State<ShareImageEditorView> {
),
),
label: Text(
(sendNextMediaToUserName == null)
(widget.sendTo == null)
? context.lang.shareImagedEditorShareWith
: sendNextMediaToUserName!,
: getContactDisplayName(widget.sendTo!),
style: TextStyle(fontSize: 17),
),
),