[Flutter] Force a username before subscribing

This commit is contained in:
2025-12-18 15:27:27 +01:00
parent e98882a0c6
commit e15d70dd0e
7 changed files with 103 additions and 6 deletions

View File

@@ -205,7 +205,7 @@ class APIClient {
fn: User.fromJson, fn: User.fromJson,
authToken: auth.getToken(), authToken: auth.getToken(),
query: { query: {
'confirm': ['true'] 'confirm': ['true'],
}, },
); );
} }
@@ -230,7 +230,7 @@ class APIClient {
static Future<Client> updateClient(TokenSource auth, String clientID, {String? fcmToken, String? agentModel, String? name, String? agentVersion}) async { static Future<Client> updateClient(TokenSource auth, String clientID, {String? fcmToken, String? agentModel, String? name, String? agentVersion}) async {
return await _request( return await _request(
name: 'updateClient', name: 'updateClient',
method: 'PUT', method: 'PATCH',
relURL: 'users/${auth.getUserID()}/clients/$clientID', relURL: 'users/${auth.getUserID()}/clients/$clientID',
jsonBody: { jsonBody: {
if (fcmToken != null) 'fcm_token': fcmToken, if (fcmToken != null) 'fcm_token': fcmToken,
@@ -259,7 +259,7 @@ class APIClient {
method: 'GET', method: 'GET',
relURL: 'users/${auth.getUserID()}/channels', relURL: 'users/${auth.getUserID()}/channels',
query: { query: {
'selector': [sel.apiKey] 'selector': [sel.apiKey],
}, },
fn: (json) => ChannelWithSubscription.fromJsonArray(json['channels'] as List<dynamic>), fn: (json) => ChannelWithSubscription.fromJsonArray(json['channels'] as List<dynamic>),
authToken: auth.getToken(), authToken: auth.getToken(),

View File

@@ -12,6 +12,7 @@ import 'package:simplecloudnotifier/state/app_auth.dart';
import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/utils/navi.dart'; import 'package:simplecloudnotifier/utils/navi.dart';
import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/toaster.dart';
import 'package:simplecloudnotifier/utils/dialogs.dart';
import 'package:simplecloudnotifier/utils/ui.dart'; import 'package:simplecloudnotifier/utils/ui.dart';
class ChannelScannerResultChannelSubscribe extends StatefulWidget { class ChannelScannerResultChannelSubscribe extends StatefulWidget {
@@ -172,6 +173,38 @@ class _ChannelScannerResultChannelSubscribeState extends State<ChannelScannerRes
void _onSubscribe() async { void _onSubscribe() async {
final auth = Provider.of<AppAuth>(context, listen: false); final auth = Provider.of<AppAuth>(context, listen: false);
// Check if username is set
try {
final user = await auth.loadUser();
if (user.username == null || user.username!.isEmpty) {
// Show modal to set username
var newusername = await UIDialogs.showUsernameRequiredDialog(context);
if (newusername == null) return; // User cancelled
newusername = newusername.trim();
if (newusername.isEmpty) {
Toaster.error("Error", 'Username cannot be empty');
return;
}
// Update username via API
try {
await APIClient.updateUser(auth, auth.userID!, username: newusername);
await auth.loadUser(force: true); // Refresh cached user
Toaster.success("Success", 'Username set');
} catch (e) {
Toaster.error("Error", 'Failed to set username: ${e.toString()}');
return;
}
}
} catch (e) {
Toaster.error("Error", 'Failed to load user data: ${e.toString()}');
return;
}
// Proceed with subscription
try { try {
var sub = await APIClient.subscribeToChannelbyID(auth, widget.value.channelID, subscribeKey: widget.value.subscribeKey); var sub = await APIClient.subscribeToChannelbyID(auth, widget.value.channelID, subscribeKey: widget.value.subscribeKey);
if (sub.confirmed) { if (sub.confirmed) {

View File

@@ -5,6 +5,7 @@ import 'package:simplecloudnotifier/api/api_client.dart';
import 'package:simplecloudnotifier/state/app_settings.dart'; import 'package:simplecloudnotifier/state/app_settings.dart';
import 'package:simplecloudnotifier/state/app_auth.dart'; import 'package:simplecloudnotifier/state/app_auth.dart';
import 'package:simplecloudnotifier/state/application_log.dart'; import 'package:simplecloudnotifier/state/application_log.dart';
import 'package:simplecloudnotifier/state/globals.dart';
import 'package:simplecloudnotifier/utils/notifier.dart'; import 'package:simplecloudnotifier/utils/notifier.dart';
import 'package:simplecloudnotifier/utils/toaster.dart'; import 'package:simplecloudnotifier/utils/toaster.dart';
import 'package:simplecloudnotifier/utils/ui.dart'; import 'package:simplecloudnotifier/utils/ui.dart';
@@ -65,22 +66,31 @@ class _DebugActionsPageState extends State<DebugActionsPage> {
onPressed: _sendTokenToServer, onPressed: _sendTokenToServer,
text: 'Send FCM Token to Server', text: 'Send FCM Token to Server',
), ),
SizedBox(height: 4),
UI.button(
big: false,
onPressed: _updateClient,
text: 'Update Client on Server',
),
SizedBox(height: 20), SizedBox(height: 20),
UI.button( UI.button(
big: false, big: false,
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, null), onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, null),
text: 'Show local notification (generic)', text: 'Show local notification (generic)',
), ),
SizedBox(height: 4),
UI.button( UI.button(
big: false, big: false,
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 0), onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 0),
text: 'Show local notification (Prio = 0)', text: 'Show local notification (Prio = 0)',
), ),
SizedBox(height: 4),
UI.button( UI.button(
big: false, big: false,
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 1), onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 1),
text: 'Show local notification (Prio = 1)', text: 'Show local notification (Prio = 1)',
), ),
SizedBox(height: 4),
UI.button( UI.button(
big: false, big: false,
onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 2), onPressed: () => Notifier.showLocalNotification('', 'TEST_CHANNEL', "Test Channel", "Channel for testing", "Hello World", "Local Notification test", null, 2),
@@ -128,6 +138,26 @@ class _DebugActionsPageState extends State<DebugActionsPage> {
} }
} }
void _updateClient() async {
try {
final auth = AppAuth();
final clientID = auth.getClientID();
if (clientID == null) {
Toaster.error("Error", "No Client set");
return;
}
final newClient = await APIClient.updateClient(auth, clientID, agentModel: Globals().deviceModel, name: Globals().nameForClient(), agentVersion: Globals().version);
auth.setClientAndClientID(newClient);
Toaster.success("Success", "Client updated");
} catch (exc, trace) {
Toaster.error("Error", "An error occurred while updating the client: ${exc.toString()}");
ApplicationLog.error("An error occurred while updating the client: ${exc.toString()}", trace: trace);
}
}
void _copyToken() async { void _copyToken() async {
try { try {
final fcmToken = await FirebaseMessaging.instance.getToken(); final fcmToken = await FirebaseMessaging.instance.getToken();

View File

@@ -135,7 +135,7 @@ class _DebugRequestViewPageState extends State<DebugRequestViewPage> {
void _copyCurl() { void _copyCurl() {
final method = '-X ${widget.request.method}'; final method = '-X ${widget.request.method}';
final header = widget.request.requestHeaders.entries.map((v) => '-H "${v.key}: ${v.value}"').join(' '); final header = widget.request.requestHeaders.entries.map((v) => '-H "${v.key}: ${v.value}"').join(' ');
final body = widget.request.requestBody.isNotEmpty ? '-d "${widget.request.requestBody}"' : ''; final body = widget.request.requestBody.isNotEmpty ? '-d \'${widget.request.requestBody}\'' : '';
final curlParts = ['curl', method, header, '"${widget.request.url}"', body]; final curlParts = ['curl', method, header, '"${widget.request.url}"', body];

View File

@@ -28,6 +28,40 @@ class UIDialogs {
); );
} }
static Future<String?> showUsernameRequiredDialog(BuildContext context) {
var _textFieldController = TextEditingController();
return showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Username Required'),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Please set a public username to subscribe to channels from other users.'),
SizedBox(height: 16),
TextField(
autofocus: true,
controller: _textFieldController,
decoration: InputDecoration(hintText: 'Enter username'),
),
],
),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('Cancel'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(_textFieldController.text),
child: Text('OK'),
),
],
),
);
}
static Future<bool> showConfirmDialog(BuildContext context, String title, {String? text, String? okText, String? cancelText}) { static Future<bool> showConfirmDialog(BuildContext context, String title, {String? text, String? okText, String? cancelText}) {
return showDialog<bool>( return showDialog<bool>(
context: context, context: context,

View File

@@ -2,7 +2,7 @@ name: simplecloudnotifier
description: "Receive push messages" description: "Receive push messages"
publish_to: 'none' publish_to: 'none'
version: 2.1.1+509 version: 2.1.1+541
environment: environment:
sdk: '>=3.9.0 <4.0.0' sdk: '>=3.9.0 <4.0.0'

View File

@@ -17,8 +17,8 @@ simple_cloud_notifier-*.sql
identifier.sqlite identifier.sqlite
.idea/dataSources.xml .idea/dataSources.xml
.idea/copilot* .idea/copilot*
.idea/go.imports.xml
.swaggobin .swaggobin