diff --git a/lib/BlindMasterScreens/groupControl/group_screen.dart b/lib/BlindMasterScreens/groupControl/group_screen.dart new file mode 100644 index 0000000..ebd0c27 --- /dev/null +++ b/lib/BlindMasterScreens/groupControl/group_screen.dart @@ -0,0 +1,277 @@ +import 'dart:convert'; + +import 'package:blind_master/BlindMasterResources/blindmaster_progress_indicator.dart'; +import 'package:blind_master/BlindMasterResources/error_snackbar.dart'; +import 'package:blind_master/BlindMasterResources/secure_transmissions.dart'; +import 'package:flutter/material.dart'; +import 'package:google_fonts/google_fonts.dart'; + +class GroupScreen extends StatefulWidget { + const GroupScreen({super.key, required this.groupId, required this.groupName}); + final int groupId; + final String groupName; + + @override + State createState() => _GroupScreenState(); +} + +class _GroupScreenState extends State { + String imagePath = ""; + bool loaded = false; + double _blindPosition = 5.0; + List> peripherals = []; + bool allCalibrated = false; + + void getImage() { + final hour = DateTime.now().hour; + + if (hour >= 5 && hour < 10) { + imagePath = 'assets/images/MorningSill.png'; + } else if (hour >= 10 && hour < 18) { + imagePath = 'assets/images/NoonSill.png'; + } else if (hour >= 18 && hour < 22) { + imagePath = 'assets/images/EveningSill.png'; + } else { + imagePath = 'assets/images/NightSill.png'; + } + } + + @override + void initState() { + super.initState(); + initAll(); + } + + Future getGroupDetails() async { + try { + final payload = {'groupId': widget.groupId.toString()}; + final response = await secureGet('group_details', queryParameters: payload); + + if (response == null) throw Exception("auth error"); + if (response.statusCode != 200) throw Exception("Server Error"); + + final body = json.decode(response.body); + peripherals = List>.from(body['peripherals']); + + // Check if all peripherals are calibrated + allCalibrated = peripherals.every((p) => p['calibrated'] == true); + + // Set position to average of all peripheral positions + if (peripherals.isNotEmpty) { + final avgPos = peripherals.map((p) => p['last_pos'] as int).reduce((a, b) => a + b) / peripherals.length; + _blindPosition = avgPos.toDouble(); + } + + getImage(); + setState(() => loaded = true); + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e)); + } + } + + Future initAll() async { + await getGroupDetails(); + } + + Future updateGroupPosition() async { + try { + final payload = { + 'groupId': widget.groupId, + 'newPos': _blindPosition.toInt(), + }; + + final response = await securePost(payload, 'group_position_update'); + if (response == null) throw Exception("Auth Error"); + if (response.statusCode != 202) { + throw Exception("Server Error"); + } + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar(errorSnackbar(e)); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text( + widget.groupName, + style: GoogleFonts.aBeeZee(), + ), + backgroundColor: Theme.of(context).primaryColorLight, + foregroundColor: Colors.white, + ), + body: loaded + ? (allCalibrated + ? Column( + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.5, + child: Container( + padding: EdgeInsets.fromLTRB(0, 20, 0, 20), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: MediaQuery.of(context).size.width * 0.15, + ), + Stack( + children: [ + // Background image + Align( + alignment: Alignment.center, + child: Image.asset( + imagePath, + width: MediaQuery.of(context).size.width * 0.7, + ), + ), + + Align( + alignment: Alignment.center, + child: LayoutBuilder( + builder: (context, constraints) { + final containerHeight = MediaQuery.of(context).size.width * 0.68; + final maxSlatHeight = containerHeight / 10; + final slatHeight = _blindPosition < 5 + ? maxSlatHeight * (5 - _blindPosition) / 5 + : maxSlatHeight * (_blindPosition - 5) / 5; + + return Container( + margin: EdgeInsets.only(top: MediaQuery.of(context).size.width * 0.05), + height: containerHeight, + width: MediaQuery.of(context).size.width * 0.7, + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: List.generate(10, (index) { + return AnimatedContainer( + duration: const Duration(milliseconds: 300), + height: slatHeight, + width: MediaQuery.of(context).size.width * 0.65, + color: const Color.fromARGB(255, 121, 85, 72), + ); + }), + ), + ); + } + ) + ) + ], + ), + // Slider on the side + Expanded( + child: Center( + child: RotatedBox( + quarterTurns: -1, + child: Slider( + value: _blindPosition, + activeColor: Theme.of(context).primaryColorDark, + thumbColor: Theme.of(context).primaryColorLight, + inactiveColor: Theme.of(context).primaryColorDark, + min: 0, + max: 10, + divisions: 10, + onChanged: (value) { + setState(() { + _blindPosition = value; + updateGroupPosition(); + }); + }, + ), + ), + ) + ) + ], + ), + ) + ), + Container( + padding: EdgeInsets.all(25), + child: Text( + '${peripherals.length} blind${peripherals.length != 1 ? 's' : ''} in this group' + ), + ), + Container( + padding: EdgeInsets.all(10), + child: ElevatedButton( + onPressed: () { + // TODO: Navigate to group schedules screen + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Group schedules coming soon!')) + ); + }, + child: Text( + "Set Group Schedules" + ) + ), + ) + ] + ) + : SizedBox( + height: MediaQuery.of(context).size.height * 0.8, + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: EdgeInsets.all(20), + child: Text( + "Some blinds in this group are not calibrated", + textAlign: TextAlign.center, + style: TextStyle(fontSize: 16), + ), + ), + SizedBox(height: 20), + Text( + "Uncalibrated blinds:", + style: TextStyle(fontWeight: FontWeight.bold), + ), + SizedBox(height: 10), + ...peripherals.where((p) => p['calibrated'] != true).map((p) => + Padding( + padding: EdgeInsets.symmetric(vertical: 4), + child: Text(p['peripheral_name']), + ) + ), + ], + ) + ) + )) + : BlindmasterProgressIndicator(), + floatingActionButton: Container( + padding: EdgeInsets.all(25), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + FloatingActionButton( + heroTag: "placeholder1", + tooltip: "Placeholder", + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Feature coming soon!')) + ); + }, + foregroundColor: Theme.of(context).highlightColor, + backgroundColor: Theme.of(context).primaryColorDark, + child: Icon(Icons.settings), + ), + FloatingActionButton( + heroTag: "placeholder2", + tooltip: "Placeholder", + onPressed: () { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar(content: Text('Feature coming soon!')) + ); + }, + foregroundColor: Theme.of(context).highlightColor, + backgroundColor: Theme.of(context).primaryColorDark, + child: Icon(Icons.tune), + ), + ], + ), + ), + floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat, + ); + } +} diff --git a/lib/BlindMasterScreens/groupControl/groups_menu.dart b/lib/BlindMasterScreens/groupControl/groups_menu.dart index 788cfb8..1fd9fda 100644 --- a/lib/BlindMasterScreens/groupControl/groups_menu.dart +++ b/lib/BlindMasterScreens/groupControl/groups_menu.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:blind_master/BlindMasterScreens/groupControl/create_group_dialog.dart'; +import 'package:blind_master/BlindMasterScreens/groupControl/group_screen.dart'; import 'package:blind_master/BlindMasterResources/secure_transmissions.dart'; import 'package:flutter/material.dart'; @@ -144,7 +145,15 @@ class _GroupsMenuState extends State { title: Text(group['name']), trailing: const Icon(Icons.arrow_forward_ios_rounded), onTap: () { - // TODO: Navigate to group details screen + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => GroupScreen( + groupId: group['id'], + groupName: group['name'], + ), + ), + ); }, ), ),