From 002df2b9d22e22c5834f3c2a71a1079cf92f2294 Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Tue, 17 Sep 2024 17:27:16 +0530 Subject: [PATCH 01/11] feat: auth and navigation --- android/app/src/main/AndroidManifest.xml | 7 + ios/Runner/Info.plist | 12 ++ lib/app/app.dart | 12 +- lib/app/router/router.dart | 111 +++++----- lib/constants/constants.dart | 1 + lib/providers/auth_provider.dart | 40 ++++ lib/view/screens/connect_screen.dart | 121 ++++++++--- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 4 + pubspec.lock | 200 ++++++++++++++++++ pubspec.yaml | 3 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 14 files changed, 441 insertions(+), 79 deletions(-) create mode 100644 lib/constants/constants.dart create mode 100644 lib/providers/auth_provider.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 8159bd1..f82ceb5 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -24,6 +24,13 @@ + + + + + + + diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index f1948ba..6fd9d97 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -45,5 +45,17 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + CFBundleURLTypes + + + CFBundleURLName + com.example.spotifycollab + CFBundleURLSchemes + + collabify + + + + diff --git a/lib/app/app.dart b/lib/app/app.dart index be5f7fc..e67db6e 100644 --- a/lib/app/app.dart +++ b/lib/app/app.dart @@ -1,19 +1,21 @@ import 'package:flutter/material.dart'; - +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify_collab_app/app/router/router.dart'; import 'package:spotify_collab_app/app/theme/theme.dart'; -class CollabifyApp extends StatelessWidget { +class CollabifyApp extends ConsumerWidget { const CollabifyApp({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final goRouter = ref.read(routerProvider); + return MaterialApp.router( - title: 'Kalculate', + title: 'Collabify', theme: darkTheme, // themeMode: ThemeMode.dark, debugShowCheckedModeBanner: false, - routerConfig: router, + routerConfig: goRouter, ); } } diff --git a/lib/app/router/router.dart b/lib/app/router/router.dart index 94076df..2ff1104 100644 --- a/lib/app/router/router.dart +++ b/lib/app/router/router.dart @@ -3,57 +3,72 @@ import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/view/screens/admin_screen.dart'; import 'package:spotify_collab_app/view/screens/create_screen.dart'; import 'package:spotify_collab_app/view/screens/join_screen.dart'; -import 'package:spotify_collab_app/view/screens/landing_screen.dart'; import 'package:spotify_collab_app/view/screens/login_screen.dart'; import 'package:spotify_collab_app/view/screens/signup_screen.dart'; import 'package:spotify_collab_app/view/screens/home_screen.dart'; import 'package:spotify_collab_app/view/screens/connect_screen.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotify_collab_app/providers/auth_provider.dart'; -final router = GoRouter( - initialLocation: '/', - routes: [ - GoRoute( - path: '/', - pageBuilder: (context, state) => const MaterialPage( - child: LandingScreen(), - ), - routes: [ - GoRoute( - path: 'login', - pageBuilder: (context, state) => - const MaterialPage(child: LoginScreen()), - ), - GoRoute( - path: 'signup', - pageBuilder: (context, state) => - const MaterialPage(child: SignupScreen()), - ), - GoRoute( - path: 'connect', - pageBuilder: (context, state) => - const MaterialPage(child: ConnectScreen()), - ), - GoRoute( - path: 'home', - pageBuilder: (context, state) => - const MaterialPage(child: HomeScreen()), - ), - GoRoute( - path: 'create', - pageBuilder: (context, state) => - const MaterialPage(child: CreateScreen()), - ), - GoRoute( - path: 'join', - pageBuilder: (context, state) => - const MaterialPage(child: JoinScreen()), - ), - GoRoute( - path: 'admin', - pageBuilder: (context, state) => - const MaterialPage(child: AdminScreen()), +final routerProvider = Provider((ref) { + final authState = ref.watch(authProvider); + + return GoRouter( + initialLocation: '/', + redirect: (context, state) { + final isLoggedIn = authState.accessToken != null; + + if (!isLoggedIn && state.matchedLocation != '/connect') { + return '/connect'; + } else if (isLoggedIn && state.matchedLocation == '/connect') { + return '/home'; + } + return null; + }, + routes: [ + GoRoute( + path: '/', + pageBuilder: (context, state) => const MaterialPage( + child: ConnectScreen(), ), - ], - ), - ], -); + routes: [ + GoRoute( + path: 'login', + pageBuilder: (context, state) => + const MaterialPage(child: LoginScreen()), + ), + GoRoute( + path: 'signup', + pageBuilder: (context, state) => + const MaterialPage(child: SignupScreen()), + ), + GoRoute( + path: 'connect', + pageBuilder: (context, state) => + const MaterialPage(child: ConnectScreen()), + ), + GoRoute( + path: 'home', + pageBuilder: (context, state) => + const MaterialPage(child: HomeScreen()), + ), + GoRoute( + path: 'create', + pageBuilder: (context, state) => + const MaterialPage(child: CreateScreen()), + ), + GoRoute( + path: 'join', + pageBuilder: (context, state) => + const MaterialPage(child: JoinScreen()), + ), + GoRoute( + path: 'admin', + pageBuilder: (context, state) => + const MaterialPage(child: AdminScreen()), + ), + ], + ), + ], + ); +}); diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart new file mode 100644 index 0000000..a8694d8 --- /dev/null +++ b/lib/constants/constants.dart @@ -0,0 +1 @@ +const devUrl = 'https://3ba5-171-79-54-237.ngrok-free.app'; diff --git a/lib/providers/auth_provider.dart b/lib/providers/auth_provider.dart new file mode 100644 index 0000000..6f21f0c --- /dev/null +++ b/lib/providers/auth_provider.dart @@ -0,0 +1,40 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class AuthState { + final String? accessToken; + + AuthState({this.accessToken}); +} + +class AuthNotifier extends StateNotifier { + AuthNotifier() : super(AuthState()) { + _loadAccessToken(); + } + + Future _loadAccessToken() async { + final prefs = await SharedPreferences.getInstance(); + final token = prefs.getString('access_token'); + if (token != null) { + state = AuthState(accessToken: token); + } + } + + Future setAccessToken(String token) async { + state = AuthState(accessToken: token); + + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('access_token', token); + } + + Future clearAccessToken() async { + state = AuthState(accessToken: null); + + final prefs = await SharedPreferences.getInstance(); + await prefs.remove('access_token'); + } +} + +final authProvider = StateNotifierProvider((ref) { + return AuthNotifier(); +}); diff --git a/lib/view/screens/connect_screen.dart b/lib/view/screens/connect_screen.dart index 0278575..0133f4b 100644 --- a/lib/view/screens/connect_screen.dart +++ b/lib/view/screens/connect_screen.dart @@ -1,15 +1,78 @@ +import 'dart:async'; +import 'dart:developer'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_svg/svg.dart'; +import 'package:spotify_collab_app/constants/constants.dart'; +import 'package:spotify_collab_app/providers/auth_provider.dart'; +import 'package:uni_links/uni_links.dart'; +import 'package:url_launcher/url_launcher.dart'; -class ConnectScreen extends StatelessWidget { +class ConnectScreen extends ConsumerStatefulWidget { const ConnectScreen({super.key}); + @override + ConnectScreenState createState() => ConnectScreenState(); +} + +class ConnectScreenState extends ConsumerState { + StreamSubscription? _sub; + + @override + void initState() { + super.initState(); + _initUniLinks(); + } + + Future _initUniLinks() async { + _sub = uriLinkStream.listen((Uri? uri) { + if (uri != null) { + final accessToken = uri.queryParameters['token']; + if (accessToken != null) { + log('Access Token: $accessToken'); + + ref.read(authProvider.notifier).setAccessToken(accessToken); + } else { + log('No access token found'); + } + } + }, onError: (err) { + log('Error in listening to URI links: $err'); + }); + } + + @override + void dispose() { + _sub?.cancel(); + super.dispose(); + } + + Future _launchSpotifyLogin() async { + const url = '$devUrl/v1/auth/spotify/login'; + launchUrl(Uri.parse(url)); + } + @override Widget build(BuildContext context) { return Scaffold( backgroundColor: const Color(0xff5822EE), appBar: AppBar( + centerTitle: true, + title: const Text( + 'Connect To Spotify', + style: TextStyle( + fontFamily: 'Gotham', + shadows: [ + Shadow( + color: Color(0xff000000), + offset: Offset(0, 3), + ), + ], + fontSize: 30, + fontWeight: FontWeight.w900, + color: Colors.white, + ), + ), automaticallyImplyLeading: false, backgroundColor: Colors.transparent, ), @@ -28,7 +91,7 @@ class ConnectScreen extends StatelessWidget { children: [ const Spacer(), const Text( - "welcome to collabify", + "Welcome To Collabify", textAlign: TextAlign.left, style: TextStyle( fontFamily: "Gotham", @@ -53,30 +116,36 @@ class ConnectScreen extends StatelessWidget { ), ), const Spacer(), - Container( - decoration: const BoxDecoration( - color: Color(0xff1db954), - borderRadius: BorderRadius.all(Radius.circular(12)), - ), - height: 45, - width: 225, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text( - "Connect", - style: TextStyle( - fontFamily: "Gotham", - fontSize: 20, - fontWeight: FontWeight.w700, - color: Colors.white, + InkWell( + onTap: () { + _launchSpotifyLogin(); + }, + borderRadius: const BorderRadius.all(Radius.circular(12)), + child: Container( + decoration: const BoxDecoration( + color: Color(0xff1db954), + borderRadius: BorderRadius.all(Radius.circular(12)), + ), + height: 45, + width: 225, + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + "Connect", + style: TextStyle( + fontFamily: "Gotham", + fontSize: 20, + fontWeight: FontWeight.w700, + color: Colors.white, + ), + ), + const SizedBox( + width: 5, ), - ), - const SizedBox( - width: 5, - ), - SvgPicture.asset("assets/logo.svg") - ], + SvgPicture.asset("assets/logo.svg") + ], + ), ), ), const SizedBox( diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 64a0ece..7299b5c 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 2db3c22..786ff5c 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 14b5f7c..21ae5af 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -6,7 +6,11 @@ import FlutterMacOS import Foundation import file_selector_macos +import shared_preferences_foundation +import url_launcher_macos func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) + SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 6f92f75..8a19df0 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -73,6 +73,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + ffi: + dependency: transitive + description: + name: ffi + sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + url: "https://pub.dev" + source: hosted + version: "2.1.3" + file: + dependency: transitive + description: + name: file + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" file_selector_linux: dependency: transitive description: @@ -328,6 +344,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider_linux: + dependency: transitive + description: + name: path_provider_linux + sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279 + url: "https://pub.dev" + source: hosted + version: "2.2.1" + path_provider_platform_interface: + dependency: transitive + description: + name: path_provider_platform_interface + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_windows: + dependency: transitive + description: + name: path_provider_windows + sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7 + url: "https://pub.dev" + source: hosted + version: "2.3.0" petitparser: dependency: transitive description: @@ -336,6 +376,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.2" + platform: + dependency: transitive + description: + name: platform + sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + url: "https://pub.dev" + source: hosted + version: "3.1.5" plugin_platform_interface: dependency: transitive description: @@ -352,6 +400,62 @@ packages: url: "https://pub.dev" source: hosted version: "2.5.1" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + url: "https://pub.dev" + source: hosted + version: "2.5.2" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "57cbf196c486bc2cf1f02b85784932c6094376284b3ad5779d1b1c6c6a816b80" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: d2ca4132d3946fec2184261726b355836a82c33d7d5b67af32692aff18a4684e + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sky_engine: dependency: transitive description: flutter @@ -421,6 +525,94 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + uni_links: + dependency: "direct main" + description: + name: uni_links + sha256: "051098acfc9e26a9fde03b487bef5d3d228ca8f67693480c6f33fd4fbb8e2b6e" + url: "https://pub.dev" + source: hosted + version: "0.5.1" + uni_links_platform_interface: + dependency: transitive + description: + name: uni_links_platform_interface + sha256: "929cf1a71b59e3b7c2d8a2605a9cf7e0b125b13bc858e55083d88c62722d4507" + url: "https://pub.dev" + source: hosted + version: "1.0.0" + uni_links_web: + dependency: transitive + description: + name: uni_links_web + sha256: "7539db908e25f67de2438e33cc1020b30ab94e66720b5677ba6763b25f6394df" + url: "https://pub.dev" + source: hosted + version: "0.1.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + url: "https://pub.dev" + source: hosted + version: "6.3.0" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + url: "https://pub.dev" + source: hosted + version: "6.3.9" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + url: "https://pub.dev" + source: hosted + version: "3.2.0" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + url: "https://pub.dev" + source: hosted + version: "3.2.0" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "772638d3b34c779ede05ba3d38af34657a05ac55b06279ea6edd409e323dca8e" + url: "https://pub.dev" + source: hosted + version: "2.3.3" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + url: "https://pub.dev" + source: hosted + version: "3.1.2" vector_graphics: dependency: transitive description: @@ -469,6 +661,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.1" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + url: "https://pub.dev" + source: hosted + version: "1.0.4" xml: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bfc9b63..3fd70be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -38,6 +38,9 @@ dependencies: go_router: ^14.2.1 flutter_riverpod: ^2.5.1 image_picker: ^1.1.2 + uni_links: ^0.5.1 + url_launcher: ^6.3.0 + shared_preferences: ^2.3.2 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 77ab7a0..043a96f 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FileSelectorWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("FileSelectorWindows")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a423a02..a95e267 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST file_selector_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST From f51bbec3193876aca0ce628039fa31db35435461 Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Thu, 19 Sep 2024 18:02:00 +0530 Subject: [PATCH 02/11] fix: auth nav --- lib/app/router/router.dart | 95 +++++++++++++--------------- lib/constants/constants.dart | 2 +- lib/view/screens/connect_screen.dart | 4 +- lib/view/screens/home_screen.dart | 1 + lib/view/screens/splash_screen.dart | 41 ++++++++++++ 5 files changed, 90 insertions(+), 53 deletions(-) create mode 100644 lib/view/screens/splash_screen.dart diff --git a/lib/app/router/router.dart b/lib/app/router/router.dart index 2ff1104..d9f2040 100644 --- a/lib/app/router/router.dart +++ b/lib/app/router/router.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/view/screens/admin_screen.dart'; import 'package:spotify_collab_app/view/screens/create_screen.dart'; @@ -7,67 +8,59 @@ import 'package:spotify_collab_app/view/screens/login_screen.dart'; import 'package:spotify_collab_app/view/screens/signup_screen.dart'; import 'package:spotify_collab_app/view/screens/home_screen.dart'; import 'package:spotify_collab_app/view/screens/connect_screen.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify_collab_app/providers/auth_provider.dart'; +import 'package:spotify_collab_app/view/screens/splash_screen.dart'; final routerProvider = Provider((ref) { - final authState = ref.watch(authProvider); - return GoRouter( initialLocation: '/', - redirect: (context, state) { - final isLoggedIn = authState.accessToken != null; - - if (!isLoggedIn && state.matchedLocation != '/connect') { - return '/connect'; - } else if (isLoggedIn && state.matchedLocation == '/connect') { - return '/home'; - } - return null; - }, routes: [ GoRoute( path: '/', + pageBuilder: (context, state) => const MaterialPage( + child: SplashScreen(), + ), + ), + GoRoute( + path: '/connect', pageBuilder: (context, state) => const MaterialPage( child: ConnectScreen(), ), - routes: [ - GoRoute( - path: 'login', - pageBuilder: (context, state) => - const MaterialPage(child: LoginScreen()), - ), - GoRoute( - path: 'signup', - pageBuilder: (context, state) => - const MaterialPage(child: SignupScreen()), - ), - GoRoute( - path: 'connect', - pageBuilder: (context, state) => - const MaterialPage(child: ConnectScreen()), - ), - GoRoute( - path: 'home', - pageBuilder: (context, state) => - const MaterialPage(child: HomeScreen()), - ), - GoRoute( - path: 'create', - pageBuilder: (context, state) => - const MaterialPage(child: CreateScreen()), - ), - GoRoute( - path: 'join', - pageBuilder: (context, state) => - const MaterialPage(child: JoinScreen()), - ), - GoRoute( - path: 'admin', - pageBuilder: (context, state) => - const MaterialPage(child: AdminScreen()), - ), - ], + ), + GoRoute( + path: '/home', + pageBuilder: (context, state) => const MaterialPage( + child: HomeScreen(), + ), + ), + GoRoute( + path: '/login', + pageBuilder: (context, state) => const MaterialPage( + child: LoginScreen(), + ), + ), + GoRoute( + path: '/signup', + pageBuilder: (context, state) => const MaterialPage( + child: SignupScreen(), + ), + ), + GoRoute( + path: '/create', + pageBuilder: (context, state) => const MaterialPage( + child: CreateScreen(), + ), + ), + GoRoute( + path: '/join', + pageBuilder: (context, state) => const MaterialPage( + child: JoinScreen(), + ), + ), + GoRoute( + path: '/admin', + pageBuilder: (context, state) => const MaterialPage( + child: AdminScreen(), + ), ), ], ); diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart index a8694d8..1645b6c 100644 --- a/lib/constants/constants.dart +++ b/lib/constants/constants.dart @@ -1 +1 @@ -const devUrl = 'https://3ba5-171-79-54-237.ngrok-free.app'; +const devUrl = 'https://c2de-136-233-9-106.ngrok-free.app'; diff --git a/lib/view/screens/connect_screen.dart b/lib/view/screens/connect_screen.dart index 0133f4b..80be69f 100644 --- a/lib/view/screens/connect_screen.dart +++ b/lib/view/screens/connect_screen.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/constants/constants.dart'; import 'package:spotify_collab_app/providers/auth_provider.dart'; import 'package:uni_links/uni_links.dart'; @@ -32,6 +33,7 @@ class ConnectScreenState extends ConsumerState { log('Access Token: $accessToken'); ref.read(authProvider.notifier).setAccessToken(accessToken); + context.replace('/home'); } else { log('No access token found'); } @@ -48,7 +50,7 @@ class ConnectScreenState extends ConsumerState { } Future _launchSpotifyLogin() async { - const url = '$devUrl/v1/auth/spotify/login'; + const url = '$devUrl/v1/auth/spotify/login/app'; launchUrl(Uri.parse(url)); } diff --git a/lib/view/screens/home_screen.dart b/lib/view/screens/home_screen.dart index b0c3561..aea4fc0 100644 --- a/lib/view/screens/home_screen.dart +++ b/lib/view/screens/home_screen.dart @@ -10,6 +10,7 @@ class HomeScreen extends StatelessWidget { return Scaffold( backgroundColor: const Color(0xff111111), appBar: AppBar( + automaticallyImplyLeading: false, title: const Title( title: "shared playlists", ), diff --git a/lib/view/screens/splash_screen.dart b/lib/view/screens/splash_screen.dart new file mode 100644 index 0000000..2d79a97 --- /dev/null +++ b/lib/view/screens/splash_screen.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class SplashScreen extends ConsumerStatefulWidget { + const SplashScreen({super.key}); + + @override + SplashScreenState createState() => SplashScreenState(); +} + +class SplashScreenState extends ConsumerState { + @override + void initState() { + super.initState(); + _checkForToken(); + } + + Future _checkForToken() async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + final accessToken = prefs.getString('access_token'); + + if (!mounted) return; + + if (accessToken != null) { + context.replace('/home'); + } else { + context.replace('/connect'); + } + } + + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: CircularProgressIndicator(), + ), + ); + } +} From fec5a05bb1af1ba54740640fafe5ed912dca69f0 Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Thu, 19 Sep 2024 18:16:27 +0530 Subject: [PATCH 03/11] feat: api util --- lib/utils/api_util.dart | 58 +++++++++++++++++++++++++++++++++++++++++ pubspec.lock | 16 ++++++++++++ pubspec.yaml | 1 + 3 files changed, 75 insertions(+) create mode 100644 lib/utils/api_util.dart diff --git a/lib/utils/api_util.dart b/lib/utils/api_util.dart new file mode 100644 index 0000000..9c1cf42 --- /dev/null +++ b/lib/utils/api_util.dart @@ -0,0 +1,58 @@ +import 'package:dio/dio.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotify_collab_app/constants/constants.dart'; + +class ApiUtil { + ApiUtil._internal() { + dio.interceptors.add(InterceptorsWrapper( + onRequest: (options, handler) async { + SharedPreferences prefs = await SharedPreferences.getInstance(); + String? token = prefs.getString('access_token'); + + if (token != null) { + options.headers['Authorization'] = 'Bearer $token'; + } + + return handler.next(options); + }, + onError: (DioException e, handler) { + return handler.next(e); + }, + )); + } + + static final ApiUtil _instance = ApiUtil._internal(); + + factory ApiUtil() => _instance; + + Dio dio = Dio(BaseOptions( + baseUrl: devUrl, + )); + + Future get(String endpoint) async { + try { + Response response = await dio.get(endpoint); + return response; + } catch (e) { + throw Exception('Failed to load data: $e'); + } + } + + Future post(String endpoint, Map data) async { + try { + Response response = await dio.post(endpoint, data: data); + return response; + } catch (e) { + throw Exception('Failed to post data: $e'); + } + } + + Future delete(String endpoint, Map data) async { + try { + Response response = await dio.post(endpoint, queryParameters: data); + return response; + } catch (e) { + throw Exception('Failed to post data: $e'); + } + } +} diff --git a/pubspec.lock b/pubspec.lock index 8a19df0..d7c89f3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -65,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dio: + dependency: "direct main" + description: + name: dio + sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + url: "https://pub.dev" + source: hosted + version: "5.7.0" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "33259a9276d6cea88774a0000cfae0d861003497755969c92faa223108620dc8" + url: "https://pub.dev" + source: hosted + version: "2.0.0" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 3fd70be..bb37779 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -41,6 +41,7 @@ dependencies: uni_links: ^0.5.1 url_launcher: ^6.3.0 shared_preferences: ^2.3.2 + dio: ^5.7.0 dev_dependencies: flutter_test: From 589269f6e42db3bd48f4de719d56b82a1e932346 Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Thu, 19 Sep 2024 18:40:26 +0530 Subject: [PATCH 04/11] feat: create playlist navigation --- lib/view/screens/create_screen.dart | 3 ++- lib/view/screens/home_screen.dart | 34 ++++++++++++++++------------- 2 files changed, 21 insertions(+), 16 deletions(-) diff --git a/lib/view/screens/create_screen.dart b/lib/view/screens/create_screen.dart index d1cd098..df28a81 100644 --- a/lib/view/screens/create_screen.dart +++ b/lib/view/screens/create_screen.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/view/widgets/background_widget.dart'; import 'package:spotify_collab_app/view/widgets/custom_text_field.dart'; import 'package:spotify_collab_app/providers/photo_upload_notifier.dart'; @@ -35,7 +36,7 @@ class CreateScreen extends ConsumerWidget { Row( children: [ IconButton( - onPressed: () {}, + onPressed: () => context.go('/home'), icon: const Icon(Icons.arrow_back_ios), color: const Color(0xff5822EE), ), diff --git a/lib/view/screens/home_screen.dart b/lib/view/screens/home_screen.dart index aea4fc0..0f6ec29 100644 --- a/lib/view/screens/home_screen.dart +++ b/lib/view/screens/home_screen.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; class HomeScreen extends StatelessWidget { const HomeScreen({super.key}); @@ -28,12 +29,12 @@ class HomeScreen extends StatelessWidget { fit: BoxFit.cover, ), ), - const Padding( - padding: EdgeInsets.all(32.0), + Padding( + padding: const EdgeInsets.all(32.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( + const Text( "Create new Playlist ·", style: TextStyle( fontFamily: "Gotham", @@ -41,14 +42,17 @@ class HomeScreen extends StatelessWidget { fontSize: 16, ), ), - SizedBox( + const SizedBox( height: 10, ), - NewPlaylistButton(), - SizedBox( + InkWell( + borderRadius: const BorderRadius.all(Radius.circular(8)), + onTap: () => context.go('/create'), + child: const NewPlaylistButton()), + const SizedBox( height: 20, ), - Text( + const Text( "Active ·", style: TextStyle( fontFamily: "Gotham", @@ -56,16 +60,16 @@ class HomeScreen extends StatelessWidget { fontSize: 16, ), ), - SizedBox( + const SizedBox( height: 10, ), - PlaylistCard( + const PlaylistCard( isActive: true, ), - SizedBox( + const SizedBox( height: 20, ), - Text( + const Text( "Recent ·", style: TextStyle( fontFamily: "Gotham", @@ -73,18 +77,18 @@ class HomeScreen extends StatelessWidget { fontSize: 16, ), ), - SizedBox( + const SizedBox( height: 10, ), - PlaylistCard( + const PlaylistCard( name: "WomenTechies' 24", participants: 234, img: "assets/wt.png", ), - SizedBox( + const SizedBox( height: 15, ), - PlaylistCard( + const PlaylistCard( name: "Hexathon' 23", participants: 1200, img: "assets/hexathon.png", From a3266924148857c14cb708e9ead028a9788f4d25 Mon Sep 17 00:00:00 2001 From: ssaakaash Date: Thu, 19 Sep 2024 19:18:35 +0530 Subject: [PATCH 05/11] feat: added yay_screen --- lib/view/screens/yay_screen.dart | 210 +++++++++++++++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 lib/view/screens/yay_screen.dart diff --git a/lib/view/screens/yay_screen.dart b/lib/view/screens/yay_screen.dart new file mode 100644 index 0000000..267ce87 --- /dev/null +++ b/lib/view/screens/yay_screen.dart @@ -0,0 +1,210 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:spotify_collab_app/constants/constants.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class YayScreen extends ConsumerStatefulWidget { + const YayScreen({super.key}); + + @override + ConnectScreenState createState() => ConnectScreenState(); +} + +class ConnectScreenState extends ConsumerState { + StreamSubscription? _sub; + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + _sub?.cancel(); + super.dispose(); + } + + Future _launchSpotifyLogin() async { + const url = '$devUrl/v1/auth/spotify/login/app'; + launchUrl(Uri.parse(url)); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: const Color(0xff5822EE), + appBar: AppBar( + backgroundColor: Colors.transparent, + ), + body: Stack(children: [ + SizedBox( + height: double.infinity, + width: double.infinity, + child: SvgPicture.asset( + 'assets/bg_splash.svg', + fit: BoxFit.cover, + ), + ), + const Padding( + padding: EdgeInsets.only(left: 40.0, right: 40.0, bottom: 40.0), + child: Column( + children: [ + Text( + 'Yay!', + style: TextStyle( + fontFamily: 'Gotham', + shadows: [ + Shadow( + color: Color(0xff000000), + offset: Offset(0, 2), + ), + ], + fontSize: 30, + fontWeight: FontWeight.w900, + color: Colors.white, + ), + ), + Text( + 'Your Event is Created!', + style: TextStyle( + fontFamily: 'Gotham', + shadows: [ + Shadow( + color: Color(0xff000000), + offset: Offset(0, 2), + ), + ], + fontSize: 27, + fontWeight: FontWeight.w900, + color: Colors.white, + ), + ), + SizedBox( + height: 20, + ), + ], + ), + ), + ]), + ); + } +} + +class PlaylistCard extends StatelessWidget { + const PlaylistCard( + {super.key, + this.isActive = false, + this.name = "DevJams' 24", + this.participants = 587, + this.img = "assets/dino.png"}); + + final bool isActive; + final String name; + final int participants; + final String img; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Container( + height: 102, + decoration: BoxDecoration( + color: isActive ? const Color(0xff5822EE) : Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(8))), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Stack(children: [ + Row( + children: [ + Container( + height: 57, + width: 57, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage(img), fit: BoxFit.cover), + shape: BoxShape.circle, + ), + ), + const SizedBox( + width: 10, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + name, + style: TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + color: !isActive + ? const Color.fromARGB(255, 0, 0, 0) + : Colors.white), + ), + Row( + children: [ + Icon( + Icons.people_alt_outlined, + size: 19, + color: !isActive + ? const Color(0xff000000) + : Colors.white, + ), + const SizedBox( + width: 5, + ), + Text( + participants.toString(), + style: TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + color: !isActive + ? const Color(0xff000000) + : Colors.white, + ), + ) + ], + ) + ], + ), + const Spacer(), + ], + ), + Align( + alignment: Alignment.bottomRight, + child: Container( + width: 97, + height: 31, + decoration: BoxDecoration( + color: + !isActive ? const Color(0xff5822EE) : Colors.white, + borderRadius: + const BorderRadius.all(Radius.circular(8)), + ), + child: Center( + child: Text( + isActive ? "Manage" : "View", + style: TextStyle( + color: isActive + ? const Color(0xff5822EE) + : Colors.white, + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + ), + ), + ) + ]), + )), + ), + ], + ); + } +} From 5361a1502c2f779553f558088da551ba7f6dd272 Mon Sep 17 00:00:00 2001 From: ssaakaash Date: Thu, 19 Sep 2024 20:02:30 +0530 Subject: [PATCH 06/11] feat: completed yay screen --- lib/view/screens/yay_screen.dart | 185 +++++++++++++++++++++---------- 1 file changed, 127 insertions(+), 58 deletions(-) diff --git a/lib/view/screens/yay_screen.dart b/lib/view/screens/yay_screen.dart index 267ce87..3525ed1 100644 --- a/lib/view/screens/yay_screen.dart +++ b/lib/view/screens/yay_screen.dart @@ -4,6 +4,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:spotify_collab_app/constants/constants.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:text_marquee/text_marquee.dart'; class YayScreen extends ConsumerStatefulWidget { const YayScreen({super.key}); @@ -87,6 +88,24 @@ class ConnectScreenState extends ConsumerState { ], ), ), + const Padding( + padding: EdgeInsets.all(30.0), + child: Column( + children: [ + Spacer(), + PlaylistCard( + isActive: true, + name: "DevJams' 24", + img: "assets/dino.png", + host: "Souvik", + ), + SizedBox( + height: 100, + ), + Spacer(), + ], + ), + ) ]), ); } @@ -97,13 +116,13 @@ class PlaylistCard extends StatelessWidget { {super.key, this.isActive = false, this.name = "DevJams' 24", - this.participants = 587, + this.host = "Souvik", this.img = "assets/dino.png"}); final bool isActive; final String name; - final int participants; final String img; + final String host; @override Widget build(BuildContext context) { @@ -111,18 +130,26 @@ class PlaylistCard extends StatelessWidget { children: [ Expanded( child: Container( - height: 102, + height: 189, decoration: BoxDecoration( - color: isActive ? const Color(0xff5822EE) : Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(8))), + color: isActive ? const Color(0xff111111) : Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(8)), + boxShadow: const [ + BoxShadow( + color: Color(0xffda84fe), + spreadRadius: 0, + offset: Offset(0, 5), + ) + ]), child: Padding( padding: const EdgeInsets.all(20.0), child: Stack(children: [ Row( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - height: 57, - width: 57, + height: 78, + width: 78, decoration: BoxDecoration( image: DecorationImage( image: AssetImage(img), fit: BoxFit.cover), @@ -130,75 +157,117 @@ class PlaylistCard extends StatelessWidget { ), ), const SizedBox( - width: 10, + width: 20, ), Column( + mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - name, - style: TextStyle( + SizedBox(height: 10,), + Row(children: [ + const Text( + "Event:", + style: TextStyle( fontFamily: "Gotham", fontWeight: FontWeight.w700, - fontSize: 16, - color: !isActive - ? const Color.fromARGB(255, 0, 0, 0) - : Colors.white), - ), - Row( - children: [ - Icon( - Icons.people_alt_outlined, - size: 19, - color: !isActive - ? const Color(0xff000000) - : Colors.white, + fontSize: 19, + color: Color(0xff5822EE), + ), + ), + const SizedBox( + width: 5, + ), + TextMarquee( + name, + style: const TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 19, ), - const SizedBox( - width: 5, + ) + ]), + Row(children: [ + const Text( + "Host:", + style: TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 19, + color: Color(0xff5822EE), + ), + ), + const SizedBox( + width: 5, + ), + TextMarquee( + host, + style: const TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 19, ), - Text( - participants.toString(), - style: TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, - color: !isActive - ? const Color(0xff000000) - : Colors.white, - ), - ) - ], - ) + ) + ]), ], ), const Spacer(), ], ), - Align( - alignment: Alignment.bottomRight, - child: Container( - width: 97, - height: 31, - decoration: BoxDecoration( - color: - !isActive ? const Color(0xff5822EE) : Colors.white, - borderRadius: - const BorderRadius.all(Radius.circular(8)), - ), - child: Center( - child: Text( - isActive ? "Manage" : "View", - style: TextStyle( + Padding( + padding: const EdgeInsets.all(8.0), + child: Align( + alignment: Alignment.bottomCenter, + child: Row(children: [ + Container( + width: 123, + height: 36, + decoration: BoxDecoration( color: isActive ? const Color(0xff5822EE) : Colors.white, - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, + borderRadius: + const BorderRadius.all(Radius.circular(8)), + ), + child: Center( + child: Text( + "Edit", + style: TextStyle( + color: !isActive + ? const Color(0xff5822EE) + : Colors.white, + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), ), ), - ), + const Spacer(), + Container( + width: 123, + height: 36, + decoration: BoxDecoration( + color: !isActive + ? const Color(0xff5822EE) + : Colors.white, + borderRadius: + const BorderRadius.all(Radius.circular(8)), + ), + child: Center( + child: Text( + "Continue", + style: TextStyle( + color: isActive + ? const Color(0xff5822EE) + : Colors.white, + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + ), + ), + ), + ), + ]), ), ) ]), From d6eb3e76666949565d68b0753c0f3570b310a124 Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Thu, 19 Sep 2024 20:59:02 +0530 Subject: [PATCH 07/11] feat: backend integration for create and get playlists --- lib/providers/playlist_provider.dart | 38 ++++ lib/utils/api_util.dart | 2 + lib/view/screens/create_screen.dart | 43 +++- lib/view/screens/home_screen.dart | 301 ++++++++++++--------------- 4 files changed, 214 insertions(+), 170 deletions(-) create mode 100644 lib/providers/playlist_provider.dart diff --git a/lib/providers/playlist_provider.dart b/lib/providers/playlist_provider.dart new file mode 100644 index 0000000..22653ce --- /dev/null +++ b/lib/providers/playlist_provider.dart @@ -0,0 +1,38 @@ +import 'dart:developer'; + +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotify_collab_app/utils/api_util.dart'; + +final playlistProvider = + StateNotifierProvider>((ref) { + return PlaylistNotifier(); +}); + +class Playlist { + final String name; + + Playlist({required this.name}); + + factory Playlist.fromJson(Map json) { + return Playlist( + name: json['name'], + ); + } +} + +class PlaylistNotifier extends StateNotifier> { + PlaylistNotifier() : super([]); + + Future fetchPlaylists() async { + final response = await apiUtil.get('/v1/playlists'); + log(response.data.toString()); + if (response.statusCode == 200) { + if (response.data["message"] == "Playlists successfully retrieved") { + final playlists = (response.data["data"] as List) + .map((data) => Playlist.fromJson(data)) + .toList(); + state = playlists; + } + } + } +} diff --git a/lib/utils/api_util.dart b/lib/utils/api_util.dart index 9c1cf42..7d53299 100644 --- a/lib/utils/api_util.dart +++ b/lib/utils/api_util.dart @@ -56,3 +56,5 @@ class ApiUtil { } } } + +ApiUtil apiUtil = ApiUtil(); diff --git a/lib/view/screens/create_screen.dart b/lib/view/screens/create_screen.dart index df28a81..f61533f 100644 --- a/lib/view/screens/create_screen.dart +++ b/lib/view/screens/create_screen.dart @@ -1,8 +1,13 @@ +import 'dart:developer'; + +import 'package:dio/dio.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:spotify_collab_app/utils/api_util.dart'; +import 'package:spotify_collab_app/view/models/create_playlist_sucess.dart'; import 'package:spotify_collab_app/view/widgets/background_widget.dart'; import 'package:spotify_collab_app/view/widgets/custom_text_field.dart'; import 'package:spotify_collab_app/providers/photo_upload_notifier.dart'; @@ -130,7 +135,43 @@ class CreateScreen extends ConsumerWidget { const Color(0xff5822EE), ), ), - onPressed: () {}, + onPressed: () async { + if (eventNameController.text.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Event name cannot be empty'), + backgroundColor: Colors.red, + ), + ); + } else { + Response response = + await apiUtil.post('/v1/playlists', { + 'name': eventNameController.text, + }); + log(response.toString()); + if (response.statusCode == 200) { + if (response.data["message"] == + "playlist successfully created") { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: + Text('Playlist successfully created'), + backgroundColor: Colors.green, + ), + ); + + context.go('/home'); + } + } else { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Failed to create playlist'), + backgroundColor: Colors.red, + ), + ); + } + } + }, child: const Padding( padding: EdgeInsets.symmetric( horizontal: 5.0, vertical: 5), diff --git a/lib/view/screens/home_screen.dart b/lib/view/screens/home_screen.dart index 0f6ec29..a23fd60 100644 --- a/lib/view/screens/home_screen.dart +++ b/lib/view/screens/home_screen.dart @@ -1,109 +1,109 @@ import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; +import 'package:spotify_collab_app/providers/playlist_provider.dart'; -class HomeScreen extends StatelessWidget { +class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final playlistNotifier = ref.read(playlistProvider.notifier); + return Scaffold( backgroundColor: const Color(0xff111111), appBar: AppBar( automaticallyImplyLeading: false, - title: const Title( + title: const CustomTitle( title: "shared playlists", ), backgroundColor: Colors.transparent, ), - body: Stack( - children: [ - SizedBox( - height: double.maxFinite, - width: double.maxFinite, - child: SvgPicture.asset( - 'assets/bg.svg', - colorFilter: - const ColorFilter.mode(Color(0xffd1dfdb), BlendMode.srcIn), - fit: BoxFit.cover, + body: RefreshIndicator( + onRefresh: () async { + await playlistNotifier.fetchPlaylists(); + }, + child: Stack( + children: [ + SizedBox( + height: double.maxFinite, + width: double.maxFinite, + child: SvgPicture.asset( + 'assets/bg.svg', + colorFilter: + const ColorFilter.mode(Color(0xffd1dfdb), BlendMode.srcIn), + fit: BoxFit.cover, + ), ), - ), - Padding( - padding: const EdgeInsets.all(32.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - const Text( - "Create new Playlist ·", - style: TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, + Padding( + padding: const EdgeInsets.all(32.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + "Create new Playlist ·", + style: TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + ), ), - ), - const SizedBox( - height: 10, - ), - InkWell( + const SizedBox(height: 10), + InkWell( borderRadius: const BorderRadius.all(Radius.circular(8)), onTap: () => context.go('/create'), - child: const NewPlaylistButton()), - const SizedBox( - height: 20, - ), - const Text( - "Active ·", - style: TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, + child: Ink(child: const NewPlaylistButton()), + ), + const SizedBox(height: 20), + const Text( + "Active ·", + style: TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + ), ), - ), - const SizedBox( - height: 10, - ), - const PlaylistCard( - isActive: true, - ), - const SizedBox( - height: 20, - ), - const Text( - "Recent ·", - style: TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, + const SizedBox(height: 10), + FutureBuilder( + future: playlistNotifier.fetchPlaylists(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return const Center( + child: Text('Failed to load playlists.')); + } else { + final playlists = ref.watch(playlistProvider); + return Expanded( + child: ListView.builder( + itemCount: playlists.length, + itemBuilder: (context, index) { + final playlist = playlists[index]; + return Padding( + padding: const EdgeInsets.only(bottom: 10), + child: PlaylistCard( + name: playlist.name, + ), + ); + }, + ), + ); + } + }, ), - ), - const SizedBox( - height: 10, - ), - const PlaylistCard( - name: "WomenTechies' 24", - participants: 234, - img: "assets/wt.png", - ), - const SizedBox( - height: 15, - ), - const PlaylistCard( - name: "Hexathon' 23", - participants: 1200, - img: "assets/hexathon.png", - ), - ], + ], + ), ), - ), - ], + ], + ), ), ); } } -class Title extends StatelessWidget { - const Title({ +class CustomTitle extends StatelessWidget { + const CustomTitle({ super.key, required this.title, }); @@ -115,27 +115,22 @@ class Title extends StatelessWidget { return Row( children: [ const Spacer(), - const SizedBox( - width: 20, - ), + const SizedBox(width: 20), Text( title, style: const TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 34, - shadows: [ - Shadow(offset: Offset(0, 1), color: Color(0xffDA84FE)) - ]), + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 34, + shadows: [ + Shadow(offset: Offset(0, 1), color: Color(0xffDA84FE)), + ], + ), ), Column( children: [ - SvgPicture.asset( - 'assets/highlight.svg', - ), - const SizedBox( - height: 30, - ), + SvgPicture.asset('assets/highlight.svg'), + const SizedBox(height: 30), ], ), const Spacer(), @@ -145,17 +140,14 @@ class Title extends StatelessWidget { } class PlaylistCard extends StatelessWidget { - const PlaylistCard( - {super.key, - this.isActive = false, - this.name = "DevJams' 24", - this.participants = 587, - this.img = "assets/dino.png"}); + const PlaylistCard({ + super.key, + this.isActive = false, + required this.name, + }); final bool isActive; final String name; - final int participants; - final String img; @override Widget build(BuildContext context) { @@ -163,70 +155,40 @@ class PlaylistCard extends StatelessWidget { children: [ Expanded( child: Container( - height: 102, - decoration: BoxDecoration( - color: isActive ? const Color(0xff5822EE) : Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(8))), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Stack(children: [ - Row( + height: 102, + decoration: BoxDecoration( + color: isActive ? const Color(0xff5822EE) : Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Row( + children: [ + Container( + height: 57, + width: 57, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey, + ), + child: const Icon(Icons.music_note, size: 32), + ), + const SizedBox(width: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Container( - height: 57, - width: 57, - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage(img), fit: BoxFit.cover), - shape: BoxShape.circle, + Text( + name, + style: TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + color: isActive ? Colors.white : Colors.black, ), ), - const SizedBox( - width: 10, - ), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name, - style: TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, - color: !isActive - ? const Color.fromARGB(255, 0, 0, 0) - : Colors.white), - ), - Row( - children: [ - Icon( - Icons.people_alt_outlined, - size: 19, - color: !isActive - ? const Color(0xff000000) - : Colors.white, - ), - const SizedBox( - width: 5, - ), - Text( - participants.toString(), - style: TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, - color: !isActive - ? const Color(0xff000000) - : Colors.white, - ), - ) - ], - ) - ], - ), - const Spacer(), ], ), + const Spacer(), Align( alignment: Alignment.bottomRight, child: Container( @@ -252,9 +214,11 @@ class PlaylistCard extends StatelessWidget { ), ), ), - ) - ]), - )), + ), + ], + ), + ), + ), ), ], ); @@ -262,9 +226,7 @@ class PlaylistCard extends StatelessWidget { } class NewPlaylistButton extends StatelessWidget { - const NewPlaylistButton({ - super.key, - }); + const NewPlaylistButton({super.key}); @override Widget build(BuildContext context) { @@ -274,8 +236,9 @@ class NewPlaylistButton extends StatelessWidget { child: Container( height: 59, decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(8))), + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), child: const Icon( Icons.add_circle, color: Color(0xff5822EE), From 5c5e08d38cab456f6a8216f7ec13bf356f2d5bdd Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Thu, 19 Sep 2024 21:06:41 +0530 Subject: [PATCH 08/11] fix: unused import --- lib/view/screens/create_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/view/screens/create_screen.dart b/lib/view/screens/create_screen.dart index f61533f..f50812c 100644 --- a/lib/view/screens/create_screen.dart +++ b/lib/view/screens/create_screen.dart @@ -7,7 +7,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/utils/api_util.dart'; -import 'package:spotify_collab_app/view/models/create_playlist_sucess.dart'; import 'package:spotify_collab_app/view/widgets/background_widget.dart'; import 'package:spotify_collab_app/view/widgets/custom_text_field.dart'; import 'package:spotify_collab_app/providers/photo_upload_notifier.dart'; From 23787503b36fe5dcf83aa9e37b7233659838fcf2 Mon Sep 17 00:00:00 2001 From: ssaakaash Date: Tue, 29 Oct 2024 12:30:52 +0530 Subject: [PATCH 09/11] fix: modified backend functions to support adding songs --- ios/Podfile.lock | 19 +++++ lib/app/router/router.dart | 1 + lib/constants/constants.dart | 2 +- lib/providers/playlist_provider.dart | 13 +++ lib/utils/api_util.dart | 4 +- lib/view/screens/admin_screen.dart | 123 +++++++++++++-------------- lib/view/screens/connect_screen.dart | 1 + lib/view/screens/home_screen.dart | 28 +++--- pubspec.lock | 8 ++ pubspec.yaml | 1 + 10 files changed, 124 insertions(+), 76 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index c7be6e0..a809249 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -2,20 +2,39 @@ PODS: - Flutter (1.0.0) - image_picker_ios (0.0.1): - Flutter + - shared_preferences_foundation (0.0.1): + - Flutter + - FlutterMacOS + - uni_links (0.0.1): + - Flutter + - url_launcher_ios (0.0.1): + - Flutter DEPENDENCIES: - Flutter (from `Flutter`) - image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`) + - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - uni_links (from `.symlinks/plugins/uni_links/ios`) + - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) EXTERNAL SOURCES: Flutter: :path: Flutter image_picker_ios: :path: ".symlinks/plugins/image_picker_ios/ios" + shared_preferences_foundation: + :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + uni_links: + :path: ".symlinks/plugins/uni_links/ios" + url_launcher_ios: + :path: ".symlinks/plugins/url_launcher_ios/ios" SPEC CHECKSUMS: Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 + shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + uni_links: d97da20c7701486ba192624d99bffaaffcfc298a + url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/lib/app/router/router.dart b/lib/app/router/router.dart index d9f2040..2e89132 100644 --- a/lib/app/router/router.dart +++ b/lib/app/router/router.dart @@ -9,6 +9,7 @@ import 'package:spotify_collab_app/view/screens/signup_screen.dart'; import 'package:spotify_collab_app/view/screens/home_screen.dart'; import 'package:spotify_collab_app/view/screens/connect_screen.dart'; import 'package:spotify_collab_app/view/screens/splash_screen.dart'; +import 'package:spotify_collab_app/view/screens/yay_screen.dart'; final routerProvider = Provider((ref) { return GoRouter( diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart index 1645b6c..0ce5eaf 100644 --- a/lib/constants/constants.dart +++ b/lib/constants/constants.dart @@ -1 +1 @@ -const devUrl = 'https://c2de-136-233-9-106.ngrok-free.app'; +const devUrl = 'https://spotify-collab-backend-w7av.onrender.com'; diff --git a/lib/providers/playlist_provider.dart b/lib/providers/playlist_provider.dart index 22653ce..2e145b2 100644 --- a/lib/providers/playlist_provider.dart +++ b/lib/providers/playlist_provider.dart @@ -35,4 +35,17 @@ class PlaylistNotifier extends StateNotifier> { } } } + + Future fetchSongs(String uuid) async { + final response = await apiUtil.get('/v1/songs', {"playlist_uuid": uuid}); + log(response.data.toString()); + if (response.statusCode == 200) { + if (response.data["message"] == "Songs successfully retrieved") { + final songs = (response.data["data"]["accepted"] as List) + .map((data) => Playlist.fromJson(data)) + .toList(); + state = songs; + } + } + } } diff --git a/lib/utils/api_util.dart b/lib/utils/api_util.dart index 7d53299..1ce49ab 100644 --- a/lib/utils/api_util.dart +++ b/lib/utils/api_util.dart @@ -29,9 +29,9 @@ class ApiUtil { baseUrl: devUrl, )); - Future get(String endpoint) async { + Future get(String endpoint, [Map? data]) async { try { - Response response = await dio.get(endpoint); + Response response = await dio.get(endpoint, data: data); return response; } catch (e) { throw Exception('Failed to load data: $e'); diff --git a/lib/view/screens/admin_screen.dart b/lib/view/screens/admin_screen.dart index ca649b6..5263963 100644 --- a/lib/view/screens/admin_screen.dart +++ b/lib/view/screens/admin_screen.dart @@ -2,7 +2,9 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/providers/admin_screen_provider.dart'; +import 'package:spotify_collab_app/providers/playlist_provider.dart'; class AdminScreen extends ConsumerStatefulWidget { const AdminScreen({super.key}); @@ -42,6 +44,7 @@ class _AdminScreenState extends ConsumerState final currentIndex = ref.watch(tabProvider); final requestAction = ref.watch(requestActionProvider); + final playlistNotifier = ref.read(playlistProvider.notifier); final List> items = [ { @@ -56,6 +59,8 @@ class _AdminScreenState extends ConsumerState }, ]; + // final List> items = playlistNotifier.fetchSongs(); + final participants = [ "Souvik", "Jothish", @@ -76,7 +81,9 @@ class _AdminScreenState extends ConsumerState centerTitle: false, automaticallyImplyLeading: false, title: IconButton( - onPressed: () {}, + onPressed: () { + context.pop(); + }, iconSize: 16, icon: const Icon( Icons.arrow_back_ios, @@ -153,15 +160,6 @@ class _AdminScreenState extends ConsumerState ), ), ), - Tab( - child: Text( - "Participants", - style: TextStyle( - fontFamily: 'Gotham', - fontWeight: FontWeight.w600, - ), - ), - ), ], ), ), @@ -174,9 +172,8 @@ class _AdminScreenState extends ConsumerState controller: _tabController, physics: const NeverScrollableScrollPhysics(), children: [ - buildTabContent(items, isPlaylist: true), + buildTabContent(playlistNotifier, isPlaylist: true), buildRequestTabContent(items, requestAction), - buildTabContent(participants, isParticipant: true), ], ), ), @@ -209,61 +206,61 @@ class _AdminScreenState extends ConsumerState ); } - Widget buildTabContent(List items, + Widget buildTabContent(PlaylistNotifier playlistNotifier, {bool isPlaylist = false, bool isRequest = false, bool isParticipant = false}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), - child: Column( - children: [ - if (isParticipant) - Align( - alignment: Alignment.topRight, - child: Text( - 'Total Participants - ${items.length}', - style: const TextStyle( - fontFamily: 'Gotham', - color: Colors.white, - fontWeight: FontWeight.w300, - fontSize: 12, - ), - ), - ) - else - const SizedBox(), - Expanded( - child: ListView.separated( - itemCount: items.length, - separatorBuilder: (context, index) => const Divider( - color: Color(0xFFCCCCCC), - height: 1, + FutureBuilder( + future: + playlistNotifier.fetchSongs("b65673a0-21ae-4dd6-a7f5-5ec6c204b731"), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.waiting) { + return const Center(child: CircularProgressIndicator()); + } else if (snapshot.hasError) { + return const Center(child: Text('Failed to load playlists.')); + } else { + return Padding( + padding: + const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), + child: Column( + children: [ + const SizedBox(), + Text("Works!") + // Expanded( + // child: ListView.separated( + // itemCount: items.length, + // separatorBuilder: (context, index) => const Divider( + // color: Color(0xFFCCCCCC), + // height: 1, + // ), + // itemBuilder: (context, index) { + // final item = items[index]; + // return isPlaylist + // ? MusicListItem( + // title: item["title"]!, + // subtitle: item["subtitle"]!, + // imageUrl: item["imageUrl"]!, + // isPlaylist: isPlaylist, + // isRequest: isRequest, + // isParticipant: isParticipant, + // ) + // : MusicListItem( + // title: item!, + // subtitle: null, + // imageUrl: null, + // isPlaylist: isPlaylist, + // isRequest: isRequest, + // isParticipant: isParticipant, + // ); + // }, + // ), + // ), + ], ), - itemBuilder: (context, index) { - final item = items[index]; - return isPlaylist - ? MusicListItem( - title: item["title"]!, - subtitle: item["subtitle"]!, - imageUrl: item["imageUrl"]!, - isPlaylist: isPlaylist, - isRequest: isRequest, - isParticipant: isParticipant, - ) - : MusicListItem( - title: item!, - subtitle: null, - imageUrl: null, - isPlaylist: isPlaylist, - isRequest: isRequest, - isParticipant: isParticipant, - ); - }, - ), - ), - ], - ), - ); + ); + } + }); + return const SizedBox(); } Widget buildRequestTabContent( diff --git a/lib/view/screens/connect_screen.dart b/lib/view/screens/connect_screen.dart index 80be69f..73e2fd1 100644 --- a/lib/view/screens/connect_screen.dart +++ b/lib/view/screens/connect_screen.dart @@ -173,3 +173,4 @@ class ConnectScreenState extends ConsumerState { ); } } + diff --git a/lib/view/screens/home_screen.dart b/lib/view/screens/home_screen.dart index a23fd60..2c344df 100644 --- a/lib/view/screens/home_screen.dart +++ b/lib/view/screens/home_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/providers/playlist_provider.dart'; +import 'package:spotify_collab_app/view/screens/admin_screen.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @@ -75,6 +76,9 @@ class HomeScreen extends ConsumerWidget { child: Text('Failed to load playlists.')); } else { final playlists = ref.watch(playlistProvider); + if (playlists.isEmpty) + return const Center( + child: Text('No playlists found')); return Expanded( child: ListView.builder( itemCount: playlists.length, @@ -201,18 +205,22 @@ class PlaylistCard extends StatelessWidget { const BorderRadius.all(Radius.circular(8)), ), child: Center( - child: Text( - isActive ? "Manage" : "View", - style: TextStyle( - color: isActive - ? const Color(0xff5822EE) - : Colors.white, - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, + child: TextButton( + onPressed: () { + context.push("/admin"); + }, + child: Text( + isActive ? "Manage" : "View", + style: TextStyle( + color: isActive + ? const Color(0xff5822EE) + : Colors.white, + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, ), ), - ), + )), ), ), ], diff --git a/pubspec.lock b/pubspec.lock index d7c89f3..c0402f5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -533,6 +533,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.0" + text_marquee: + dependency: "direct main" + description: + name: text_marquee + sha256: "56dfa74b847a6acbf033fcfca88a0dcbc68134d4154f1bc9e68f8f75e196dc25" + url: "https://pub.dev" + source: hosted + version: "0.0.2" typed_data: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index bb37779..bdc9963 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -42,6 +42,7 @@ dependencies: url_launcher: ^6.3.0 shared_preferences: ^2.3.2 dio: ^5.7.0 + text_marquee: ^0.0.2 dev_dependencies: flutter_test: From b5a5f4833bdc37548e5228d5d32f448721946615 Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Sun, 29 Dec 2024 22:27:34 +0530 Subject: [PATCH 10/11] feat: admin screen backend integration --- android/build.gradle | 33 ++ .../gradle/wrapper/gradle-wrapper.properties | 2 +- android/settings.gradle | 4 +- lib/constants/constants.dart | 3 +- lib/providers/admin_screen_provider.dart | 51 ++ lib/providers/playlist_provider.dart | 45 +- lib/utils/api_util.dart | 16 + .../models/playlist_success_response.dart | 73 +++ lib/view/models/songs_response.dart | 507 ++++++++++++++++++ lib/view/screens/admin_screen.dart | 493 +++++++---------- lib/view/screens/connect_screen.dart | 2 +- lib/view/screens/home_screen.dart | 48 +- lib/view/widgets/music_list_item.dart | 124 +++++ pubspec.lock | 178 +++--- 14 files changed, 1156 insertions(+), 423 deletions(-) create mode 100644 lib/view/models/playlist_success_response.dart create mode 100644 lib/view/models/songs_response.dart create mode 100644 lib/view/widgets/music_list_item.dart diff --git a/android/build.gradle b/android/build.gradle index d2ffbff..b7e4f90 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -8,6 +8,39 @@ allprojects { rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" + + afterEvaluate { + // check if android block is available + + if (it.hasProperty('android')) { + if (it.android.namespace == null) { + def manifest = new XmlSlurper().parse(file(it.android.sourceSets.main.manifest.srcFile)) + def packageName = manifest.@package.text() + println("Setting ${packageName} as android namespace") + android.namespace = packageName + } + + def javaVersion = JavaVersion.VERSION_17 + android { + def androidApiVersion = 34 + compileSdkVersion androidApiVersion + defaultConfig { + targetSdkVersion androidApiVersion + } + compileOptions { + sourceCompatibility javaVersion + targetCompatibility javaVersion + } + tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).configureEach { + kotlinOptions { + jvmTarget = javaVersion.toString() + } + } + + } + } + + } } subprojects { project.evaluationDependsOn(":app") diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e1ca574..5af0f53 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-all.zip diff --git a/android/settings.gradle b/android/settings.gradle index 536165d..9465e06 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -18,8 +18,8 @@ pluginManagement { plugins { id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "7.3.0" apply false - id "org.jetbrains.kotlin.android" version "1.7.10" apply false + id "com.android.application" version "8.6.1" apply false + id "org.jetbrains.kotlin.android" version "1.9.0" apply false } include ":app" diff --git a/lib/constants/constants.dart b/lib/constants/constants.dart index 0ce5eaf..74a4099 100644 --- a/lib/constants/constants.dart +++ b/lib/constants/constants.dart @@ -1 +1,2 @@ -const devUrl = 'https://spotify-collab-backend-w7av.onrender.com'; +const devUrl = + 'https://spotify-collab-backend-jothishkamal.onrender.com'; diff --git a/lib/providers/admin_screen_provider.dart b/lib/providers/admin_screen_provider.dart index 2fa9d49..e19ef71 100644 --- a/lib/providers/admin_screen_provider.dart +++ b/lib/providers/admin_screen_provider.dart @@ -1,4 +1,10 @@ +import 'dart:convert'; +import 'dart:developer'; + import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotify_collab_app/utils/api_util.dart'; +import 'package:spotify_collab_app/view/models/songs_response.dart'; enum RequestAction { none, acceptAll, rejectAll } @@ -6,3 +12,48 @@ final tabProvider = StateProvider((ref) => 0); final requestActionProvider = StateProvider((ref) => RequestAction.none); + +final songsProvider = + StateNotifierProvider((ref) { + return SongsNotifier(); +}); + +class SongsNotifier extends StateNotifier { + SongsNotifier() : super(SongsResponse()); + + Future fetchSongs(String uuid) async { + var prefs = await SharedPreferences.getInstance(); + log(prefs.getString('access_token').toString()); + log(uuid); + try { + final response = await apiUtil.get( + '/v1/songs/$uuid', + ); + + log('Songs fetched'); + if (response.statusCode == 200) { + final data = SongsResponse.fromJson(response.data); + + state = state.copyWith( + data: data.data, + message: data.message, + statusCode: data.statusCode, + success: data.success, + ); + } + } catch (e) { + log(e.toString()); + } + } + + void clearSongs() { + final emptySongs = SongsResponse(); + + state = state.copyWith( + data: emptySongs.data, + message: emptySongs.message, + statusCode: emptySongs.statusCode, + success: emptySongs.success, + ); + } +} diff --git a/lib/providers/playlist_provider.dart b/lib/providers/playlist_provider.dart index 2e145b2..d67b26a 100644 --- a/lib/providers/playlist_provider.dart +++ b/lib/providers/playlist_provider.dart @@ -1,7 +1,9 @@ import 'dart:developer'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotify_collab_app/utils/api_util.dart'; +import 'package:spotify_collab_app/view/models/playlist_success_response.dart'; final playlistProvider = StateNotifierProvider>((ref) { @@ -9,13 +11,15 @@ final playlistProvider = }); class Playlist { - final String name; + final String? name; + final String? playlistUuid; - Playlist({required this.name}); + Playlist({required this.name, this.playlistUuid}); factory Playlist.fromJson(Map json) { return Playlist( name: json['name'], + playlistUuid: json['playlist_uuid'], ); } } @@ -23,29 +27,34 @@ class Playlist { class PlaylistNotifier extends StateNotifier> { PlaylistNotifier() : super([]); + String? selectedPlaylistUuid; + String? selectedPlaylistName; + Future fetchPlaylists() async { final response = await apiUtil.get('/v1/playlists'); - log(response.data.toString()); + if (response.statusCode == 200) { - if (response.data["message"] == "Playlists successfully retrieved") { - final playlists = (response.data["data"] as List) - .map((data) => Playlist.fromJson(data)) - .toList(); + final playlistResponse = PlaylistSuccessResponse.fromJson(response.data); + + if (playlistResponse.message == "Playlists successfully retrieved") { + final playlists = playlistResponse.data + ?.map((data) => Playlist( + name: data.name, + playlistUuid: data.playlistUuid, + )) + .toList() ?? + []; + state = playlists; } } } - Future fetchSongs(String uuid) async { - final response = await apiUtil.get('/v1/songs', {"playlist_uuid": uuid}); - log(response.data.toString()); - if (response.statusCode == 200) { - if (response.data["message"] == "Songs successfully retrieved") { - final songs = (response.data["data"]["accepted"] as List) - .map((data) => Playlist.fromJson(data)) - .toList(); - state = songs; - } - } + void selectPlaylist(String uuid) { + selectedPlaylistUuid = uuid; + } + + void selectPlaylistName(String name) { + selectedPlaylistName = name; } } diff --git a/lib/utils/api_util.dart b/lib/utils/api_util.dart index 1ce49ab..bb7f080 100644 --- a/lib/utils/api_util.dart +++ b/lib/utils/api_util.dart @@ -1,3 +1,6 @@ +import 'dart:convert'; +import 'dart:developer'; + import 'package:dio/dio.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotify_collab_app/constants/constants.dart'; @@ -29,9 +32,20 @@ class ApiUtil { baseUrl: devUrl, )); + Future _updateTokenFromMetadata(dynamic responseData) async { + if (responseData is Map && responseData.containsKey('metadata')) { + final metadata = responseData['metadata']; + if (metadata is Map && metadata.containsKey('access_token')) { + final prefs = await SharedPreferences.getInstance(); + await prefs.setString('access_token', metadata['access_token']); + } + } + } + Future get(String endpoint, [Map? data]) async { try { Response response = await dio.get(endpoint, data: data); + await _updateTokenFromMetadata(response.data); return response; } catch (e) { throw Exception('Failed to load data: $e'); @@ -41,6 +55,7 @@ class ApiUtil { Future post(String endpoint, Map data) async { try { Response response = await dio.post(endpoint, data: data); + await _updateTokenFromMetadata(response.data); return response; } catch (e) { throw Exception('Failed to post data: $e'); @@ -50,6 +65,7 @@ class ApiUtil { Future delete(String endpoint, Map data) async { try { Response response = await dio.post(endpoint, queryParameters: data); + await _updateTokenFromMetadata(response.data); return response; } catch (e) { throw Exception('Failed to post data: $e'); diff --git a/lib/view/models/playlist_success_response.dart b/lib/view/models/playlist_success_response.dart new file mode 100644 index 0000000..9f95cce --- /dev/null +++ b/lib/view/models/playlist_success_response.dart @@ -0,0 +1,73 @@ +class PlaylistSuccessResponse { + bool? success; + String? message; + List? data; + int? statusCode; + + PlaylistSuccessResponse( + {this.success, this.message, this.data, this.statusCode}); + + PlaylistSuccessResponse.fromJson(Map json) { + success = json['success']; + message = json['message']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data!.add(Data.fromJson(v)); + }); + } + statusCode = json['status_code']; + } + + Map toJson() { + final Map data = {}; + data['success'] = success; + data['message'] = message; + if (this.data != null) { + data['data'] = this.data!.map((v) => v.toJson()).toList(); + } + data['status_code'] = statusCode; + return data; + } +} + +class Data { + String? userUuid; + String? playlistUuid; + String? playlistId; + String? name; + String? playlistCode; + String? createdAt; + String? updatedAt; + + Data( + {this.userUuid, + this.playlistUuid, + this.playlistId, + this.name, + this.playlistCode, + this.createdAt, + this.updatedAt}); + + Data.fromJson(Map json) { + userUuid = json['user_uuid']; + playlistUuid = json['playlist_uuid']; + playlistId = json['playlist_id']; + name = json['name']; + playlistCode = json['playlist_code']; + createdAt = json['created_at']; + updatedAt = json['updated_at']; + } + + Map toJson() { + final Map data = {}; + data['user_uuid'] = userUuid; + data['playlist_uuid'] = playlistUuid; + data['playlist_id'] = playlistId; + data['name'] = name; + data['playlist_code'] = playlistCode; + data['created_at'] = createdAt; + data['updated_at'] = updatedAt; + return data; + } +} diff --git a/lib/view/models/songs_response.dart b/lib/view/models/songs_response.dart new file mode 100644 index 0000000..90f86f1 --- /dev/null +++ b/lib/view/models/songs_response.dart @@ -0,0 +1,507 @@ +import 'dart:convert'; + +class SongsResponse { + bool? success; + String? message; + Data? data; + int? statusCode; + + SongsResponse({ + this.success, + this.message, + this.data, + this.statusCode, + }); + + factory SongsResponse.fromRawJson(String str) => + SongsResponse.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory SongsResponse.fromJson(Map json) => SongsResponse( + success: json["success"], + message: json["message"], + data: json["data"] == null ? null : Data.fromJson(json["data"]), + statusCode: json["status_code"], + ); + + Map toJson() => { + "success": success, + "message": message, + "data": data?.toJson(), + "status_code": statusCode, + }; + + SongsResponse copyWith({ + bool? success, + String? message, + Data? data, + int? statusCode, + }) { + return SongsResponse( + success: success ?? this.success, + message: message ?? this.message, + data: data ?? this.data, + statusCode: statusCode ?? this.statusCode, + ); + } +} + +class Data { + List? accepted; + List? submitted; + + Data({ + this.accepted, + this.submitted, + }); + + factory Data.fromRawJson(String str) => Data.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Data.fromJson(Map json) => Data( + accepted: json["accepted"] == null + ? [] + : List.from( + json["accepted"]!.map((x) => Accepted.fromJson(x))), + submitted: json["submitted"] == null + ? [] + : List.from( + json["submitted"]!.map((x) => Submitted.fromJson(x))), + ); + + Map toJson() => { + "accepted": accepted == null + ? [] + : List.from(accepted!.map((x) => x.toJson())), + "submitted": submitted == null + ? [] + : List.from(submitted!.map((x) => x.toJson())), + }; +} + +class Accepted { + DateTime? addedAt; + AddedBy? addedBy; + bool? isLocal; + Track? track; + + Accepted({ + this.addedAt, + this.addedBy, + this.isLocal, + this.track, + }); + + factory Accepted.fromRawJson(String str) => + Accepted.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Accepted.fromJson(Map json) => Accepted( + addedAt: + json["added_at"] == null ? null : DateTime.parse(json["added_at"]), + addedBy: json["added_by"] == null + ? null + : AddedBy.fromJson(json["added_by"]), + isLocal: json["is_local"], + track: json["track"] == null ? null : Track.fromJson(json["track"]), + ); + + Map toJson() => { + "added_at": addedAt?.toIso8601String(), + "added_by": addedBy?.toJson(), + "is_local": isLocal, + "track": track?.toJson(), + }; +} + +class AddedBy { + String? displayName; + ExternalUrls? externalUrls; + Followers? followers; + String? href; + String? id; + dynamic images; + String? uri; + + AddedBy({ + this.displayName, + this.externalUrls, + this.followers, + this.href, + this.id, + this.images, + this.uri, + }); + + factory AddedBy.fromRawJson(String str) => AddedBy.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory AddedBy.fromJson(Map json) => AddedBy( + displayName: json["display_name"], + externalUrls: json["external_urls"] == null + ? null + : ExternalUrls.fromJson(json["external_urls"]), + followers: json["followers"] == null + ? null + : Followers.fromJson(json["followers"]), + href: json["href"], + id: json["id"], + images: json["images"], + uri: json["uri"], + ); + + Map toJson() => { + "display_name": displayName, + "external_urls": externalUrls?.toJson(), + "followers": followers?.toJson(), + "href": href, + "id": id, + "images": images, + "uri": uri, + }; +} + +class ExternalUrls { + String? spotify; + + ExternalUrls({ + this.spotify, + }); + + factory ExternalUrls.fromRawJson(String str) => + ExternalUrls.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory ExternalUrls.fromJson(Map json) => ExternalUrls( + spotify: json["spotify"], + ); + + Map toJson() => { + "spotify": spotify, + }; +} + +class Followers { + int? total; + String? href; + + Followers({ + this.total, + this.href, + }); + + factory Followers.fromRawJson(String str) => + Followers.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Followers.fromJson(Map json) => Followers( + total: json["total"], + href: json["href"], + ); + + Map toJson() => { + "total": total, + "href": href, + }; +} + +class Track { + Submitted? track; + dynamic episode; + + Track({ + this.track, + this.episode, + }); + + factory Track.fromRawJson(String str) => Track.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Track.fromJson(Map json) => Track( + track: json["Track"] == null ? null : Submitted.fromJson(json["Track"]), + episode: json["Episode"], + ); + + Map toJson() => { + "Track": track?.toJson(), + "Episode": episode, + }; +} + +class Submitted { + List? artists; + List? availableMarkets; + int? discNumber; + int? durationMs; + bool? explicit; + ExternalUrls? externalUrls; + String? href; + String? id; + String? name; + String? previewUrl; + int? trackNumber; + String? uri; + String? type; + Album? album; + ExternalIds? externalIds; + int? popularity; + dynamic isPlayable; + dynamic linkedFrom; + + Submitted({ + this.artists, + this.availableMarkets, + this.discNumber, + this.durationMs, + this.explicit, + this.externalUrls, + this.href, + this.id, + this.name, + this.previewUrl, + this.trackNumber, + this.uri, + this.type, + this.album, + this.externalIds, + this.popularity, + this.isPlayable, + this.linkedFrom, + }); + + factory Submitted.fromRawJson(String str) => + Submitted.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Submitted.fromJson(Map json) => Submitted( + artists: json["artists"] == null + ? [] + : List.from( + json["artists"]!.map((x) => Artist.fromJson(x))), + availableMarkets: json["available_markets"] == null + ? [] + : List.from(json["available_markets"]!.map((x) => x)), + discNumber: json["disc_number"], + durationMs: json["duration_ms"], + explicit: json["explicit"], + externalUrls: json["external_urls"] == null + ? null + : ExternalUrls.fromJson(json["external_urls"]), + href: json["href"], + id: json["id"], + name: json["name"], + previewUrl: json["preview_url"], + trackNumber: json["track_number"], + uri: json["uri"], + type: json["type"], + album: json["album"] == null ? null : Album.fromJson(json["album"]), + externalIds: json["external_ids"] == null + ? null + : ExternalIds.fromJson(json["external_ids"]), + popularity: json["popularity"], + isPlayable: json["is_playable"], + linkedFrom: json["linked_from"], + ); + + Map toJson() => { + "artists": artists == null + ? [] + : List.from(artists!.map((x) => x.toJson())), + "available_markets": availableMarkets == null + ? [] + : List.from(availableMarkets!.map((x) => x)), + "disc_number": discNumber, + "duration_ms": durationMs, + "explicit": explicit, + "external_urls": externalUrls?.toJson(), + "href": href, + "id": id, + "name": name, + "preview_url": previewUrl, + "track_number": trackNumber, + "uri": uri, + "type": type, + "album": album?.toJson(), + "external_ids": externalIds?.toJson(), + "popularity": popularity, + "is_playable": isPlayable, + "linked_from": linkedFrom, + }; +} + +class Album { + String? name; + List? artists; + String? albumGroup; + String? albumType; + String? id; + String? uri; + List? availableMarkets; + String? href; + List? images; + ExternalUrls? externalUrls; + DateTime? releaseDate; + String? releaseDatePrecision; + + Album({ + this.name, + this.artists, + this.albumGroup, + this.albumType, + this.id, + this.uri, + this.availableMarkets, + this.href, + this.images, + this.externalUrls, + this.releaseDate, + this.releaseDatePrecision, + }); + + factory Album.fromRawJson(String str) => Album.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Album.fromJson(Map json) => Album( + name: json["name"], + artists: json["artists"] == null + ? [] + : List.from( + json["artists"]!.map((x) => Artist.fromJson(x))), + albumGroup: json["album_group"], + albumType: json["album_type"], + id: json["id"], + uri: json["uri"], + availableMarkets: json["available_markets"] == null + ? [] + : List.from(json["available_markets"]!.map((x) => x)), + href: json["href"], + images: json["images"] == null + ? [] + : List.from(json["images"]!.map((x) => Image.fromJson(x))), + externalUrls: json["external_urls"] == null + ? null + : ExternalUrls.fromJson(json["external_urls"]), + releaseDate: json["release_date"] == null + ? null + : DateTime.parse(json["release_date"]), + releaseDatePrecision: json["release_date_precision"], + ); + + Map toJson() => { + "name": name, + "artists": artists == null + ? [] + : List.from(artists!.map((x) => x.toJson())), + "album_group": albumGroup, + "album_type": albumType, + "id": id, + "uri": uri, + "available_markets": availableMarkets == null + ? [] + : List.from(availableMarkets!.map((x) => x)), + "href": href, + "images": images == null + ? [] + : List.from(images!.map((x) => x.toJson())), + "external_urls": externalUrls?.toJson(), + "release_date": + "${releaseDate!.year.toString().padLeft(4, '0')}-${releaseDate!.month.toString().padLeft(2, '0')}-${releaseDate!.day.toString().padLeft(2, '0')}", + "release_date_precision": releaseDatePrecision, + }; +} + +class Artist { + String? name; + String? id; + String? uri; + String? href; + ExternalUrls? externalUrls; + + Artist({ + this.name, + this.id, + this.uri, + this.href, + this.externalUrls, + }); + + factory Artist.fromRawJson(String str) => Artist.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Artist.fromJson(Map json) => Artist( + name: json["name"], + id: json["id"], + uri: json["uri"], + href: json["href"], + externalUrls: json["external_urls"] == null + ? null + : ExternalUrls.fromJson(json["external_urls"]), + ); + + Map toJson() => { + "name": name, + "id": id, + "uri": uri, + "href": href, + "external_urls": externalUrls?.toJson(), + }; +} + +class Image { + int? height; + int? width; + String? url; + + Image({ + this.height, + this.width, + this.url, + }); + + factory Image.fromRawJson(String str) => Image.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory Image.fromJson(Map json) => Image( + height: json["height"], + width: json["width"], + url: json["url"], + ); + + Map toJson() => { + "height": height, + "width": width, + "url": url, + }; +} + +class ExternalIds { + String? isrc; + + ExternalIds({ + this.isrc, + }); + + factory ExternalIds.fromRawJson(String str) => + ExternalIds.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory ExternalIds.fromJson(Map json) => ExternalIds( + isrc: json["isrc"], + ); + + Map toJson() => { + "isrc": isrc, + }; +} diff --git a/lib/view/screens/admin_screen.dart b/lib/view/screens/admin_screen.dart index 5263963..a087b4f 100644 --- a/lib/view/screens/admin_screen.dart +++ b/lib/view/screens/admin_screen.dart @@ -1,10 +1,13 @@ -import 'dart:math'; +import 'dart:developer'; + import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/providers/admin_screen_provider.dart'; import 'package:spotify_collab_app/providers/playlist_provider.dart'; +import 'package:spotify_collab_app/view/models/songs_response.dart' as songs; +import 'package:spotify_collab_app/view/widgets/music_list_item.dart'; class AdminScreen extends ConsumerStatefulWidget { const AdminScreen({super.key}); @@ -16,13 +19,15 @@ class AdminScreen extends ConsumerStatefulWidget { class _AdminScreenState extends ConsumerState with SingleTickerProviderStateMixin { late TabController _tabController; + bool _isLoading = true; @override void initState() { super.initState(); + _fetchSongs(); final initialIndex = ref.read(tabProvider); _tabController = - TabController(length: 3, vsync: this, initialIndex: initialIndex); + TabController(length: 2, vsync: this, initialIndex: initialIndex); _tabController.addListener(() { if (_tabController.indexIsChanging) { @@ -31,6 +36,23 @@ class _AdminScreenState extends ConsumerState }); } + Future _fetchSongs() async { + final playlistNotifier = ref.read(playlistProvider.notifier); + final songsNotifier = ref.read(songsProvider.notifier); + await playlistNotifier.fetchPlaylists(); + await songsNotifier.fetchSongs(playlistNotifier.selectedPlaylistUuid!); + + try { + await songsNotifier.fetchSongs(playlistNotifier.selectedPlaylistUuid!); + } catch (e) { + log(e.toString()); + } finally { + setState(() { + _isLoading = false; + }); + } + } + @override void dispose() { _tabController.dispose(); @@ -44,30 +66,8 @@ class _AdminScreenState extends ConsumerState final currentIndex = ref.watch(tabProvider); final requestAction = ref.watch(requestActionProvider); - final playlistNotifier = ref.read(playlistProvider.notifier); - - final List> items = [ - { - "title": "One of Wun", - "subtitle": "Gunna", - "imageUrl": "assets/OneOfWun.png", - }, - { - "title": "Chanel", - "subtitle": "Frank Ocean", - "imageUrl": "assets/Chanel.png", - }, - ]; + final songsData = ref.watch(songsProvider); - // final List> items = playlistNotifier.fetchSongs(); - - final participants = [ - "Souvik", - "Jothish", - "Aakaash", - "Aditya", - "Karan", - ]; if (_tabController.index != currentIndex) { _tabController.animateTo(currentIndex); } @@ -108,11 +108,11 @@ class _AdminScreenState extends ConsumerState padding: EdgeInsets.only( top: height * 0.03, ), - child: const Align( + child: Align( alignment: Alignment.topCenter, child: Text( - "DevJams’ 24", - style: TextStyle( + ref.watch(playlistProvider.notifier).selectedPlaylistName!, + style: const TextStyle( fontFamily: 'Gotham', color: Colors.white, fontWeight: FontWeight.w700, @@ -124,7 +124,12 @@ class _AdminScreenState extends ConsumerState Container( margin: EdgeInsets.only(top: height * 0.12), decoration: const BoxDecoration( - color: Color.fromRGBO(12, 13, 14, 1), + color: Color.fromRGBO( + 12, + 13, + 14, + 1, + ), borderRadius: BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -133,49 +138,56 @@ class _AdminScreenState extends ConsumerState child: Column( children: [ SizedBox(height: height * 0.02), - Padding( - padding: EdgeInsets.only(right: width * 0.138), - child: TabBar( - controller: _tabController, - indicatorColor: const Color(0xffF674A2), - labelColor: Colors.white, - dividerColor: Colors.transparent, - unselectedLabelColor: Colors.grey, - tabs: const [ - Tab( - child: Text( - "Playlist", - style: TextStyle( - fontFamily: 'Gotham', - fontWeight: FontWeight.w600, - ), + TabBar( + controller: _tabController, + indicatorColor: const Color(0xffF674A2), + labelColor: Colors.white, + dividerColor: Colors.transparent, + unselectedLabelColor: Colors.grey, + tabs: const [ + Tab( + child: Text( + "Playlist", + style: TextStyle( + fontFamily: 'Gotham', + fontWeight: FontWeight.w600, ), ), - Tab( - child: Text( - "Requests", - style: TextStyle( - fontFamily: 'Gotham', - fontWeight: FontWeight.w600, - ), + ), + Tab( + child: Text( + "Requests", + style: TextStyle( + fontFamily: 'Gotham', + fontWeight: FontWeight.w600, ), ), - ], - ), + ), + ], ), const Divider( color: Color(0xffCCCCCC), height: 1, ), Expanded( - child: TabBarView( - controller: _tabController, - physics: const NeverScrollableScrollPhysics(), - children: [ - buildTabContent(playlistNotifier, isPlaylist: true), - buildRequestTabContent(items, requestAction), - ], - ), + child: (_isLoading) + ? const Center( + child: CircularProgressIndicator(), + ) + : TabBarView( + controller: _tabController, + physics: const NeverScrollableScrollPhysics(), + children: [ + buildTabContent( + songsData.data!.accepted!, + isPlaylist: true, + ), + buildRequestTabContent( + songsData.data!.submitted!, + requestAction, + ), + ], + ), ), ], ), @@ -206,249 +218,150 @@ class _AdminScreenState extends ConsumerState ); } - Widget buildTabContent(PlaylistNotifier playlistNotifier, + Widget buildTabContent(List items, {bool isPlaylist = false, bool isRequest = false, bool isParticipant = false}) { - FutureBuilder( - future: - playlistNotifier.fetchSongs("b65673a0-21ae-4dd6-a7f5-5ec6c204b731"), - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.waiting) { - return const Center(child: CircularProgressIndicator()); - } else if (snapshot.hasError) { - return const Center(child: Text('Failed to load playlists.')); - } else { - return Padding( - padding: - const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), - child: Column( - children: [ - const SizedBox(), - Text("Works!") - // Expanded( - // child: ListView.separated( - // itemCount: items.length, - // separatorBuilder: (context, index) => const Divider( - // color: Color(0xFFCCCCCC), - // height: 1, - // ), - // itemBuilder: (context, index) { - // final item = items[index]; - // return isPlaylist - // ? MusicListItem( - // title: item["title"]!, - // subtitle: item["subtitle"]!, - // imageUrl: item["imageUrl"]!, - // isPlaylist: isPlaylist, - // isRequest: isRequest, - // isParticipant: isParticipant, - // ) - // : MusicListItem( - // title: item!, - // subtitle: null, - // imageUrl: null, - // isPlaylist: isPlaylist, - // isRequest: isRequest, - // isParticipant: isParticipant, - // ); - // }, - // ), - // ), - ], - ), - ); - } - }); - return const SizedBox(); - } - - Widget buildRequestTabContent( - List> items, RequestAction action) { - return Column( - children: [ - Align( - alignment: Alignment.topRight, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Radio( - value: RequestAction.rejectAll, - groupValue: action, - onChanged: (value) => - ref.read(requestActionProvider.notifier).state = value!, - activeColor: Colors.red, - fillColor: const WidgetStatePropertyAll(Colors.red), - ), - const Text( - "Reject All", - style: TextStyle( - fontFamily: 'Gotham', - color: Colors.white, - fontWeight: FontWeight.w600, - fontSize: 10, - ), - ), - Radio( - value: RequestAction.acceptAll, - groupValue: action, - onChanged: (value) => - ref.read(requestActionProvider.notifier).state = value!, - activeColor: Colors.green, - fillColor: const WidgetStatePropertyAll(Colors.green), - ), - const Text( - "Accept All", - style: TextStyle( - fontFamily: 'Gotham', - color: Colors.white, - fontWeight: FontWeight.w600, - fontSize: 10, - ), + return (items.isEmpty) + ? const Center( + child: Text( + "No songs", + style: TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, ), - ], - ), - ), - Expanded( - child: ListView.separated( + ), + ) + : Padding( padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), - itemCount: items.length, - separatorBuilder: (context, index) => const Divider( - color: Color(0xFFCCCCCC), - height: 1, - ), - itemBuilder: (context, index) { - final item = items[index]; - return MusicListItem( - title: item["title"]!, - subtitle: item["subtitle"]!, - imageUrl: item["imageUrl"]!, - isRequest: true, - ); - }, - ), - ), - ], - ); - } -} - -class MusicListItem extends StatelessWidget { - final String title; - final String? subtitle; - final String? imageUrl; - final bool isPlaylist; - final bool isRequest; - final bool isParticipant; - - const MusicListItem({ - super.key, - required this.title, - this.subtitle, - this.imageUrl, - this.isPlaylist = false, - this.isRequest = false, - this.isParticipant = false, - }); - - Color getRandomColor() { - final Random random = Random(); - return Color.fromARGB( - 255, - random.nextInt(256), - random.nextInt(256), - random.nextInt(256), - ); - } - - @override - Widget build(BuildContext context) { - return ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), - leading: isParticipant - ? ClipRRect( - borderRadius: BorderRadius.circular(50), - child: Container( - width: 50, - height: 50, - decoration: BoxDecoration( - gradient: LinearGradient( - colors: [ - getRandomColor(), - Colors.black, - ], - begin: Alignment.topLeft, - end: Alignment.bottomRight, - ), - shape: BoxShape.circle, - ), - child: Center( - child: Text( - title[0].toUpperCase(), - style: TextStyle( - color: const Color(0xFFFFFFFF).withOpacity(0.8), - fontWeight: FontWeight.bold, - fontSize: 24, + child: Column( + children: [ + Expanded( + child: ListView.separated( + itemCount: items.length, + separatorBuilder: (context, index) => const Divider( + color: Color(0xFFCCCCCC), + height: 1, ), + itemBuilder: (context, index) { + final item = items[index]; + return isPlaylist + ? MusicListItem( + title: item.track!.track!.name!, + subtitle: item.track!.track!.artists! + .map((artist) => artist.name!) + .join(', '), + imageUrl: + item.track!.track!.album!.images![0].url!, + isPlaylist: isPlaylist, + isRequest: isRequest, + isParticipant: isParticipant, + ) + : MusicListItem( + title: item.track!.track!.album!.name!, + subtitle: null, + imageUrl: null, + isPlaylist: isPlaylist, + isRequest: isRequest, + isParticipant: isParticipant, + ); + }, ), ), - ), - ) - : Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8.0), - ), - child: Image.asset( - imageUrl!, - ), + ], ), - title: Text( - title, - style: const TextStyle( - fontFamily: 'Gotham', - fontWeight: FontWeight.w700, - color: Colors.white, - fontSize: 14, - ), - ), - subtitle: isParticipant - ? null - : Text( - subtitle ?? '', - style: const TextStyle( + ); + } + + Widget buildRequestTabContent( + List items, RequestAction action) { + return (items.isEmpty) + ? const Center( + child: Text( + "No requests", + style: TextStyle( fontFamily: 'Gotham', - fontWeight: FontWeight.w300, - color: Colors.grey, - fontSize: 10, + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, ), ), - trailing: isRequest - ? Row( - mainAxisSize: MainAxisSize.min, - children: [ - IconButton( - icon: const Icon(Icons.close, color: Colors.red), - onPressed: () {}, - ), - IconButton( - icon: const Icon(Icons.check, color: Colors.green), - onPressed: () {}, + ) + : Column( + children: [ + Align( + alignment: Alignment.topRight, + child: Padding( + padding: const EdgeInsets.only(right: 8.0), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Radio( + value: RequestAction.rejectAll, + groupValue: action, + onChanged: (value) => ref + .read(requestActionProvider.notifier) + .state = value!, + activeColor: Colors.red, + fillColor: const WidgetStatePropertyAll(Colors.red), + ), + const Text( + "Reject All", + style: TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + Radio( + value: RequestAction.acceptAll, + groupValue: action, + onChanged: (value) => ref + .read(requestActionProvider.notifier) + .state = value!, + activeColor: Colors.green, + fillColor: const WidgetStatePropertyAll(Colors.green), + ), + const Text( + "Accept All", + style: TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 10, + ), + ), + ], + ), ), - ], - ) - : isParticipant - ? null - : InkWell( - onTap: () {}, - child: InkWell( - onTap: () {}, - child: const Icon( - Icons.more_vert, - color: Color(0xff737373), - ), + ), + Expanded( + child: ListView.separated( + padding: const EdgeInsets.symmetric( + vertical: 8.0, horizontal: 16.0), + itemCount: items.length, + separatorBuilder: (context, index) => const Divider( + color: Color(0xFFCCCCCC), + height: 1, ), + itemBuilder: (context, index) { + final item = items[index]; + return MusicListItem( + title: item.name!, + subtitle: item.artists! + .map((artist) => artist.name!) + .join(', '), + imageUrl: item.album!.images![0].url!, + isRequest: true, + ); + }, ), - ); + ), + ], + ); } } diff --git a/lib/view/screens/connect_screen.dart b/lib/view/screens/connect_screen.dart index 73e2fd1..4efb66c 100644 --- a/lib/view/screens/connect_screen.dart +++ b/lib/view/screens/connect_screen.dart @@ -33,6 +33,7 @@ class ConnectScreenState extends ConsumerState { log('Access Token: $accessToken'); ref.read(authProvider.notifier).setAccessToken(accessToken); + if (!mounted) return; context.replace('/home'); } else { log('No access token found'); @@ -173,4 +174,3 @@ class ConnectScreenState extends ConsumerState { ); } } - diff --git a/lib/view/screens/home_screen.dart b/lib/view/screens/home_screen.dart index 2c344df..ddbe220 100644 --- a/lib/view/screens/home_screen.dart +++ b/lib/view/screens/home_screen.dart @@ -3,7 +3,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/providers/playlist_provider.dart'; -import 'package:spotify_collab_app/view/screens/admin_screen.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @@ -76,9 +75,10 @@ class HomeScreen extends ConsumerWidget { child: Text('Failed to load playlists.')); } else { final playlists = ref.watch(playlistProvider); - if (playlists.isEmpty) + if (playlists.isEmpty) { return const Center( child: Text('No playlists found')); + } return Expanded( child: ListView.builder( itemCount: playlists.length, @@ -87,8 +87,8 @@ class HomeScreen extends ConsumerWidget { return Padding( padding: const EdgeInsets.only(bottom: 10), child: PlaylistCard( - name: playlist.name, - ), + name: playlist.name, + id: playlist.playlistUuid), ); }, ), @@ -143,18 +143,22 @@ class CustomTitle extends StatelessWidget { } } -class PlaylistCard extends StatelessWidget { +class PlaylistCard extends ConsumerWidget { const PlaylistCard({ super.key, this.isActive = false, required this.name, + required this.id, }); final bool isActive; - final String name; + final String? name; + final String? id; @override - Widget build(BuildContext context) { + Widget build(BuildContext context, WidgetRef ref) { + final playlistNotifier = ref.read(playlistProvider.notifier); + return Row( children: [ Expanded( @@ -182,7 +186,7 @@ class PlaylistCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - name, + name ?? '', style: TextStyle( fontFamily: "Gotham", fontWeight: FontWeight.w700, @@ -205,19 +209,21 @@ class PlaylistCard extends StatelessWidget { const BorderRadius.all(Radius.circular(8)), ), child: Center( - child: TextButton( - onPressed: () { - context.push("/admin"); - }, - child: Text( - isActive ? "Manage" : "View", - style: TextStyle( - color: isActive - ? const Color(0xff5822EE) - : Colors.white, - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, + child: TextButton( + onPressed: () { + playlistNotifier.selectedPlaylistUuid = id ?? ''; + playlistNotifier.selectedPlaylistName = name ?? ''; + context.push("/admin"); + }, + child: Text( + isActive ? "Manage" : "View", + style: TextStyle( + color: isActive + ? const Color(0xff5822EE) + : Colors.white, + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, ), ), )), diff --git a/lib/view/widgets/music_list_item.dart b/lib/view/widgets/music_list_item.dart new file mode 100644 index 0000000..13e17e3 --- /dev/null +++ b/lib/view/widgets/music_list_item.dart @@ -0,0 +1,124 @@ +import 'dart:math'; + +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +class MusicListItem extends ConsumerWidget { + final String title; + final String? subtitle; + final String? imageUrl; + final bool isPlaylist; + final bool isRequest; + final bool isParticipant; + + const MusicListItem({ + super.key, + required this.title, + this.subtitle, + this.imageUrl, + this.isPlaylist = false, + this.isRequest = false, + this.isParticipant = false, + }); + + Color getRandomColor() { + final Random random = Random(); + return Color.fromARGB( + 255, + random.nextInt(256), + random.nextInt(256), + random.nextInt(256), + ); + } + + @override + Widget build(BuildContext context, WidgetRef ref) { + return ListTile( + contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), + leading: isParticipant + ? ClipRRect( + borderRadius: BorderRadius.circular(50), + child: Container( + width: 50, + height: 50, + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + getRandomColor(), + Colors.black, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ), + shape: BoxShape.circle, + ), + child: Center( + child: Text( + title[0].toUpperCase(), + style: TextStyle( + color: const Color(0xFFFFFFFF) + .withAlpha((0.8 * 255).toInt()), + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + ), + ), + ) + : Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), + ), + child: Image.network( + imageUrl!, + ), + ), + title: Text( + title, + style: const TextStyle( + fontFamily: 'Gotham', + fontWeight: FontWeight.w700, + color: Colors.white, + fontSize: 14, + ), + ), + subtitle: isParticipant + ? null + : Text( + subtitle ?? '', + style: const TextStyle( + fontFamily: 'Gotham', + fontWeight: FontWeight.w300, + color: Colors.grey, + fontSize: 10, + ), + ), + trailing: isRequest + ? Row( + mainAxisSize: MainAxisSize.min, + children: [ + IconButton( + icon: const Icon(Icons.close, color: Colors.red), + onPressed: () {}, + ), + IconButton( + icon: const Icon(Icons.check, color: Colors.green), + onPressed: () {}, + ), + ], + ) + : isParticipant + ? null + : InkWell( + onTap: () {}, + child: InkWell( + onTap: () {}, + child: const Icon( + Icons.more_vert, + color: Color(0xff737373), + ), + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index c0402f5..4d91e8f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: args - sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" + sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 url: "https://pub.dev" source: hosted - version: "2.5.0" + version: "2.6.0" async: dependency: transitive description: @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: collection - sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a + sha256: a1ace0a119f20aabc852d165077c036cd864315bd99b7eaa10a60100341941bf url: "https://pub.dev" source: hosted - version: "1.18.0" + version: "1.19.0" cross_file: dependency: transitive description: @@ -101,26 +101,26 @@ packages: dependency: transitive description: name: file - sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" + sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4 url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.0.1" file_selector_linux: dependency: transitive description: name: file_selector_linux - sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492" + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" url: "https://pub.dev" source: hosted - version: "0.9.2+1" + version: "0.9.3+2" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" url: "https://pub.dev" source: hosted - version: "0.9.4" + version: "0.9.4+2" file_selector_platform_interface: dependency: transitive description: @@ -133,10 +133,10 @@ packages: dependency: transitive description: name: file_selector_windows - sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69" + sha256: "8f5d2f6590d51ecd9179ba39c64f722edc15226cc93dcc8698466ad36a4a85a4" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.3+3" flutter: dependency: "direct main" description: flutter @@ -154,26 +154,26 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de" + sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" url: "https://pub.dev" source: hosted - version: "2.0.21" + version: "2.0.24" flutter_riverpod: dependency: "direct main" description: name: flutter_riverpod - sha256: "0f1974eff5bbe774bf1d870e406fc6f29e3d6f1c46bd9c58e7172ff68a785d7d" + sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.1" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" + sha256: "54900a1a1243f3c4a5506d853a2b5c2dbc38d5f27e52a52618a8054401431123" url: "https://pub.dev" source: hosted - version: "2.0.10+1" + version: "2.0.16" flutter_test: dependency: "direct dev" description: flutter @@ -188,26 +188,26 @@ packages: dependency: "direct main" description: name: go_router - sha256: "39dd52168d6c59984454183148dc3a5776960c61083adfc708cc79a7b3ce1ba8" + sha256: "2fd11229f59e23e967b0775df8d5948a519cd7e1e8b6e849729e010587b46539" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.6.2" http: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: name: http_parser - sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b" + sha256: "76d306a1c3afb33fe82e2bbacad62a61f409b5634c915fceb0d799de1a913360" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.1" image_picker: dependency: "direct main" description: @@ -220,26 +220,26 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "8c5abf0dcc24fe6e8e0b4a5c0b51a5cf30cefdf6407a3213dae61edc75a70f56" + sha256: aa6f1280b670861ac45220cc95adc59bb6ae130259d36f980ccb62220dc5e59f url: "https://pub.dev" source: hosted - version: "0.8.12+12" + version: "0.8.12+19" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50" + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.6" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447" + sha256: "4f0568120c6fcc0aaa04511cb9f9f4d29fc3d0139884b1d06be88dcec7641d6b" url: "https://pub.dev" source: hosted - version: "0.8.12" + version: "0.8.12+1" image_picker_linux: dependency: transitive description: @@ -276,18 +276,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "7bb2830ebd849694d1ec25bf1f44582d6ac531a57a365a803a6034ff751d2d06" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.7" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "9491a714cca3667b60b5c420da8217e6de0d1ba7a5ec322fab01758f6998f379" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.8" leak_tracker_testing: dependency: transitive description: @@ -308,10 +308,10 @@ packages: dependency: transitive description: name: logging - sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" + sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" matcher: dependency: transitive description: @@ -324,26 +324,26 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" mime: dependency: transitive description: name: mime - sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "2.0.0" path: dependency: transitive description: @@ -356,10 +356,10 @@ packages: dependency: transitive description: name: path_parsing - sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf + sha256: "883402936929eac138ee0a45da5b0f2c80f89913e6dc3bf77eb65b84b409c6ca" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" path_provider_linux: dependency: transitive description: @@ -396,10 +396,10 @@ packages: dependency: transitive description: name: platform - sha256: "9b71283fc13df574056616011fb138fd3b793ea47cc509c189a6c3fa5f8a1a65" + sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984" url: "https://pub.dev" source: hosted - version: "3.1.5" + version: "3.1.6" plugin_platform_interface: dependency: transitive description: @@ -412,34 +412,34 @@ packages: dependency: transitive description: name: riverpod - sha256: f21b32ffd26a36555e501b04f4a5dca43ed59e16343f1a30c13632b2351dfa4d + sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.1" shared_preferences: dependency: "direct main" description: name: shared_preferences - sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051" + sha256: "3c7e73920c694a436afaf65ab60ce3453d91f84208d761fbd83fc21182134d93" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.4" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e" + sha256: "02a7d8a9ef346c9af715811b01fbd8e27845ad2c41148eefd31321471b41863d" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" shared_preferences_foundation: dependency: transitive description: name: shared_preferences_foundation - sha256: c4b35f6cb8f63c147312c054ce7c2254c8066745125264f0c88739c417fc9d9f + sha256: "6a52cfcdaeac77cad8c97b539ff688ccfc458c007b4db12be584fbe5c0e49e03" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.4" shared_preferences_linux: dependency: transitive description: @@ -476,7 +476,7 @@ packages: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: @@ -489,10 +489,10 @@ packages: dependency: transitive description: name: stack_trace - sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b" + sha256: "9f47fd3630d76be3ab26f0ee06d213679aa425996925ff3feffdec504931c377" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.12.0" state_notifier: dependency: transitive description: @@ -513,10 +513,10 @@ packages: dependency: transitive description: name: string_scanner - sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde" + sha256: "688af5ed3402a4bde5b3a6c15fd768dbf2621a614950b17f04626c431ab3c4c3" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.0" term_glyph: dependency: transitive description: @@ -529,10 +529,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "664d3a9a64782fcdeb83ce9c6b39e78fd2971d4e37827b9b06c3aa1edc5e760c" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.3" text_marquee: dependency: "direct main" description: @@ -545,10 +545,10 @@ packages: dependency: transitive description: name: typed_data - sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c + sha256: f9049c039ebfeb4cf7a7104a675823cd72dba8297f264b6637062516699fa006 url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.4.0" uni_links: dependency: "direct main" description: @@ -577,42 +577,42 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3" + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79 + sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" url: "https://pub.dev" source: hosted - version: "6.3.9" + version: "6.3.14" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e + sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" url: "https://pub.dev" source: hosted - version: "6.3.1" + version: "6.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: e2b9622b4007f97f504cd64c0128309dfb978ae66adbe944125ed9e1750f06af + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.2" url_launcher_platform_interface: dependency: transitive description: @@ -633,34 +633,34 @@ packages: dependency: transitive description: name: url_launcher_windows - sha256: "49c10f879746271804767cb45551ec5592cdab00ee105c06dddde1a98f73b185" + sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" vector_graphics: dependency: transitive description: name: vector_graphics - sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" + sha256: "27d5fefe86fb9aace4a9f8375b56b3c292b64d8c04510df230f849850d912cb7" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.15" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da + sha256: "2430b973a4ca3c4dbc9999b62b8c719a160100dcbae5c819bae0cacce32c9cdb" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.12" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" + sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" url: "https://pub.dev" source: hosted - version: "1.1.11+1" + version: "1.1.16" vector_math: dependency: transitive description: @@ -673,26 +673,26 @@ packages: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: f6be3ed8bd01289b34d679c2b62226f63c0e69f9fd2e50a6b3c1c729a961041b url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.3.0" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.1.0" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.1.0" xml: dependency: transitive description: @@ -702,5 +702,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.4.3 <4.0.0" - flutter: ">=3.22.0" + dart: ">=3.5.0 <4.0.0" + flutter: ">=3.24.0" From ccb3cee1d60798be006c4c27e8c124ecf7632956 Mon Sep 17 00:00:00 2001 From: JothishKamal Date: Mon, 30 Dec 2024 00:42:22 +0530 Subject: [PATCH 11/11] feat: accept/reject integrated --- lib/app/router/router.dart | 4 +- lib/providers/admin_screen_provider.dart | 43 +++++- lib/providers/playlist_provider.dart | 3 - lib/utils/api_util.dart | 3 - lib/view/screens/admin_screen.dart | 7 +- lib/view/screens/create_screen.dart | 38 ++++- lib/view/screens/home_screen.dart | 160 +--------------------- lib/view/screens/yay_screen.dart | 13 +- lib/view/widgets/custom_title.dart | 39 ++++++ lib/view/widgets/music_list_item.dart | 58 +++++++- lib/view/widgets/new_playlist_button.dart | 27 ++++ lib/view/widgets/playlist_card.dart | 96 +++++++++++++ 12 files changed, 303 insertions(+), 188 deletions(-) create mode 100644 lib/view/widgets/custom_title.dart create mode 100644 lib/view/widgets/new_playlist_button.dart create mode 100644 lib/view/widgets/playlist_card.dart diff --git a/lib/app/router/router.dart b/lib/app/router/router.dart index 2e89132..a2e2c6c 100644 --- a/lib/app/router/router.dart +++ b/lib/app/router/router.dart @@ -9,10 +9,12 @@ import 'package:spotify_collab_app/view/screens/signup_screen.dart'; import 'package:spotify_collab_app/view/screens/home_screen.dart'; import 'package:spotify_collab_app/view/screens/connect_screen.dart'; import 'package:spotify_collab_app/view/screens/splash_screen.dart'; -import 'package:spotify_collab_app/view/screens/yay_screen.dart'; final routerProvider = Provider((ref) { return GoRouter( + errorBuilder: (context, state) { + return const HomeScreen(); + }, initialLocation: '/', routes: [ GoRoute( diff --git a/lib/providers/admin_screen_provider.dart b/lib/providers/admin_screen_provider.dart index e19ef71..f37c265 100644 --- a/lib/providers/admin_screen_provider.dart +++ b/lib/providers/admin_screen_provider.dart @@ -1,4 +1,3 @@ -import 'dart:convert'; import 'dart:developer'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -56,4 +55,46 @@ class SongsNotifier extends StateNotifier { success: emptySongs.success, ); } + + Future acceptSong(String uuid, String uri) async { + try { + final response = await apiUtil.post( + '/v1/songs/accept', + { + 'playlist_uuid': uuid, + 'song_uri': uri, + }, + ); + + log('Song accepted'); + if (response.statusCode == 200) { + fetchSongs(uuid); + return true; + } + } catch (e) { + log(e.toString()); + } + return false; + } + + Future rejectSong(String uuid, String uri) async { + try { + final response = await apiUtil.post( + '/v1/songs/reject', + { + 'playlist_uuid': uuid, + 'song_uri': uri, + }, + ); + + log('Song rejected'); + if (response.statusCode == 200) { + fetchSongs(uuid); + return true; + } + } catch (e) { + log(e.toString()); + } + return false; + } } diff --git a/lib/providers/playlist_provider.dart b/lib/providers/playlist_provider.dart index d67b26a..1169554 100644 --- a/lib/providers/playlist_provider.dart +++ b/lib/providers/playlist_provider.dart @@ -1,7 +1,4 @@ -import 'dart:developer'; - import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotify_collab_app/utils/api_util.dart'; import 'package:spotify_collab_app/view/models/playlist_success_response.dart'; diff --git a/lib/utils/api_util.dart b/lib/utils/api_util.dart index bb7f080..39df58c 100644 --- a/lib/utils/api_util.dart +++ b/lib/utils/api_util.dart @@ -1,6 +1,3 @@ -import 'dart:convert'; -import 'dart:developer'; - import 'package:dio/dio.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotify_collab_app/constants/constants.dart'; diff --git a/lib/view/screens/admin_screen.dart b/lib/view/screens/admin_screen.dart index a087b4f..eced647 100644 --- a/lib/view/screens/admin_screen.dart +++ b/lib/view/screens/admin_screen.dart @@ -39,8 +39,6 @@ class _AdminScreenState extends ConsumerState Future _fetchSongs() async { final playlistNotifier = ref.read(playlistProvider.notifier); final songsNotifier = ref.read(songsProvider.notifier); - await playlistNotifier.fetchPlaylists(); - await songsNotifier.fetchSongs(playlistNotifier.selectedPlaylistUuid!); try { await songsNotifier.fetchSongs(playlistNotifier.selectedPlaylistUuid!); @@ -225,7 +223,7 @@ class _AdminScreenState extends ConsumerState return (items.isEmpty) ? const Center( child: Text( - "No songs", + "No songs in playlist.", style: TextStyle( fontFamily: 'Gotham', color: Colors.white, @@ -250,6 +248,7 @@ class _AdminScreenState extends ConsumerState final item = items[index]; return isPlaylist ? MusicListItem( + id: item.track!.track!.id!, title: item.track!.track!.name!, subtitle: item.track!.track!.artists! .map((artist) => artist.name!) @@ -261,6 +260,7 @@ class _AdminScreenState extends ConsumerState isParticipant: isParticipant, ) : MusicListItem( + id: item.track!.track!.id!, title: item.track!.track!.album!.name!, subtitle: null, imageUrl: null, @@ -351,6 +351,7 @@ class _AdminScreenState extends ConsumerState itemBuilder: (context, index) { final item = items[index]; return MusicListItem( + id: item.id!, title: item.name!, subtitle: item.artists! .map((artist) => artist.name!) diff --git a/lib/view/screens/create_screen.dart b/lib/view/screens/create_screen.dart index f50812c..e401382 100644 --- a/lib/view/screens/create_screen.dart +++ b/lib/view/screens/create_screen.dart @@ -138,7 +138,15 @@ class CreateScreen extends ConsumerWidget { if (eventNameController.text.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: Text('Event name cannot be empty'), + content: Text( + 'Event name cannot be empty', + style: TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), backgroundColor: Colors.red, ), ); @@ -151,10 +159,18 @@ class CreateScreen extends ConsumerWidget { if (response.statusCode == 200) { if (response.data["message"] == "playlist successfully created") { + if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( const SnackBar( - content: - Text('Playlist successfully created'), + content: Text( + 'Playlist successfully created', + style: TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), backgroundColor: Colors.green, ), ); @@ -162,10 +178,20 @@ class CreateScreen extends ConsumerWidget { context.go('/home'); } } else { + if (!context.mounted) return; ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Failed to create playlist'), - backgroundColor: Colors.red, + SnackBar( + content: const Text( + 'Failed to create playlist', + style: TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + backgroundColor: + Colors.red.withOpacity(0.8), ), ); } diff --git a/lib/view/screens/home_screen.dart b/lib/view/screens/home_screen.dart index ddbe220..639727c 100644 --- a/lib/view/screens/home_screen.dart +++ b/lib/view/screens/home_screen.dart @@ -3,6 +3,9 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:go_router/go_router.dart'; import 'package:spotify_collab_app/providers/playlist_provider.dart'; +import 'package:spotify_collab_app/view/widgets/custom_title.dart'; +import 'package:spotify_collab_app/view/widgets/new_playlist_button.dart'; +import 'package:spotify_collab_app/view/widgets/playlist_card.dart'; class HomeScreen extends ConsumerWidget { const HomeScreen({super.key}); @@ -106,161 +109,4 @@ class HomeScreen extends ConsumerWidget { } } -class CustomTitle extends StatelessWidget { - const CustomTitle({ - super.key, - required this.title, - }); - final String title; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - const Spacer(), - const SizedBox(width: 20), - Text( - title, - style: const TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 34, - shadows: [ - Shadow(offset: Offset(0, 1), color: Color(0xffDA84FE)), - ], - ), - ), - Column( - children: [ - SvgPicture.asset('assets/highlight.svg'), - const SizedBox(height: 30), - ], - ), - const Spacer(), - ], - ); - } -} - -class PlaylistCard extends ConsumerWidget { - const PlaylistCard({ - super.key, - this.isActive = false, - required this.name, - required this.id, - }); - - final bool isActive; - final String? name; - final String? id; - - @override - Widget build(BuildContext context, WidgetRef ref) { - final playlistNotifier = ref.read(playlistProvider.notifier); - - return Row( - children: [ - Expanded( - child: Container( - height: 102, - decoration: BoxDecoration( - color: isActive ? const Color(0xff5822EE) : Colors.white, - borderRadius: const BorderRadius.all(Radius.circular(8)), - ), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Row( - children: [ - Container( - height: 57, - width: 57, - decoration: const BoxDecoration( - shape: BoxShape.circle, - color: Colors.grey, - ), - child: const Icon(Icons.music_note, size: 32), - ), - const SizedBox(width: 10), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - name ?? '', - style: TextStyle( - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, - color: isActive ? Colors.white : Colors.black, - ), - ), - ], - ), - const Spacer(), - Align( - alignment: Alignment.bottomRight, - child: Container( - width: 97, - height: 31, - decoration: BoxDecoration( - color: - !isActive ? const Color(0xff5822EE) : Colors.white, - borderRadius: - const BorderRadius.all(Radius.circular(8)), - ), - child: Center( - child: TextButton( - onPressed: () { - playlistNotifier.selectedPlaylistUuid = id ?? ''; - playlistNotifier.selectedPlaylistName = name ?? ''; - context.push("/admin"); - }, - child: Text( - isActive ? "Manage" : "View", - style: TextStyle( - color: isActive - ? const Color(0xff5822EE) - : Colors.white, - fontFamily: "Gotham", - fontWeight: FontWeight.w700, - fontSize: 16, - ), - ), - )), - ), - ), - ], - ), - ), - ), - ), - ], - ); - } -} - -class NewPlaylistButton extends StatelessWidget { - const NewPlaylistButton({super.key}); - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Expanded( - child: Container( - height: 59, - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.all(Radius.circular(8)), - ), - child: const Icon( - Icons.add_circle, - color: Color(0xff5822EE), - size: 43, - ), - ), - ), - ], - ); - } -} diff --git a/lib/view/screens/yay_screen.dart b/lib/view/screens/yay_screen.dart index 3525ed1..a7da2b1 100644 --- a/lib/view/screens/yay_screen.dart +++ b/lib/view/screens/yay_screen.dart @@ -2,8 +2,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:spotify_collab_app/constants/constants.dart'; -import 'package:url_launcher/url_launcher.dart'; import 'package:text_marquee/text_marquee.dart'; class YayScreen extends ConsumerStatefulWidget { @@ -27,11 +25,6 @@ class ConnectScreenState extends ConsumerState { super.dispose(); } - Future _launchSpotifyLogin() async { - const url = '$devUrl/v1/auth/spotify/login/app'; - launchUrl(Uri.parse(url)); - } - @override Widget build(BuildContext context) { return Scaffold( @@ -117,7 +110,7 @@ class PlaylistCard extends StatelessWidget { this.isActive = false, this.name = "DevJams' 24", this.host = "Souvik", - this.img = "assets/dino.png"}); + this.img = "assets/dino.png", String? id}); final bool isActive; final String name; @@ -163,7 +156,9 @@ class PlaylistCard extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 10,), + const SizedBox( + height: 10, + ), Row(children: [ const Text( "Event:", diff --git a/lib/view/widgets/custom_title.dart b/lib/view/widgets/custom_title.dart new file mode 100644 index 0000000..4df5c4f --- /dev/null +++ b/lib/view/widgets/custom_title.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; + +class CustomTitle extends StatelessWidget { + const CustomTitle({ + super.key, + required this.title, + }); + + final String title; + + @override + Widget build(BuildContext context) { + return Row( + children: [ + const Spacer(), + const SizedBox(width: 20), + Text( + title, + style: const TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 34, + shadows: [ + Shadow(offset: Offset(0, 1), color: Color(0xffDA84FE)), + ], + ), + ), + Column( + children: [ + SvgPicture.asset('assets/highlight.svg'), + const SizedBox(height: 30), + ], + ), + const Spacer(), + ], + ); + } +} diff --git a/lib/view/widgets/music_list_item.dart b/lib/view/widgets/music_list_item.dart index 13e17e3..39c309e 100644 --- a/lib/view/widgets/music_list_item.dart +++ b/lib/view/widgets/music_list_item.dart @@ -2,17 +2,20 @@ import 'dart:math'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:spotify_collab_app/providers/admin_screen_provider.dart'; +import 'package:spotify_collab_app/providers/playlist_provider.dart'; class MusicListItem extends ConsumerWidget { + final String? id; final String title; final String? subtitle; final String? imageUrl; final bool isPlaylist; final bool isRequest; final bool isParticipant; - const MusicListItem({ super.key, + required this.id, required this.title, this.subtitle, this.imageUrl, @@ -33,6 +36,8 @@ class MusicListItem extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final songsNotifier = ref.read(songsProvider.notifier); + final playlistsProvider = ref.watch(playlistProvider.notifier); return ListTile( contentPadding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8), leading: isParticipant @@ -99,12 +104,55 @@ class MusicListItem extends ConsumerWidget { children: [ IconButton( icon: const Icon(Icons.close, color: Colors.red), - onPressed: () {}, + onPressed: () async { + final success = await songsNotifier.rejectSong( + playlistsProvider.selectedPlaylistUuid!, id!); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + success ? 'Song rejected' : 'Failed to reject song', + style: const TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + backgroundColor: success + ? Colors.green + : Colors.red.withOpacity(0.8), + ), + ); + } + }, ), IconButton( - icon: const Icon(Icons.check, color: Colors.green), - onPressed: () {}, - ), + icon: const Icon(Icons.check, color: Colors.green), + onPressed: () async { + final success = await songsNotifier.acceptSong( + playlistsProvider.selectedPlaylistUuid!, id!); + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + success + ? 'Song accepted' + : 'Failed to accept song', + style: const TextStyle( + fontFamily: 'Gotham', + color: Colors.white, + fontWeight: FontWeight.w600, + fontSize: 14, + ), + ), + backgroundColor: success + ? Colors.green + : Colors.red.withOpacity(0.8), + ), + ); + } + }), ], ) : isParticipant diff --git a/lib/view/widgets/new_playlist_button.dart b/lib/view/widgets/new_playlist_button.dart new file mode 100644 index 0000000..1a64131 --- /dev/null +++ b/lib/view/widgets/new_playlist_button.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; + +class NewPlaylistButton extends StatelessWidget { + const NewPlaylistButton({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + Expanded( + child: Container( + height: 59, + decoration: const BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(8)), + ), + child: const Icon( + Icons.add_circle, + color: Color(0xff5822EE), + size: 43, + ), + ), + ), + ], + ); + } +} diff --git a/lib/view/widgets/playlist_card.dart b/lib/view/widgets/playlist_card.dart new file mode 100644 index 0000000..d16b01f --- /dev/null +++ b/lib/view/widgets/playlist_card.dart @@ -0,0 +1,96 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:go_router/go_router.dart'; +import 'package:spotify_collab_app/providers/playlist_provider.dart'; + +class PlaylistCard extends ConsumerWidget { + const PlaylistCard({ + super.key, + this.isActive = false, + required this.name, + required this.id, + }); + + final bool isActive; + final String? name; + final String? id; + + @override + Widget build(BuildContext context, WidgetRef ref) { + final playlistNotifier = ref.read(playlistProvider.notifier); + + return Row( + children: [ + Expanded( + child: Container( + height: 102, + decoration: BoxDecoration( + color: isActive ? const Color(0xff5822EE) : Colors.white, + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16), + child: Row( + children: [ + Container( + height: 57, + width: 57, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: Colors.grey, + ), + child: const Icon(Icons.music_note, + size: 32, color: Colors.white), + ), + const SizedBox(width: 16), + Expanded( + child: Text( + name ?? '', + style: TextStyle( + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 16, + color: isActive ? Colors.white : Colors.black, + ), + overflow: TextOverflow.ellipsis, + maxLines: 2, + ), + ), + const SizedBox(width: 16), + Container( + width: 97, + height: 31, + decoration: BoxDecoration( + color: isActive ? Colors.white : const Color(0xff5822EE), + borderRadius: const BorderRadius.all(Radius.circular(8)), + ), + child: TextButton( + onPressed: () { + playlistNotifier.selectedPlaylistUuid = id ?? ''; + playlistNotifier.selectedPlaylistName = name ?? ''; + context.push("/admin"); + }, + style: TextButton.styleFrom( + padding: EdgeInsets.zero, + ), + child: Text( + isActive ? "Manage" : "View", + style: TextStyle( + color: + isActive ? const Color(0xff5822EE) : Colors.white, + fontFamily: "Gotham", + fontWeight: FontWeight.w700, + fontSize: 14, + ), + ), + ), + ), + ], + ), + ), + ), + ), + ], + ); + } +}