diff --git a/live_score_flutter_app/README.md b/live_score_flutter_app/README.md index 25efd67..8295c39 100644 --- a/live_score_flutter_app/README.md +++ b/live_score_flutter_app/README.md @@ -1,16 +1,87 @@ -# live_score_flutter_app +# Live Score Flutter App +

+ +

-A new Flutter project. +Many times a lot of games like cricket, football, badminton are occuing in your college but you are very lazy to attend these live or maybe you are at your home or not available at college and unfortunately you have to miss these events and its thrill. +But now no need to be disheartened, Live Score App will not only let you know the live score but also see its highlights moments. You will also get notified for the upcoming games in your college or any other college. You can take a look at the previous games too. +Made using Firebase and Flutter -## Getting Started +## Features -This project is a starting point for a Flutter application. +- **Ongoing Section:** View the score and highlights live +

+ + +

-A few resources to get you started if this is your first Flutter project: -- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) -For help getting started with Flutter development, view the -[online documentation](https://docs.flutter.dev/), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +- **Previous Section:** View the score, highlights and winner of the previous games + +

+ + +

+ +- **Announcement Section:** Get notified about the upcoming games as well as choose to subscribe to your interest college notifications + +

+ + +

+ +- **Authentication Section:** Login or signup to create games +

+ + +

+ +- **Admin Section:** Create games occuring in your college and update its score accordingly. You can also choose to notify users about upcoming games + +

+ + + +

+ + +# Demo Video + +Here is the demo video of working of this app: [Watch the Video](https://youtu.be/RqpSwHATNSU) + +## How to Run + +1. Download or Clone the repo +2. Install all the packages by typing the following command into your terminal + + ```sh + flutter pub get + ``` +3. Go to [games-admin-provider](lib/providers/games_admin_provider.dart) file and replace **${dotenv.env["SERVER_KEY"]}** with the firebase server key + +```dart + Future sendNotificationToDevices( + String title, String body, String topic) async { + await http.post(Uri.parse('https://fcm.googleapis.com/fcm/send'), + headers: { + 'Content-Type': 'application/json', + 'Authorization': 'key=${dotenv.env["SERVER_KEY"]}', + }, + body: jsonEncode({ + "condition": "'$topic' in topics || 'All' in topics", + 'notification': { + 'android_channel_id': 'pushnotificationapp', + 'title': title, + 'body': body, + }, + })); + } +``` + +### Apk link: +[Live Score Apk Download](https://drive.google.com/file/d/15Oe7inzXqqoRK0KAg_L7alOm9GaOZX8C/view?usp=share_link) + +## Contact + +- [Faiz Khan](https://github.com/FaizFk/) | [LinkedIn](https://linkedin.com/in/faiz-khan-4793731ba/) diff --git a/live_score_flutter_app/Screenshots/admin-screen.jpg b/live_score_flutter_app/Screenshots/admin-screen.jpg new file mode 100644 index 0000000..9be26de Binary files /dev/null and b/live_score_flutter_app/Screenshots/admin-screen.jpg differ diff --git a/live_score_flutter_app/Screenshots/announcement-settings.jpg b/live_score_flutter_app/Screenshots/announcement-settings.jpg new file mode 100644 index 0000000..063b792 Binary files /dev/null and b/live_score_flutter_app/Screenshots/announcement-settings.jpg differ diff --git a/live_score_flutter_app/Screenshots/announcements.jpg b/live_score_flutter_app/Screenshots/announcements.jpg new file mode 100644 index 0000000..b20df17 Binary files /dev/null and b/live_score_flutter_app/Screenshots/announcements.jpg differ diff --git a/live_score_flutter_app/Screenshots/auth-screen.jpg b/live_score_flutter_app/Screenshots/auth-screen.jpg new file mode 100644 index 0000000..d3c3483 Binary files /dev/null and b/live_score_flutter_app/Screenshots/auth-screen.jpg differ diff --git a/live_score_flutter_app/Screenshots/create-game.jpg b/live_score_flutter_app/Screenshots/create-game.jpg new file mode 100644 index 0000000..b2ca961 Binary files /dev/null and b/live_score_flutter_app/Screenshots/create-game.jpg differ diff --git a/live_score_flutter_app/Screenshots/live_score_icon.png b/live_score_flutter_app/Screenshots/live_score_icon.png new file mode 100644 index 0000000..064b7f6 Binary files /dev/null and b/live_score_flutter_app/Screenshots/live_score_icon.png differ diff --git a/live_score_flutter_app/Screenshots/login.jpg b/live_score_flutter_app/Screenshots/login.jpg new file mode 100644 index 0000000..3618cb5 Binary files /dev/null and b/live_score_flutter_app/Screenshots/login.jpg differ diff --git a/live_score_flutter_app/Screenshots/ongoing-details.jpg b/live_score_flutter_app/Screenshots/ongoing-details.jpg new file mode 100644 index 0000000..81c994a Binary files /dev/null and b/live_score_flutter_app/Screenshots/ongoing-details.jpg differ diff --git a/live_score_flutter_app/Screenshots/ongoing.jpg b/live_score_flutter_app/Screenshots/ongoing.jpg new file mode 100644 index 0000000..243dbaf Binary files /dev/null and b/live_score_flutter_app/Screenshots/ongoing.jpg differ diff --git a/live_score_flutter_app/Screenshots/previous-details.jpg b/live_score_flutter_app/Screenshots/previous-details.jpg new file mode 100644 index 0000000..150ad24 Binary files /dev/null and b/live_score_flutter_app/Screenshots/previous-details.jpg differ diff --git a/live_score_flutter_app/Screenshots/previous.jpg b/live_score_flutter_app/Screenshots/previous.jpg new file mode 100644 index 0000000..54d0f66 Binary files /dev/null and b/live_score_flutter_app/Screenshots/previous.jpg differ diff --git a/live_score_flutter_app/Screenshots/score-controller.jpg b/live_score_flutter_app/Screenshots/score-controller.jpg new file mode 100644 index 0000000..813c255 Binary files /dev/null and b/live_score_flutter_app/Screenshots/score-controller.jpg differ diff --git a/live_score_flutter_app/Screenshots/signup.jpg b/live_score_flutter_app/Screenshots/signup.jpg new file mode 100644 index 0000000..147c07b Binary files /dev/null and b/live_score_flutter_app/Screenshots/signup.jpg differ diff --git a/live_score_flutter_app/android/app/src/main/AndroidManifest.xml b/live_score_flutter_app/android/app/src/main/AndroidManifest.xml index f757622..330b4b1 100644 --- a/live_score_flutter_app/android/app/src/main/AndroidManifest.xml +++ b/live_score_flutter_app/android/app/src/main/AndroidManifest.xml @@ -1,7 +1,7 @@ backgroundHandler(RemoteMessage message) async { print(message.data.toString()); print(message.notification!.title); @@ -74,13 +73,15 @@ class _MyAppState extends State { } }, ); + + //Subscribing to topic 'All' if first installing the app + GameUsersProvider.subscribeToAllIfFirstTime(); } // This widget is the root of your application. @override Widget build(BuildContext context) { bool isAuth = FirebaseAuth.instance.currentUser != null; - return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => GamesAdminProvider()), diff --git a/live_score_flutter_app/lib/providers/games_admin_provider.dart b/live_score_flutter_app/lib/providers/games_admin_provider.dart index 107c8f8..ffe8769 100644 --- a/live_score_flutter_app/lib/providers/games_admin_provider.dart +++ b/live_score_flutter_app/lib/providers/games_admin_provider.dart @@ -32,16 +32,15 @@ class GamesAdminProvider extends ChangeNotifier { notifyListeners(); } - Future sendNotificationToDevices(String title, String body) async { + Future sendNotificationToDevices( + String title, String body, String topic) async { await http.post(Uri.parse('https://fcm.googleapis.com/fcm/send'), headers: { 'Content-Type': 'application/json', 'Authorization': 'key=${dotenv.env["SERVER_KEY"]}', }, body: jsonEncode({ - "registration_ids": [ - "f63uvo1ORx-dMmGaZkkO3w:APA91bH4Js6NtsmiFvEWAYHDk_1MWsKAsJYtp621i5QnHBxwnq6otIjhTH52raY4iOW4_juE--BPj2rpBVRdG1krUdR4t8YWN1JBAonQLBTVxd1RMz7FvhYypij6yLELZye67SsIt3WW" - ], + "condition": "'$topic' in topics || 'All' in topics", 'notification': { 'android_channel_id': 'pushnotificationapp', 'title': title, @@ -64,7 +63,10 @@ class GamesAdminProvider extends ChangeNotifier { id: myDoc.id, ); await myDoc.set(announcement.toJson()); - await sendNotificationToDevices('${announcement.creatorName} (${announcement.collegeName})', announcement.message); + await sendNotificationToDevices( + '${announcement.creatorName} (${announcement.collegeName})', + announcement.message, + announcement.collegeName.replaceAll(' ', '')); Utils.showSnackbar('Your message was announced'); } catch (e) { print(e); @@ -117,28 +119,32 @@ class GamesAdminProvider extends ChangeNotifier { Future changeScore( {bool isIncrease = true, bool isScore1 = true}) async { - if (isScore1) { - if (isIncrease) { - await _dbOngoingGamesRT - .child('${currentGame.id}/score1') - .set(++_currentGame.score1); - } else { - await _dbOngoingGamesRT - .child('${currentGame.id}/score1') - .set(_currentGame.score1 > 0 ? --_currentGame.score1 : 0); - } - } else { - if (isIncrease) { - await _dbOngoingGamesRT - .child('${currentGame.id}/score2') - .set(++_currentGame.score2); + try { + if (isScore1) { + if (isIncrease) { + await _dbOngoingGamesRT + .child('${currentGame.id}/score1') + .set(++_currentGame.score1); + } else { + await _dbOngoingGamesRT + .child('${currentGame.id}/score1') + .set(_currentGame.score1 > 0 ? --_currentGame.score1 : 0); + } } else { - await _dbOngoingGamesRT - .child('${currentGame.id}/score2') - .set((_currentGame.score2) > 0 ? --_currentGame.score2 : 0); + if (isIncrease) { + await _dbOngoingGamesRT + .child('${currentGame.id}/score2') + .set(++_currentGame.score2); + } else { + await _dbOngoingGamesRT + .child('${currentGame.id}/score2') + .set((_currentGame.score2) > 0 ? --_currentGame.score2 : 0); + } } + notifyListeners(); + } catch (e) { + Utils.showSnackbar('Please check your internet connection'); } - notifyListeners(); } Future sendKeyMoments(String message) async { diff --git a/live_score_flutter_app/lib/providers/games_users_provider.dart b/live_score_flutter_app/lib/providers/games_users_provider.dart index 8fd037d..654d9ac 100644 --- a/live_score_flutter_app/lib/providers/games_users_provider.dart +++ b/live_score_flutter_app/lib/providers/games_users_provider.dart @@ -95,14 +95,76 @@ class GameUsersProvider extends ChangeNotifier { Future> getAllCollegesOngoing() async { List collegeList = []; - final snapshotGames = await _dbOngoingGamesRT.get(); - if (snapshotGames.exists) { - Map result = - snapshotGames.value as Map; - collegeList = List.from(result.values.map((e) { - return e['college']; - }).toSet()); + try { + final snapshotGames = await _dbOngoingGamesRT.get(); + if (snapshotGames.exists) { + Map result = + snapshotGames.value as Map; + collegeList = List.from(result.values.map((e) { + return e['college']; + }).toSet()); + } + } catch (e) { + Utils.showSnackbar("Something went wrong"); } return collegeList; } + + Future> getAllColleges() async { + Map collegeMap = {}; + List collegeList = []; + try { + final snapshotUsers = await _dbUsers.get(); + if (snapshotUsers.size > 0) { + collegeList = List.from( + snapshotUsers.docs.map((e) => e['collegeName']).toSet()); + } + for (String college in ['All', ...collegeList]) { + collegeMap[college] = await isSubscribedToCollege(college); + } + } catch (e) { + print(e); + Utils.showSnackbar('Something went wrong, Check your network connection'); + } + return collegeMap; + } + + static Future subscribeToAllIfFirstTime() async { + try { + final pref = await SharedPreferences.getInstance(); + if (pref.getBool('All') == null) { + await FirebaseMessaging.instance.subscribeToTopic('All'); + await pref.setBool('All', true); + } + } catch (e) { + print(e); + Utils.showSnackbar('something went wrong'); + } + } + + Future subscribeToCollege(bool value, String college) async { + college = college.replaceAll(' ', ''); + try { + final pref = await SharedPreferences.getInstance(); + if (value) { + await FirebaseMessaging.instance.subscribeToTopic(college); + await pref.setBool(college, true); + } else { + await FirebaseMessaging.instance.unsubscribeFromTopic(college); + await pref.setBool(college, false); + } + } catch (e) { + print(e); + Utils.showSnackbar( + "Can't subscribe to the college. Check your connection"); + } + } + + Future isSubscribedToCollege(String college) async { + college = college.replaceAll(' ', ''); + bool isSubscribed = false; + final pref = await SharedPreferences.getInstance(); + isSubscribed = pref.getBool(college) ?? false; + return isSubscribed; + } } diff --git a/live_score_flutter_app/lib/screens/admin_screen.dart b/live_score_flutter_app/lib/screens/admin_screen.dart index 1d05cd2..9b3d707 100644 --- a/live_score_flutter_app/lib/screens/admin_screen.dart +++ b/live_score_flutter_app/lib/screens/admin_screen.dart @@ -9,9 +9,14 @@ import 'package:provider/provider.dart'; import '../models/game.dart'; import 'edit_game_screen.dart'; -class AdminScreen extends StatelessWidget { +class AdminScreen extends StatefulWidget { static const id = 'adminscreen'; + @override + State createState() => _AdminScreenState(); +} + +class _AdminScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( @@ -36,7 +41,17 @@ class AdminScreen extends StatelessWidget { future: AuthProvider.currentUser, builder: (context, snapshot) { if (snapshot.hasData) { - return Text('Hello ${snapshot.data?.name}'); + return Text.rich( + TextSpan( + style: const TextStyle(color: Colors.black), + children: [ + const TextSpan(text: "Hello, "), + TextSpan( + text: "${snapshot.data?.name}", + style: + const TextStyle(fontSize: 25.0)) + ]), + ); } else { return const Text('Hello Loading...'); } @@ -84,31 +99,37 @@ class AdminScreen extends StatelessWidget { ), ), FutureBuilder( - future: Provider.of(context,listen: false).getAdminGames, + future: Provider.of(context, listen: false) + .getAdminGames, builder: (context, snapshot) { - if (snapshot.hasData) { - return Expanded( - child: ListView.builder( - itemCount: snapshot.data?.length, - itemBuilder: (context, index) => GameCard( - game: snapshot.data![index], - isAdminCard: true, - onPressed: () { - Provider.of(context, - listen: false) - .setCurrentGame(snapshot.data![index]); - Map args = { - 'game': snapshot.data![index] - }; - Navigator.pushNamed(context, EditGameScreen.id, - arguments: args); - }, + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.hasData) { + List gamesList=snapshot.data??[]; + gamesList.sort((a,b) => -1*a.createdOn.compareTo(b.createdOn)); + return Expanded( + child: ListView.builder( + itemCount: gamesList.length, + itemBuilder: (context, index) => GameCard( + game: gamesList[index], + isAdminCard: true, + onPressed: () { + Provider.of(context, + listen: false) + .setCurrentGame(gamesList[index]); + Map args = { + 'game': gamesList[index] + }; + Navigator.pushNamed(context, EditGameScreen.id, + arguments: args).then((value){setState(() { + + });}); + }, + ), ), - ), - ); - } else { - return const Center(child: CircularProgressIndicator()); + ); + } } + return const Center(child: CircularProgressIndicator()); }), ], ), @@ -130,37 +151,35 @@ class _AnnouncementDialogBoxState extends State { super.dispose(); } - @override Widget build(BuildContext context) { return AlertDialog( - icon: const Icon(Icons.announcement), - title: TextField( - controller: messageController, - autofocus: true, - decoration: const InputDecoration(hintText: "Enter your message"), - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: const Text('Cancel')), - TextButton( - onPressed: () async { - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center( - child: CircularProgressIndicator(), - )); - await Provider.of(context, - listen: false) - .addAnnouncement(messageController.text); - Navigator.popUntil(context, ModalRoute.withName('adminscreen')); - }, - child: const Text('Announce')) - ], - ); + icon: const Icon(Icons.announcement), + title: TextField( + controller: messageController, + autofocus: true, + decoration: const InputDecoration(hintText: "Enter your message"), + ), + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: const Text('Cancel')), + TextButton( + onPressed: () async { + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center( + child: CircularProgressIndicator(), + )); + await Provider.of(context, listen: false) + .addAnnouncement(messageController.text); + Navigator.popUntil(context, ModalRoute.withName('adminscreen')); + }, + child: const Text('Announce')) + ], + ); } } diff --git a/live_score_flutter_app/lib/screens/announcements_screen.dart b/live_score_flutter_app/lib/screens/announcements_screen.dart index 6e95f7b..41858c3 100644 --- a/live_score_flutter_app/lib/screens/announcements_screen.dart +++ b/live_score_flutter_app/lib/screens/announcements_screen.dart @@ -1,5 +1,6 @@ import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:flutter/material.dart'; +import 'package:grouped_list/grouped_list.dart'; import 'package:intl/intl.dart'; import 'package:live_score_flutter_app/providers/games_users_provider.dart'; import 'package:provider/provider.dart'; @@ -12,49 +13,174 @@ class AnnouncementScreen extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( - appBar: AppBar(), - body: FutureBuilder( - future: Provider.of(context, listen: false) - .getAnnouncements(), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center( - child: CircularProgressIndicator(), - ); - } - - if (snapshot.hasError) { - return const Text('Some error occured'); - } - - if (!snapshot.hasData) { - return const Text('No Announcements 👍'); - } - - List announcementList = []; - announcementList = List.from( - snapshot.data.docs.map((DocumentSnapshot e) { - return Announcement.fromJson(e.data() as Map); - }).toList()); - - announcementList - .sort((a, b) => -1 * a.createdOn.compareTo(b.createdOn)); - - return ListView.builder( - itemCount: announcementList.length, - itemBuilder: (context, index) { - return ListTile( - title: Text(announcementList[index].message), - subtitle: Text( - '${announcementList[index].creatorName} (${announcementList[index].collegeName})'), - trailing: Text( - DateFormat('dd MMMM yyyy\nkk:mm').format( - DateTime.parse(announcementList[index].createdOn)), - textAlign: TextAlign.right, + appBar: AppBar( + title: const Text("Announcements 🔊"), + ), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12.0), + child: FutureBuilder( + future: Provider.of(context, listen: false) + .getAnnouncements(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center( + child: CircularProgressIndicator(), + ); + } + + if (snapshot.hasError) { + return const Text('Some error occured'); + } + + if (!snapshot.hasData) { + return const Text('No Announcements 👍'); + } + + List announcementList = []; + announcementList = List.from( + snapshot.data.docs.map((DocumentSnapshot e) { + return Announcement.fromJson(e.data() as Map); + }).toList()); + + return Column( + children: [ + NotificationDropdown(), + Expanded( + child: GroupedListView( + elements: announcementList, + groupComparator: (value1, value2) => + -1 * value1.compareTo(value2), + groupBy: (element) => element.createdOn.substring(0, 10), + groupSeparatorBuilder: (String value) => Align( + alignment: Alignment.center, + child: Container( + width: MediaQuery.of(context).size.width * 0.40, + padding: const EdgeInsets.all(10.0), + margin: const EdgeInsets.symmetric(vertical: 10.0), + decoration: const BoxDecoration( + color: Colors.purple, + borderRadius: BorderRadius.all(Radius.circular(20.0)), + ), + child: Text( + DateFormat('dd MMM yyyy') + .format(DateTime.parse(value)), + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 18, + color: Colors.white, + fontWeight: FontWeight.bold), + ), + ), + ), + itemBuilder: (context, element) { + return Container( + margin: const EdgeInsets.symmetric(vertical: 10.0), + child: ListTile( + title: Text( + '${element.creatorName} (${element.collegeName})', + style: const TextStyle( + fontSize: 18.0, fontWeight: FontWeight.w700)), + subtitle: Text(element.message), + trailing: Text( + DateFormat('kk:mm') + .format(DateTime.parse(element.createdOn)), + textAlign: TextAlign.right, + ), + textColor: Colors.white, + tileColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(20.0), + // side: const BorderSide(width: 1, color: Colors.grey), + ), + ), + ); + }, ), - ); - }); - }, + ), + ], + ); + }, + ), + ), + ); + } +} + +class NotificationDropdown extends StatefulWidget { + @override + State createState() => _NotificationDropdownState(); +} + +class _NotificationDropdownState extends State { + @override + Widget build(BuildContext context) { + final dropDownNode = FocusNode(); + final myFuture = + Provider.of(context, listen: false).getAllColleges(); + return Container( + padding: const EdgeInsets.symmetric(vertical: 7.5), + color: Colors.white, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text('Subscribe to Notification: '), + FutureBuilder>( + future: myFuture, + builder: (context, snapshot) { + Map collegeMap = {'All': false}; + if (snapshot.hasData) { + collegeMap = snapshot.data ?? {}; + return DropdownButton( + focusNode: dropDownNode, + hint: const Text('Select Colleges'), + items: collegeMap.keys + .map((e) => DropdownMenuItem( + value: e, + child: Row(children: [ + Checkbox( + tristate: true, + value: collegeMap[e], + onChanged: (value) async { + Navigator.pop(dropDownNode.context!); + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center( + child: + CircularProgressIndicator(), + )); + await Provider.of( + context, + listen: false) + .subscribeToCollege( + value ?? false, e); + Navigator.pop(context); + setState(() { + }); + }), + Text(e), + ]))) + .toList(), + onChanged: (value) {}); + } else { + return DropdownButton( + hint: const Text('Select College'), + items: ['All'] + .map((e) => DropdownMenuItem( + value: e, + child: Row(children: [ + Checkbox( + value: collegeMap[e], + onChanged: (value) async {}), + Text(e) + ]))) + .toList(), + onChanged: (value) { + Navigator.pop(context); + }); + } + }) + ], ), ); } diff --git a/live_score_flutter_app/lib/screens/auth_screen.dart b/live_score_flutter_app/lib/screens/auth_screen.dart index c00863a..e2a2c44 100644 --- a/live_score_flutter_app/lib/screens/auth_screen.dart +++ b/live_score_flutter_app/lib/screens/auth_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:live_score_flutter_app/screens/login_screen.dart'; import 'package:live_score_flutter_app/screens/signup_screen.dart'; +import 'package:live_score_flutter_app/widgets/custom_button.dart'; class AuthScreen extends StatelessWidget { static final id = 'authscreen'; @@ -9,46 +10,35 @@ class AuthScreen extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AuthButton( - title: 'Login', - color: Colors.blue, - onTap: () { - Navigator.pushNamed(context, LoginScreen.id); - }, - ), - const SizedBox(height: 20,), - AuthButton( - title: 'Signup', - color: Colors.red, - onTap: () { - Navigator.pushNamed(context, SignupScreen.id); - }, - ) - ], - )), + body: SingleChildScrollView( + child: Column( + children: [ + const SizedBox(height:20.0), + Hero(tag:"main-image",child: Image.asset("assets/sports_auth.png")), + SizedBox(height:MediaQuery.of(context).size.height * 0.1), + Column(children: [ + CustomButton( + title: 'Login', + color: Colors.blue, + textColor:Colors.white, + onTap: () { + Navigator.pushNamed(context, LoginScreen.id); + }, + ), + const SizedBox(height:20.0), + CustomButton( + title: 'Signup', + color: Colors.white, + textColor: Colors.black, + onTap: () { + Navigator.pushNamed(context, SignupScreen.id); + }, + ) + ]), + ], + ), + ), ); } } -class AuthButton extends StatelessWidget { - String? title; - Color? color; - VoidCallback? onTap; - AuthButton({this.title, this.color, this.onTap}); - - @override - Widget build(BuildContext context) { - return MaterialButton( - minWidth: 150, - height: 50, - shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(50))), - color: color, - onPressed: onTap, - child: Text(title ?? '',style: TextStyle(color: Colors.white,fontSize: 25),), - ); - } -} diff --git a/live_score_flutter_app/lib/screens/create_game_screen.dart b/live_score_flutter_app/lib/screens/create_game_screen.dart index 8dca8ba..f354670 100644 --- a/live_score_flutter_app/lib/screens/create_game_screen.dart +++ b/live_score_flutter_app/lib/screens/create_game_screen.dart @@ -4,6 +4,8 @@ import 'package:live_score_flutter_app/screens/edit_game_screen.dart'; import 'package:live_score_flutter_app/screens/admin_screen.dart'; import 'package:provider/provider.dart'; +import '../widgets/custom_textfield.dart'; + class CreateGameScreen extends StatefulWidget { static const id = 'creategame'; @@ -22,7 +24,7 @@ class _CreateGameScreenState extends State { String selectedItem = 'Football ⚽'; - List gamesList = ['Football ⚽', 'Cricket 🏏', 'Tennis 🎾']; + List gamesList = ['Football ⚽', 'Cricket 🏏', 'Tennis 🎾','Other ❓']; @override Widget build(BuildContext context) { @@ -106,9 +108,10 @@ class _CreateGameScreenState extends State { ), ); await gamesProvider.addGame( - team1TextController.text, - team2TextController.text, - descriptionTextController.text, + team1TextController.text.toUpperCase(), + team2TextController.text.toUpperCase(), + descriptionTextController.text.isEmpty?descriptionTextController.text: + descriptionTextController.text[0].toUpperCase()+descriptionTextController.text.substring(1), selectedItem); Navigator.of(context).pop(); Navigator.pushNamed(context, AdminScreen.id); @@ -125,37 +128,4 @@ class _CreateGameScreenState extends State { } } -class CustomTextField extends StatelessWidget { - const CustomTextField({ - this.hideText = false, - this.lines = 1, - required this.textController, - required this.placeholderText, - }); - - final bool hideText; - final TextEditingController textController; - final String placeholderText; - final int lines; - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: SizedBox( - width: 0.75 * MediaQuery.of(context).size.width, - child: TextFormField( - controller: textController, - obscureText: hideText, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) => - value != null && value.isEmpty ? 'Enter min 1 character' : null, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: placeholderText, - ), - ), - ), - ); - } -} diff --git a/live_score_flutter_app/lib/screens/edit_game_screen.dart b/live_score_flutter_app/lib/screens/edit_game_screen.dart index 1fb6aca..24b12b4 100644 --- a/live_score_flutter_app/lib/screens/edit_game_screen.dart +++ b/live_score_flutter_app/lib/screens/edit_game_screen.dart @@ -32,67 +32,69 @@ class _EditGameScreenState extends State { return Scaffold( appBar: AppBar(), body: SingleChildScrollView( - child: Padding( - padding: const EdgeInsets.all(10.0), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - currentGame.gameType, - style: const TextStyle( - fontSize: 40.0, - fontWeight: FontWeight.w300, - ), - ), - TextButton( - onPressed: () async { - showDialog( - context: context, - builder: (context) => - EndGameDialogBox(currentGame: currentGame)); - }, - style: TextButton.styleFrom( - backgroundColor: Colors.red, - foregroundColor: Colors.white, + child: SizedBox( + height: MediaQuery.of(context).size.height * 0.88, + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + currentGame.gameType, + style: const TextStyle( + fontSize: 40.0, + fontWeight: FontWeight.w300, + ), ), - child: const Text("End Game", - style: TextStyle(fontSize: 18.0)), - ) - ], - ), - const SizedBox(height: 30.0), - ControlsBox( - teamName: currentGame.team1, - isScore1: true, - ), - const SizedBox(height: 20.0), - ControlsBox( - teamName: currentGame.team2, - isScore1: false, - ), - const SizedBox(height: 10.0), - TextField( - controller: keyMomentsTextController, - decoration: InputDecoration( - hintText: "Key moments", - fillColor: Colors.red, - border: const OutlineInputBorder( - borderSide: BorderSide(color: Colors.black), - ), - suffixIcon: IconButton( + TextButton( onPressed: () async { - await Provider.of(context, - listen: false) - .sendKeyMoments(keyMomentsTextController.text); - keyMomentsTextController.clear(); + showDialog( + context: context, + builder: (context) => + EndGameDialogBox(currentGame: currentGame)); }, - icon: const Icon(Icons.send)), + style: TextButton.styleFrom( + backgroundColor: Colors.red, + foregroundColor: Colors.white, + ), + child: const Text("End Game", + style: TextStyle(fontSize: 18.0)), + ) + ], ), - ), - ], + ControlsBox( + teamName: currentGame.team1, + isScore1: true, + col:Colors.blue, + ), + ControlsBox( + teamName: currentGame.team2, + isScore1: false, + col:Colors.green + ), + TextField( + controller: keyMomentsTextController, + decoration: InputDecoration( + hintText: "Key moments", + fillColor: Colors.red, + border: const OutlineInputBorder( + borderSide: BorderSide(color: Colors.black), + ), + suffixIcon: IconButton( + onPressed: () async { + await Provider.of(context, + listen: false) + .sendKeyMoments(keyMomentsTextController.text); + keyMomentsTextController.clear(); + }, + icon: const Icon(Icons.send)), + ), + ), + ], + ), ), ), ), @@ -159,9 +161,11 @@ class _EndGameDialogBoxState extends State { class ControlsBox extends StatelessWidget { String teamName; bool isScore1; + Color col; ControlsBox({ this.teamName = '', this.isScore1 = true, + this.col = Colors.white, }); @override @@ -170,69 +174,65 @@ class ControlsBox extends StatelessWidget { return Container( padding: const EdgeInsets.all(10.0), decoration: BoxDecoration( + color:col, borderRadius: const BorderRadius.all(Radius.circular(10.0)), - border: Border.all(width: 2.0), ), child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( teamName, style: const TextStyle( + color: Colors.white, fontSize: 25.0, ), ), - const SizedBox(height: 20.0), - Column( + Padding( + padding: const EdgeInsets.all(30.0), + child: Text( + (isScore1 ? currentGame.score1 : currentGame.score2) + .toString(), + style: const TextStyle( + color: Colors.white, + fontSize: 35.0, + ), + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Padding( - padding: const EdgeInsets.all(30.0), - child: Text( - (isScore1 ? currentGame.score1 : currentGame.score2) - .toString(), - style: const TextStyle( - fontSize: 35.0, + Container( + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.0), + ), + child: IconButton( + onPressed: () { + Provider.of(context, listen: false) + .changeScore(isIncrease: true, isScore1: isScore1); + }, + icon: const Icon( + Icons.add, ), ), ), - const SizedBox(height: 10.0), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - decoration: BoxDecoration( - color: Colors.green, - borderRadius: BorderRadius.circular(50.0), - ), - child: IconButton( - onPressed: () { - Provider.of(context, listen: false) - .changeScore(isIncrease: true, isScore1: isScore1); - }, - icon: const Icon( - Icons.add, - color: Colors.white, - ), - ), - ), - const SizedBox(width: 50.0), - Container( - decoration: BoxDecoration( - color: Colors.red, - borderRadius: BorderRadius.circular(50.0), - ), - child: IconButton( - onPressed: () { - Provider.of(context, listen: false) - .changeScore(isIncrease: false, isScore1: isScore1); - }, - icon: const Icon( - Icons.remove, - color: Colors.white, - ), - ), + const SizedBox(width: 50.0), + Container( + decoration: BoxDecoration( + color: Colors.black, + borderRadius: BorderRadius.circular(10.0), + ), + child: IconButton( + onPressed: () { + Provider.of(context, listen: false) + .changeScore(isIncrease: false, isScore1: isScore1); + }, + icon: const Icon( + Icons.remove, + color: Colors.white, ), - ], - ) + ), + ), ], ), ], diff --git a/live_score_flutter_app/lib/screens/login_screen.dart b/live_score_flutter_app/lib/screens/login_screen.dart index 4e3bbd3..59a26c8 100644 --- a/live_score_flutter_app/lib/screens/login_screen.dart +++ b/live_score_flutter_app/lib/screens/login_screen.dart @@ -2,9 +2,11 @@ import 'package:firebase_auth/firebase_auth.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; import 'package:live_score_flutter_app/screens/admin_screen.dart'; +import 'package:live_score_flutter_app/widgets/custom_button.dart'; import 'package:provider/provider.dart'; import '../providers/auth_provider.dart'; +import '../widgets/custom_textfield.dart'; class LoginScreen extends StatefulWidget { static const id = 'loginscreen'; @@ -37,92 +39,62 @@ class _LoginScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), - body: Center( - child: Form( - key: formKey, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CustomTextField( - textController: emailTextController, - placeholderText: "Email", - ), - CustomTextField( - textController: passwordTextController, - placeholderText: "Password", - hideText: true, - ), - MaterialButton( - height: 50, - minWidth: 120, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10))), - color: Colors.green, - onPressed: () async { - final isValid = formKey.currentState!.validate(); - if (!isValid) return; - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center( - child: CircularProgressIndicator(), - )); - await AuthProvider.logIn( - email: emailTextController.text, - password: passwordTextController.text); - Navigator.pop(context); - if (auth.currentUser != null) { - Navigator.pushNamedAndRemoveUntil( - context, - AdminScreen.id, - (Route route) => false, - ); - } - }, - child: const Text('Next', - style: TextStyle(color: Colors.white, fontSize: 22)), - ) - ], + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + const SizedBox(height:40.0), + Image.asset("assets/login_bg.png"), + SizedBox(height:MediaQuery.of(context).size.height * 0.10), + Form( + key: formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CustomTextField( + textController: emailTextController, + placeholderText: "Email", + icon:Icons.email, + ), + CustomTextField( + textController: passwordTextController, + placeholderText: "Password", + icon:Icons.lock, + hideText: true, + ), + CustomButton( + title: "Login", + color: Colors.blue, + textColor: Colors.white, + onTap: () async { + final isValid = formKey.currentState!.validate(); + if (!isValid) return; + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center( + child: CircularProgressIndicator(), + )); + await AuthProvider.logIn( + email: emailTextController.text.toUpperCase(), + password: passwordTextController.text.toUpperCase()); + Navigator.pop(context); + if (auth.currentUser != null) { + Navigator.pushNamedAndRemoveUntil( + context, + AdminScreen.id, + (Route route) => false, + ); + } + }) + ], + ), ), - ), + ], ), )); } } -class CustomTextField extends StatelessWidget { - const CustomTextField({ - this.hideText = false, - required this.textController, - required this.placeholderText, - }); - final bool hideText; - final TextEditingController textController; - final String placeholderText; - - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: SizedBox( - width: 0.75 * size.width, - child: TextFormField( - controller: textController, - obscureText: hideText, - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) => - value != null && value.isEmpty ? 'Enter min 1 character' : null, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: placeholderText, - ), - ), - ), - ); - } -} diff --git a/live_score_flutter_app/lib/screens/ongoing_game_details_screen.dart b/live_score_flutter_app/lib/screens/ongoing_game_details_screen.dart index fd67ec3..3496617 100644 --- a/live_score_flutter_app/lib/screens/ongoing_game_details_screen.dart +++ b/live_score_flutter_app/lib/screens/ongoing_game_details_screen.dart @@ -1,7 +1,7 @@ import 'package:firebase_database/firebase_database.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; - +import 'package:expandable_text/expandable_text.dart'; import '../models/game.dart'; import '../providers/games_users_provider.dart'; @@ -13,6 +13,7 @@ class OngoingGameDetailsScreen extends StatelessWidget { @override Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; return Scaffold( appBar: AppBar(), body: StreamBuilder( @@ -36,60 +37,121 @@ class OngoingGameDetailsScreen extends StatelessWidget { ); } } - return Column(children: [ - Container( - decoration: const BoxDecoration(color: Colors.white), - child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - game.gameType, - style: const TextStyle( - fontSize: 25, fontWeight: FontWeight.w700), - ), - const SizedBox( - height: 30, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Column( - children: [ - Text(game.team1), - const SizedBox(height: 20), - Text(game.score1.toString()) - ], - ), - Column( - children: [ - Text(game.team2), - const SizedBox(height: 20), - Text(game.score2.toString()) - ], - ) - ], - ), - const SizedBox( - height: 8, - ), - ]), - ), - Expanded( - child: ListView( - children: game.keyMoments.reversed - .map((e) => Padding( - padding: - const EdgeInsets.symmetric(vertical: 10.0), - child: ListTile( - tileColor: Color.fromARGB(112, 157, 151, 151), - title: Text( - e, + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Column(children: [ + Container( + decoration: const BoxDecoration(color: Colors.white), + padding: const EdgeInsets.only(top: 10.0), + child: Column(children: [ + Text( + game.gameType, + style: const TextStyle( + fontSize: 25, fontWeight: FontWeight.w700), + ), + const SizedBox( + height: 30, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + height: 0.18 * size.height, + width: 0.35 * size.width, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(15.0), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(game.team1.toUpperCase(), textAlign: TextAlign.center, - style: const TextStyle(fontSize: 20), + style: + const TextStyle(color: Colors.white)), + const SizedBox(height: 10.0), + Text(game.score1.toString(), + style: const TextStyle( + color: Colors.white, fontSize: 40.0)) + ], + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + "VS", + style: TextStyle( + fontSize: 30.0, fontWeight: FontWeight.w500), + ), + ), + Container( + height: 0.18 * size.height, + width: 0.35 * size.width, + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(15.0), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(game.team2.toUpperCase(), + textAlign: TextAlign.center, + style: + const TextStyle(color: Colors.white)), + const SizedBox(height: 10.0), + Text(game.score2.toString(), + style: const TextStyle( + color: Colors.white, fontSize: 40.0)) + ], + ), + ), + ], + ), + const SizedBox( + height: 20, + ), + Align( + alignment: Alignment.centerLeft, + child: ExpandableText( + game.description, + expandText: 'show more', + collapseText: 'show less', + maxLines: 1, + linkColor: Colors.blue, + ), + ), + const SizedBox( + height: 20, + ), + const Align( + alignment: Alignment.centerLeft, + child: Text("Highlights", + style: TextStyle( + fontSize: 25.0, fontWeight: FontWeight.w500))), + const SizedBox( + height: 8, + ), + ]), + ), + Expanded( + child: ListView( + children: game.keyMoments.reversed + .map((e) => Container( + padding: + const EdgeInsets.symmetric(vertical: 10.0), + child: ListTile( + textColor: Colors.white, + tileColor: const Color(0xDFEA3C3C), + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(15.0)), + title: Text(e), ), - ), - )) - .toList()), - ), - ]); + )) + .toList()), + ), + ]), + ); }), ); } diff --git a/live_score_flutter_app/lib/screens/ongoing_games_screen.dart b/live_score_flutter_app/lib/screens/ongoing_games_screen.dart index e74993c..9043fe4 100644 --- a/live_score_flutter_app/lib/screens/ongoing_games_screen.dart +++ b/live_score_flutter_app/lib/screens/ongoing_games_screen.dart @@ -19,25 +19,28 @@ class OngoingGamesScreen extends StatelessWidget { ), drawer: const AppDrawer(), body: Padding( - padding: const EdgeInsets.symmetric(vertical: 32.0), + padding: const EdgeInsets.symmetric(vertical: 5.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const SizedBox( - height: 10, + height: 5, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const Text( - 'Ongoing Games', - style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500), - ), - CollegeDropdown(), - ], + Padding( + padding: const EdgeInsets.symmetric(horizontal:15.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + const Text( + 'Ongoing Games', + style: TextStyle(fontSize: 22, fontWeight: FontWeight.w500), + ), + CollegeDropdown(), + ], + ), ), const SizedBox( - height: 25, + height: 5, ), const OngoingGamesListWidget(), ]), diff --git a/live_score_flutter_app/lib/screens/previous_game_details_screen.dart b/live_score_flutter_app/lib/screens/previous_game_details_screen.dart index 024daee..f8ee177 100644 --- a/live_score_flutter_app/lib/screens/previous_game_details_screen.dart +++ b/live_score_flutter_app/lib/screens/previous_game_details_screen.dart @@ -1,3 +1,4 @@ +import 'package:expandable_text/expandable_text.dart'; import 'package:flutter/material.dart'; import '../models/game.dart'; @@ -6,73 +7,146 @@ class PreviousGameDetailsScreen extends StatefulWidget { PreviousGameDetailsScreen({required this.game}); @override - State createState() => _PreviousGameDetailsScreenState(); + State createState() => + _PreviousGameDetailsScreenState(); } class _PreviousGameDetailsScreenState extends State { - @override Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; return Scaffold( - appBar: AppBar(), - body: Column( - children: [ - Container( - padding: const EdgeInsets.all(16), - decoration: const BoxDecoration(color: Colors.white), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - widget.game.gameType, - style: const TextStyle( - fontSize: 25, fontWeight: FontWeight.w700), + appBar: AppBar(), + body: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Column(children: [ + Container( + decoration: const BoxDecoration(color: Colors.white), + padding: const EdgeInsets.only(top: 10.0), + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + widget.game.gameType, + style: + const TextStyle(fontSize: 25, fontWeight: FontWeight.w700), + ), + const SizedBox(width: 10.0), + Flexible( + child: Text( + '${widget.game.winner} 🏆', + style: + const TextStyle(color:Colors.purple,fontSize: 28, fontWeight: FontWeight.w700), + ), + ), + ], + ), + const SizedBox( + height: 15, + ), + const SizedBox( + height: 30, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + Container( + height: 0.18 * size.height, + width: 0.35 * size.width, + decoration: BoxDecoration( + color: Colors.blue, + borderRadius: BorderRadius.circular(15.0), ), - const SizedBox( - height: 30, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(widget.game.team1.toUpperCase(), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white)), + const SizedBox(height: 10.0), + Text(widget.game.score1.toString(), + style: const TextStyle( + color: Colors.white, fontSize: 40.0)) + ], + ), + ), + const Padding( + padding: EdgeInsets.symmetric(horizontal: 8.0), + child: Text( + "VS", + style: TextStyle( + fontSize: 30.0, fontWeight: FontWeight.w500), + ), + ), + Container( + height: 0.18 * size.height, + width: 0.35 * size.width, + decoration: BoxDecoration( + color: Colors.green, + borderRadius: BorderRadius.circular(15.0), ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - Column( - children: [ - Text(widget.game.team1), - const SizedBox(height: 20), - Text(widget.game.score1.toString()) - ], - ), - Column( - children: [ - Text(widget.game.team2), - const SizedBox(height: 20), - Text(widget.game.score2.toString()) - ], - ) + Text(widget.game.team2.toUpperCase(), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white)), + const SizedBox(height: 10.0), + Text(widget.game.score2.toString(), + style: const TextStyle( + color: Colors.white, fontSize: 40.0)) ], ), - ]), - ), - const SizedBox( - height: 8, - ), - Expanded( - child: ListView( - children: widget.game.keyMoments.reversed - .map((e) => Padding( - padding: const EdgeInsets.symmetric(vertical: 10.0), - child: ListTile( - tileColor: - const Color.fromARGB(112, 157, 151, 151), - title: Text( - e, - textAlign: TextAlign.center, - style: const TextStyle(fontSize: 20), - ), - ), - )) - .toList()), - ) - ], - )); + ), + ], + ), + const SizedBox( + height: 20, + ), + Align( + alignment: Alignment.centerLeft, + child: ExpandableText( + widget.game.description, + expandText: 'show more', + collapseText: 'show less', + maxLines: 1, + linkColor: Colors.blue, + ), + ), + const SizedBox( + height: 20, + ), + const Align( + alignment: Alignment.centerLeft, + child: Text("Highlights", + style: TextStyle( + fontSize: 25.0, fontWeight: FontWeight.w500))), + const SizedBox( + height: 8, + ), + ]), + ), + Expanded( + child: ListView( + children: widget.game.keyMoments.reversed + .map((e) => Container( + padding: const EdgeInsets.symmetric(vertical: 10.0), + child: ListTile( + textColor: Colors.white, + tileColor: Color(0xDFEA3C3C), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(15.0)), + title: Text(e), + ), + )) + .toList()), + ), + ]), + ), + ); } } diff --git a/live_score_flutter_app/lib/screens/previous_games_screen.dart b/live_score_flutter_app/lib/screens/previous_games_screen.dart index 1e19261..6a4d45b 100644 --- a/live_score_flutter_app/lib/screens/previous_games_screen.dart +++ b/live_score_flutter_app/lib/screens/previous_games_screen.dart @@ -20,7 +20,7 @@ class PreviousGamesScreen extends StatelessWidget { drawer: const AppDrawer(), body: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - padding: const EdgeInsets.symmetric(vertical: 20, horizontal: 15), + padding: const EdgeInsets.symmetric(vertical: 7.5, horizontal: 15), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -117,6 +117,7 @@ class PreviousGamesListWidget extends StatelessWidget { itemCount: gamesList.length, itemBuilder: (context, index) => GameCard( game: gamesList[index], + isPreviousCard: true, onPressed: () { Navigator.push( context, diff --git a/live_score_flutter_app/lib/screens/signup_screen.dart b/live_score_flutter_app/lib/screens/signup_screen.dart index adeb3b7..08eb52d 100644 --- a/live_score_flutter_app/lib/screens/signup_screen.dart +++ b/live_score_flutter_app/lib/screens/signup_screen.dart @@ -3,8 +3,11 @@ import 'package:flutter/material.dart'; import 'package:live_score_flutter_app/providers/auth_provider.dart'; import 'package:live_score_flutter_app/screens/admin_screen.dart'; import 'package:live_score_flutter_app/utils.dart'; +import 'package:live_score_flutter_app/widgets/custom_button.dart'; import 'package:provider/provider.dart'; +import '../widgets/custom_textfield.dart'; + class SignupScreen extends StatefulWidget { static const id = 'signupscreen'; @@ -39,104 +42,80 @@ class _SignupScreenState extends State { Widget build(BuildContext context) { return Scaffold( appBar: AppBar(), - body: Center( - child: Form( - key: formKey, - child: SingleChildScrollView( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - CustomTextField( - textController: nameTextController, - placeholderText: "Name", - ), - CustomTextField( - textController: collegeNameTextController, - placeholderText: "College Name", - ), - CustomTextField( - textController: emailTextController, - placeholderText: "Email", - ), - CustomTextField( - textController: passwordTextController, - placeholderText: "Password", - hideText: true, - ), - MaterialButton( - height: 50, - minWidth: 120, - shape: const RoundedRectangleBorder( - borderRadius: BorderRadius.all(Radius.circular(10))), - color: Colors.green, - onPressed: () async { - final valid = formKey.currentState!.validate(); - if (!valid) return; - //Firebase Signup will take place here + body: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SizedBox( + height: MediaQuery.of(context).size.height * 0.40, + width: MediaQuery.of(context).size.width, + child: Image.asset( + "assets/signup_bg.png", + fit: BoxFit.cover, + ), + ), + Form( + key: formKey, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + CustomTextField( + textController: nameTextController, + placeholderText: "Name", + icon: Icons.person, + ), + CustomTextField( + textController: collegeNameTextController, + placeholderText: "College Name", + icon: Icons.school, + ), + CustomTextField( + textController: emailTextController, + placeholderText: "Email", + icon: Icons.email, + ), + CustomTextField( + textController: passwordTextController, + placeholderText: "Password", + icon: Icons.lock, + hideText: true, + ), + CustomButton( + title: "Signup", + color:Colors.green, + textColor: Colors.white, + onTap: () async { + final valid = formKey.currentState!.validate(); + if (!valid) return; + //Firebase Signup will take place here - showDialog( - context: context, - barrierDismissible: false, - builder: (context) => const Center( - child: CircularProgressIndicator(), - )); - await AuthProvider.signUp( - email: emailTextController.text, - password: passwordTextController.text, - name: nameTextController.text, - collegeName: collegeNameTextController.text); - Navigator.pop(context); - if (auth.currentUser != null) { - Navigator.pushNamedAndRemoveUntil( - context, - AdminScreen.id, - (Route route) => false, - ); - } - }, - child: const Text('Next', - style: TextStyle(color: Colors.white, fontSize: 22)), - ) - ], + showDialog( + context: context, + barrierDismissible: false, + builder: (context) => const Center( + child: CircularProgressIndicator(), + )); + await AuthProvider.signUp( + email: emailTextController.text.toUpperCase(), + password: passwordTextController.text.toUpperCase(), + name: nameTextController.text.toUpperCase(), + collegeName: collegeNameTextController.text.toUpperCase()); + Navigator.pop(context); + if (auth.currentUser != null) { + Navigator.pushNamedAndRemoveUntil( + context, + AdminScreen.id, + (Route route) => false, + ); + } + }, + ) + ], + ), ), - ), + ], ), )); } } - -class CustomTextField extends StatelessWidget { - CustomTextField({ - this.hideText = false, - required this.textController, - required this.placeholderText, - }); - - bool hideText; - TextEditingController textController; - String placeholderText; - - @override - Widget build(BuildContext context) { - final size = MediaQuery.of(context).size; - - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: SizedBox( - width: 0.75 * size.width, - child: TextFormField( - autovalidateMode: AutovalidateMode.onUserInteraction, - validator: (value) => - value != null && value.isEmpty ? 'Enter min 1 characters' : null, - controller: textController, - obscureText: hideText, - decoration: InputDecoration( - border: const OutlineInputBorder(), - labelText: placeholderText, - ), - ), - ), - ); - } -} diff --git a/live_score_flutter_app/lib/widgets/app_drawer.dart b/live_score_flutter_app/lib/widgets/app_drawer.dart index b9fb3ec..ffffba8 100644 --- a/live_score_flutter_app/lib/widgets/app_drawer.dart +++ b/live_score_flutter_app/lib/widgets/app_drawer.dart @@ -12,7 +12,12 @@ class AppDrawer extends StatelessWidget { Widget build(BuildContext context) { return Drawer( child: ListView(children: [ + Padding( + padding: const EdgeInsets.all(12.0), + child: Hero(tag:"main-image",child: Image.asset("assets/sports_auth.png")), + ), ListTile( + leading: const Icon(Icons.person,color:Colors.blue), title: const Text('Login/Signup'), onTap: () { Navigator.pop(context); @@ -20,6 +25,7 @@ class AppDrawer extends StatelessWidget { }, ), ListTile( + leading: const Icon(Icons.visibility_rounded,color:Colors.blue), title: const Text('Ongoing Games'), onTap: () { Navigator.pop(context); @@ -27,6 +33,7 @@ class AppDrawer extends StatelessWidget { }, ), ListTile( + leading: const Icon(Icons.history,color:Colors.blue), title: const Text('Previous Games'), onTap: () { Navigator.pop(context); @@ -34,6 +41,7 @@ class AppDrawer extends StatelessWidget { }, ), ListTile( + leading: const Icon(Icons.newspaper,color:Colors.blue), title: const Text('Announcements'), onTap: () { Navigator.pop(context); diff --git a/live_score_flutter_app/lib/widgets/custom_button.dart b/live_score_flutter_app/lib/widgets/custom_button.dart new file mode 100644 index 0000000..229d1cb --- /dev/null +++ b/live_score_flutter_app/lib/widgets/custom_button.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; + +class CustomButton extends StatelessWidget { + String? title; + Color? color; + Color? textColor; + VoidCallback? onTap; + CustomButton({this.title, this.color, this.onTap, this.textColor}); + + @override + Widget build(BuildContext context) { + return MaterialButton( + highlightElevation: 0, + elevation: 0, + minWidth: 200, + height: 50, + shape: RoundedRectangleBorder( + borderRadius: const BorderRadius.all(Radius.circular(50)), + side: BorderSide(color: textColor ?? Colors.black), + ), + color: color, + onPressed: onTap, + child: Text( + title ?? '', + style: TextStyle(color: textColor, fontSize: 25), + ), + ); + } +} diff --git a/live_score_flutter_app/lib/widgets/custom_textfield.dart b/live_score_flutter_app/lib/widgets/custom_textfield.dart new file mode 100644 index 0000000..42618df --- /dev/null +++ b/live_score_flutter_app/lib/widgets/custom_textfield.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class CustomTextField extends StatelessWidget { + const CustomTextField({ + this.hideText = false, + required this.textController, + required this.placeholderText, + this.icon, + this.maxLength=20, + }); + + final bool hideText; + final TextEditingController textController; + final String placeholderText; + final IconData? icon; + final int maxLength; + + @override + Widget build(BuildContext context) { + final size = MediaQuery.of(context).size; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 5.0), + child: SizedBox( + width: 0.75 * size.width, + child: TextFormField( + controller: textController, + obscureText: hideText, + autovalidateMode: AutovalidateMode.onUserInteraction, + validator: (value) => + value != null &&(value.isEmpty || value.length>=maxLength)? 'Enter min 1 character and max $maxLength':null, + decoration: InputDecoration( + prefixIcon:Icon(icon), + border: const OutlineInputBorder(borderRadius:BorderRadius.all(Radius.circular(30.0))), + labelText: placeholderText, + ), + ), + ), + ); + } +} \ No newline at end of file diff --git a/live_score_flutter_app/lib/widgets/game_card.dart b/live_score_flutter_app/lib/widgets/game_card.dart index b25c5ea..ef6c2e2 100644 --- a/live_score_flutter_app/lib/widgets/game_card.dart +++ b/live_score_flutter_app/lib/widgets/game_card.dart @@ -7,100 +7,243 @@ class GameCard extends StatelessWidget { Game game; VoidCallback? onPressed; bool isAdminCard; - GameCard({required this.game, this.onPressed, this.isAdminCard = false}); + bool isPreviousCard; + GameCard( + {required this.game, + this.onPressed, + this.isAdminCard = false, + this.isPreviousCard = false}); @override Widget build(BuildContext context) { - final size=MediaQuery.of(context).size; - + final size = MediaQuery.of(context).size; + return GestureDetector( onTap: onPressed, behavior: HitTestBehavior.opaque, child: Container( - margin: const EdgeInsets.symmetric(vertical: 16), - height: 210, - decoration: const BoxDecoration(color: Color.fromARGB(124, 158, 158, 158)), + margin: const EdgeInsets.symmetric(vertical: 16, horizontal: 8), + height: 230, + decoration: const BoxDecoration( + borderRadius: BorderRadius.all(Radius.circular(15.0)), + boxShadow: [ + BoxShadow( + color: Colors.black26, + blurRadius: 2.0, + spreadRadius: 1.0, + ) + ], + color: Colors.amberAccent, + ), width: size.width, child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, children: [ - isAdminCard - ? const SizedBox() - : Container( - padding: const EdgeInsets.symmetric(horizontal: 6), - alignment: Alignment.center, - height: 25, - decoration: const BoxDecoration(color: Colors.black), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Column( + children: [ + Row( children: [ - Text( - 'Creator: ${game.creatorName}', - overflow: TextOverflow.fade, - softWrap: false, - style: const TextStyle(color: Colors.white), + Expanded( + child: Container( + height:110, + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(15.0)), + color: Colors.blue, + ), + padding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 15.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + textAlign:TextAlign.center, + TextSpan(children:[TextSpan(text:game.winner == game.team1 ? "🏆 ":""),TextSpan(text: game.team1)]), + style: const TextStyle( + color: Colors.white, fontSize: 16)), + const SizedBox(height: 10.0), + Text(game.score1.toString(), + style: const TextStyle( + color: Colors.white, fontSize: 25)), + ], + ), + ), ), - Text( - 'College: ${game.college}', - overflow: TextOverflow.fade, - softWrap: false, - style: const TextStyle(color: Colors.white), + Expanded( + child: Container( + height: 110, + decoration: const BoxDecoration( + borderRadius: BorderRadius.only( + topRight: Radius.circular(15.0)), + color: Colors.green), + padding: const EdgeInsets.symmetric( + horizontal: 10.0, vertical: 15.0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text.rich( + textAlign:TextAlign.center, + TextSpan(children:[TextSpan(text:game.winner == game.team2 ? "🏆 ":""),TextSpan(text: game.team2)]), + style: const TextStyle( + color: Colors.white, fontSize: 16)), + const SizedBox(height: 10.0), + Text(game.score2.toString(), + style: const TextStyle( + color: Colors.white, fontSize: 25)), + ], + ), + ), ), ], ), - ), - const SizedBox( - height: 8, - ), - Container( - padding: const EdgeInsets.symmetric(horizontal:16), - child: Column(children: [ - Container( - alignment: Alignment.center, - child: Text( - game.gameType, - textAlign: TextAlign.center, - style: - const TextStyle(fontSize: 20, fontWeight: FontWeight.w700), - ), - ), - const SizedBox( - height: 30, - ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - Column( - children: [ - Text(game.team1), - const SizedBox(height: 20), - Text(game.score1.toString()) - ], - ), - Column( - children: [ - Text(game.team2), - const SizedBox(height: 20), - Text(game.score2.toString()) - ], + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, vertical: 10.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + flex: 3, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(game.college, + style: const TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.w700)), + isAdminCard + ? const Text("") + : Text("Created By : ${game.creatorName}"), + const SizedBox(height: 20.0), + Text( + DateFormat('dd MMMM yyyy \nkk:mm') + .format(DateTime.parse(game.createdOn)), + style: const TextStyle( + color: Colors.black54, fontSize: 10.0)), + ], + ), + ), + Expanded( + flex: 2, + child: Column( + children: [ + Text( + game.gameType + .substring(game.gameType.length - 2), + style: const TextStyle(fontSize: 50.0)), + const SizedBox(height: 10.0), + Text(game.gameType), + ], + ), + ), + ], + ), ) ], ), - Container( - margin: const EdgeInsets.only(top: 35,bottom: 5), - alignment: Alignment.bottomRight, - child: Text( - DateFormat('dd MMMM yyyy \n kk:mm') - .format(DateTime.parse(game.createdOn)), - textAlign: TextAlign.end, - style: TextStyle(fontSize: 12,color: Colors.black.withOpacity(0.6)), - ), - ), ]), - ), - ]), ), ); } } + + +// Container( +// padding: const EdgeInsets.symmetric(horizontal: 6), +// alignment: Alignment.center, +// height: 25, +// decoration: const BoxDecoration(color: Colors.black,borderRadius: BorderRadius.only(topLeft: Radius.circular(15.0),topRight: Radius.circular(15.0))), +// child: Row( +// crossAxisAlignment: CrossAxisAlignment.start, +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// Text( +// 'Creator: ${game.creatorName}', +// overflow: TextOverflow.fade, +// softWrap: false, +// style: const TextStyle(color: Colors.white), +// ), +// Text( +// 'College: ${game.college}', +// overflow: TextOverflow.fade, +// softWrap: false, +// style: const TextStyle(color: Colors.white), +// ), +// ], +// ), +// ), +// const SizedBox( +// height: 8, +// ), +// Container( +// padding: const EdgeInsets.symmetric(horizontal: 16), +// child: Column(children: [ +// Container( +// alignment: Alignment.center, +// child: Text( +// game.gameType, +// textAlign: TextAlign.center, +// style: const TextStyle( +// fontSize: 20, fontWeight: FontWeight.w700), +// ), +// ), +// const SizedBox( +// height: 30, +// ), +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceAround, +// children: [ +// Container( +// padding: const EdgeInsets.all(12.0), +// decoration: BoxDecoration( +// color:Colors.blue, +// borderRadius: BorderRadius.circular(10.0), +// ), +// child: Column( +// children: [ +// Text(game.team1,style:const TextStyle(color:Colors.white)), +// const SizedBox(height: 8), +// Text(game.score1.toString(),style:const TextStyle(color:Colors.white)) +// ], +// ), +// ), +// Container( +// padding: const EdgeInsets.all(12.0), +// decoration: BoxDecoration( +// color:Colors.green, +// borderRadius: BorderRadius.circular(10.0), +// ), +// child: Column( +// children: [ +// Text(game.team2,style:const TextStyle(color:Colors.white)), +// const SizedBox(height: 8), +// Text(game.score2.toString(),style:const TextStyle(color:Colors.white)) +// ], +// ), +// ), +// ], +// ), +// const SizedBox(height: 15.0), +// Row( +// mainAxisAlignment: MainAxisAlignment.spaceBetween, +// children: [ +// isPreviousCard ? Text("Winner : ${game.winner}") : const Text(""), +// Text( +// DateFormat('dd MMMM yyyy \n kk:mm') +// .format(DateTime.parse(game.createdOn)), +// textAlign: TextAlign.end, +// style: TextStyle( +// fontSize: 12, color: Colors.black.withOpacity(0.6)), +// ), +// ], +// ), +// const SizedBox(height:10.0), +// ]), +// ), +// ]), +// ), +// ); +// } +// } + diff --git a/live_score_flutter_app/pubspec.lock b/live_score_flutter_app/pubspec.lock index 5fc2d73..825b918 100644 --- a/live_score_flutter_app/pubspec.lock +++ b/live_score_flutter_app/pubspec.lock @@ -85,6 +85,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.7.8" + expandable_text: + dependency: "direct main" + description: + name: expandable_text + url: "https://pub.dartlang.org" + source: hosted + version: "2.3.0" fake_async: dependency: transitive description: diff --git a/live_score_flutter_app/pubspec.yaml b/live_score_flutter_app/pubspec.yaml index aec5e24..55fbacc 100644 --- a/live_score_flutter_app/pubspec.yaml +++ b/live_score_flutter_app/pubspec.yaml @@ -48,6 +48,7 @@ dependencies: http: ^0.13.5 shared_preferences: ^2.0.15 grouped_list: ^5.1.2 + expandable_text: ^2.3.0 dev_dependencies: flutter_test: @@ -74,6 +75,7 @@ flutter: # To add assets to your application, add an assets section, like this: assets: - .env + - assets/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware