mirror of
https://github.com/twonlyapp/twonly-app.git
synced 2026-01-15 09:08:40 +00:00
test
This commit is contained in:
parent
809f677626
commit
3b3c5e61b9
11 changed files with 378 additions and 418 deletions
|
|
@ -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.
|
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.
|
### Three to rule them all.
|
||||||
|
|
||||||
1. Security by design: No one except your device can access your data.
|
1. Security by design: No one except your device can access your data.
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:twonly/src/providers/api_provider.dart';
|
import 'package:twonly/src/providers/api_provider.dart';
|
||||||
import 'package:twonly/src/providers/db_provider.dart';
|
import 'package:twonly/src/providers/db_provider.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
@ -9,7 +10,6 @@ import 'src/settings/settings_controller.dart';
|
||||||
import 'src/settings/settings_service.dart';
|
import 'src/settings/settings_service.dart';
|
||||||
|
|
||||||
late DbProvider dbProvider;
|
late DbProvider dbProvider;
|
||||||
late ApiProvider apiProvider;
|
|
||||||
|
|
||||||
void main() async {
|
void main() async {
|
||||||
// Set up the SettingsController, which will glue user settings to multiple
|
// 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
|
// check if release build or debug build
|
||||||
final kDebugMode = true;
|
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) {
|
Logger.root.onRecord.listen((record) {
|
||||||
if (kDebugMode) {
|
// if (kDebugMode) {
|
||||||
// ignore: avoid_print
|
// ignore: avoid_print
|
||||||
print('${record.level.name}: ${record.time}: ${record.message}');
|
print('${record.level.name}: ${record.time}: ${record.message}');
|
||||||
}
|
// }
|
||||||
});
|
});
|
||||||
|
|
||||||
var cameras = await availableCameras();
|
var cameras = await availableCameras();
|
||||||
|
t
|
||||||
// Create or open the database
|
// Create or open the database
|
||||||
dbProvider = DbProvider();
|
dbProvider = DbProvider();
|
||||||
await dbProvider.ready;
|
await dbProvider.ready;
|
||||||
|
|
||||||
// Create an option to select different servers.
|
// Create an option to select different servers.
|
||||||
var apiUrl = "ws://api.theconnectapp.de/v0/";
|
var apiUrl = "ws://api.theconnectapp.de/v-1/";
|
||||||
if (kDebugMode) {
|
if (true) {
|
||||||
// Overwrite the domain in your local network so you can test the app locally
|
// 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 {
|
// Workmanager.executeTask((task, inputData) async {
|
||||||
// await _HomeState().manager();
|
// await _HomeState().manager();
|
||||||
// print('Background Services are Working!');//This is Working
|
// print('Background Services are Working!');//This is Working
|
||||||
// return true;
|
// return true;
|
||||||
// });
|
// });
|
||||||
|
|
||||||
// Run the app and pass in the SettingsController. The app listens to the
|
runApp(ChangeNotifierProvider<ApiProvider>(
|
||||||
// SettingsController for changes, then passes it further down to the
|
child: MyApp(settingsController: settingsController, cameras: cameras),
|
||||||
// SettingsView.
|
create: (_) => ApiProvider(apiUrl: apiUrl, backupApiUrl: apiUrl)));
|
||||||
runApp(MyApp(settingsController: settingsController, cameras: cameras));
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
lib/src/app.dart
110
lib/src/app.dart
|
|
@ -1,11 +1,15 @@
|
||||||
import 'package:camera/camera.dart';
|
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/home_view.dart';
|
||||||
import 'views/register_view.dart';
|
import 'views/register_view.dart';
|
||||||
import 'utils.dart';
|
import 'utils.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:flutter_localizations/flutter_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';
|
import 'settings/settings_controller.dart';
|
||||||
|
|
||||||
/// The Widget that configures your application.
|
/// The Widget that configures your application.
|
||||||
|
|
@ -22,9 +26,41 @@ class MyApp extends StatefulWidget {
|
||||||
|
|
||||||
class _MyAppState extends State<MyApp> {
|
class _MyAppState extends State<MyApp> {
|
||||||
Future<bool> _isUserCreated = isUserCreated();
|
Future<bool> _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
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
double screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
var isConnected = context.watch<ApiProvider>().isConnected;
|
||||||
// Glue the SettingsController to the MaterialApp.
|
// Glue the SettingsController to the MaterialApp.
|
||||||
//
|
//
|
||||||
// The ListenableBuilder Widget listens to the SettingsController for changes.
|
// The ListenableBuilder Widget listens to the SettingsController for changes.
|
||||||
|
|
@ -58,27 +94,59 @@ class _MyAppState extends State<MyApp> {
|
||||||
const InputDecorationTheme(border: OutlineInputBorder()),
|
const InputDecorationTheme(border: OutlineInputBorder()),
|
||||||
),
|
),
|
||||||
themeMode: widget.settingsController.themeMode,
|
themeMode: widget.settingsController.themeMode,
|
||||||
home: FutureBuilder<bool>(
|
home: Stack(
|
||||||
future: _isUserCreated,
|
children: [
|
||||||
builder: (context, snapshot) {
|
FutureBuilder<bool>(
|
||||||
if (snapshot.hasData) {
|
future: context.watch<ApiProvider>().startBackend(),
|
||||||
return snapshot.data!
|
builder: (context, snapshot) {
|
||||||
? HomeView(
|
return FutureBuilder<bool>(
|
||||||
settingsController: widget.settingsController,
|
future: _isUserCreated,
|
||||||
cameras: widget.cameras)
|
builder: (context, snapshot) {
|
||||||
: RegisterView(callbackOnSuccess: () {
|
if (snapshot.hasData) {
|
||||||
_isUserCreated = isUserCreated();
|
return snapshot.data!
|
||||||
setState(() {});
|
? 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(
|
if (!isConnected)
|
||||||
child: SizedBox(
|
Positioned(
|
||||||
width: 60,
|
top: 3, // Position it at the top
|
||||||
height: 60,
|
left: (screenWidth * 0.5) / 2, // Center it horizontally
|
||||||
child: CircularProgressIndicator(),
|
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),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
|
||||||
26
lib/src/model/messages_model.dart
Normal file
26
lib/src/model/messages_model.dart
Normal file
|
|
@ -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<int>(columnMessageId);
|
||||||
|
|
||||||
|
static const columnCreatedAt = "created_at";
|
||||||
|
final createdAt = CvField<DateTime>(columnCreatedAt);
|
||||||
|
|
||||||
|
static String getCreateTableString() {
|
||||||
|
return """
|
||||||
|
CREATE TABLE $tableName (
|
||||||
|
$columnMessageId INTEGER NOT NULL,
|
||||||
|
$columnCreatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
PRIMARY KEY ($columnMessageId)
|
||||||
|
)
|
||||||
|
""";
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<CvField> get fields => [messageId, createdAt];
|
||||||
|
}
|
||||||
|
|
@ -1,15 +1,17 @@
|
||||||
import 'dart:collection';
|
import 'dart:collection';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:ffi';
|
import 'dart:ffi';
|
||||||
|
import 'dart:isolate';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:logging/logging.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/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.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:twonly/src/signal/signal_helper.dart';
|
||||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||||
import 'package:web_socket_channel/web_socket_channel.dart';
|
import 'package:web_socket_channel/web_socket_channel.dart';
|
||||||
|
|
@ -25,22 +27,176 @@ class Result<T, E> {
|
||||||
Result.error(this.error) : value = null;
|
Result.error(this.error) : value = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class ApiProvider {
|
enum ExchangeKind { connectionStateChange, sendRequestV0 }
|
||||||
ApiProvider({required this.apiUrl});
|
|
||||||
|
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 String apiUrl;
|
||||||
final log = Logger("connect::ApiProvider");
|
final String backupApiUrl;
|
||||||
final HashMap<String, List<Function>> _callbacks = HashMap();
|
bool _isConnected = false;
|
||||||
|
final Map<int, ServerToClient> _sendRequestV0Resp = HashMap();
|
||||||
|
final log = Logger("twonly::ApiProvider");
|
||||||
|
|
||||||
|
final ReceivePort _receivePort = ReceivePort();
|
||||||
|
SendPort? _sendPort;
|
||||||
|
|
||||||
|
Future<ServerToClient?> _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<bool> 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<Result> 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<Int64, server.ServerToClient?> messagesV0 = HashMap();
|
final HashMap<Int64, server.ServerToClient?> messagesV0 = HashMap();
|
||||||
|
|
||||||
WebSocketChannel? _channel;
|
WebSocketChannel? _channel;
|
||||||
|
|
||||||
Future<bool> connect() async {
|
Future<bool> _connectTo(String apiUrl) async {
|
||||||
if (_channel != null && _channel!.closeCode != null) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
log.info("Trying to connect to the backend $apiUrl!");
|
|
||||||
try {
|
try {
|
||||||
var channel = WebSocketChannel.connect(
|
var channel = WebSocketChannel.connect(
|
||||||
Uri.parse(apiUrl),
|
Uri.parse(apiUrl),
|
||||||
|
|
@ -49,6 +205,7 @@ class ApiProvider {
|
||||||
_channel!.stream.listen(_onData);
|
_channel!.stream.listen(_onData);
|
||||||
await _channel!.ready;
|
await _channel!.ready;
|
||||||
log.info("Websocket is connected!");
|
log.info("Websocket is connected!");
|
||||||
|
print("Websocket is connected!");
|
||||||
return true;
|
return true;
|
||||||
} on WebSocketChannelException catch (e) {
|
} on WebSocketChannelException catch (e) {
|
||||||
log.shout("Error: $e");
|
log.shout("Error: $e");
|
||||||
|
|
@ -56,25 +213,39 @@ class ApiProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<bool> 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) {
|
void _onData(dynamic msgBuffer) {
|
||||||
try {
|
try {
|
||||||
final msg = server.ServerToClient.fromBuffer(msgBuffer);
|
final msg = server.ServerToClient.fromBuffer(msgBuffer);
|
||||||
print("New message: $msg");
|
if (msg.v0.hasResponse()) {
|
||||||
messagesV0[msg.v0.seq] = msg;
|
messagesV0[msg.v0.seq] = msg;
|
||||||
|
} else {
|
||||||
|
print("Got a new message from the server: $msg");
|
||||||
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log.shout("Error parsing the servers message: $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 :/
|
// TODO: There must be a smarter move to do that :/
|
||||||
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
Future<server.ServerToClient?> _waitForResponse(Int64 seq) async {
|
||||||
final startTime = DateTime.now();
|
final startTime = DateTime.now();
|
||||||
|
|
@ -95,7 +266,8 @@ class ApiProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<server.ServerToClient?> _sendRequestV0(ClientToServer request) async {
|
Future<server.ServerToClient?> _sendRequestV0(
|
||||||
|
c.ClientToServer request) async {
|
||||||
var seq = Int64(Random().nextInt(4294967296));
|
var seq = Int64(Random().nextInt(4294967296));
|
||||||
while (messagesV0.containsKey(seq)) {
|
while (messagesV0.containsKey(seq)) {
|
||||||
seq = Int64(Random().nextInt(4294967296));
|
seq = Int64(Random().nextInt(4294967296));
|
||||||
|
|
@ -115,18 +287,6 @@ class ApiProvider {
|
||||||
return await _waitForResponse(seq);
|
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) {
|
static String getLocalizedString(BuildContext context, ErrorCode code) {
|
||||||
switch (code.toString()) {
|
switch (code.toString()) {
|
||||||
case "Unknown":
|
case "Unknown":
|
||||||
|
|
@ -159,41 +319,4 @@ class ApiProvider {
|
||||||
return code.toString(); // Fallback for unrecognized keys
|
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<Result> 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,11 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
import 'package:twonly/main.dart';
|
import 'package:twonly/main.dart';
|
||||||
import 'package:twonly/src/signal/signal_helper.dart';
|
import 'package:twonly/src/signal/signal_helper.dart';
|
||||||
import 'package:twonly/src/providers/api_provider.dart';
|
import 'package:twonly/src/providers/api_provider.dart';
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
import 'model/user_data_json.dart';
|
import 'model/user_data_json.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
import 'package:logging/logging.dart';
|
import 'package:logging/logging.dart';
|
||||||
|
|
||||||
// Just a helper function to get the secure storage
|
// Just a helper function to get the secure storage
|
||||||
|
|
@ -44,13 +46,18 @@ Future<bool> deleteLocalUserData() async {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Result> createNewUser(String username, String inviteCode) async {
|
Future<Result> createNewUser(
|
||||||
|
BuildContext context, String username, String inviteCode) async {
|
||||||
final storage = getSecureStorage();
|
final storage = getSecureStorage();
|
||||||
|
if (!context.mounted) {
|
||||||
|
return Result.error("not mounted");
|
||||||
|
}
|
||||||
|
|
||||||
await SignalHelper.createIfNotExistsSignalIdentity();
|
await SignalHelper.createIfNotExistsSignalIdentity();
|
||||||
|
|
||||||
// TODO: API call to server to check username and inviteCode
|
// 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<ApiProvider>().register(username, inviteCode);
|
||||||
|
|
||||||
if (res.isSuccess) {
|
if (res.isSuccess) {
|
||||||
print("Got user_id ${res.value}");
|
print("Got user_id ${res.value}");
|
||||||
|
|
|
||||||
|
|
@ -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<CameraEditorView> createState() => _CameraEditorViewState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _CameraEditorViewState extends State<CameraEditorView> {
|
|
||||||
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<bool>(
|
|
||||||
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<ReactiveCustomWidget> _buildMainBodyWidgets(
|
|
||||||
ProImageEditorState editor,
|
|
||||||
Stream<dynamic> rebuildStream,
|
|
||||||
) {
|
|
||||||
return [
|
|
||||||
if (editor.selectedLayerIndex < 0)
|
|
||||||
ReactiveCustomWidget(
|
|
||||||
stream: rebuildStream,
|
|
||||||
builder: (_) => FrostedGlassActionBar(
|
|
||||||
editor: editor,
|
|
||||||
openStickerEditor: () => _openStickerEditor(editor),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ReactiveCustomWidget> _buildPaintEditorBody(
|
|
||||||
PaintingEditorState paintEditor,
|
|
||||||
Stream<dynamic> 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<ReactiveCustomWidget> _buildTuneEditorBody(
|
|
||||||
TuneEditorState tuneEditor,
|
|
||||||
Stream<dynamic> rebuildStream,
|
|
||||||
) {
|
|
||||||
return [
|
|
||||||
/// Appbar
|
|
||||||
ReactiveCustomWidget(
|
|
||||||
stream: rebuildStream,
|
|
||||||
builder: (_) {
|
|
||||||
return FrostedGlassTuneAppbar(tuneEditor: tuneEditor);
|
|
||||||
},
|
|
||||||
),
|
|
||||||
|
|
||||||
/// Bottombar
|
|
||||||
ReactiveCustomWidget(
|
|
||||||
stream: rebuildStream,
|
|
||||||
builder: (_) => FrostedGlassTuneBottombar(tuneEditor: tuneEditor),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
List<ReactiveCustomWidget> _buildTextEditorBody(
|
|
||||||
TextEditorState textEditor,
|
|
||||||
Stream<dynamic> 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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import 'package:camera/camera.dart';
|
import 'package:camera/camera.dart';
|
||||||
import 'camera_editor_view.dart';
|
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
// import 'package:camerawesome/camerawesome_plugin.dart';
|
// import 'package:camerawesome/camerawesome_plugin.dart';
|
||||||
|
|
@ -71,13 +70,13 @@ class CameraPreviewViewState extends State<CameraPreviewView> {
|
||||||
final image = await _controller.takePicture();
|
final image = await _controller.takePicture();
|
||||||
|
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
await Navigator.of(context).push(
|
// await Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
// MaterialPageRoute(
|
||||||
builder: (context) => CameraEditorView(
|
// builder: (context) => CameraEditorView(
|
||||||
imagePath: image.path,
|
// imagePath: image.path,
|
||||||
),
|
// ),
|
||||||
),
|
// ),
|
||||||
);
|
// );
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If an error occurs, log the error to the console.
|
// If an error occurs, log the error to the console.
|
||||||
print(e);
|
print(e);
|
||||||
|
|
|
||||||
|
|
@ -120,7 +120,7 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTryingToRegister = true;
|
_isTryingToRegister = true;
|
||||||
});
|
});
|
||||||
final res = await createNewUser(
|
final res = await createNewUser(context,
|
||||||
usernameController.text, inviteCodeController.text);
|
usernameController.text, inviteCodeController.text);
|
||||||
setState(() {
|
setState(() {
|
||||||
_isTryingToRegister = false;
|
_isTryingToRegister = false;
|
||||||
|
|
@ -130,7 +130,7 @@ class _RegisterViewState extends State<RegisterView> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final errMsg =
|
final errMsg =
|
||||||
ApiProvider.getLocalizedString(context, res.error);
|
Backend.getLocalizedString(context, res.error);
|
||||||
showAlertDialog(context, "Oh no!", errMsg);
|
showAlertDialog(context, "Oh no!", errMsg);
|
||||||
},
|
},
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
|
|
|
||||||
32
pubspec.lock
32
pubspec.lock
|
|
@ -306,10 +306,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: emoji_picker_flutter
|
name: emoji_picker_flutter
|
||||||
sha256: "08567e6f914d36c32091a96cf2f51d2558c47aa2bd47a590dc4f50e42e0965f6"
|
sha256: "63dee6be976c51c8b971eccbc73fc637f021b6b679eed1b2ec3b503947304734"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "4.2.0"
|
||||||
fake_async:
|
fake_async:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -642,6 +642,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.3"
|
version: "6.0.3"
|
||||||
|
nested:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: nested
|
||||||
|
sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.0.0"
|
||||||
optional:
|
optional:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -814,10 +822,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: pro_image_editor
|
name: pro_image_editor
|
||||||
sha256: "27190b0333af71e9949f366ac303496511ef6d67607f6f9797c9f136371a321f"
|
sha256: "918f156f28a72b9185d950f865032f6aa83da485d6d45b22fb63d78b63bf7e21"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.2.3"
|
version: "7.6.4"
|
||||||
protobuf:
|
protobuf:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -826,6 +834,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.0"
|
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:
|
pub_semver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1047,6 +1063,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
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:
|
vector_math:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,8 @@ dependencies:
|
||||||
path: ^1.9.0
|
path: ^1.9.0
|
||||||
path_provider: ^2.1.5
|
path_provider: ^2.1.5
|
||||||
permission_handler: ^11.3.1
|
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
|
restart_app: ^1.3.2
|
||||||
sqflite_sqlcipher: ^3.1.0+1
|
sqflite_sqlcipher: ^3.1.0+1
|
||||||
web_socket_channel: ^3.0.1
|
web_socket_channel: ^3.0.1
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue