diff --git a/lib/app.dart b/lib/app.dart index c53fe96..b8f4b2a 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -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 with WidgetsBindingObserver { bool wasPaused = false; + bool appIsOutdated = false; @override void initState() { @@ -62,6 +66,12 @@ class _AppState extends State with WidgetsBindingObserver { Future initAsync() async { setUserPlan(); + globalCallbackAppIsOutdated = () async { + context.read().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 with WidgetsBindingObserver { void dispose() { WidgetsBinding.instance.removeObserver(this); globalCallbackConnectionState = (a) {}; + globalCallbackAppIsOutdated = () {}; super.dispose(); } @@ -133,8 +144,10 @@ class _AppState extends State with WidgetsBindingObserver { themeMode: context.watch().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 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 createState() => _AppMainWidgetState(); } @@ -187,6 +202,55 @@ class _AppMainWidgetState extends State { ); }, ), + 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), + ), + ), + ], + ), + ), + ), + ), ], ); } diff --git a/lib/globals.dart b/lib/globals.dart index 69f362e..3800bef 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -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; diff --git a/lib/src/localization/app_de.arb b/lib/src/localization/app_de.arb index 1fb4a30..af7671e 100644 --- a/lib/src/localization/app_de.arb +++ b/lib/src/localization/app_de.arb @@ -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." } \ No newline at end of file diff --git a/lib/src/localization/app_en.arb b/lib/src/localization/app_en.arb index 5fa6e88..97e7246 100644 --- a/lib/src/localization/app_en.arb +++ b/lib/src/localization/app_en.arb @@ -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" } \ No newline at end of file diff --git a/lib/src/localization/generated/app_localizations.dart b/lib/src/localization/generated/app_localizations.dart index 64ca280..56127d5 100644 --- a/lib/src/localization/generated/app_localizations.dart +++ b/lib/src/localization/generated/app_localizations.dart @@ -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 diff --git a/lib/src/localization/generated/app_localizations_de.dart b/lib/src/localization/generated/app_localizations_de.dart index aa28745..d3989ea 100644 --- a/lib/src/localization/generated/app_localizations_de.dart +++ b/lib/src/localization/generated/app_localizations_de.dart @@ -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.'; } diff --git a/lib/src/localization/generated/app_localizations_en.dart b/lib/src/localization/generated/app_localizations_en.dart index af0d8ca..73ccca7 100644 --- a/lib/src/localization/generated/app_localizations_en.dart +++ b/lib/src/localization/generated/app_localizations_en.dart @@ -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'; } diff --git a/lib/src/services/api.service.dart b/lib/src/services/api.service.dart index 834cc6e..9d9c90a 100644 --- a/lib/src/services/api.service.dart +++ b/lib/src/services/api.service.dart @@ -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>? connectivitySubscription; Future _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) { diff --git a/lib/src/views/camera/camera_preview_components/save_to_gallery.dart b/lib/src/views/camera/camera_preview_components/save_to_gallery.dart index ac89da1..3cc1f7f 100644 --- a/lib/src/views/camera/camera_preview_components/save_to_gallery.dart +++ b/lib/src/views/camera/camera_preview_components/save_to_gallery.dart @@ -11,7 +11,7 @@ import 'package:twonly/src/utils/storage.dart'; class SaveToGalleryButton extends StatefulWidget { final Future 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 { : _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) diff --git a/lib/src/views/camera/share_image_editor_view.dart b/lib/src/views/camera/share_image_editor_view.dart index 469de3c..176ff89 100644 --- a/lib/src/views/camera/share_image_editor_view.dart +++ b/lib/src/views/camera/share_image_editor_view.dart @@ -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 { 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 { 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 get actionsAtTheRight { if (layers.isNotEmpty && layers.last.isEditing && @@ -449,10 +438,6 @@ class _ShareImageEditorView extends State { 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 { 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 { 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 { ), ), label: Text( - (sendNextMediaToUserName == null) + (widget.sendTo == null) ? context.lang.shareImagedEditorShareWith - : sendNextMediaToUserName!, + : getContactDisplayName(widget.sendTo!), style: TextStyle(fontSize: 17), ), ),