From 9fd0c210ae79d56df20af640d3802775016aa684 Mon Sep 17 00:00:00 2001 From: Marcus Kida Date: Sun, 1 Feb 2026 22:42:00 +0100 Subject: [PATCH] Improve Login and Settings --- lib/views/auth_view.dart | 31 ++++++++- lib/views/devices_view.dart | 33 ++++++++-- lib/views/main_view.dart | 4 +- lib/views/more_view.dart | 2 +- linux/flutter/generated_plugin_registrant.cc | 4 ++ linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 64 +++++++++++++++++++ pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 11 files changed, 140 insertions(+), 8 deletions(-) diff --git a/lib/views/auth_view.dart b/lib/views/auth_view.dart index 4c3d44c..d3c728d 100644 --- a/lib/views/auth_view.dart +++ b/lib/views/auth_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; import '../services/authentication_manager.dart'; import '../services/brandmeister_client.dart'; @@ -133,11 +134,39 @@ class _AuthViewState extends State { const SizedBox(height: 24), TextButton.icon( onPressed: () { - // Open BrandMeister website + final url = Uri.parse('https://brandmeister.network/?page=profile-api'); + launchUrl(url, mode: LaunchMode.externalApplication); }, icon: const Icon(Icons.open_in_new), label: const Text('Get API Token from BrandMeister'), ), + const SizedBox(height: 32), + Container( + padding: const EdgeInsets.all(16), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.lock_outline, + size: 20, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + const SizedBox(width: 12), + Expanded( + child: Text( + 'Your API token is stored securely in encrypted storage on your device. It is never shared with anyone and is only used to authenticate with the BrandMeister network.', + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ), + ], + ), + ), ], ), ), diff --git a/lib/views/devices_view.dart b/lib/views/devices_view.dart index 9ea60d3..3cc35d1 100644 --- a/lib/views/devices_view.dart +++ b/lib/views/devices_view.dart @@ -143,6 +143,7 @@ class _DevicesViewState extends State { const Divider(), ..._devices.map((device) => _DeviceRow( device: device, + baseRadioId: _devices.isNotEmpty ? _devices.first.id : null, onTap: () { Navigator.push( context, @@ -159,22 +160,46 @@ class _DevicesViewState extends State { class _DeviceRow extends StatelessWidget { final Device device; + final int? baseRadioId; final VoidCallback onTap; const _DeviceRow({ required this.device, required this.onTap, + this.baseRadioId, }); + String? get _deviceExtension { + if (baseRadioId == null) return null; + final baseStr = baseRadioId.toString(); + final idStr = device.id.toString(); + // Check if device ID starts with the base radio ID + if (idStr.startsWith(baseStr)) { + final extension = idStr.substring(baseStr.length); + return extension.isNotEmpty ? extension : '0'; + } + return null; + } + @override Widget build(BuildContext context) { + final extension = _deviceExtension; + final showExtension = extension != null && extension != '0'; return ListTile( leading: CircleAvatar( backgroundColor: Theme.of(context).colorScheme.primaryContainer, - child: Icon( - Icons.settings_input_antenna, - color: Theme.of(context).colorScheme.onPrimaryContainer, - ), + child: showExtension + ? Text( + extension, + style: TextStyle( + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), + ) + : Icon( + Icons.settings_input_antenna, + color: Theme.of(context).colorScheme.onPrimaryContainer, + ), ), title: Text(device.callsign ?? 'Unknown'), subtitle: Column( diff --git a/lib/views/main_view.dart b/lib/views/main_view.dart index e5ba82c..2a63022 100644 --- a/lib/views/main_view.dart +++ b/lib/views/main_view.dart @@ -37,8 +37,8 @@ class _MainViewState extends State { label: 'Last Activity', ), BottomNavigationBarItem( - icon: Icon(Icons.more_horiz), - label: 'More', + icon: Icon(Icons.settings), + label: 'Settings', ), ], ), diff --git a/lib/views/more_view.dart b/lib/views/more_view.dart index 828c8e2..e0e2c61 100644 --- a/lib/views/more_view.dart +++ b/lib/views/more_view.dart @@ -11,7 +11,7 @@ class MoreView extends StatelessWidget { return Scaffold( appBar: AppBar( - title: const Text('More'), + title: const Text('Settings'), ), body: ListView( children: [ diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d0e7f79..38dd0bc 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b29e9ba..65240e9 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index e84ed5a..e58cc93 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,9 @@ import FlutterMacOS import Foundation import flutter_secure_storage_macos +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index adde4ca..0f070c0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -509,6 +509,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: f6a7e5c4835bb4e3026a04793a4199ca2d14c739ec378fdfe23fc8075d0439f8 + url: "https://pub.dev" + source: hosted + version: "6.3.2" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "767344bf3063897b5cf0db830e94f904528e6dd50a6dfaf839f0abf509009611" + url: "https://pub.dev" + source: hosted + version: "6.3.28" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: cfde38aa257dae62ffe79c87fab20165dfdf6988c1d31b58ebf59b9106062aad + url: "https://pub.dev" + source: hosted + version: "6.3.6" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" + url: "https://pub.dev" + source: hosted + version: "3.2.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: d0412fcf4c6b31ecfdb7762359b7206ffba3bbffd396c6d9f9c4616ece476c1f + url: "https://pub.dev" + source: hosted + version: "2.4.2" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" + url: "https://pub.dev" + source: hosted + version: "3.1.5" vector_math: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 7e92338..c94ab05 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,9 @@ dependencies: # WebSocket client for real-time updates web_socket_channel: ^3.0.1 + # URL launcher for opening external links + url_launcher: ^6.2.0 + dev_dependencies: flutter_test: sdk: flutter diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0c50753..2048c45 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4fc759c..de626cc 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST