Initial commit
This commit is contained in:
@@ -0,0 +1,56 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
part 'appconfig.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class AppConfig {
|
||||
List<Device> devices = [];
|
||||
|
||||
AppConfig._constructor();
|
||||
static AppConfig _instance = AppConfig._constructor();
|
||||
|
||||
factory AppConfig({Map<String, dynamic>? json}) {
|
||||
if (json != null) {
|
||||
_instance = _$AppConfigFromJson(json);
|
||||
}
|
||||
|
||||
return _instance;
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => _$AppConfigToJson(this);
|
||||
|
||||
/// Saves configuration to file.
|
||||
///
|
||||
/// WARN: This method assumes the config file already exists.
|
||||
Future<void> save() async {
|
||||
final appDocsDir = await getApplicationDocumentsDirectory();
|
||||
var configFile = File(p.join(appDocsDir.path, "app.json"));
|
||||
await configFile.writeAsString(jsonEncode(toJson()));
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class Device {
|
||||
String name;
|
||||
String routerSsid;
|
||||
String routerBssid;
|
||||
String networkPassword;
|
||||
String? ip;
|
||||
String? bssid;
|
||||
|
||||
Device({
|
||||
required this.name,
|
||||
required this.routerSsid,
|
||||
required this.routerBssid,
|
||||
this.networkPassword = "",
|
||||
});
|
||||
|
||||
factory Device.fromJson(Map<String, dynamic> json) => _$DeviceFromJson(json);
|
||||
|
||||
Map<String, dynamic> toJson() => _$DeviceToJson(this);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'appconfig.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
AppConfig _$AppConfigFromJson(Map<String, dynamic> json) => AppConfig()
|
||||
..devices = (json['devices'] as List<dynamic>)
|
||||
.map((e) => Device.fromJson(e as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
Map<String, dynamic> _$AppConfigToJson(AppConfig instance) => <String, dynamic>{
|
||||
'devices': instance.devices,
|
||||
};
|
||||
|
||||
Device _$DeviceFromJson(Map<String, dynamic> json) =>
|
||||
Device(
|
||||
name: json['name'] as String,
|
||||
routerSsid: json['routerSsid'] as String,
|
||||
routerBssid: json['routerBssid'] as String,
|
||||
networkPassword: json['networkPassword'] as String? ?? "",
|
||||
)
|
||||
..ip = json['ip'] as String?
|
||||
..bssid = json['bssid'] as String?;
|
||||
|
||||
Map<String, dynamic> _$DeviceToJson(Device instance) => <String, dynamic>{
|
||||
'name': instance.name,
|
||||
'routerSsid': instance.routerSsid,
|
||||
'routerBssid': instance.routerBssid,
|
||||
'networkPassword': instance.networkPassword,
|
||||
'ip': instance.ip,
|
||||
'bssid': instance.bssid,
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
import 'package:esptouch_flutter/esptouch_flutter.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liteauthconfig/l10n/app_localizations.dart';
|
||||
|
||||
import '../appconfig.dart';
|
||||
|
||||
class ESPTouchDialog extends StatefulWidget {
|
||||
final String name;
|
||||
final String ssid;
|
||||
final String bssid;
|
||||
final String password;
|
||||
final int? entryIndex;
|
||||
final bool addNewEntry;
|
||||
|
||||
const ESPTouchDialog({
|
||||
super.key,
|
||||
required this.name,
|
||||
required this.ssid,
|
||||
required this.bssid,
|
||||
required this.password,
|
||||
this.addNewEntry = false,
|
||||
this.entryIndex
|
||||
});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ESPTouchDialog();
|
||||
}
|
||||
|
||||
class _ESPTouchDialog extends State<ESPTouchDialog> {
|
||||
@override
|
||||
void initState() {
|
||||
execute();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
void execute() {
|
||||
final task = ESPTouchTask(
|
||||
ssid: widget.ssid,
|
||||
bssid: widget.bssid,
|
||||
password: widget.password,
|
||||
packet: ESPTouchPacket.multicast
|
||||
);
|
||||
|
||||
final touchStream = task.execute();
|
||||
final sub = touchStream.listen((data) async {
|
||||
final dev = Device(
|
||||
name: widget.name,
|
||||
routerSsid: widget.ssid,
|
||||
routerBssid: widget.bssid,
|
||||
networkPassword: widget.password,
|
||||
);
|
||||
|
||||
dev.ip = data.ip;
|
||||
dev.bssid = data.bssid;
|
||||
|
||||
var conf = AppConfig();
|
||||
|
||||
if (widget.addNewEntry) {
|
||||
conf.devices.add(dev);
|
||||
} else {
|
||||
conf.devices[widget.entryIndex!] = dev;
|
||||
}
|
||||
await conf.save();
|
||||
|
||||
if (context.mounted) {
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
Future.delayed(Duration(minutes: 1), () {
|
||||
sub.cancel();
|
||||
|
||||
if (context.mounted) {
|
||||
// ignore: use_build_context_synchronously
|
||||
Navigator.pop(context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appLocal = AppLocalizations.of(context)!;
|
||||
|
||||
return AlertDialog(
|
||||
title: Text(appLocal.networkInitHeadline),
|
||||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Text(appLocal.networkInitDetails),
|
||||
LinearProgressIndicator(),
|
||||
],
|
||||
),
|
||||
actions: [],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
{
|
||||
"deviceList": "Device List",
|
||||
"noDevices": "It seems that you have no devices! You can register one with the big plus button.",
|
||||
"setupNewDevice": "Setup new device",
|
||||
"addExistingDevice": "Add existing device",
|
||||
|
||||
"registerDevice": "Register Device",
|
||||
"deviceName": "Name",
|
||||
"networkName": "Network Name (SSID)",
|
||||
"routerMacAddress": "Router MAC Address (BSSID)",
|
||||
"networkPassword": "Network Password",
|
||||
"networkLimitation": "Only 2.4 GHz networks allowed!",
|
||||
"networkLimitationDesc": "We suspect that your network may be 5 GHz based on the name. Please note that liteauth can only connect to 2.4 GHz networks.",
|
||||
"networkInitHeadline": "Network Initialization",
|
||||
"networkInitDetails": "Broadcasting network information to your liteauth device, This will be done in a minute or two. Please also make sure your device is powered on.",
|
||||
|
||||
"networkInfoHeadline": "Network Information",
|
||||
"networkInfoPrompt": "Do you wish to automatically fill in the current network information? The app will ask to access your fine location if you continue.",
|
||||
"continueText": "Continue",
|
||||
"cancel": "Cancel",
|
||||
|
||||
"deviceInfo": "Device Information",
|
||||
"delete": "Delete",
|
||||
"deletion": "Deletion",
|
||||
"deviceDeleteConfirm": "Are you sure you want to delete this device forever?",
|
||||
"reconfigureNetwork": "Reconfigure Network",
|
||||
"status": "Status: ",
|
||||
"online": "Online",
|
||||
"offline": "Offline",
|
||||
"refresh": "Refresh",
|
||||
"lastKnownIP": "Last Known IP: {ip}",
|
||||
"@lastKnownIP": {
|
||||
"placeholders": {
|
||||
"ip": {
|
||||
"type": "String",
|
||||
"example": "192.168.1.10"
|
||||
}
|
||||
}
|
||||
},
|
||||
"routerSsid": "Router SSID: {ssid}",
|
||||
"@routerSsid": {
|
||||
"placeholders": {
|
||||
"ssid": {
|
||||
"type": "String",
|
||||
"example": "ABC_WiFi"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,302 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations_en.dart';
|
||||
import 'app_localizations_th.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// Callers can lookup localized strings with an instance of AppLocalizations
|
||||
/// returned by `AppLocalizations.of(context)`.
|
||||
///
|
||||
/// Applications need to include `AppLocalizations.delegate()` in their app's
|
||||
/// `localizationDelegates` list, and the locales they support in the app's
|
||||
/// `supportedLocales` list. For example:
|
||||
///
|
||||
/// ```dart
|
||||
/// import 'l10n/app_localizations.dart';
|
||||
///
|
||||
/// return MaterialApp(
|
||||
/// localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
/// supportedLocales: AppLocalizations.supportedLocales,
|
||||
/// home: MyApplicationHome(),
|
||||
/// );
|
||||
/// ```
|
||||
///
|
||||
/// ## Update pubspec.yaml
|
||||
///
|
||||
/// Please make sure to update your pubspec.yaml to include the following
|
||||
/// packages:
|
||||
///
|
||||
/// ```yaml
|
||||
/// dependencies:
|
||||
/// # Internationalization support.
|
||||
/// flutter_localizations:
|
||||
/// sdk: flutter
|
||||
/// intl: any # Use the pinned version from flutter_localizations
|
||||
///
|
||||
/// # Rest of dependencies
|
||||
/// ```
|
||||
///
|
||||
/// ## iOS Applications
|
||||
///
|
||||
/// iOS applications define key application metadata, including supported
|
||||
/// locales, in an Info.plist file that is built into the application bundle.
|
||||
/// To configure the locales supported by your app, you’ll need to edit this
|
||||
/// file.
|
||||
///
|
||||
/// First, open your project’s ios/Runner.xcworkspace Xcode workspace file.
|
||||
/// Then, in the Project Navigator, open the Info.plist file under the Runner
|
||||
/// project’s Runner folder.
|
||||
///
|
||||
/// Next, select the Information Property List item, select Add Item from the
|
||||
/// Editor menu, then select Localizations from the pop-up menu.
|
||||
///
|
||||
/// Select and expand the newly-created Localizations item then, for each
|
||||
/// locale your application supports, add a new item and select the locale
|
||||
/// you wish to add from the pop-up menu in the Value field. This list should
|
||||
/// be consistent with the languages listed in the AppLocalizations.supportedLocales
|
||||
/// property.
|
||||
abstract class AppLocalizations {
|
||||
AppLocalizations(String locale)
|
||||
: localeName = intl.Intl.canonicalizedLocale(locale.toString());
|
||||
|
||||
final String localeName;
|
||||
|
||||
static AppLocalizations? of(BuildContext context) {
|
||||
return Localizations.of<AppLocalizations>(context, AppLocalizations);
|
||||
}
|
||||
|
||||
static const LocalizationsDelegate<AppLocalizations> delegate =
|
||||
_AppLocalizationsDelegate();
|
||||
|
||||
/// A list of this localizations delegate along with the default localizations
|
||||
/// delegates.
|
||||
///
|
||||
/// Returns a list of localizations delegates containing this delegate along with
|
||||
/// GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate,
|
||||
/// and GlobalWidgetsLocalizations.delegate.
|
||||
///
|
||||
/// Additional delegates can be added by appending to this list in
|
||||
/// MaterialApp. This list does not have to be used at all if a custom list
|
||||
/// of delegates is preferred or required.
|
||||
static const List<LocalizationsDelegate<dynamic>> localizationsDelegates =
|
||||
<LocalizationsDelegate<dynamic>>[
|
||||
delegate,
|
||||
GlobalMaterialLocalizations.delegate,
|
||||
GlobalCupertinoLocalizations.delegate,
|
||||
GlobalWidgetsLocalizations.delegate,
|
||||
];
|
||||
|
||||
/// A list of this localizations delegate's supported locales.
|
||||
static const List<Locale> supportedLocales = <Locale>[
|
||||
Locale('en'),
|
||||
Locale('th'),
|
||||
];
|
||||
|
||||
/// No description provided for @deviceList.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Device List'**
|
||||
String get deviceList;
|
||||
|
||||
/// No description provided for @noDevices.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'It seems that you have no devices! You can register one with the big plus button.'**
|
||||
String get noDevices;
|
||||
|
||||
/// No description provided for @setupNewDevice.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Setup new device'**
|
||||
String get setupNewDevice;
|
||||
|
||||
/// No description provided for @addExistingDevice.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Add existing device'**
|
||||
String get addExistingDevice;
|
||||
|
||||
/// No description provided for @registerDevice.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Register Device'**
|
||||
String get registerDevice;
|
||||
|
||||
/// No description provided for @deviceName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Name'**
|
||||
String get deviceName;
|
||||
|
||||
/// No description provided for @networkName.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Network Name (SSID)'**
|
||||
String get networkName;
|
||||
|
||||
/// No description provided for @routerMacAddress.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Router MAC Address (BSSID)'**
|
||||
String get routerMacAddress;
|
||||
|
||||
/// No description provided for @networkPassword.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Network Password'**
|
||||
String get networkPassword;
|
||||
|
||||
/// No description provided for @networkLimitation.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Only 2.4 GHz networks allowed!'**
|
||||
String get networkLimitation;
|
||||
|
||||
/// No description provided for @networkLimitationDesc.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'We suspect that your network may be 5 GHz based on the name. Please note that liteauth can only connect to 2.4 GHz networks.'**
|
||||
String get networkLimitationDesc;
|
||||
|
||||
/// No description provided for @networkInitHeadline.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Network Initialization'**
|
||||
String get networkInitHeadline;
|
||||
|
||||
/// No description provided for @networkInitDetails.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Broadcasting network information to your liteauth device, This will be done in a minute or two. Please also make sure your device is powered on.'**
|
||||
String get networkInitDetails;
|
||||
|
||||
/// No description provided for @networkInfoHeadline.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Network Information'**
|
||||
String get networkInfoHeadline;
|
||||
|
||||
/// No description provided for @networkInfoPrompt.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Do you wish to automatically fill in the current network information? The app will ask to access your fine location if you continue.'**
|
||||
String get networkInfoPrompt;
|
||||
|
||||
/// No description provided for @continueText.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Continue'**
|
||||
String get continueText;
|
||||
|
||||
/// No description provided for @cancel.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cancel'**
|
||||
String get cancel;
|
||||
|
||||
/// No description provided for @deviceInfo.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Device Information'**
|
||||
String get deviceInfo;
|
||||
|
||||
/// No description provided for @delete.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Delete'**
|
||||
String get delete;
|
||||
|
||||
/// No description provided for @deletion.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Deletion'**
|
||||
String get deletion;
|
||||
|
||||
/// No description provided for @deviceDeleteConfirm.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Are you sure you want to delete this device forever?'**
|
||||
String get deviceDeleteConfirm;
|
||||
|
||||
/// No description provided for @reconfigureNetwork.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Reconfigure Network'**
|
||||
String get reconfigureNetwork;
|
||||
|
||||
/// No description provided for @status.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Status: '**
|
||||
String get status;
|
||||
|
||||
/// No description provided for @online.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Online'**
|
||||
String get online;
|
||||
|
||||
/// No description provided for @offline.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Offline'**
|
||||
String get offline;
|
||||
|
||||
/// No description provided for @refresh.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Refresh'**
|
||||
String get refresh;
|
||||
|
||||
/// No description provided for @lastKnownIP.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Last Known IP: {ip}'**
|
||||
String lastKnownIP(String ip);
|
||||
|
||||
/// No description provided for @routerSsid.
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Router SSID: {ssid}'**
|
||||
String routerSsid(String ssid);
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
extends LocalizationsDelegate<AppLocalizations> {
|
||||
const _AppLocalizationsDelegate();
|
||||
|
||||
@override
|
||||
Future<AppLocalizations> load(Locale locale) {
|
||||
return SynchronousFuture<AppLocalizations>(lookupAppLocalizations(locale));
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupported(Locale locale) =>
|
||||
<String>['en', 'th'].contains(locale.languageCode);
|
||||
|
||||
@override
|
||||
bool shouldReload(_AppLocalizationsDelegate old) => false;
|
||||
}
|
||||
|
||||
AppLocalizations lookupAppLocalizations(Locale locale) {
|
||||
// Lookup logic when only language code is specified.
|
||||
switch (locale.languageCode) {
|
||||
case 'en':
|
||||
return AppLocalizationsEn();
|
||||
case 'th':
|
||||
return AppLocalizationsTh();
|
||||
}
|
||||
|
||||
throw FlutterError(
|
||||
'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely '
|
||||
'an issue with the localizations generation tool. Please file an issue '
|
||||
'on GitHub with a reproducible sample app and the gen-l10n configuration '
|
||||
'that was used.',
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for English (`en`).
|
||||
class AppLocalizationsEn extends AppLocalizations {
|
||||
AppLocalizationsEn([String locale = 'en']) : super(locale);
|
||||
|
||||
@override
|
||||
String get deviceList => 'Device List';
|
||||
|
||||
@override
|
||||
String get noDevices =>
|
||||
'It seems that you have no devices! You can register one with the big plus button.';
|
||||
|
||||
@override
|
||||
String get setupNewDevice => 'Setup new device';
|
||||
|
||||
@override
|
||||
String get addExistingDevice => 'Add existing device';
|
||||
|
||||
@override
|
||||
String get registerDevice => 'Register Device';
|
||||
|
||||
@override
|
||||
String get deviceName => 'Name';
|
||||
|
||||
@override
|
||||
String get networkName => 'Network Name (SSID)';
|
||||
|
||||
@override
|
||||
String get routerMacAddress => 'Router MAC Address (BSSID)';
|
||||
|
||||
@override
|
||||
String get networkPassword => 'Network Password';
|
||||
|
||||
@override
|
||||
String get networkLimitation => 'Only 2.4 GHz networks allowed!';
|
||||
|
||||
@override
|
||||
String get networkLimitationDesc =>
|
||||
'We suspect that your network may be 5 GHz based on the name. Please note that liteauth can only connect to 2.4 GHz networks.';
|
||||
|
||||
@override
|
||||
String get networkInitHeadline => 'Network Initialization';
|
||||
|
||||
@override
|
||||
String get networkInitDetails =>
|
||||
'Broadcasting network information to your liteauth device, This will be done in a minute or two. Please also make sure your device is powered on.';
|
||||
|
||||
@override
|
||||
String get networkInfoHeadline => 'Network Information';
|
||||
|
||||
@override
|
||||
String get networkInfoPrompt =>
|
||||
'Do you wish to automatically fill in the current network information? The app will ask to access your fine location if you continue.';
|
||||
|
||||
@override
|
||||
String get continueText => 'Continue';
|
||||
|
||||
@override
|
||||
String get cancel => 'Cancel';
|
||||
|
||||
@override
|
||||
String get deviceInfo => 'Device Information';
|
||||
|
||||
@override
|
||||
String get delete => 'Delete';
|
||||
|
||||
@override
|
||||
String get deletion => 'Deletion';
|
||||
|
||||
@override
|
||||
String get deviceDeleteConfirm =>
|
||||
'Are you sure you want to delete this device forever?';
|
||||
|
||||
@override
|
||||
String get reconfigureNetwork => 'Reconfigure Network';
|
||||
|
||||
@override
|
||||
String get status => 'Status: ';
|
||||
|
||||
@override
|
||||
String get online => 'Online';
|
||||
|
||||
@override
|
||||
String get offline => 'Offline';
|
||||
|
||||
@override
|
||||
String get refresh => 'Refresh';
|
||||
|
||||
@override
|
||||
String lastKnownIP(String ip) {
|
||||
return 'Last Known IP: $ip';
|
||||
}
|
||||
|
||||
@override
|
||||
String routerSsid(String ssid) {
|
||||
return 'Router SSID: $ssid';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
|
||||
/// The translations for Thai (`th`).
|
||||
class AppLocalizationsTh extends AppLocalizations {
|
||||
AppLocalizationsTh([String locale = 'th']) : super(locale);
|
||||
|
||||
@override
|
||||
String get deviceList => 'รายการอุปกรณ์';
|
||||
|
||||
@override
|
||||
String get noDevices =>
|
||||
'ดูเหมือนว่าคุณจะไม่มีอุปกรณ์ที่ลงทะเบียนไว้นะ! คุณสามารถลงทะเบียนอุปกรณ์ใหม่ได้ที่ปุ่มบวกด้านล่าง';
|
||||
|
||||
@override
|
||||
String get setupNewDevice => 'ตั้งค่าอุปกรณ์ใหม่';
|
||||
|
||||
@override
|
||||
String get addExistingDevice => 'เพิ่มอุปกรณ์ที่ตั้งค่าแล้ว';
|
||||
|
||||
@override
|
||||
String get registerDevice => 'ลงทะเบียนอุปกรณ์';
|
||||
|
||||
@override
|
||||
String get deviceName => 'ชื่อ';
|
||||
|
||||
@override
|
||||
String get networkName => 'ชื่อเครือข่าย (SSID)';
|
||||
|
||||
@override
|
||||
String get routerMacAddress => 'MAC Address ของเราเตอร์ (BSSID)';
|
||||
|
||||
@override
|
||||
String get networkPassword => 'รหัสผ่านเครือข่าย';
|
||||
|
||||
@override
|
||||
String get networkLimitation => 'อนุญาตแค่เครือข่าย 2.4 GHz เท่านั้น!';
|
||||
|
||||
@override
|
||||
String get networkLimitationDesc =>
|
||||
'เราสงสัยว่าเครือข่ายคุณอาจเป็นเครือข่าย 5 GHz จากชื่อ โปรดจำไว้ว่า liteauth สามารถเชื่อมต่อได้กับเครือข่าย 2.4 GHz เท่านั้น';
|
||||
|
||||
@override
|
||||
String get networkInitHeadline => 'การตั้งค่าเครือข่าย';
|
||||
|
||||
@override
|
||||
String get networkInitDetails =>
|
||||
'กำลังเผยแพร่ข้อมูลเครือข่ายไปยังอุปกรณ์ liteauth ของคุณ กระบวนการนี้อาจใช้เวลาหนึ่งถึงสองนาที และโปรดตรวจสอบให้แน่ใจว่าคุณเปิดอุปกรณ์ของคุณแล้ว';
|
||||
|
||||
@override
|
||||
String get networkInfoHeadline => 'ข้อมูลเครือข่าย';
|
||||
|
||||
@override
|
||||
String get networkInfoPrompt =>
|
||||
'คุณต้องการที่จะใส่ข้อมูลเครือข่ายปัจจุบันโดยอัตโนมัติหรือไม่ แอพลิเคชั่นจะขอความอนุญาตในการเข้าถึงข้อมูลตำแหน่งแบบละเอียดหากคุณดำเนินการต่อ';
|
||||
|
||||
@override
|
||||
String get continueText => 'ดำเนินการต่อ';
|
||||
|
||||
@override
|
||||
String get cancel => 'ยกเลิก';
|
||||
|
||||
@override
|
||||
String get deviceInfo => 'ข้อมูลอุปกรณ์';
|
||||
|
||||
@override
|
||||
String get delete => 'ลบ';
|
||||
|
||||
@override
|
||||
String get deletion => 'การลบ';
|
||||
|
||||
@override
|
||||
String get deviceDeleteConfirm => 'คุณแน่ใจหรือไม่ที่จะลบอุปกรณ์นี้อย่างถาวร';
|
||||
|
||||
@override
|
||||
String get reconfigureNetwork => 'ตั้งค่าเครือข่ายใหม่';
|
||||
|
||||
@override
|
||||
String get status => 'สถานะ: ';
|
||||
|
||||
@override
|
||||
String get online => 'ออนไลน์';
|
||||
|
||||
@override
|
||||
String get offline => 'ออฟไลน์';
|
||||
|
||||
@override
|
||||
String get refresh => 'รีเฟรช';
|
||||
|
||||
@override
|
||||
String lastKnownIP(String ip) {
|
||||
return 'ที่อยู่ IP ที่ทราบล่าสุด: $ip';
|
||||
}
|
||||
|
||||
@override
|
||||
String routerSsid(String ssid) {
|
||||
return 'SSID เราเตอร์: $ssid';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
"deviceList": "รายการอุปกรณ์",
|
||||
"noDevices": "ดูเหมือนว่าคุณจะไม่มีอุปกรณ์ที่ลงทะเบียนไว้นะ! คุณสามารถลงทะเบียนอุปกรณ์ใหม่ได้ที่ปุ่มบวกด้านล่าง",
|
||||
"setupNewDevice": "ตั้งค่าอุปกรณ์ใหม่",
|
||||
"addExistingDevice": "เพิ่มอุปกรณ์ที่ตั้งค่าแล้ว",
|
||||
|
||||
"registerDevice": "ลงทะเบียนอุปกรณ์",
|
||||
"deviceName": "ชื่อ",
|
||||
"networkName": "ชื่อเครือข่าย (SSID)",
|
||||
"routerMacAddress": "MAC Address ของเราเตอร์ (BSSID)",
|
||||
"networkPassword": "รหัสผ่านเครือข่าย",
|
||||
"networkLimitation": "อนุญาตแค่เครือข่าย 2.4 GHz เท่านั้น!",
|
||||
"networkLimitationDesc": "เราสงสัยว่าเครือข่ายคุณอาจเป็นเครือข่าย 5 GHz จากชื่อ โปรดจำไว้ว่า liteauth สามารถเชื่อมต่อได้กับเครือข่าย 2.4 GHz เท่านั้น",
|
||||
"networkInitHeadline": "การตั้งค่าเครือข่าย",
|
||||
"networkInitDetails": "กำลังเผยแพร่ข้อมูลเครือข่ายไปยังอุปกรณ์ liteauth ของคุณ กระบวนการนี้อาจใช้เวลาหนึ่งถึงสองนาที และโปรดตรวจสอบให้แน่ใจว่าคุณเปิดอุปกรณ์ของคุณแล้ว",
|
||||
|
||||
"networkInfoHeadline": "ข้อมูลเครือข่าย",
|
||||
"networkInfoPrompt": "คุณต้องการที่จะใส่ข้อมูลเครือข่ายปัจจุบันโดยอัตโนมัติหรือไม่ แอพลิเคชั่นจะขอความอนุญาตในการเข้าถึงข้อมูลตำแหน่งแบบละเอียดหากคุณดำเนินการต่อ",
|
||||
"continueText": "ดำเนินการต่อ",
|
||||
"cancel": "ยกเลิก",
|
||||
|
||||
"deviceInfo": "ข้อมูลอุปกรณ์",
|
||||
"delete": "ลบ",
|
||||
"deletion": "การลบ",
|
||||
"deviceDeleteConfirm": "คุณแน่ใจหรือไม่ที่จะลบอุปกรณ์นี้อย่างถาวร",
|
||||
"reconfigureNetwork": "ตั้งค่าเครือข่ายใหม่",
|
||||
"status": "สถานะ: ",
|
||||
"online": "ออนไลน์",
|
||||
"offline": "ออฟไลน์",
|
||||
"refresh": "รีเฟรช",
|
||||
"lastKnownIP": "ที่อยู่ IP ที่ทราบล่าสุด: {ip}",
|
||||
"routerSsid": "SSID เราเตอร์: {ssid}"
|
||||
}
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liteauthconfig/appconfig.dart';
|
||||
import 'package:liteauthconfig/pages/devicelist.dart';
|
||||
import 'package:liteauthconfig/l10n/app_localizations.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:system_theme/system_theme.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
SystemTheme.fallbackColor = const Color(0xFF4C1D95);
|
||||
await SystemTheme.accentColor.load();
|
||||
|
||||
final appDocsDir = await getApplicationDocumentsDirectory();
|
||||
|
||||
var configFile = File(p.join(appDocsDir.path, "app.json"));
|
||||
|
||||
if (await configFile.exists()) {
|
||||
AppConfig(json: jsonDecode(await configFile.readAsString()));
|
||||
} else {
|
||||
await configFile.create();
|
||||
await configFile.writeAsString(jsonEncode(AppConfig().toJson()));
|
||||
}
|
||||
|
||||
runApp(const MyApp());
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
const MyApp({super.key});
|
||||
|
||||
// This widget is the root of your application.
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var darkColors = ColorScheme.fromSeed(
|
||||
seedColor: SystemTheme.accentColor.accent,
|
||||
);
|
||||
var darkTheme = ThemeData.dark();
|
||||
|
||||
return MaterialApp(
|
||||
title: 'liteauthconfig',
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
locale: Locale("th"),
|
||||
theme: ThemeData.light().copyWith(
|
||||
colorScheme: ColorScheme.fromSeed(
|
||||
seedColor: SystemTheme.accentColor.accent,
|
||||
),
|
||||
),
|
||||
darkTheme: darkTheme.copyWith(
|
||||
colorScheme: darkColors,
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: darkColors.inverseSurface,
|
||||
),
|
||||
cardTheme: CardThemeData(color: darkColors.primary),
|
||||
listTileTheme: ListTileThemeData(
|
||||
textColor: Colors.white,
|
||||
iconColor: Colors.white,
|
||||
),
|
||||
),
|
||||
home: DeviceListPage(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyHomePage extends StatefulWidget {
|
||||
const MyHomePage({super.key, required this.title});
|
||||
|
||||
// This widget is the home page of your application. It is stateful, meaning
|
||||
// that it has a State object (defined below) that contains fields that affect
|
||||
// how it looks.
|
||||
|
||||
// This class is the configuration for the state. It holds the values (in this
|
||||
// case the title) provided by the parent (in this case the App widget) and
|
||||
// used by the build method of the State. Fields in a Widget subclass are
|
||||
// always marked "final".
|
||||
|
||||
final String title;
|
||||
|
||||
@override
|
||||
State<MyHomePage> createState() => _MyHomePageState();
|
||||
}
|
||||
|
||||
class _MyHomePageState extends State<MyHomePage> {
|
||||
int _counter = 0;
|
||||
|
||||
void _incrementCounter() {
|
||||
setState(() {
|
||||
// This call to setState tells the Flutter framework that something has
|
||||
// changed in this State, which causes it to rerun the build method below
|
||||
// so that the display can reflect the updated values. If we changed
|
||||
// _counter without calling setState(), then the build method would not be
|
||||
// called again, and so nothing would appear to happen.
|
||||
_counter++;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
// This method is rerun every time setState is called, for instance as done
|
||||
// by the _incrementCounter method above.
|
||||
//
|
||||
// The Flutter framework has been optimized to make rerunning build methods
|
||||
// fast, so that you can just rebuild anything that needs updating rather
|
||||
// than having to individually change instances of widgets.
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
// TRY THIS: Try changing the color here to a specific color (to
|
||||
// Colors.amber, perhaps?) and trigger a hot reload to see the AppBar
|
||||
// change color while the other colors stay the same.
|
||||
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
|
||||
// Here we take the value from the MyHomePage object that was created by
|
||||
// the App.build method, and use it to set our appbar title.
|
||||
title: Text(widget.title),
|
||||
),
|
||||
body: Center(
|
||||
// Center is a layout widget. It takes a single child and positions it
|
||||
// in the middle of the parent.
|
||||
child: Column(
|
||||
// Column is also a layout widget. It takes a list of children and
|
||||
// arranges them vertically. By default, it sizes itself to fit its
|
||||
// children horizontally, and tries to be as tall as its parent.
|
||||
//
|
||||
// Column has various properties to control how it sizes itself and
|
||||
// how it positions its children. Here we use mainAxisAlignment to
|
||||
// center the children vertically; the main axis here is the vertical
|
||||
// axis because Columns are vertical (the cross axis would be
|
||||
// horizontal).
|
||||
//
|
||||
// TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint"
|
||||
// action in the IDE, or press "p" in the console), to see the
|
||||
// wireframe for each widget.
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
const Text('You have pushed the button this many times:'),
|
||||
Text(
|
||||
'$_counter',
|
||||
style: Theme.of(context).textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: _incrementCounter,
|
||||
tooltip: 'Increment',
|
||||
child: const Icon(Icons.add),
|
||||
), // This trailing comma makes auto-formatting nicer for build methods.
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liteauthconfig/appconfig.dart';
|
||||
import 'package:liteauthconfig/l10n/app_localizations.dart';
|
||||
import 'package:liteauthconfig/pages/reconfigure_network.dart';
|
||||
import 'package:liteauthconfig/utils/network.dart';
|
||||
|
||||
class DeviceInfoPage extends StatefulWidget {
|
||||
final int deviceIndex;
|
||||
|
||||
const DeviceInfoPage({super.key, required this.deviceIndex});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _DeviceInfoPageState();
|
||||
}
|
||||
|
||||
class _DeviceInfoPageState extends State<DeviceInfoPage> {
|
||||
late Device device;
|
||||
String statusMessage = "";
|
||||
bool online = false;
|
||||
bool loading = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
refreshDevice();
|
||||
checkStatus();
|
||||
}
|
||||
|
||||
void refreshDevice() {
|
||||
device = AppConfig().devices[widget.deviceIndex];
|
||||
}
|
||||
|
||||
void checkStatus() async {
|
||||
setState(() {
|
||||
loading = true;
|
||||
});
|
||||
var client = await createEspHttpClient();
|
||||
|
||||
try {
|
||||
var resp = await client.get(Uri.parse("https://liteauth.local"));
|
||||
setState(() {
|
||||
online = resp.statusCode >= 200 && resp.statusCode < 300;
|
||||
});
|
||||
} catch (e) {
|
||||
statusMessage = e.toString();
|
||||
}
|
||||
|
||||
setState(() {
|
||||
loading = false;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appTheme = Theme.of(context);
|
||||
final appLocal = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(device.name),
|
||||
bottom: loading
|
||||
? PreferredSize(
|
||||
preferredSize: const Size.fromHeight(4),
|
||||
child: LinearProgressIndicator(),
|
||||
)
|
||||
: null,
|
||||
actions: [
|
||||
MenuAnchor(
|
||||
builder: (context, controller, child) {
|
||||
return IconButton(
|
||||
onPressed: () {
|
||||
if (controller.isOpen) {
|
||||
controller.close();
|
||||
} else {
|
||||
controller.open();
|
||||
}
|
||||
},
|
||||
icon: const Icon(Icons.more_vert),
|
||||
);
|
||||
},
|
||||
menuChildren: [
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.delete),
|
||||
child: Text(appLocal.delete),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
title: Text(appLocal.deletion),
|
||||
content: Text(appLocal.deviceDeleteConfirm),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
AppConfig()
|
||||
..devices.removeAt(widget.deviceIndex)
|
||||
..save();
|
||||
Navigator.pop(context);
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.delete),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.network_wifi),
|
||||
child: Text(appLocal.reconfigureNetwork),
|
||||
onPressed: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) =>
|
||||
ReconfigureNetworkPage(widget.deviceIndex),
|
||||
),
|
||||
).then((_) => refreshDevice());
|
||||
},
|
||||
),
|
||||
MenuItemButton(
|
||||
leadingIcon: const Icon(Icons.refresh),
|
||||
onPressed: checkStatus,
|
||||
child: Text(appLocal.refresh),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: EdgeInsetsGeometry.all(16),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
spacing: 16,
|
||||
children: [
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(device.name, style: appTheme.textTheme.headlineLarge),
|
||||
Row(children: [
|
||||
Text(
|
||||
appLocal.status,
|
||||
style: appTheme.textTheme.bodyLarge,
|
||||
),
|
||||
Text(
|
||||
online ? appLocal.online : appLocal.offline,
|
||||
style: appTheme.textTheme.bodyLarge?.copyWith(
|
||||
color: online ? Colors.green : Colors.red,
|
||||
),
|
||||
),
|
||||
],),
|
||||
],
|
||||
),
|
||||
if (statusMessage.isNotEmpty) Text(statusMessage),
|
||||
Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Row(
|
||||
spacing: 8,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Icon(Icons.info),
|
||||
Text(
|
||||
appLocal.deviceInfo,
|
||||
style: appTheme.textTheme.headlineMedium,
|
||||
),
|
||||
],
|
||||
),
|
||||
Text(appLocal.lastKnownIP(device.ip ?? "Unknown")),
|
||||
Text("BSSID: ${device.bssid ?? "Unknown"}"),
|
||||
Text(appLocal.routerSsid(device.routerSsid)),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
|
||||
import 'package:liteauthconfig/appconfig.dart';
|
||||
import 'package:liteauthconfig/l10n/app_localizations.dart';
|
||||
import 'package:liteauthconfig/pages/deviceinfo.dart';
|
||||
import 'package:liteauthconfig/pages/registerpage.dart';
|
||||
|
||||
class DeviceListPage extends StatefulWidget {
|
||||
const DeviceListPage({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _DeviceListPageState();
|
||||
}
|
||||
|
||||
class _DeviceListPageState extends State<DeviceListPage> {
|
||||
List<Device> devices = [];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
refreshList();
|
||||
}
|
||||
|
||||
void refreshList() {
|
||||
setState(() {
|
||||
devices = AppConfig().devices;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var appLocal = AppLocalizations.of(context)!;
|
||||
var appTheme = Theme.of(context);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(appLocal.deviceList)),
|
||||
body: Visibility(
|
||||
visible: devices.isEmpty,
|
||||
replacement: ListView.builder(
|
||||
itemBuilder: (context, index) {
|
||||
final dev = devices[index];
|
||||
|
||||
return ListTile(
|
||||
title: Text(dev.name),
|
||||
onTap: () {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => DeviceInfoPage(deviceIndex: index),
|
||||
),
|
||||
).then((_) => refreshList());
|
||||
},
|
||||
);
|
||||
},
|
||||
itemCount: devices.length,
|
||||
shrinkWrap: true,
|
||||
),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Text(
|
||||
appLocal.noDevices,
|
||||
textAlign: TextAlign.center,
|
||||
style: appTheme.textTheme.bodyLarge,
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: SpeedDial(
|
||||
overlayColor: Colors.black,
|
||||
spacing: 8,
|
||||
children: [
|
||||
SpeedDialChild(
|
||||
child: const Icon(Icons.settings),
|
||||
label: appLocal.setupNewDevice,
|
||||
shape: CircleBorder(),
|
||||
labelStyle: appTheme.textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.black,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => RegisterPage(true)))
|
||||
.then((_) => refreshList());
|
||||
},
|
||||
),
|
||||
SpeedDialChild(
|
||||
child: const Icon(Icons.add_box_outlined),
|
||||
label: appLocal.addExistingDevice,
|
||||
shape: CircleBorder(),
|
||||
labelStyle: appTheme.textTheme.bodyMedium?.copyWith(
|
||||
color: Colors.black,
|
||||
),
|
||||
onTap: () {
|
||||
Navigator.of(context)
|
||||
.push(MaterialPageRoute(builder: (context) => RegisterPage(false)))
|
||||
.then((_) => refreshList());
|
||||
}
|
||||
),
|
||||
],
|
||||
child: const Icon(Icons.add),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liteauthconfig/appconfig.dart';
|
||||
import 'package:liteauthconfig/dialogs/esptouchdialog.dart';
|
||||
import 'package:liteauthconfig/l10n/app_localizations.dart';
|
||||
import 'package:network_info_plus/network_info_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
class ReconfigureNetworkPage extends StatefulWidget {
|
||||
final int deviceIndex;
|
||||
|
||||
const ReconfigureNetworkPage(this.deviceIndex, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _ReconfigureNetworkPageState();
|
||||
}
|
||||
|
||||
class _ReconfigureNetworkPageState extends State<ReconfigureNetworkPage> {
|
||||
TextEditingController ssidController = TextEditingController();
|
||||
TextEditingController bssidController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
bool potentially5GHz = false;
|
||||
bool obscurePassword = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
askForNetworkInfo();
|
||||
}
|
||||
|
||||
void askForNetworkInfo() async {
|
||||
await Future.delayed(Duration(milliseconds: 250));
|
||||
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var appLocal = AppLocalizations.of(context)!;
|
||||
var appTheme = Theme.of(context);
|
||||
|
||||
if (await Permission.location.isDenied) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog.adaptive(
|
||||
backgroundColor: appTheme.cardColor,
|
||||
title: Text(appLocal.networkInfoHeadline),
|
||||
content: Text(appLocal.networkInfoPrompt),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
if (await Permission.location.request().isGranted) {
|
||||
fetchNetworkInfo();
|
||||
}
|
||||
},
|
||||
child: Text(appLocal.continueText),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (await Permission.location.isGranted) {
|
||||
fetchNetworkInfo();
|
||||
}
|
||||
}
|
||||
|
||||
void fetchNetworkInfo() async {
|
||||
final dev = AppConfig().devices[widget.deviceIndex];
|
||||
|
||||
var netInfo = NetworkInfo();
|
||||
var name = await netInfo.getWifiName() ?? dev.routerSsid;
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
name = name.substring(1, name.length - 1);
|
||||
}
|
||||
|
||||
ssidController.text = name;
|
||||
|
||||
var bssid = await netInfo.getWifiBSSID();
|
||||
|
||||
if (bssid == dev.routerBssid) {
|
||||
passwordController.text = dev.networkPassword;
|
||||
}
|
||||
|
||||
bssidController.text = bssid ?? dev.routerBssid;
|
||||
|
||||
if (name.contains("5G")) {
|
||||
setState(() {
|
||||
potentially5GHz = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void submit() async {
|
||||
var dev = AppConfig().devices[widget.deviceIndex];
|
||||
|
||||
await showDialog(
|
||||
context: context,
|
||||
builder: (context) => ESPTouchDialog(
|
||||
name: dev.name,
|
||||
ssid: ssidController.text,
|
||||
bssid: bssidController.text,
|
||||
password: passwordController.text,
|
||||
entryIndex: widget.deviceIndex,
|
||||
),
|
||||
);
|
||||
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final appLocal = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(appLocal.reconfigureNetwork),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: submit,
|
||||
child: const Icon(Icons.check),
|
||||
),
|
||||
body: Padding(
|
||||
padding: EdgeInsetsGeometry.all(16),
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
if (potentially5GHz)
|
||||
Card(
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.warning),
|
||||
title: Text(appLocal.networkLimitation),
|
||||
subtitle: Text(appLocal.networkLimitationDesc),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: appLocal.networkName,
|
||||
),
|
||||
controller: ssidController,
|
||||
onChanged: (name) {
|
||||
setState(() {
|
||||
potentially5GHz = name.contains("5G");
|
||||
});
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: appLocal.routerMacAddress,
|
||||
),
|
||||
controller: bssidController,
|
||||
),
|
||||
TextField(
|
||||
obscureText: obscurePassword,
|
||||
controller: passwordController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: appLocal.networkPassword,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
obscurePassword = !obscurePassword;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
obscurePassword
|
||||
? Icons.remove_red_eye
|
||||
: Icons.remove_red_eye_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liteauthconfig/appconfig.dart';
|
||||
import 'package:liteauthconfig/dialogs/esptouchdialog.dart';
|
||||
import 'package:liteauthconfig/l10n/app_localizations.dart';
|
||||
import 'package:multicast_dns/multicast_dns.dart';
|
||||
import 'package:network_info_plus/network_info_plus.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
// ignore_for_file: use_build_context_synchronously
|
||||
|
||||
class RegisterPage extends StatefulWidget {
|
||||
final bool setup;
|
||||
|
||||
const RegisterPage(this.setup, {super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _RegisterPageState();
|
||||
}
|
||||
|
||||
class _RegisterPageState extends State<RegisterPage> {
|
||||
TextEditingController nameController = TextEditingController();
|
||||
TextEditingController ssidController = TextEditingController();
|
||||
TextEditingController bssidController = TextEditingController();
|
||||
TextEditingController passwordController = TextEditingController();
|
||||
bool potentially5GHz = false;
|
||||
bool obscurePassword = true;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
askForNetworkInfo();
|
||||
}
|
||||
|
||||
void askForNetworkInfo() async {
|
||||
await Future.delayed(Duration(milliseconds: 250));
|
||||
|
||||
if (!context.mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
var appLocal = AppLocalizations.of(context)!;
|
||||
var appTheme = Theme.of(context);
|
||||
|
||||
if (await Permission.location.isDenied) {
|
||||
showDialog(
|
||||
context: context,
|
||||
builder: (context) => AlertDialog.adaptive(
|
||||
backgroundColor: appTheme.cardColor,
|
||||
title: Text(appLocal.networkInfoHeadline),
|
||||
content: Text(appLocal.networkInfoPrompt),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
child: Text(appLocal.cancel),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () async {
|
||||
Navigator.pop(context);
|
||||
if (await Permission.location.request().isGranted) {
|
||||
fetchNetworkInfo();
|
||||
}
|
||||
},
|
||||
child: Text(appLocal.continueText),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
} else if (await Permission.location.isGranted) {
|
||||
fetchNetworkInfo();
|
||||
}
|
||||
}
|
||||
|
||||
void fetchNetworkInfo() async {
|
||||
var netInfo = NetworkInfo();
|
||||
var name = await netInfo.getWifiName() ?? "";
|
||||
|
||||
if (Platform.isAndroid) {
|
||||
name = name.substring(1, name.length - 1);
|
||||
}
|
||||
|
||||
ssidController.text = name;
|
||||
|
||||
bssidController.text = await netInfo.getWifiBSSID() ?? "";
|
||||
|
||||
if (name.contains("5G")) {
|
||||
setState(() {
|
||||
potentially5GHz = true;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void submit() async {
|
||||
if (widget.setup) {
|
||||
await showDialog(
|
||||
context: context,
|
||||
barrierDismissible: false,
|
||||
builder: (context) => ESPTouchDialog(
|
||||
name: nameController.text,
|
||||
ssid: ssidController.text,
|
||||
bssid: bssidController.text,
|
||||
password: passwordController.text,
|
||||
addNewEntry: true,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
final dev = Device(
|
||||
name: nameController.text,
|
||||
routerSsid: ssidController.text,
|
||||
routerBssid: bssidController.text,
|
||||
networkPassword: passwordController.text,
|
||||
);
|
||||
|
||||
final MDnsClient client = MDnsClient();
|
||||
await client.start();
|
||||
|
||||
// TODO: Change to dynamic mDNS name
|
||||
String name = "liteauth.local";
|
||||
|
||||
await for (final PtrResourceRecord ptr
|
||||
in client.lookup<PtrResourceRecord>(
|
||||
ResourceRecordQuery.serverPointer(name),
|
||||
)) {
|
||||
await for (final SrvResourceRecord srv in client.lookup(ResourceRecordQuery.service(ptr.domainName))) {
|
||||
// TODO: Actually implement record lookup
|
||||
}
|
||||
}
|
||||
AppConfig().devices.add(dev);
|
||||
}
|
||||
if (context.mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
var appLocal = AppLocalizations.of(context)!;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: Text(appLocal.registerDevice)),
|
||||
body: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: Column(
|
||||
spacing: 16,
|
||||
children: [
|
||||
if (potentially5GHz)
|
||||
Card(
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.warning),
|
||||
title: Text(appLocal.networkLimitation),
|
||||
subtitle: Text(appLocal.networkLimitationDesc),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: nameController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: appLocal.deviceName,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
controller: ssidController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: appLocal.networkName,
|
||||
),
|
||||
onChanged: (name) {
|
||||
setState(() {
|
||||
potentially5GHz = name.contains("5G");
|
||||
});
|
||||
},
|
||||
),
|
||||
TextField(
|
||||
controller: bssidController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: appLocal.routerMacAddress,
|
||||
),
|
||||
),
|
||||
TextField(
|
||||
obscureText: obscurePassword,
|
||||
controller: passwordController,
|
||||
decoration: InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: appLocal.networkPassword,
|
||||
suffixIcon: IconButton(
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
obscurePassword = !obscurePassword;
|
||||
});
|
||||
},
|
||||
icon: Icon(
|
||||
obscurePassword
|
||||
? Icons.remove_red_eye
|
||||
: Icons.remove_red_eye_outlined,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
floatingActionButton: FloatingActionButton(
|
||||
onPressed: submit,
|
||||
child: const Icon(Icons.check),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:http/io_client.dart';
|
||||
|
||||
Future<http.Client> createEspHttpClient() async {
|
||||
final certData = await rootBundle.load("assets/certificates/rootCA.crt");
|
||||
final certBytes = certData.buffer.asUint8List();
|
||||
|
||||
SecurityContext securityContext = SecurityContext.defaultContext;
|
||||
securityContext.setTrustedCertificatesBytes(certBytes);
|
||||
|
||||
HttpClient client = HttpClient(context: securityContext);
|
||||
|
||||
return IOClient(client);
|
||||
}
|
||||
Reference in New Issue
Block a user