1 Commits

Author SHA1 Message Date
iGoX 400dee57d4 Home Screen: Inform on connection lose 2026-03-24 21:58:42 +01:00
14 changed files with 97 additions and 391 deletions
-4
View File
@@ -49,7 +49,3 @@ android/key.properties
windows/installer/* windows/installer/*
bugreport* bugreport*
!downloads/* !downloads/*
android/builds.json
macos/builds.json
ios/builds.json
windows/busylight-buddy-windows-installer-builder.iss
+7 -6
View File
@@ -2,19 +2,20 @@
Multiplatform Flutter app to control your DIY [BusyLight](https://code.igox.org/iGoX/busylight) (ESP32 + MicroPython + Microdot). Multiplatform Flutter app to control your DIY [BusyLight](https://code.igox.org/iGoX/busylight) (ESP32 + MicroPython + Microdot).
Supports **Android**, **macOS**, and **Windows**. Supports **iOS**, **ipadOS**, **Android**, **macOS**, and **Windows**.
--- ---
## Downloads ## Downloads
[![Android](https://img.shields.io/badge/Android-APK-3DDC84?style=for-the-badge&logo=android&logoColor=white)](https://code.igox.org/iGoX/busylight-buddy/releases/) [![Windows](https://img.shields.io/badge/Windows-Installer-0078D4?style=for-the-badge&logo=windows&logoColor=white)](https://code.igox.org/iGoX/busylight-buddy/releases/download/v0.0.1/BusyLight-Buddy-Installer.exe)
[![macOS](https://img.shields.io/badge/macOS-APP-3DDC84?style=for-the-badge&logo=apple&logoColor=white)](https://code.igox.org/iGoX/busylight-buddy/releases/) [![Android](https://img.shields.io/badge/Android-APK-3DDC84?style=for-the-badge&logo=android&logoColor=white)](https://code.igox.org/iGoX/busylight-buddy/releases/download/v0.0.1/org.igox.apps.android.busylight-buddy-release.apk)
[![Windows](https://img.shields.io/badge/Windows-Installer-0078D4?style=for-the-badge&logo=windows&logoColor=white)](https://code.igox.org/iGoX/busylight-buddy/releases/)
Or browse all releases on the [Releases page](https://code.igox.org/iGoX/busylight-buddy/releases).
--- ---
## Screenshots ## Screenshots (iOS)
<img src="doc/screenshots/ios-screenshot-main.png" width="300" alt="BusyLight Companion — main screen" /> <img src="doc/screenshots/ios-screenshot-config.png" width="300" alt="BusyLight Companion — settings" /> <img src="doc/screenshots/ios-screenshot-main.png" width="300" alt="BusyLight Companion — main screen" /> <img src="doc/screenshots/ios-screenshot-config.png" width="300" alt="BusyLight Companion — settings" />
@@ -41,7 +42,7 @@ Supports **Android**, **macOS**, and **Windows**.
### Background polling ### Background polling
- Automatically pulls status + color from device at a configurable interval - Automatically pulls status + color from device at a configurable interval
- Silent updates — no loading screen interruption - Silent updates — no loading screen interruption
- Configurable in Settings (default: every second, can be disabled) - Configurable in Settings (default: every 5 seconds, can be disabled)
### Settings ### Settings
- Device address (hostname or IP, e.g. `http://igox-busylight.local`) - Device address (hostname or IP, e.g. `http://igox-busylight.local`)
+10 -80
View File
@@ -1,93 +1,23 @@
$buildType = if ($args[0]) { $args[0] } else { "debug" }
<#
.SYNOPSIS
Builds a Flutter APK with versioning support.
.DESCRIPTION
This script builds a Flutter APK, manages build numbers, and renames output files.
.PARAMETER buildType
The build type (release or debug). Default is "release".
.PARAMETER buildName
The build name (version). Default is "0.0.0".
.EXAMPLE
.\flutter-build-apk.ps1 -buildType debug -buildName 1.0.0
Builds a debug APK with version 1.0.0.
#>
[CmdletBinding()]
param (
[string]$buildType = "release", # Default value is "release"
[string]$buildName = "0.0.0" # Default value is "0.0.0"
)
$buildsRef = "builds.json"
# Check if the file exists
if (-not (Test-Path -Path $buildsRef)) {
# Create the file if it doesn't exist
New-Item -Path $buildsRef -ItemType File
$buildsMap = @{
$buildName = 0
}
$buildsMap | ConvertTo-Json | Out-File $buildsRef
Write-Output "File created: $buildsRef"
} else {
Write-Output "File already exists: $buildsRef"
}
# Read the JSON file and convert it to a hashtable
$jsonContent = Get-Content $buildsRef -Raw | ConvertFrom-Json
$buildsMap = @{}
$jsonContent.PSObject.Properties | ForEach-Object {
$buildsMap[$_.Name] = $_.Value
}
if ($buildsMap.ContainsKey($buildName)) {
Write-Output "Build exists: $buildName"
$buildNumber = $buildsMap[$buildName]
$buildNumber++
Write-Output "Next build number for ${buildName}: $buildNumber"
$buildsMap[$buildName] = $buildNumber
}
else {
$buildsMap[$buildName] = 1
}
$buildsMap | ConvertTo-Json | Out-File $buildsRef
$apkDir = "..\build\app\outputs\flutter-apk" $apkDir = "..\build\app\outputs\flutter-apk"
$baseName = "org.igox.apps.android.busylight-buddy" $baseName = "org.igox.apps.android.busylight-buddy"
# Build an array for arguments flutter build apk --$buildType
$flutterArgs = @(
"--$buildType",
"--build-name=$buildName",
"--build-number=$buildNumber",
"--target-platform=android-arm,android-arm64,android-x64"
)
Write-Output "Building APK with arguments: $($flutterArgs -join ' ')"
& flutter build apk @flutterArgs
# Rename APK # Rename APK
$oldApk = "$apkDir\app-${buildType}.apk" $oldApk = "$apkDir\app-$buildType.apk"
$newApk = "$apkDir\$baseName-${buildType}.apk" $newApk = "$apkDir\$baseName-$buildType.apk"
if (Test-Path $oldApk) { if (Test-Path $oldApk) {
if (Test-Path $newApk) { Remove-Item $newApk -Force } if (Test-Path $newApk) { Remove-Item $newApk -Force }
Rename-Item -Path $oldApk -NewName "$baseName-${buildType}.apk" Rename-Item -Path $oldApk -NewName "$baseName-$buildType.apk"
Write-Host "APK renamed to: $baseName-${buildType}.apk" Write-Host "APK renamed to: $baseName-$buildType.apk"
} }
# Rename SHA1 (if exists) # Rename SHA1 (if exists)
$oldSha1 = "$apkDir\app-${buildType}.apk.sha1" $oldSha1 = "$apkDir\app-$buildType.apk.sha1"
$newSha1 = "$apkDir\$baseName-${buildType}.apk.sha1" $newSha1 = "$apkDir\$baseName-$buildType.apk.sha1"
if (Test-Path $oldSha1) { if (Test-Path $oldSha1) {
if (Test-Path $newSha1) { Remove-Item $newSha1 -Force } if (Test-Path $newSha1) { Remove-Item $newSha1 -Force }
Rename-Item -Path $oldSha1 -NewName "$baseName-${buildType}.apk.sha1" Rename-Item -Path $oldSha1 -NewName "$baseName-$buildType.apk.sha1"
Write-Host "SHA1 renamed to: $baseName-${buildType}.apk.sha1" Write-Host "SHA1 renamed to: $baseName-$buildType.apk.sha1"
} }
+16 -82
View File
@@ -1,90 +1,24 @@
#!/bin/bash #!/bin/bash
BUILD_TYPE=${1:-debug}
APK_DIR="../build/app/outputs/flutter-apk"
BASE_NAME="org.igox.apps.android.busylight-buddy"
# Default values flutter build apk --$BUILD_TYPE
buildType="release"
buildName="0.0.0"
buildsRef="builds.json"
# Help message
show_help() {
echo "Usage: $0 [-t buildType] [-n buildName]"
echo "Builds a Flutter APK with versioning support."
echo ""
echo "Options:"
echo " -t, --buildType Build type (release or debug). Default: release"
echo " -n, --buildName Build name (version). Default: 0.0.0"
echo " -h, --help Show this help message"
exit 0
}
# Parse command-line arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
-t|--buildType) buildType="$2"; shift ;;
-n|--buildName) buildName="$2"; shift ;;
-h|--help) show_help ;;
*) echo "Unknown parameter: $1"; show_help; exit 1 ;;
esac
shift
done
# Check if builds.json exists
if [ ! -f "$buildsRef" ]; then
echo "File created: $buildsRef"
echo "{\"$buildName\": 0}" > "$buildsRef"
else
echo "File already exists: $buildsRef"
fi
# Read the JSON file
buildsMap=$(cat "$buildsRef")
# Check if buildName exists in the JSON
if jq -e --arg key "$buildName" 'has($key)' <<< "$buildsMap" > /dev/null; then
echo "Build exists: $buildName"
buildNumber=$(jq --arg key "$buildName" '.[$key]' <<< "$buildsMap")
buildNumber=$((buildNumber + 1))
echo "Next build number for $buildName: $buildNumber"
buildsMap=$(jq --arg key "$buildName" --argjson value "$buildNumber" '.[$key] = $value' <<< "$buildsMap")
else
echo "New build: $buildName, starting at build number: 1"
buildNumber=1
buildsMap=$(jq --arg key "$buildName" --argjson value "$buildNumber" '.[$key] = $value' <<< "$buildsMap")
fi
# Save the updated JSON back to the file
echo "$buildsMap" > "$buildsRef"
# Define paths
apkDir="../build/app/outputs/flutter-apk"
baseName="org.igox.apps.android.busylight-buddy"
# Build APK
echo "Building APK with arguments: --$buildType --build-name=$buildName --build-number=$buildNumber --target-platform=android-arm,android-arm64,android-x64"
flutter build apk --"$buildType" --build-name="$buildName" --build-number="$buildNumber" --target-platform=android-arm,android-arm64,android-x64
# Rename APK # Rename APK
oldApk="$apkDir/app-$buildType.apk" OLD_APK="$APK_DIR/app-$BUILD_TYPE.apk"
newApk="$apkDir/$baseName-$buildType.apk" NEW_APK="$APK_DIR/$BASE_NAME-$BUILD_TYPE.apk"
if [ -f "$oldApk" ]; then if [ -f "$OLD_APK" ]; then
if [ -f "$newApk" ]; then [ -f "$NEW_APK" ] && rm -f "$NEW_APK"
rm -f "$newApk" mv "$OLD_APK" "$NEW_APK"
fi echo "APK renamed to: $BASE_NAME-$BUILD_TYPE.apk"
mv "$oldApk" "$newApk"
echo "APK renamed to: $baseName-$buildType.apk"
else
echo "Warning: APK not found at $oldApk"
fi fi
# Rename SHA1 (if exists) # Rename SHA1 (if exists)
oldSha1="$apkDir/app-$buildType.apk.sha1" OLD_SHA1="$APK_DIR/app-$BUILD_TYPE.apk.sha1"
newSha1="$apkDir/$baseName-$buildType.apk.sha1" NEW_SHA1="$APK_DIR/$BASE_NAME-$BUILD_TYPE.apk.sha1"
if [ -f "$oldSha1" ]; then if [ -f "$OLD_SHA1" ]; then
if [ -f "$newSha1" ]; then [ -f "$NEW_SHA1" ] && rm -f "$NEW_SHA1"
rm -f "$newSha1" mv "$OLD_SHA1" "$NEW_SHA1"
fi echo "SHA1 renamed to: $BASE_NAME-$BUILD_TYPE.apk.sha1"
mv "$oldSha1" "$newSha1"
echo "SHA1 renamed to: $baseName-$buildType.apk.sha1"
else
echo "Warning: SHA1 not found at $oldSha1"
fi fi
@@ -3,7 +3,7 @@
; Non-commercial use only ; Non-commercial use only
#define MyAppName "BusyLight Buddy" #define MyAppName "BusyLight Buddy"
#define MyAppVersion "%%MyAppVersion%%" #define MyAppVersion "0.1"
#define MyAppPublisher "iGoX" #define MyAppPublisher "iGoX"
#define MyAppURL "https://github.com/igox/busylight-buddy" #define MyAppURL "https://github.com/igox/busylight-buddy"
#define MyAppExeName "busylight_buddy.exe" #define MyAppExeName "busylight_buddy.exe"
@@ -30,13 +30,13 @@ ArchitecturesAllowed=x64compatible
; the 64-bit view of the registry. ; the 64-bit view of the registry.
ArchitecturesInstallIn64BitMode=x64compatible ArchitecturesInstallIn64BitMode=x64compatible
DisableProgramGroupPage=yes DisableProgramGroupPage=yes
LicenseFile="..\LICENSE" LicenseFile="LICENSE"
; Uncomment the following line to run in non administrative install mode (install for current user only). ; Uncomment the following line to run in non administrative install mode (install for current user only).
;PrivilegesRequired=lowest ;PrivilegesRequired=lowest
PrivilegesRequiredOverridesAllowed=dialog PrivilegesRequiredOverridesAllowed=dialog
OutputDir="installer" OutputDir="windows\installer"
OutputBaseFilename=BusyLight-Buddy-Installer OutputBaseFilename=BusyLight-Buddy-Installer
SetupIconFile="runner\resources\app_icon.ico" SetupIconFile="windows\runner\resources\app_icon.ico"
SolidCompression=yes SolidCompression=yes
WizardStyle=modern dynamic WizardStyle=modern dynamic
@@ -47,9 +47,9 @@ Name: "english"; MessagesFile: "compiler:Default.isl"
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
[Files] [Files]
Source: "..\build\windows\x64\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion Source: "build\windows\x64\runner\Release\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\build\windows\x64\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion Source: "build\windows\x64\runner\Release\flutter_windows.dll"; DestDir: "{app}"; Flags: ignoreversion
Source: "..\build\windows\x64\runner\Release\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs Source: "build\windows\x64\runner\Release\data\*"; DestDir: "{app}\data"; Flags: ignoreversion recursesubdirs createallsubdirs
; NOTE: Don't use "Flags: ignoreversion" on any shared system files ; NOTE: Don't use "Flags: ignoreversion" on any shared system files
[Icons] [Icons]
Binary file not shown.
@@ -0,0 +1 @@
17b71b5af077e19e826bdd5d84ec2de014cd91fe
-60
View File
@@ -1,60 +0,0 @@
#!/bin/bash
# Default values
buildType="release"
buildName="0.0.0"
buildsRef="builds.json"
# Help message
show_help() {
echo "Usage: $0 [-t buildType] [-n buildName]"
echo "Builds a Flutter iOS application with versioning support."
echo ""
echo "Options:"
echo " -t, --buildType Build type (release or debug). Default: release"
echo " -n, --buildName Build name (version). Default: 0.0.0"
echo " -h, --help Show this help message"
exit 0
}
# Parse command-line arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
-t|--buildType) buildType="$2"; shift ;;
-n|--buildName) buildName="$2"; shift ;;
-h|--help) show_help ;;
*) echo "Unknown parameter: $1"; show_help; exit 1 ;;
esac
shift
done
# Check if builds.json exists
if [ ! -f "$buildsRef" ]; then
echo "File created: $buildsRef"
echo "{\"$buildName\": 0}" > "$buildsRef"
else
echo "File already exists: $buildsRef"
fi
# Read the JSON file
buildsMap=$(cat "$buildsRef")
# Check if buildName exists in the JSON
if jq -e --arg key "$buildName" 'has($key)' <<< "$buildsMap" > /dev/null; then
echo "Build exists: $buildName"
buildNumber=$(jq --arg key "$buildName" '.[$key]' <<< "$buildsMap")
buildNumber=$((buildNumber + 1))
echo "Next build number for $buildName: $buildNumber"
buildsMap=$(jq --arg key "$buildName" --argjson value "$buildNumber" '.[$key] = $value' <<< "$buildsMap")
else
echo "New build: $buildName, starting at build number: 1"
buildNumber=1
buildsMap=$(jq --arg key "$buildName" --argjson value "$buildNumber" '.[$key] = $value' <<< "$buildsMap")
fi
# Save the updated JSON back to the file
echo "$buildsMap" > "$buildsRef"
# Build iOS application
echo "Building iOS application with arguments: --$buildType --build-name=$buildName --build-number=$buildNumber"
flutter build ios --"$buildType" --build-name="$buildName" --build-number="$buildNumber"
+15 -8
View File
@@ -10,7 +10,7 @@ import '../services/busylight_service.dart';
const _kHostKey = 'busylight_host'; const _kHostKey = 'busylight_host';
const _kDefaultHost = 'http://igox-busylight.local'; const _kDefaultHost = 'http://igox-busylight.local';
const _kPollIntervalKey = 'busylight_poll_interval'; const _kPollIntervalKey = 'busylight_poll_interval';
const _kDefaultPollInterval = 1.0; // seconds const _kDefaultPollInterval = 5; // seconds
final sharedPreferencesProvider = FutureProvider<SharedPreferences>( final sharedPreferencesProvider = FutureProvider<SharedPreferences>(
(_) => SharedPreferences.getInstance(), (_) => SharedPreferences.getInstance(),
@@ -21,9 +21,9 @@ final deviceHostProvider = StateProvider<String>((ref) {
return prefs?.getString(_kHostKey) ?? _kDefaultHost; return prefs?.getString(_kHostKey) ?? _kDefaultHost;
}); });
final pollIntervalProvider = StateProvider<double>((ref) { final pollIntervalProvider = StateProvider<int>((ref) {
final prefs = ref.watch(sharedPreferencesProvider).valueOrNull; final prefs = ref.watch(sharedPreferencesProvider).valueOrNull;
return prefs?.getDouble(_kPollIntervalKey) ?? _kDefaultPollInterval.toDouble(); return prefs?.getInt(_kPollIntervalKey) ?? _kDefaultPollInterval;
}); });
// ── Service ────────────────────────────────────────────────────────────────── // ── Service ──────────────────────────────────────────────────────────────────
@@ -149,6 +149,11 @@ final colorProvider = StateNotifierProvider<ColorNotifier, BusylightColor>(
}, },
); );
// ── Connection lost indicator ─────────────────────────────────────────────────
// Set to true by the polling notifier when a poll fails, false on success.
final connectionLostProvider = StateProvider<bool>((_) => false);
// ── Background polling ──────────────────────────────────────────────────────── // ── Background polling ────────────────────────────────────────────────────────
// Periodically pulls status + color from the device and silently updates state. // Periodically pulls status + color from the device and silently updates state.
@@ -161,11 +166,10 @@ class PollingNotifier extends StateNotifier<void> {
Timer? _timer; Timer? _timer;
void _start() { void _start() {
final intervalSeconds = _ref.read(pollIntervalProvider); final interval = _ref.read(pollIntervalProvider);
final intervalMillis = (intervalSeconds * 1000).toInt();
_timer?.cancel(); _timer?.cancel();
if (intervalMillis <= 0) return; if (interval <= 0) return;
_timer = Timer.periodic(Duration(milliseconds: intervalMillis), (_) => _poll()); _timer = Timer.periodic(Duration(seconds: interval), (_) => _poll());
} }
void restart() => _start(); void restart() => _start();
@@ -182,8 +186,11 @@ class PollingNotifier extends StateNotifier<void> {
_ref.read(busylightStatusProvider.notifier).setLocalStatus(status); _ref.read(busylightStatusProvider.notifier).setLocalStatus(status);
_ref.read(colorProvider.notifier).silentSet(color); _ref.read(colorProvider.notifier).silentSet(color);
_ref.read(brightnessProvider.notifier).silentSet(color.brightness); _ref.read(brightnessProvider.notifier).silentSet(color.brightness);
// Connection restored — clear the lost flag
_ref.read(connectionLostProvider.notifier).state = false;
} catch (_) { } catch (_) {
// Silently ignore poll errors — connection issues are shown on manual refresh // Signal connectivity issue to the UI
_ref.read(connectionLostProvider.notifier).state = true;
} }
} }
+20 -8
View File
@@ -122,6 +122,7 @@ class _BodyState extends ConsumerState<_Body> {
final brightness = ref.watch(brightnessProvider); final brightness = ref.watch(brightnessProvider);
final color = ref.watch(colorProvider); final color = ref.watch(colorProvider);
final presets = ref.watch(presetsProvider); final presets = ref.watch(presetsProvider);
final connectionLost = ref.watch(connectionLostProvider);
final status = statusAsync.valueOrNull ?? BusylightStatus.off; final status = statusAsync.valueOrNull ?? BusylightStatus.off;
final displayColor = _statusColor(status, color); final displayColor = _statusColor(status, color);
@@ -129,7 +130,7 @@ class _BodyState extends ConsumerState<_Body> {
return ListView( return ListView(
padding: const EdgeInsets.all(24), padding: const EdgeInsets.all(24),
children: [ children: [
// Live color preview dot // Live color preview dot — shows wifi-off icon when connection lost
Center( Center(
child: AnimatedContainer( child: AnimatedContainer(
duration: const Duration(milliseconds: 400), duration: const Duration(milliseconds: 400),
@@ -137,10 +138,12 @@ class _BodyState extends ConsumerState<_Body> {
height: 100, height: 100,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: status == BusylightStatus.off color: connectionLost
? Colors.grey.shade900
: status == BusylightStatus.off
? Colors.grey.shade900 ? Colors.grey.shade900
: displayColor.withOpacity(brightness), : displayColor.withOpacity(brightness),
boxShadow: status != BusylightStatus.off boxShadow: (!connectionLost && status != BusylightStatus.off)
? [BoxShadow( ? [BoxShadow(
color: displayColor.withOpacity(0.5 * brightness), color: displayColor.withOpacity(0.5 * brightness),
blurRadius: 40, blurRadius: 40,
@@ -148,13 +151,20 @@ class _BodyState extends ConsumerState<_Body> {
)] )]
: null, : null,
), ),
child: connectionLost
? const Icon(Icons.wifi_off, color: Colors.grey, size: 36)
: null,
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Center( Center(
child: Text( child: Text(
status.label.toUpperCase(), connectionLost ? 'NO CONNECTION' : status.label.toUpperCase(),
style: const TextStyle(color: Colors.grey, letterSpacing: 2, fontSize: 13), style: TextStyle(
color: connectionLost ? Colors.red.shade700 : Colors.grey,
letterSpacing: 2,
fontSize: 13,
),
), ),
), ),
const SizedBox(height: 36), const SizedBox(height: 36),
@@ -177,9 +187,11 @@ class _BodyState extends ConsumerState<_Body> {
BusylightStatus.off, BusylightStatus.off,
].map((s) => StatusButton( ].map((s) => StatusButton(
status: s, status: s,
isActive: status == s, isActive: !connectionLost && status == s,
isPending: _pendingStatus == s, isPending: _pendingStatus == s,
onTap: _pendingStatus == null ? () => _setStatus(s) : () {}, onTap: (!connectionLost && _pendingStatus == null)
? () => _setStatus(s)
: () {},
)).toList(), )).toList(),
), ),
const SizedBox(height: 32), const SizedBox(height: 32),
@@ -188,7 +200,7 @@ class _BodyState extends ConsumerState<_Body> {
_PresetsScroller( _PresetsScroller(
presets: presets, presets: presets,
pendingPresetId: _pendingPresetId, pendingPresetId: _pendingPresetId,
onPresetTap: (_pendingStatus == null && _pendingPresetId == null) onPresetTap: (!connectionLost && _pendingStatus == null && _pendingPresetId == null)
? _applyPreset ? _applyPreset
: (_) {}, : (_) {},
onPresetDelete: (preset) => ref.read(presetsProvider.notifier).remove(preset.id), onPresetDelete: (preset) => ref.read(presetsProvider.notifier).remove(preset.id),
+15 -15
View File
@@ -14,7 +14,7 @@ class SettingsScreen extends ConsumerStatefulWidget {
class _SettingsScreenState extends ConsumerState<SettingsScreen> { class _SettingsScreenState extends ConsumerState<SettingsScreen> {
late TextEditingController _hostController; late TextEditingController _hostController;
late double _pollInterval; late int _pollInterval;
bool _startWithSession = false; bool _startWithSession = false;
@override @override
@@ -48,7 +48,7 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
final prefs = await SharedPreferences.getInstance(); final prefs = await SharedPreferences.getInstance();
await prefs.setString('busylight_host', host); await prefs.setString('busylight_host', host);
await prefs.setDouble('busylight_poll_interval', _pollInterval); await prefs.setInt('busylight_poll_interval', _pollInterval);
if (mounted) { if (mounted) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@@ -58,10 +58,10 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
} }
} }
String _intervalLabel(double seconds) { String _intervalLabel(int seconds) {
if (seconds == 0.0) return 'Off'; if (seconds == 0) return 'Off';
if (seconds < 2.0) return '${seconds}s'; if (seconds < 60) return '${seconds}s';
return '${ (seconds)}s'; return '${seconds ~/ 60}m';
} }
@override @override
@@ -124,21 +124,21 @@ class _SettingsScreenState extends ConsumerState<SettingsScreen> {
overlayColor: Colors.amber.withOpacity(0.2), overlayColor: Colors.amber.withOpacity(0.2),
), ),
child: Slider( child: Slider(
value: _pollInterval, value: _pollInterval.toDouble(),
min: 0.0, min: 0,
max: 2.0, max: 60,
divisions: 4, divisions: 12,
onChanged: (v) => setState(() => _pollInterval = v), onChanged: (v) => setState(() => _pollInterval = v.round()),
), ),
), ),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Text('Off', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)), Text('Off', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)),
Text('0.5s', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)), Text('5s', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)),
Text('1s', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)), Text('10s', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)),
Text('1.5s', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)), Text('30s', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)),
Text('2s', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)), Text('1m', style: TextStyle(color: Colors.grey.shade600, fontSize: 11)),
], ],
), ),
const SizedBox(height: 6), const SizedBox(height: 6),
-60
View File
@@ -1,60 +0,0 @@
#!/bin/bash
# Default values
buildType="release"
buildName="0.0.0"
buildsRef="builds.json"
# Help message
show_help() {
echo "Usage: $0 [-t buildType] [-n buildName]"
echo "Builds a Flutter macOS application with versioning support."
echo ""
echo "Options:"
echo " -t, --buildType Build type (release or debug). Default: release"
echo " -n, --buildName Build name (version). Default: 0.0.0"
echo " -h, --help Show this help message"
exit 0
}
# Parse command-line arguments
while [[ "$#" -gt 0 ]]; do
case $1 in
-t|--buildType) buildType="$2"; shift ;;
-n|--buildName) buildName="$2"; shift ;;
-h|--help) show_help ;;
*) echo "Unknown parameter: $1"; show_help; exit 1 ;;
esac
shift
done
# Check if builds.json exists
if [ ! -f "$buildsRef" ]; then
echo "File created: $buildsRef"
echo "{\"$buildName\": 0}" > "$buildsRef"
else
echo "File already exists: $buildsRef"
fi
# Read the JSON file
buildsMap=$(cat "$buildsRef")
# Check if buildName exists in the JSON
if jq -e --arg key "$buildName" 'has($key)' <<< "$buildsMap" > /dev/null; then
echo "Build exists: $buildName"
buildNumber=$(jq --arg key "$buildName" '.[$key]' <<< "$buildsMap")
buildNumber=$((buildNumber + 1))
echo "Next build number for $buildName: $buildNumber"
buildsMap=$(jq --arg key "$buildName" --argjson value "$buildNumber" '.[$key] = $value' <<< "$buildsMap")
else
echo "New build: $buildName, starting at build number: 1"
buildNumber=1
buildsMap=$(jq --arg key "$buildName" --argjson value "$buildNumber" '.[$key] = $value' <<< "$buildsMap")
fi
# Save the updated JSON back to the file
echo "$buildsMap" > "$buildsRef"
# Build macOS application
echo "Building macOS application with arguments: --$buildType --build-name=$buildName --build-number=$buildNumber"
flutter build macos --"$buildType" --build-name="$buildName" --build-number="$buildNumber"
-55
View File
@@ -1,55 +0,0 @@
<#
.SYNOPSIS
Builds a Flutter Windows installer with versioning support.
.DESCRIPTION
This script builds a Flutter Windows application and creates an installer using Inno Setup.
.PARAMETER buildType
The build type (release or debug). Default is "release".
.PARAMETER buildName
The build name (version). Default is "0.0.0".
.EXAMPLE
.\build-windows-installer.ps1 -buildType debug -buildName 1.0.0
Builds a debug Windows application with version 1.0.0.
#>
[CmdletBinding()]
param (
[string]$buildType = "release", # Default value is "release"
[string]$buildName = "0.0.0" # Default value is "0.0.0"
)
$buildNumber = "$(Get-Date -Format 'yyyyMMddHHmmss')"
# Define the file path and the new version value
$issTplFile = "./busylight-buddy-windows-installer-builder.iss.tpl"
$issFile = "./busylight-buddy-windows-installer-builder.iss"
cd $PSScriptRoot
# Build an array for arguments
$flutterArgs = @(
"--$buildType",
"--build-name=$buildName",
"--build-number=$buildNumber"
)
Write-Output "Building Windows application with arguments: $($flutterArgs -join ' ')"
# Build the Windows application using Flutter
flutter build windows @flutterArgs
# Build the Windows installer using Inno Setup Compiler (ISCC.exe)
# Read the content of Inno Setup template file
$content = Get-Content -Path $issTplFile -Raw
# Replace the placeholder with the new version value
$updatedContent = $content -replace '%%MyAppVersion%%', $buildName
# Write the updated content back to Inno Setup file
$updatedContent | Set-Content -Path $issFile
ISCC.exe $issFile