forked from Henry-Hiles/nexus
working login page
This commit is contained in:
parent
cd0d10555b
commit
47a758a33b
12 changed files with 133 additions and 245 deletions
|
|
@ -1,61 +1,71 @@
|
|||
import "dart:ffi";
|
||||
import "dart:isolate";
|
||||
import "package:ffi/ffi.dart";
|
||||
import "package:flutter/foundation.dart";
|
||||
import "package:nexus/controllers/sync_status_controller.dart";
|
||||
import "package:nexus/helpers/extensions/gomuks_buffer.dart";
|
||||
import "package:nexus/models/client_state.dart";
|
||||
import "package:nexus/models/login.dart";
|
||||
import "package:nexus/models/sync_status.dart";
|
||||
import "package:nexus/src/third_party/gomuks.g.dart";
|
||||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
|
||||
void gomuksCallback(
|
||||
Pointer<Char> command,
|
||||
int requestId,
|
||||
GomuksBorrowedBuffer data,
|
||||
) {
|
||||
try {
|
||||
final muksEventType = command.cast<Utf8>().toDartString();
|
||||
final Map<String, dynamic> decodedMuksEvent = data.toJson();
|
||||
|
||||
switch (muksEventType) {
|
||||
case "client_state":
|
||||
final clientState = ClientState.fromJson(decodedMuksEvent);
|
||||
debugPrint("Received event: $clientState");
|
||||
break;
|
||||
case "sync_status":
|
||||
final syncStatus = SyncStatus.fromJson(decodedMuksEvent);
|
||||
debugPrint("Received event: $syncStatus");
|
||||
break;
|
||||
default:
|
||||
debugPrint("Unhandled event: $muksEventType: $decodedMuksEvent");
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
debugPrintStack(stackTrace: stackTrace, label: error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
class ClientController extends Notifier<int> {
|
||||
class ClientController extends AsyncNotifier<int> {
|
||||
@override
|
||||
int build() {
|
||||
final handle = GomuksInit();
|
||||
Future<int> build() async {
|
||||
final handle = await Isolate.run(GomuksInit);
|
||||
ref.onDispose(() => GomuksDestroy(handle));
|
||||
|
||||
GomuksStart(
|
||||
handle,
|
||||
NativeCallable<
|
||||
Void Function(Pointer<Char>, Int64, GomuksBorrowedBuffer)
|
||||
>.listener(gomuksCallback)
|
||||
>.listener((
|
||||
Pointer<Char> command,
|
||||
int requestId,
|
||||
GomuksBorrowedBuffer data,
|
||||
) {
|
||||
try {
|
||||
final muksEventType = command.cast<Utf8>().toDartString();
|
||||
final Map<String, dynamic> decodedMuksEvent = data.toJson();
|
||||
|
||||
switch (muksEventType) {
|
||||
case "client_state":
|
||||
final clientState = ClientState.fromJson(decodedMuksEvent);
|
||||
debugPrint("Received event: $clientState");
|
||||
break;
|
||||
case "sync_status":
|
||||
ref
|
||||
.watch(SyncStatusController.provider.notifier)
|
||||
.set(SyncStatus.fromJson(decodedMuksEvent));
|
||||
break;
|
||||
default:
|
||||
debugPrint(
|
||||
"Unhandled event: $muksEventType: $decodedMuksEvent",
|
||||
);
|
||||
}
|
||||
} catch (error, stackTrace) {
|
||||
debugPrintStack(stackTrace: stackTrace, label: error.toString());
|
||||
}
|
||||
})
|
||||
.nativeFunction,
|
||||
);
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
Map<String, dynamic> sendCommand(String command, Map<String, dynamic> data) {
|
||||
Future<Map<String, dynamic>> sendCommand(
|
||||
String command,
|
||||
Map<String, dynamic> data,
|
||||
) async {
|
||||
final bufferPointer = data.toGomuksBufferPtr();
|
||||
final response = GomuksSubmitCommand(
|
||||
state,
|
||||
command.toNativeUtf8().cast<Char>(),
|
||||
bufferPointer.ref,
|
||||
final handle = await future;
|
||||
final response = await Isolate.run(
|
||||
() => GomuksSubmitCommand(
|
||||
handle,
|
||||
command.toNativeUtf8().cast<Char>(),
|
||||
bufferPointer.ref,
|
||||
),
|
||||
);
|
||||
|
||||
calloc.free(bufferPointer);
|
||||
|
|
@ -63,7 +73,27 @@ class ClientController extends Notifier<int> {
|
|||
return response.buf.toJson();
|
||||
}
|
||||
|
||||
static final provider = NotifierProvider<ClientController, int>(
|
||||
Future<bool> login(Login login) async {
|
||||
try {
|
||||
await sendCommand("login", login.toJson());
|
||||
return true;
|
||||
} catch (_) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> discoverHomeserver(Uri homeserver) async {
|
||||
try {
|
||||
final response = await sendCommand("discover_homeserver", {
|
||||
"user_id": "@fakeuser:${homeserver.host}",
|
||||
});
|
||||
return (response["m.homeserver"] as Map<String, dynamic>)["base_url"];
|
||||
} catch (_) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
static final provider = AsyncNotifierProvider<ClientController, int>(
|
||||
ClientController.new,
|
||||
);
|
||||
}
|
||||
|
|
|
|||
13
lib/controllers/sync_status_controller.dart
Normal file
13
lib/controllers/sync_status_controller.dart
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/models/sync_status.dart";
|
||||
|
||||
class SyncStatusController extends Notifier<SyncStatus?> {
|
||||
@override
|
||||
Null build() => null;
|
||||
|
||||
void set(SyncStatus newStatus) => state = newStatus;
|
||||
|
||||
static final provider = NotifierProvider<SyncStatusController, SyncStatus?>(
|
||||
SyncStatusController.new,
|
||||
);
|
||||
}
|
||||
|
|
@ -3,8 +3,11 @@ import "package:flutter/foundation.dart";
|
|||
import "package:flutter_riverpod/flutter_riverpod.dart";
|
||||
import "package:nexus/controllers/client_controller.dart";
|
||||
import "package:nexus/controllers/shared_prefs_controller.dart";
|
||||
import "package:nexus/controllers/sync_status_controller.dart";
|
||||
import "package:nexus/helpers/extensions/better_when.dart";
|
||||
import "package:nexus/helpers/extensions/scheme_to_theme.dart";
|
||||
import "package:nexus/models/sync_status.dart";
|
||||
import "package:nexus/pages/login_page.dart";
|
||||
import "package:nexus/widgets/error_dialog.dart";
|
||||
import "package:window_manager/window_manager.dart";
|
||||
import "package:flutter/material.dart";
|
||||
|
|
@ -96,50 +99,7 @@ class App extends ConsumerWidget {
|
|||
home: Builder(
|
||||
builder: (context) => ref
|
||||
.watch(SharedPrefsController.provider)
|
||||
.betterWhen(
|
||||
data: (_) => ElevatedButton(
|
||||
onPressed: () async {
|
||||
final response = ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.sendCommand("login", {
|
||||
"homeserver_url": "https://matrix.federated.nexus",
|
||||
"username": "quadradical",
|
||||
"password": "Quadfnrad1!",
|
||||
});
|
||||
print(response);
|
||||
},
|
||||
child: Text("foo"),
|
||||
),
|
||||
// .betterWhen(
|
||||
// data: (client) =>
|
||||
// client.accessToken == null ? LoginPage() : ChatPage(),
|
||||
// loading: () => Scaffold(
|
||||
// body: Center(
|
||||
// child: Column(
|
||||
// mainAxisSize: MainAxisSize.min,
|
||||
// spacing: 16,
|
||||
// children: [
|
||||
// Text(
|
||||
// "Syncing...",
|
||||
// style: Theme.of(context).textTheme.headlineMedium,
|
||||
// ),
|
||||
// Loading(),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// appBar: Appbar(
|
||||
// actions: [
|
||||
// IconButton(
|
||||
// onPressed: () => Navigator.of(context).push(
|
||||
// MaterialPageRoute(builder: (_) => SettingsPage()),
|
||||
// ),
|
||||
// icon: Icon(Icons.settings),
|
||||
// ),
|
||||
// ],
|
||||
// ),
|
||||
// ),
|
||||
// ),
|
||||
),
|
||||
.betterWhen(data: (_) => LoginPage()),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
12
lib/models/homeserver.dart
Normal file
12
lib/models/homeserver.dart
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
part "homeserver.freezed.dart";
|
||||
|
||||
@freezed
|
||||
abstract class Homeserver with _$Homeserver {
|
||||
const factory Homeserver({
|
||||
required String name,
|
||||
required String description,
|
||||
required Uri url,
|
||||
required String iconUrl,
|
||||
}) = _Homeserver;
|
||||
}
|
||||
14
lib/models/login.dart
Normal file
14
lib/models/login.dart
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import "package:freezed_annotation/freezed_annotation.dart";
|
||||
part "login.freezed.dart";
|
||||
part "login.g.dart";
|
||||
|
||||
@freezed
|
||||
abstract class Login with _$Login {
|
||||
const factory Login({
|
||||
required String username,
|
||||
required String password,
|
||||
required String homeserverUrl,
|
||||
}) = _Login;
|
||||
|
||||
factory Login.fromJson(Map<String, Object?> json) => _$LoginFromJson(json);
|
||||
}
|
||||
|
|
@ -5,7 +5,7 @@ part "sync_status.g.dart";
|
|||
@freezed
|
||||
abstract class SyncStatus with _$SyncStatus {
|
||||
const factory SyncStatus({
|
||||
required Type type,
|
||||
required SyncStatusType type,
|
||||
required int errorCount,
|
||||
required int lastSync,
|
||||
}) = _SyncStatus;
|
||||
|
|
@ -15,4 +15,4 @@ abstract class SyncStatus with _$SyncStatus {
|
|||
}
|
||||
|
||||
@JsonEnum(fieldRename: FieldRename.snake)
|
||||
enum Type { ok, waiting, erroring, permanentlyFailed }
|
||||
enum SyncStatusType { ok, waiting, erroring, permanentlyFailed }
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import "package:hooks_riverpod/hooks_riverpod.dart";
|
|||
import "package:nexus/controllers/client_controller.dart";
|
||||
import "package:nexus/helpers/launch_helper.dart";
|
||||
import "package:nexus/models/homeserver.dart";
|
||||
import "package:nexus/models/login.dart";
|
||||
import "package:nexus/widgets/appbar.dart";
|
||||
import "package:nexus/widgets/divider_text.dart";
|
||||
import "package:nexus/widgets/loading.dart";
|
||||
|
|
@ -15,27 +16,25 @@ class LoginPage extends HookConsumerWidget {
|
|||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final theme = Theme.of(context);
|
||||
final client = ref.watch(ClientController.provider.notifier);
|
||||
|
||||
final isLoading = useState(false);
|
||||
final allowLogin = useState(false);
|
||||
final homeserver = useState<String?>(null);
|
||||
|
||||
final launch = ref.watch(LaunchHelper.provider).launchUrl;
|
||||
|
||||
Future<void> setHomeserver(Uri? homeserver) async {
|
||||
Future<void> setHomeserver(Uri? newHomeserver) async {
|
||||
isLoading.value = true;
|
||||
final succeeded = homeserver == null
|
||||
? false
|
||||
: await ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.setHomeserver(
|
||||
homeserver.hasScheme
|
||||
? homeserver
|
||||
: Uri.https(homeserver.path),
|
||||
);
|
||||
|
||||
if (succeeded) {
|
||||
allowLogin.value = true;
|
||||
} else if (context.mounted) {
|
||||
homeserver.value = newHomeserver == null
|
||||
? null
|
||||
: await client.discoverHomeserver(
|
||||
newHomeserver.hasScheme
|
||||
? newHomeserver
|
||||
: Uri.https(newHomeserver.path),
|
||||
);
|
||||
|
||||
if (homeserver.value == null && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
|
|
@ -157,7 +156,7 @@ class LoginPage extends HookConsumerWidget {
|
|||
),
|
||||
if (isLoading.value)
|
||||
Padding(padding: EdgeInsets.only(top: 32), child: Loading())
|
||||
else if (allowLogin.value) ...[
|
||||
else if (homeserver.value != null) ...[
|
||||
DividerText("Then, sign in:"),
|
||||
SizedBox(height: 4),
|
||||
TextField(
|
||||
|
|
@ -174,9 +173,13 @@ class LoginPage extends HookConsumerWidget {
|
|||
ElevatedButton(
|
||||
onPressed: () async {
|
||||
isLoading.value = true;
|
||||
final succeeded = await ref
|
||||
.watch(ClientController.provider.notifier)
|
||||
.login(username.text, password.text);
|
||||
final succeeded = await client.login(
|
||||
Login(
|
||||
username: username.text,
|
||||
password: password.text,
|
||||
homeserverUrl: homeserver.value!,
|
||||
),
|
||||
);
|
||||
|
||||
if (!succeeded && context.mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue