diff --git a/README.md b/README.md index 5b472e0..f5602a7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,12 @@ Don't be lonely, get twonly! Send pictures to a friend in real time and be sure This app was started because of the three main features I missed out by popular alternatives. + +## Background Notification + +The server will first try to send via an open websocket connection. +If not available then it sends a wakeup via FCM. This will trigger the app to reopen the websocket. + ### Three to rule them all. 1. Security by design: No one except your device can access your data. diff --git a/lib/main.dart b/lib/main.dart index bbe4cdb..e354c5d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,4 +1,5 @@ import 'package:camera/camera.dart'; +import 'package:provider/provider.dart'; import 'package:twonly/src/providers/api_provider.dart'; import 'package:twonly/src/providers/db_provider.dart'; import 'package:flutter/material.dart'; @@ -9,7 +10,6 @@ import 'src/settings/settings_controller.dart'; import 'src/settings/settings_service.dart'; late DbProvider dbProvider; -late ApiProvider apiProvider; void main() async { // Set up the SettingsController, which will glue user settings to multiple @@ -27,40 +27,34 @@ void main() async { // check if release build or debug build final kDebugMode = true; - Logger.root.level = Level.ALL; // defaults to Level.INFO + Logger.root.level = Level.FINEST; // defaults to Level.INFO Logger.root.onRecord.listen((record) { - if (kDebugMode) { - // ignore: avoid_print - print('${record.level.name}: ${record.time}: ${record.message}'); - } + // if (kDebugMode) { + // ignore: avoid_print + print('${record.level.name}: ${record.time}: ${record.message}'); + // } }); var cameras = await availableCameras(); - - // Create or open the database - dbProvider = DbProvider(); + t + // Create or open the database + dbProvider = DbProvider(); await dbProvider.ready; // Create an option to select different servers. - var apiUrl = "ws://api.theconnectapp.de/v0/"; - if (kDebugMode) { + var apiUrl = "ws://api.theconnectapp.de/v-1/"; + if (true) { // Overwrite the domain in your local network so you can test the app locally - apiUrl = "ws://10.99.0.6:3030/api/client"; + apiUrl = "ws://9.99.0.6:3030/api/client"; } - apiProvider = ApiProvider(apiUrl: apiUrl); - - // TODO: Open the connection in the background so the app launch is not delayed. - //await apiProvider.connect(); - // Workmanager.executeTask((task, inputData) async { // await _HomeState().manager(); // print('Background Services are Working!');//This is Working // return true; // }); - // Run the app and pass in the SettingsController. The app listens to the - // SettingsController for changes, then passes it further down to the - // SettingsView. - runApp(MyApp(settingsController: settingsController, cameras: cameras)); + runApp(ChangeNotifierProvider( + child: MyApp(settingsController: settingsController, cameras: cameras), + create: (_) => ApiProvider(apiUrl: apiUrl, backupApiUrl: apiUrl))); } diff --git a/lib/src/app.dart b/lib/src/app.dart index 938989c..c9cc0fd 100644 --- a/lib/src/app.dart +++ b/lib/src/app.dart @@ -1,11 +1,15 @@ import 'package:camera/camera.dart'; +import 'package:path/path.dart'; +import 'package:twonly/src/providers/api_provider.dart'; import 'views/home_view.dart'; import 'views/register_view.dart'; import 'utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; - +import 'package:provider/provider.dart'; +import 'dart:isolate'; +import 'dart:async'; import 'settings/settings_controller.dart'; /// The Widget that configures your application. @@ -22,9 +26,41 @@ class MyApp extends StatefulWidget { class _MyAppState extends State { Future _isUserCreated = isUserCreated(); + int redColorOpacity = 0; // Start with dark red + bool redColorGoUp = true; + bool isConnected = false; + + @override + void initState() { + super.initState(); + // Start the color animation + _startColorAnimation(); + } + + void _startColorAnimation() { + // Change the color every second + Future.delayed(Duration(milliseconds: 200), () { + setState(() { + if (redColorOpacity <= 100) { + redColorGoUp = true; + } + if (redColorOpacity >= 150) { + redColorGoUp = false; + } + if (redColorGoUp) { + redColorOpacity += 10; + } else { + redColorOpacity -= 10; + } + }); + _startColorAnimation(); // Repeat the animation + }); + } @override Widget build(BuildContext context) { + double screenWidth = MediaQuery.of(context).size.width; + var isConnected = context.watch().isConnected; // Glue the SettingsController to the MaterialApp. // // The ListenableBuilder Widget listens to the SettingsController for changes. @@ -58,27 +94,59 @@ class _MyAppState extends State { const InputDecorationTheme(border: OutlineInputBorder()), ), themeMode: widget.settingsController.themeMode, - home: FutureBuilder( - future: _isUserCreated, - builder: (context, snapshot) { - if (snapshot.hasData) { - return snapshot.data! - ? HomeView( - settingsController: widget.settingsController, - cameras: widget.cameras) - : RegisterView(callbackOnSuccess: () { - _isUserCreated = isUserCreated(); - setState(() {}); + home: Stack( + children: [ + FutureBuilder( + future: context.watch().startBackend(), + builder: (context, snapshot) { + return FutureBuilder( + future: _isUserCreated, + builder: (context, snapshot) { + if (snapshot.hasData) { + return snapshot.data! + ? HomeView( + settingsController: + widget.settingsController, + cameras: widget.cameras) + : RegisterView( + callbackOnSuccess: () { + _isUserCreated = isUserCreated(); + setState(() {}); + }, + ); // Show the red line if not connected + } else { + return Container(); + } }); - } else { - return Center( - child: SizedBox( - width: 60, - height: 60, - child: CircularProgressIndicator(), - )); - } - }), + }), + if (!isConnected) + Positioned( + top: 3, // Position it at the top + left: (screenWidth * 0.5) / 2, // Center it horizontally + child: AnimatedContainer( + duration: Duration(milliseconds: 100), + width: screenWidth * 0.5, // 50% of the screen width + decoration: BoxDecoration( + border: Border.all( + color: Colors.red[600]!.withAlpha(redColorOpacity), + width: 2.0), // Red border + borderRadius: BorderRadius.all( + Radius.circular(10.0)), // Rounded top corners + ), + // child: Padding( + // padding: const EdgeInsets.all( + // 8.0), // Padding around the child + // child: Center( + // child: Text( + // 'Not Connected', + // style: TextStyle(fontSize: 24), + // ), + // ), + // ), + ), + ), + ], + ), ); }, ); diff --git a/lib/src/model/messages_model.dart b/lib/src/model/messages_model.dart new file mode 100644 index 0000000..fb87d27 --- /dev/null +++ b/lib/src/model/messages_model.dart @@ -0,0 +1,26 @@ +import 'dart:typed_data'; + +import 'package:cv/cv.dart'; + +class DbMessages extends CvModelBase { + static const tableName = "messages"; + + static const columnMessageId = "messageId"; + final messageId = CvField(columnMessageId); + + static const columnCreatedAt = "created_at"; + final createdAt = CvField(columnCreatedAt); + + static String getCreateTableString() { + return """ + CREATE TABLE $tableName ( + $columnMessageId INTEGER NOT NULL, + $columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY ($columnMessageId) + ) + """; + } + + @override + List get fields => [messageId, createdAt]; +} diff --git a/lib/src/providers/api_provider.dart b/lib/src/providers/api_provider.dart index c22dd28..6177fd2 100644 --- a/lib/src/providers/api_provider.dart +++ b/lib/src/providers/api_provider.dart @@ -1,15 +1,17 @@ import 'dart:collection'; import 'dart:convert'; import 'dart:ffi'; +import 'dart:isolate'; import 'dart:math'; import 'dart:typed_data'; import 'package:fixnum/fixnum.dart'; import 'package:flutter/material.dart'; import 'package:logging/logging.dart'; -import 'package:twonly/src/proto/api/client_to_server.pb.dart'; +import 'package:twonly/src/proto/api/client_to_server.pb.dart' as c; import 'package:twonly/src/proto/api/error.pb.dart'; import 'package:twonly/src/proto/api/server_to_client.pb.dart' as server; +import 'package:twonly/src/proto/api/server_to_client.pbserver.dart'; import 'package:twonly/src/signal/signal_helper.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:web_socket_channel/web_socket_channel.dart'; @@ -25,22 +27,176 @@ class Result { Result.error(this.error) : value = null; } -class ApiProvider { - ApiProvider({required this.apiUrl}); +enum ExchangeKind { connectionStateChange, sendRequestV0 } + +class Exchange { + Exchange({required this.kind, required this.body, required this.seq}); + final int seq; + final ExchangeKind kind; + final dynamic body; +} + +c.ClientToServer createClientToServerFromHandshake(c.Handshake handshake) { + // Create the V0 message + var v0 = c.V0() + ..seq = Int64(0) // You can set this to the appropriate sequence number + ..handshake = handshake; + + // Create the ClientToServer message + var clientToServer = c.ClientToServer()..v0 = v0; + + return clientToServer; +} + +Result asResult(server.ServerToClient msg) { + if (msg.v0.response.hasOk()) { + return Result.success(msg.v0.response.ok); + } else { + return Result.error(msg.v0.response.error); + } +} + +class BackendIsolatedArgs { + BackendIsolatedArgs( + {required this.apiUrl, + required this.backupApiUrl, + required this.receivePort, + required this.sendPort}); + final String apiUrl; + final String backupApiUrl; + final ReceivePort receivePort; + final SendPort sendPort; +} + +class ApiProvider with ChangeNotifier { + ApiProvider({required this.apiUrl, required this.backupApiUrl}); final String apiUrl; - final log = Logger("connect::ApiProvider"); - final HashMap> _callbacks = HashMap(); + final String backupApiUrl; + bool _isConnected = false; + final Map _sendRequestV0Resp = HashMap(); + final log = Logger("twonly::ApiProvider"); + + final ReceivePort _receivePort = ReceivePort(); + SendPort? _sendPort; + + Future _waitForResponse(int seq) async { + final startTime = DateTime.now(); + + final timeout = Duration(seconds: 5); + + while (true) { + if (_sendRequestV0Resp[seq] != null) { + final tmp = _sendRequestV0Resp[seq]; + _sendRequestV0Resp.remove(seq); + return tmp; + } + if (DateTime.now().difference(startTime) > timeout) { + log.shout("Timeout for message $seq"); + return null; + } + await Future.delayed(Duration(milliseconds: 10)); + } + } + + static void _startBackendIsolated(BackendIsolatedArgs args) async { + final backend = + Backend(apiUrl: args.apiUrl, backupApiUrl: args.backupApiUrl); + + await backend.connect(); + + args.receivePort.listen((msg) async { + switch (msg.kind) { + case ExchangeKind.sendRequestV0: + final resp = await backend._sendRequestV0(msg.body); + args.sendPort.send(Exchange( + kind: ExchangeKind.sendRequestV0, body: resp, seq: msg.seq)); + break; + default: + } + }); + args.sendPort.send( + Exchange(kind: ExchangeKind.connectionStateChange, body: true, seq: 0)); + } + + Future startBackend() async { + ReceivePort port = ReceivePort(); + _sendPort = port.sendPort; + + await Isolate.spawn( + _startBackendIsolated, + BackendIsolatedArgs( + sendPort: _receivePort.sendPort, + receivePort: port, + apiUrl: apiUrl, + backupApiUrl: backupApiUrl)); + + _receivePort.listen((msg) { + switch (msg.kind) { + case ExchangeKind.sendRequestV0: + _sendRequestV0Resp[msg.seq] = msg.body; + // final resp = await backend._sendRequestV0(msg.body); + // args.sendPort.send(Exchange( + // kind: ExchangeKind.sendRequestV0, body: resp, seq: msg.seq)); + break; + case ExchangeKind.connectionStateChange: + _isConnected = msg.body; + default: + } + }); + return true; + } + + bool get isConnected => _isConnected; + + Future register(String username, String? inviteCode) async { + if (_sendPort == null) return Result.error("Unknown error"); + final reqSignal = await SignalHelper.getRegisterData(); + + if (reqSignal == null) { + return Result.error( + "There was an fatal error. Try reinstalling the app."); + } + + var register = c.Handshake_Register() + ..username = username + ..publicIdentityKey = reqSignal["identityKey"] + ..signedPrekey = reqSignal["signedPreKey"]?["key"] + ..signedPrekeySignature = reqSignal["signedPreKey"]?["signature"] + ..signedPrekeyId = Int64(reqSignal["signedPreKey"]?["id"]); + + if (inviteCode != null && inviteCode != "") { + register.inviteCode = inviteCode; + } + // Create the Handshake message + var handshake = c.Handshake()..register = register; + var req = createClientToServerFromHandshake(handshake); + + var seq = Random().nextInt(4294967296); + final tmp = Exchange(seq: seq, kind: ExchangeKind.sendRequestV0, body: req); + _sendPort!.send(tmp); + + final resp = await _waitForResponse(seq); + + if (resp == null) { + return Result.error("Server is not reachable!"); + } + return asResult(resp); + } +} + +class Backend { + Backend({required this.apiUrl, required this.backupApiUrl}); + + final String apiUrl; + final String? backupApiUrl; + final log = Logger("twonly::backend"); final HashMap messagesV0 = HashMap(); WebSocketChannel? _channel; - Future connect() async { - if (_channel != null && _channel!.closeCode != null) { - return true; - } - log.info("Trying to connect to the backend $apiUrl!"); + Future _connectTo(String apiUrl) async { try { var channel = WebSocketChannel.connect( Uri.parse(apiUrl), @@ -49,6 +205,7 @@ class ApiProvider { _channel!.stream.listen(_onData); await _channel!.ready; log.info("Websocket is connected!"); + print("Websocket is connected!"); return true; } on WebSocketChannelException catch (e) { log.shout("Error: $e"); @@ -56,25 +213,39 @@ class ApiProvider { } } + Future connect() async { + print("Trying to connect to the backend $apiUrl!"); + if (_channel != null && _channel!.closeCode != null) { + return true; + } + log.info("Trying to connect to the backend $apiUrl!"); + if (await _connectTo(apiUrl)) { + return true; + } + if (backupApiUrl != null) { + log.info("Trying to connect to the backup backend $backupApiUrl!"); + if (await _connectTo(backupApiUrl!)) { + return true; + } + } + return false; + } + + bool get isConnected => _channel != null && _channel!.closeCode != null; + void _onData(dynamic msgBuffer) { try { final msg = server.ServerToClient.fromBuffer(msgBuffer); - print("New message: $msg"); - messagesV0[msg.v0.seq] = msg; + if (msg.v0.hasResponse()) { + messagesV0[msg.v0.seq] = msg; + } else { + print("Got a new message from the server: $msg"); + } } catch (e) { log.shout("Error parsing the servers message: $e"); } } - // void _reconnect() {} - - void addNotifier(String messageType, Function callBackFunction) { - if (!_callbacks.containsKey(messageType)) { - _callbacks[messageType] = []; - } - _callbacks[messageType]!.add(callBackFunction); - } - // TODO: There must be a smarter move to do that :/ Future _waitForResponse(Int64 seq) async { final startTime = DateTime.now(); @@ -95,7 +266,8 @@ class ApiProvider { } } - Future _sendRequestV0(ClientToServer request) async { + Future _sendRequestV0( + c.ClientToServer request) async { var seq = Int64(Random().nextInt(4294967296)); while (messagesV0.containsKey(seq)) { seq = Int64(Random().nextInt(4294967296)); @@ -115,18 +287,6 @@ class ApiProvider { return await _waitForResponse(seq); } - ClientToServer createClientToServerFromHandshake(Handshake handshake) { - // Create the V0 message - var v0 = V0() - ..seq = Int64(0) // You can set this to the appropriate sequence number - ..handshake = handshake; - - // Create the ClientToServer message - var clientToServer = ClientToServer()..v0 = v0; - - return clientToServer; - } - static String getLocalizedString(BuildContext context, ErrorCode code) { switch (code.toString()) { case "Unknown": @@ -159,41 +319,4 @@ class ApiProvider { return code.toString(); // Fallback for unrecognized keys } } - - Result _asResult(server.ServerToClient msg) { - if (msg.v0.response.hasOk()) { - return Result.success(msg.v0.response.ok); - } else { - return Result.error(msg.v0.response.error); - } - } - - Future register(String username, String? inviteCode) async { - final reqSignal = await SignalHelper.getRegisterData(); - - if (reqSignal == null) { - return Result.error( - "There was an fatal error. Try reinstalling the app."); - } - - var register = Handshake_Register() - ..username = username - ..publicIdentityKey = reqSignal["identityKey"] - ..signedPrekey = reqSignal["signedPreKey"]?["key"] - ..signedPrekeySignature = reqSignal["signedPreKey"]?["signature"] - ..signedPrekeyId = Int64(reqSignal["signedPreKey"]?["id"]); - - if (inviteCode != null && inviteCode != "") { - register.inviteCode = inviteCode; - } - // Create the Handshake message - var handshake = Handshake()..register = register; - var req = createClientToServerFromHandshake(handshake); - - final resp = await _sendRequestV0(req); - if (resp == null) { - return Result.error("Server is not reachable!"); - } - return _asResult(resp); - } } diff --git a/lib/src/utils.dart b/lib/src/utils.dart index f7af799..852bffd 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -1,9 +1,11 @@ import 'dart:convert'; +import 'package:flutter/material.dart'; import 'package:twonly/main.dart'; import 'package:twonly/src/signal/signal_helper.dart'; import 'package:twonly/src/providers/api_provider.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'model/user_data_json.dart'; +import 'package:provider/provider.dart'; import 'package:logging/logging.dart'; // Just a helper function to get the secure storage @@ -44,13 +46,18 @@ Future deleteLocalUserData() async { return true; } -Future createNewUser(String username, String inviteCode) async { +Future createNewUser( + BuildContext context, String username, String inviteCode) async { final storage = getSecureStorage(); + if (!context.mounted) { + return Result.error("not mounted"); + } await SignalHelper.createIfNotExistsSignalIdentity(); // TODO: API call to server to check username and inviteCode - final res = await apiProvider.register(username, inviteCode); + // final res = await apiProvider.register(username, inviteCode); + final res = await context.watch().register(username, inviteCode); if (res.isSuccess) { print("Got user_id ${res.value}"); diff --git a/lib/src/views/camera_editor_view.dart b/lib/src/views/camera_editor_view.dart deleted file mode 100644 index 5571da5..0000000 --- a/lib/src/views/camera_editor_view.dart +++ /dev/null @@ -1,288 +0,0 @@ -// Dart imports: -import 'dart:io'; -import 'dart:math'; - -// Flutter imports: -// import 'package:example/widgets/demo_build_stickers.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -// Package imports: -import 'package:google_fonts/google_fonts.dart'; -import 'package:pro_image_editor/designs/frosted_glass/frosted_glass.dart'; -import 'package:pro_image_editor/pro_image_editor.dart'; - -// Project imports: -// import '../../utils/example_helper.dart'; - -/// The frosted glass design example -class CameraEditorView extends StatefulWidget { - /// Creates a new [CameraEditorView] widget. - const CameraEditorView({ - super.key, - required this.imagePath, - }); - - /// The URL of the image to display. - final String imagePath; - - @override - State createState() => _CameraEditorViewState(); -} - -class _CameraEditorViewState extends State { - final bool _useMaterialDesign = - platformDesignMode == ImageEditorDesignModeE.material; - - /// Opens the sticker/emoji editor. - void _openStickerEditor(ProImageEditorState editor) async { - Layer? layer = await editor.openPage(FrostedGlassStickerPage( - configs: editor.configs, - callbacks: editor.callbacks, - )); - - if (layer == null || !mounted) return; - - if (layer.runtimeType != StickerLayerData) { - layer.scale = editor.configs.emojiEditorConfigs.initScale; - } - - editor.addLayer(layer); - } - - /// Calculates the number of columns for the EmojiPicker. - int _calculateEmojiColumns(BoxConstraints constraints) => - max(1, (_useMaterialDesign ? 6 : 10) / 400 * constraints.maxWidth - 1) - .floor(); - - @override - Widget build(BuildContext context) { - return LayoutBuilder(builder: (context, constraints) { - return ProImageEditor.file( - File(widget.imagePath), - // key: editorKey, - callbacks: ProImageEditorCallbacks( - onImageEditingStarted: () {}, - onImageEditingComplete: (image) async { - Navigator.pop(context); - }, - onCloseEditor: () {}, - stickerEditorCallbacks: StickerEditorCallbacks( - onSearchChanged: (value) { - /// Filter your stickers - debugPrint(value); - }, - )), - configs: ProImageEditorConfigs( - designMode: platformDesignMode, - theme: Theme.of(context).copyWith( - iconTheme: - Theme.of(context).iconTheme.copyWith(color: Colors.white)), - icons: const ImageEditorIcons( - paintingEditor: IconsPaintingEditor( - bottomNavBar: Icons.edit, - ), - ), - imageEditorTheme: ImageEditorTheme( - textEditor: TextEditorTheme( - textFieldMargin: const EdgeInsets.only(top: kToolbarHeight), - bottomBarBackgroundColor: Colors.transparent, - bottomBarMainAxisAlignment: !_useMaterialDesign - ? MainAxisAlignment.spaceEvenly - : MainAxisAlignment.start), - paintingEditor: const PaintingEditorTheme( - initialStrokeWidth: 5, - ), - // filterEditor: const FilterEditorTheme( - // filterListSpacing: 7, - // filterListMargin: EdgeInsets.fromLTRB(8, 15, 8, 10), - // ), - emojiEditor: EmojiEditorTheme( - backgroundColor: Colors.transparent, - textStyle: DefaultEmojiTextStyle.copyWith( - fontFamily: - !kIsWeb ? null : GoogleFonts.notoColorEmoji().fontFamily, - fontSize: _useMaterialDesign ? 48 : 30, - ), - emojiViewConfig: EmojiViewConfig( - gridPadding: EdgeInsets.zero, - horizontalSpacing: 0, - verticalSpacing: 0, - recentsLimit: 40, - backgroundColor: Colors.transparent, - buttonMode: !_useMaterialDesign - ? ButtonMode.CUPERTINO - : ButtonMode.MATERIAL, - loadingIndicator: - const Center(child: CircularProgressIndicator()), - columns: _calculateEmojiColumns(constraints), - emojiSizeMax: !_useMaterialDesign ? 32 : 64, - replaceEmojiOnLimitExceed: false, - ), - bottomActionBarConfig: - const BottomActionBarConfig(enabled: false), - ), - layerInteraction: const ThemeLayerInteraction( - removeAreaBackgroundInactive: Colors.black12, - ), - ), - textEditorConfigs: TextEditorConfigs( - customTextStyles: [ - GoogleFonts.roboto(), - GoogleFonts.averiaLibre(), - GoogleFonts.lato(), - GoogleFonts.comicNeue(), - GoogleFonts.actor(), - GoogleFonts.odorMeanChey(), - GoogleFonts.nabla(), - ], - ), - emojiEditorConfigs: const EmojiEditorConfigs( - checkPlatformCompatibility: !kIsWeb, - ), - customWidgets: ImageEditorCustomWidgets( - loadingDialog: (message, configs) => FrostedGlassLoadingDialog( - message: message, - configs: configs, - ), - mainEditor: CustomWidgetsMainEditor( - closeWarningDialog: (editor) async { - if (!context.mounted) return false; - return await showDialog( - context: context, - builder: (BuildContext context) => - FrostedGlassCloseDialog(editor: editor), - ) ?? - false; - }, - appBar: (editor, rebuildStream) => null, - bottomBar: (editor, rebuildStream, key) => null, - bodyItems: _buildMainBodyWidgets, - ), - paintEditor: CustomWidgetsPaintEditor( - appBar: (paintEditor, rebuildStream) => null, - bottomBar: (paintEditor, rebuildStream) => null, - colorPicker: - (paintEditor, rebuildStream, currentColor, setColor) => null, - bodyItems: _buildPaintEditorBody, - ), - textEditor: CustomWidgetsTextEditor( - appBar: (textEditor, rebuildStream) => null, - colorPicker: - (textEditor, rebuildStream, currentColor, setColor) => null, - bottomBar: (textEditor, rebuildStream) => null, - bodyItems: _buildTextEditorBody, - ), - cropRotateEditor: CustomWidgetsCropRotateEditor(), - ), - ), - ); - }); - } - - List _buildMainBodyWidgets( - ProImageEditorState editor, - Stream rebuildStream, - ) { - return [ - if (editor.selectedLayerIndex < 0) - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) => FrostedGlassActionBar( - editor: editor, - openStickerEditor: () => _openStickerEditor(editor), - ), - ), - ]; - } - - List _buildPaintEditorBody( - PaintingEditorState paintEditor, - Stream rebuildStream, - ) { - return [ - /// Appbar - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) { - return paintEditor.activePainting - ? const SizedBox.shrink() - : FrostedGlassPaintingAppbar(paintEditor: paintEditor); - }, - ), - - /// Bottombar - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) => FrostedGlassPaintBottomBar(paintEditor: paintEditor), - ), - ]; - } - - List _buildTuneEditorBody( - TuneEditorState tuneEditor, - Stream rebuildStream, - ) { - return [ - /// Appbar - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) { - return FrostedGlassTuneAppbar(tuneEditor: tuneEditor); - }, - ), - - /// Bottombar - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) => FrostedGlassTuneBottombar(tuneEditor: tuneEditor), - ), - ]; - } - - List _buildTextEditorBody( - TextEditorState textEditor, - Stream rebuildStream, - ) { - return [ - /// Background - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) => const FrostedGlassEffect( - radius: BorderRadius.zero, - child: SizedBox.expand(), - ), - ), - - /// Slider Text size - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) => Padding( - padding: const EdgeInsets.only(top: kToolbarHeight), - child: FrostedGlassTextSizeSlider(textEditor: textEditor), - ), - ), - - /// Appbar - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) { - return FrostedGlassTextAppbar(textEditor: textEditor); - }, - ), - - /// Bottombar - ReactiveCustomWidget( - stream: rebuildStream, - builder: (_) => FrostedGlassTextBottomBar( - configs: textEditor.configs, - initColor: textEditor.primaryColor, - onColorChanged: (color) { - textEditor.primaryColor = color; - }, - selectedStyle: textEditor.selectedTextStyle, - onFontChange: textEditor.setTextStyle, - ), - ), - ]; - } -} diff --git a/lib/src/views/camera_preview_view.dart b/lib/src/views/camera_preview_view.dart index 8bd18de..900f711 100644 --- a/lib/src/views/camera_preview_view.dart +++ b/lib/src/views/camera_preview_view.dart @@ -1,5 +1,4 @@ import 'package:camera/camera.dart'; -import 'camera_editor_view.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; // import 'package:camerawesome/camerawesome_plugin.dart'; @@ -71,13 +70,13 @@ class CameraPreviewViewState extends State { final image = await _controller.takePicture(); if (!context.mounted) return; - await Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => CameraEditorView( - imagePath: image.path, - ), - ), - ); + // await Navigator.of(context).push( + // MaterialPageRoute( + // builder: (context) => CameraEditorView( + // imagePath: image.path, + // ), + // ), + // ); } catch (e) { // If an error occurs, log the error to the console. print(e); diff --git a/lib/src/views/register_view.dart b/lib/src/views/register_view.dart index 6577594..319c8ca 100644 --- a/lib/src/views/register_view.dart +++ b/lib/src/views/register_view.dart @@ -120,7 +120,7 @@ class _RegisterViewState extends State { setState(() { _isTryingToRegister = true; }); - final res = await createNewUser( + final res = await createNewUser(context, usernameController.text, inviteCodeController.text); setState(() { _isTryingToRegister = false; @@ -130,7 +130,7 @@ class _RegisterViewState extends State { return; } final errMsg = - ApiProvider.getLocalizedString(context, res.error); + Backend.getLocalizedString(context, res.error); showAlertDialog(context, "Oh no!", errMsg); }, style: ButtonStyle( diff --git a/pubspec.lock b/pubspec.lock index d28ad11..9c3fb06 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -306,10 +306,10 @@ packages: dependency: transitive description: name: emoji_picker_flutter - sha256: "08567e6f914d36c32091a96cf2f51d2558c47aa2bd47a590dc4f50e42e0965f6" + sha256: "63dee6be976c51c8b971eccbc73fc637f021b6b679eed1b2ec3b503947304734" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "4.2.0" fake_async: dependency: transitive description: @@ -642,6 +642,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.3" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" optional: dependency: transitive description: @@ -814,10 +822,10 @@ packages: dependency: "direct main" description: name: pro_image_editor - sha256: "27190b0333af71e9949f366ac303496511ef6d67607f6f9797c9f136371a321f" + sha256: "918f156f28a72b9185d950f865032f6aa83da485d6d45b22fb63d78b63bf7e21" url: "https://pub.dev" source: hosted - version: "6.2.3" + version: "7.6.4" protobuf: dependency: transitive description: @@ -826,6 +834,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" pub_semver: dependency: transitive description: @@ -1047,6 +1063,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + universal_io: + dependency: transitive + description: + name: universal_io + sha256: "1722b2dcc462b4b2f3ee7d188dad008b6eb4c40bbd03a3de451d82c78bba9aad" + url: "https://pub.dev" + source: hosted + version: "2.2.2" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e202990..d4bb722 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,7 +30,8 @@ dependencies: path: ^1.9.0 path_provider: ^2.1.5 permission_handler: ^11.3.1 - pro_image_editor: ^6.1.3 + pro_image_editor: ^7.6.4 + provider: ^6.1.2 restart_app: ^1.3.2 sqflite_sqlcipher: ^3.1.0+1 web_socket_channel: ^3.0.1