From 45bc226c0db312d916c76ceb31887a33e8b01509 Mon Sep 17 00:00:00 2001 From: Ian B Date: Sat, 14 Oct 2023 16:29:52 -0400 Subject: [PATCH 1/2] Dynamic Users and Devices --- app/watt_wizard/lib/homescreen.dart | 30 +++- app/watt_wizard/lib/main.dart | 17 ++ app/watt_wizard/lib/profile.dart | 30 +++- .../lib/widgets/homes_leaderboard.dart | 145 ++++++++++++++++++ .../users_leaderboard.dart} | 55 ++++--- 5 files changed, 245 insertions(+), 32 deletions(-) create mode 100644 app/watt_wizard/lib/widgets/homes_leaderboard.dart rename app/watt_wizard/lib/{leaderboard.dart => widgets/users_leaderboard.dart} (77%) diff --git a/app/watt_wizard/lib/homescreen.dart b/app/watt_wizard/lib/homescreen.dart index 3f693ce..8df6ab5 100644 --- a/app/watt_wizard/lib/homescreen.dart +++ b/app/watt_wizard/lib/homescreen.dart @@ -6,7 +6,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:watt_wizard/profile.dart'; -import 'leaderboard.dart'; +import 'widgets/users_leaderboard.dart'; +import 'widgets/homes_leaderboard.dart'; class HomeScreen extends StatefulWidget { const HomeScreen({super.key}); @@ -113,7 +114,19 @@ class _HomeScreenState extends State { } else {} }, child: const Text("Turn Bluetooth On"), - ) + ), + // Center( + // child: Column( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // SizedBox( + // width: sizedBoxWidth, + // height: sizedBoxHeight, + // child: UserList(), + // ) + // ], + // ), + // ), ], ), ), @@ -129,7 +142,18 @@ class _HomeScreenState extends State { ], ), ), - const Center(child: Text("hello")) + Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: sizedBoxWidth, + height: sizedBoxHeight, + child: HomeList(), + ) + ], + ), + ), ], ), ), diff --git a/app/watt_wizard/lib/main.dart b/app/watt_wizard/lib/main.dart index af4300a..3181de0 100644 --- a/app/watt_wizard/lib/main.dart +++ b/app/watt_wizard/lib/main.dart @@ -1,9 +1,12 @@ +import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:firebase_core/firebase_core.dart'; import 'firebase_options.dart'; import 'homescreen.dart'; +final db = FirebaseFirestore.instance; + Future main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform); @@ -49,6 +52,20 @@ class MyHomePage extends StatelessWidget { void _signInWithGitHub() async { GithubAuthProvider githubProvider = GithubAuthProvider(); await FirebaseAuth.instance.signInWithProvider(githubProvider); + if (FirebaseAuth.instance.currentUser != null) { + var userUID = await db.collection('users').doc(FirebaseAuth.instance.currentUser?.uid).get(); + if (!userUID.exists) { + await db.collection('users').doc(FirebaseAuth.instance.currentUser?.uid).set( + { + 'devices': [], + 'friends': [], + 'home': "", + 'name': FirebaseAuth.instance.currentUser?.displayName, + 'pfp': 'https://m.media-amazon.com/images/I/612-e1vHBAL._AC_SL1145_.jpg' + }, + ); + } + } } @override diff --git a/app/watt_wizard/lib/profile.dart b/app/watt_wizard/lib/profile.dart index 71f8151..0b56dd9 100644 --- a/app/watt_wizard/lib/profile.dart +++ b/app/watt_wizard/lib/profile.dart @@ -1,11 +1,15 @@ import 'dart:async'; +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:flutter/material.dart'; import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:watt_wizard/widgets/connected_device_tile.dart'; import 'package:watt_wizard/widgets/scan_result_tile.dart'; import 'package:watt_wizard/utils/extra.dart'; +final db = FirebaseFirestore.instance; + class ProfileScreen extends StatefulWidget { const ProfileScreen({super.key, required this.username}); @@ -40,8 +44,7 @@ class _ProfileScreenState extends State { _scanResultsSubscription = FlutterBluePlus.scanResults.listen((results) { List result = []; for (ScanResult r in results) { - if (r.device.platformName == "Light Control" && - !_connectedDevices.contains(r.device)) { + if (r.device.platformName == "Light Control" && !_connectedDevices.contains(r.device)) { result.add(r); } } @@ -71,9 +74,27 @@ class _ProfileScreenState extends State { FlutterBluePlus.stopScan(); } - void onConnectPressed(BluetoothDevice device) { + void onConnectPressed(BluetoothDevice device) async { device.connectAndUpdateStream(); _connectedDevices.add(device); + + var userDoc = await db.collection('users').doc(FirebaseAuth.instance.currentUser?.uid).get(); + List devices = await userDoc.get('devices'); + + int deviceHash = device.hashCode; + + if (!devices.any((element) => element['id'] == deviceHash)) { + await db.collection('users').doc(FirebaseAuth.instance.currentUser?.uid).update({ + 'devices': FieldValue.arrayUnion([ + { + 'id': deviceHash, + 'name': device.platformName, + 'power': 0 + } + ]) + }); + } + setState(() {}); } @@ -119,8 +140,7 @@ class _ProfileScreenState extends State { child: const Icon(Icons.stop), ); } else { - return FloatingActionButton( - onPressed: onScanPressed, child: const Text("SCAN")); + return FloatingActionButton(onPressed: onScanPressed, child: const Text("SCAN")); } } diff --git a/app/watt_wizard/lib/widgets/homes_leaderboard.dart b/app/watt_wizard/lib/widgets/homes_leaderboard.dart new file mode 100644 index 0000000..1aa6848 --- /dev/null +++ b/app/watt_wizard/lib/widgets/homes_leaderboard.dart @@ -0,0 +1,145 @@ +// ignore_for_file: prefer_const_constructors_in_immutables,unnecessary_const,library_private_types_in_public_api,avoid_print +// Copyright 2021, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/material.dart'; +import '../firebase_options.dart'; + +// Future main() async { +// WidgetsFlutterBinding.ensureInitialized(); +// await Firebase.initializeApp(options: defaultFirebaseOptions); +// } + +final _db = FirebaseFirestore.instance; + +/// A reference to the list of movies. +/// We are using `withConverter` to ensure that interactions with the collection +/// are type-safe. +final _homesRef = _db.collection('homes').withConverter<_Home>( + fromFirestore: (snapshots, _) => _Home.fromJson(snapshots.data()!), + toFirestore: (home, _) => home.toJson(), + ); + +/// Holds all example app films +class HomeList extends StatefulWidget { + const HomeList({Key? key}) : super(key: key); + + @override + _HomeListState createState() => _HomeListState(); +} + +class _HomeListState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: StreamBuilder>( + stream: _homesRef.snapshots(), + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text(snapshot.error.toString()), + ); + } + + if (!snapshot.hasData) { + return const Center(child: CircularProgressIndicator()); + } + + final data = snapshot.requireData; + + return ListView.builder( + itemCount: data.size, + itemBuilder: (context, index) { + return _HomeItem( + data.docs[index].data(), + data.docs[index].reference, + ); + }, + ); + }, + ), + ); + } +} + +/// A single movie row. +class _HomeItem extends StatelessWidget { + _HomeItem(this.home, this.reference); + + final _Home home; + final DocumentReference<_Home> reference; + + /// Returns the movie poster. + Widget get pfp { + return SizedBox( + width: 100, + child: Image.network(home.pfp), + ); + } + + /// Returns user details. + Widget get details { + return Padding( + padding: const EdgeInsets.only(left: 8, right: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + name, + ], + ), + ); + } + + // Return the home name. + Widget get name { + return Text( + '${home.name}', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ); + } + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(bottom: 4, top: 4), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + pfp, + Flexible(child: details), + ], + ), + ); + } +} + +@immutable +class _Home { + _Home({ + required this.pfp, + required this.users, + required this.name, + }); + + _Home.fromJson(Map json) + : this( + pfp: json['pfp']! as String, + users: json['users']! as List, + name: json['name']! as String, + ); + + final String pfp; + final List users; + final String name; + + Map toJson() { + return { + 'users': users, + 'pfp': pfp, + 'name': name, + }; + } +} diff --git a/app/watt_wizard/lib/leaderboard.dart b/app/watt_wizard/lib/widgets/users_leaderboard.dart similarity index 77% rename from app/watt_wizard/lib/leaderboard.dart rename to app/watt_wizard/lib/widgets/users_leaderboard.dart index 4140957..03e845c 100644 --- a/app/watt_wizard/lib/leaderboard.dart +++ b/app/watt_wizard/lib/widgets/users_leaderboard.dart @@ -6,19 +6,19 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; -import 'firebase_options.dart'; +import '../firebase_options.dart'; // Future main() async { // WidgetsFlutterBinding.ensureInitialized(); // await Firebase.initializeApp(options: defaultFirebaseOptions); // } -final db = FirebaseFirestore.instance; +final _db = FirebaseFirestore.instance; /// A reference to the list of movies. /// We are using `withConverter` to ensure that interactions with the collection /// are type-safe. -final usersRef = db.collection('users').withConverter<_User>( +final _usersRef = _db.collection('users').withConverter<_User>( fromFirestore: (snapshots, _) => _User.fromJson(snapshots.data()!), toFirestore: (user, _) => user.toJson(), ); @@ -36,7 +36,7 @@ class _UserListState extends State { Widget build(BuildContext context) { return Scaffold( body: StreamBuilder>( - stream: usersRef.snapshots(), + stream: _usersRef.snapshots(), builder: (context, snapshot) { if (snapshot.hasError) { return Center( @@ -82,24 +82,24 @@ class _UserItem extends StatelessWidget { /// Returns user details. Widget get details { - return const Padding( + return Padding( padding: const EdgeInsets.only(left: 8, right: 8), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // name, + name, ], ), ); } - /// Return the user name. - // Widget get name { - // return Text( - // '${user.name}', - // style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), - // ); - // } + // Return the user name. + Widget get name { + return Text( + '${user.name}', + style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ); + } @override Widget build(BuildContext context) { @@ -118,29 +118,36 @@ class _UserItem extends StatelessWidget { @immutable class _User { - _User({required this.pfp, required this.friends, required this.household - // required this.name, - }); + _User({ + required this.pfp, + required this.friends, + required this.home, + required this.name, + required this.devices, + }); _User.fromJson(Map json) : this( pfp: json['pfp']! as String, - household: json['household']! as String, - friends: json['friends']! as String, - // name: json['name']! as String, + home: json['home']! as String, + friends: json['friends']! as List, + name: json['name']! as String, + devices: json['devices']! as List, ); final String pfp; - final String friends; - final String household; - // final String name; + final List friends; + final String home; + final String name; + final List devices; Map toJson() { return { - 'household': household, + 'home': home, 'friends': friends, 'pfp': pfp, - // 'name': name, + 'name': name, + 'devices': devices }; } } From 9ea3eb6994244a254da141cc7e470c2b3b798613 Mon Sep 17 00:00:00 2001 From: Ian B Date: Sat, 14 Oct 2023 18:47:11 -0400 Subject: [PATCH 2/2] Add Selection Button --- app/watt_wizard/lib/profile.dart | 2 +- .../lib/widgets/homes_leaderboard.dart | 47 ++++++++++++++++--- .../lib/widgets/users_leaderboard.dart | 21 ++++++++- 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/app/watt_wizard/lib/profile.dart b/app/watt_wizard/lib/profile.dart index 0b56dd9..624d2f3 100644 --- a/app/watt_wizard/lib/profile.dart +++ b/app/watt_wizard/lib/profile.dart @@ -89,7 +89,7 @@ class _ProfileScreenState extends State { { 'id': deviceHash, 'name': device.platformName, - 'power': 0 + 'power': [] } ]) }); diff --git a/app/watt_wizard/lib/widgets/homes_leaderboard.dart b/app/watt_wizard/lib/widgets/homes_leaderboard.dart index 1aa6848..faf82b5 100644 --- a/app/watt_wizard/lib/widgets/homes_leaderboard.dart +++ b/app/watt_wizard/lib/widgets/homes_leaderboard.dart @@ -4,6 +4,7 @@ // BSD-style license that can be found in the LICENSE file. import 'package:cloud_firestore/cloud_firestore.dart'; +import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import '../firebase_options.dart'; @@ -19,7 +20,7 @@ final _db = FirebaseFirestore.instance; /// We are using `withConverter` to ensure that interactions with the collection /// are type-safe. final _homesRef = _db.collection('homes').withConverter<_Home>( - fromFirestore: (snapshots, _) => _Home.fromJson(snapshots.data()!), + fromFirestore: (snapshots, _) => _Home.fromJson(snapshots.data()!, snapshots.id), toFirestore: (home, _) => home.toJson(), ); @@ -65,6 +66,29 @@ class _HomeListState extends State { } } +Future updateHome(_Home home) async { + String userUID = FirebaseAuth.instance.currentUser!.uid; + + var userSnapshot = await _db.collection('users').doc(userUID).get(); + String userHome = userSnapshot.get('home') as String; + + await _db.collection('users').doc(userUID).update({ + 'home': home.id, + }); + + await _db.collection('homes').doc(userHome).update({ + 'users': FieldValue.arrayRemove([ + userUID + ]) + }).onError((error, stackTrace) => null); + + await _db.collection('homes').doc(home.id).update({ + 'users': FieldValue.arrayUnion([ + userUID + ]) + }); +} + /// A single movie row. class _HomeItem extends StatelessWidget { _HomeItem(this.home, this.reference); @@ -101,15 +125,26 @@ class _HomeItem extends StatelessWidget { ); } + Widget updateHomeButton(BuildContext context, _Home home) { + return FloatingActionButton( + onPressed: () async { + await updateHome(home); + }, + child: const Text("SELECT"), + ); + } + @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.only(bottom: 4, top: 4), child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, crossAxisAlignment: CrossAxisAlignment.start, children: [ pfp, Flexible(child: details), + updateHomeButton(context, home), ], ), ); @@ -118,22 +153,20 @@ class _HomeItem extends StatelessWidget { @immutable class _Home { - _Home({ - required this.pfp, - required this.users, - required this.name, - }); + _Home({required this.pfp, required this.users, required this.name, required this.id}); - _Home.fromJson(Map json) + _Home.fromJson(Map json, String id) : this( pfp: json['pfp']! as String, users: json['users']! as List, name: json['name']! as String, + id: id, ); final String pfp; final List users; final String name; + final String id; Map toJson() { return { diff --git a/app/watt_wizard/lib/widgets/users_leaderboard.dart b/app/watt_wizard/lib/widgets/users_leaderboard.dart index 03e845c..18dee92 100644 --- a/app/watt_wizard/lib/widgets/users_leaderboard.dart +++ b/app/watt_wizard/lib/widgets/users_leaderboard.dart @@ -88,6 +88,7 @@ class _UserItem extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ name, + power, ], ), ); @@ -96,11 +97,29 @@ class _UserItem extends StatelessWidget { // Return the user name. Widget get name { return Text( - '${user.name}', + user.name, style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold), ); } + Widget get power { + int power = 0; + for (var i in user.devices) { + Map device = i as Map; + try { + List powerList = (device['power'] as List).map((item) => item as int).toList(); + power += powerList.last; + } catch (e) { + print('Power Array Empty for: ${user.name}'); + } + } + + return Text( + 'Active Power Consumption: ${power.toString()}', + style: const TextStyle(fontSize: 12), + ); + } + @override Widget build(BuildContext context) { return Padding(