1.0.0 #2

Merged
me merged 26 commits from dev into main 2026-09-28 10:50:03 -07:00
Showing only changes of commit 7ab13dd4de - Show all commits
+169 -166
View File
@@ -368,183 +368,186 @@ class _HomeScreenState extends State<HomeScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
appBar: AppBar(title: const Text('Sunset Alarm')), appBar: AppBar(title: const Text('Sunset Alarm')),
body: Padding( resizeToAvoidBottomInset: true, // default, but be explicit
padding: const EdgeInsets.all(16), body: SingleChildScrollView(
child: Column( child: Padding(
crossAxisAlignment: CrossAxisAlignment.start, padding: const EdgeInsets.all(16),
children: [ child: Column(
// --- Location section --- crossAxisAlignment: CrossAxisAlignment.start,
const Text( children: [
'Location', // --- Location section ---
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), const Text(
), 'Location',
const SizedBox(height: 8), style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
Row( ),
children: [ const SizedBox(height: 8),
Expanded( Row(
child: TextField( children: [
controller: _latController,
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
decoration: const InputDecoration(
labelText: 'Latitude',
border: OutlineInputBorder(),
),
onChanged: (_) => _savePrefs(),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _lngController,
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
decoration: const InputDecoration(
labelText: 'Longitude',
border: OutlineInputBorder(),
),
onChanged: (_) => _savePrefs(),
),
),
const SizedBox(width: 8),
IconButton.filledTonal(
icon: const Icon(Icons.my_location),
tooltip: 'Get GPS Location',
onPressed: _getGpsLocation,
),
],
),
const SizedBox(height: 16),
// --- Sunset time section ---
const Text(
'Sunset',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: TextField(
controller: _sunsetController,
decoration: InputDecoration(
labelText: 'Sunset Time (HH:MM)',
border: OutlineInputBorder(),
errorText: _sunsetErrorText,
suffixIcon: IconButton(
icon: Icon(Icons.access_time),
onPressed: () async {
final picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null) {
_sunsetController.text =
'${picked.hour.toString().padLeft(2, '0')}:'
'${picked.minute.toString().padLeft(2, '0')}';
setState(
() =>
_sunsetErrorText = _validateTime(
_sunsetController.text,
),
);
if (_alarmEnabled) {
_scheduleAlarm();
}
}
},
),
),
onChanged: (v) {
final val = _validateTime(v);
if (val == null) {
_savePrefs();
if (_alarmEnabled) {
_scheduleAlarm();
}
}
setState(() {
_sunsetErrorText = val;
});
},
),
),
const SizedBox(width: 8),
IconButton.filledTonal(
icon:
_loading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Icon(Icons.cloud_done_outlined),
tooltip: 'Fetch Open-Meteo Sunset Data',
onPressed: () {
if (!_loading) {
_fetchSunset();
}
},
),
],
),
const SizedBox(height: 16),
const Text(
'Alert',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
// --- Minutes before ---
Row(
children: [
if (!_alarmEnabled)
Expanded( Expanded(
child: TextField( child: TextField(
controller: _minutesController, controller: _latController,
keyboardType: TextInputType.number, keyboardType: const TextInputType.numberWithOptions(
decoration: InputDecoration( signed: true,
decimal: true,
),
decoration: const InputDecoration(
labelText: 'Latitude',
border: OutlineInputBorder(), border: OutlineInputBorder(),
labelText: 'Minutes Before Sunset', ),
errorText: _minuteErrorText, onChanged: (_) => _savePrefs(),
),
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _lngController,
keyboardType: const TextInputType.numberWithOptions(
signed: true,
decimal: true,
),
decoration: const InputDecoration(
labelText: 'Longitude',
border: OutlineInputBorder(),
),
onChanged: (_) => _savePrefs(),
),
),
const SizedBox(width: 8),
IconButton.filledTonal(
icon: const Icon(Icons.my_location),
tooltip: 'Get GPS Location',
onPressed: _getGpsLocation,
),
],
),
const SizedBox(height: 16),
// --- Sunset time section ---
const Text(
'Sunset',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: TextField(
controller: _sunsetController,
decoration: InputDecoration(
labelText: 'Sunset Time (HH:MM)',
border: OutlineInputBorder(),
errorText: _sunsetErrorText,
suffixIcon: IconButton(
icon: Icon(Icons.access_time),
onPressed: () async {
final picked = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
if (picked != null) {
_sunsetController.text =
'${picked.hour.toString().padLeft(2, '0')}:'
'${picked.minute.toString().padLeft(2, '0')}';
setState(
() =>
_sunsetErrorText = _validateTime(
_sunsetController.text,
),
);
if (_alarmEnabled) {
_scheduleAlarm();
}
}
},
),
), ),
onChanged: (v) { onChanged: (v) {
final val = int.tryParse(v); final val = _validateTime(v);
setState(() { if (val == null) {
if (val != null) { _savePrefs();
_minuteErrorText = null; if (_alarmEnabled) {
_minutesBefore = val; _scheduleAlarm();
} else {
_minuteErrorText = 'Invalid Minute Value';
} }
_plural = (_minutesBefore == 1) ? '' : 's'; }
setState(() {
_sunsetErrorText = val;
}); });
}, },
), ),
), ),
const SizedBox(width: 8),
// --- Toggle --- IconButton.filledTonal(
Expanded( icon:
child: SwitchListTile( _loading
//title: const Text('Enable Sunset Alarm'), ? const SizedBox(
title: Text(_alarmEnabled ? 'Sunset Alarm Enabled' : ''), width: 20,
subtitle: Text( height: 20,
_alarmEnabled child: CircularProgressIndicator(strokeWidth: 2),
? '${_minutesBefore} Minute${_plural} Before @ $_alarmDisplayTime' )
: '', : const Icon(Icons.cloud_done_outlined),
), tooltip: 'Fetch Open-Meteo Sunset Data',
value: _alarmEnabled, onPressed: () {
onChanged: _toggleAlarm, if (!_loading) {
_fetchSunset();
}
},
), ),
), ],
], ),
), const SizedBox(height: 16),
],
const Text(
'Alert',
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
// --- Minutes before ---
Row(
children: [
if (!_alarmEnabled)
Expanded(
child: TextField(
controller: _minutesController,
keyboardType: TextInputType.number,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Minutes Before Sunset',
errorText: _minuteErrorText,
),
onChanged: (v) {
final val = int.tryParse(v);
setState(() {
if (val != null) {
_minuteErrorText = null;
_minutesBefore = val;
} else {
_minuteErrorText = 'Invalid Minute Value';
}
_plural = (_minutesBefore == 1) ? '' : 's';
});
},
),
),
// --- Toggle ---
Expanded(
child: SwitchListTile(
//title: const Text('Enable Sunset Alarm'),
title: Text(_alarmEnabled ? 'Sunset Alarm Enabled' : ''),
subtitle: Text(
_alarmEnabled
? '${_minutesBefore} Minute${_plural} Before @ $_alarmDisplayTime'
: '',
),
value: _alarmEnabled,
onChanged: _toggleAlarm,
),
),
],
),
],
),
), ),
), ),
); );