diff --git a/Sources/dafl_project_flutter/lib/api/api.dart b/Sources/dafl_project_flutter/lib/api/api.dart deleted file mode 100644 index 56f3488..0000000 --- a/Sources/dafl_project_flutter/lib/api/api.dart +++ /dev/null @@ -1,300 +0,0 @@ -import 'dart:convert'; -import 'dart:math'; -import 'package:dafl_project_flutter/main.dart'; -import 'package:http/http.dart' as http; -import 'package:crypto/crypto.dart'; -import 'dart:developer' as dev; -import '../exceptions/api_exception.dart'; - -class Api { - //from dashboard - final _clientId = '7ceb49d874b9404492246027e4d68cf8'; - final _clientSecret = '98f9cb960bf54ebbb9ad306e7ff919cb'; - - //for web api - get redirectUri => 'https://daflmusic.000webhostapp.com/callback/'; - final _scopes = - 'user-read-playback-state user-read-currently-playing user-read-recently-played playlist-modify-public ugc-image-upload user-modify-playback-state'; - late String _state; - dynamic _codeVerifier; - dynamic _codeChallenge; - late String _encodedLogs; - final _tokenType = 'Bearer '; - late http.Response _response; //use _setResponse() as kind of a private setter - final _playlistName = "Dafl's discovery"; - - //from web api - String? _code; - int? _expiresIn; - String? _refreshToken; - String? _accessToken; //use _getAccessToken() as kind of a private getter - - //other - final _client = http.Client(); - late Uri _urlAuthorize; - - get urlAuthorize => _urlAuthorize; - DateTime? _tokenEnd; - - Api() { - _state = _generateRandomString(16); - _codeVerifier = _generateRandomString(_generateRandomInt(43, 128)); - _codeChallenge = _generateCodeChallenge(); - _encodedLogs = base64.encode(utf8.encode("$_clientId:$_clientSecret")); - _urlAuthorize = Uri.https('accounts.spotify.com', 'authorize', { - 'client_id': _clientId, - 'response_type': 'code', - 'redirect_uri': redirectUri, - 'state': _state, - 'scope': _scopes, - 'show_dialog': 'false', - 'code_challenge_method': 'S256', - 'code_challenge': _codeChallenge - }); - } - - //PKCE generations - - _generateRandomInt(int min, int max) { - return min + Random().nextInt(max - min); - } - - _generateRandomString(int length) { - const chars = - 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; - return String.fromCharCodes(Iterable.generate( - length, (_) => chars.codeUnitAt(Random().nextInt(chars.length)))); - } - - _generateCodeChallenge() { - return base64Encode(sha256.convert(utf8.encode(_codeVerifier)).bytes) - .replaceAll('+', '-') - .replaceAll('/', '_') - .replaceAll('=', ''); - } - - //session management - - requestUserAuthorization(Uri url) async { - if (url.queryParameters['state'] != _state.toString()) { - throw ApiException('state'); - } - _code = url.queryParameters['code']; - await _requestAccessToken(); - } - - _requestAccessToken() async { - var urlToken = Uri.https('accounts.spotify.com', 'api/token', { - 'code': _code, - 'redirect_uri': redirectUri, - 'grant_type': 'authorization_code', - 'client_id': _clientId, - 'code_verifier': _codeVerifier - }); - _setResponse(await _client.post(urlToken, headers: { - 'Authorization': 'Basic $_encodedLogs', - 'Content-Type': 'application/x-www-form-urlencoded' - })); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - _accessToken = decodedResponse['access_token']; - _expiresIn = decodedResponse['expires_in']; - _tokenEnd = DateTime.now().add(Duration(seconds: _expiresIn!)); - _refreshToken = decodedResponse['refresh_token']; - } - - Future _getAccessToken() async { - if (DateTime.now().isAfter(_tokenEnd!)) { - await _getRefreshedAccessToken(); - } - return _accessToken; - } - - _setResponse(value) { - int sc = value.statusCode; - if (sc >= 300) { - dev.log(value.body.toString()); - throw ApiException(sc); - } - _response = value; - } - - _getRefreshedAccessToken() async { - var urlToken = Uri.https('accounts.spotify.com', 'api/token', { - 'grant_type': 'refresh_token', - 'refresh_token': _refreshToken, - 'client_id': _clientId - }); - _setResponse(await _client.post(urlToken, headers: { - 'Content-Type': 'application/x-www-form-urlencoded' - })); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - _accessToken = decodedResponse['access_token']; - _expiresIn = decodedResponse['expires_in']; - _tokenEnd = DateTime.now().add(Duration(seconds: _expiresIn!)); - } - - //functional methods - - Future getCurrentlyPlayingTrack() async { - var url = Uri.https('api.spotify.com', 'v1/me/player/currently-playing'); - var token = await _getAccessToken(); - var response = await _client.get(url, headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - }); - if (response.statusCode == 204) { - return _getRecentlyPlayedTrack(); - } - var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; - return decodedResponse['item']['id']; - } - - Future _getRecentlyPlayedTrack() async { - var url = Uri.https( - 'api.spotify.com', 'v1/me/player/recently-played', {'limit': '1'}); - var token = await _getAccessToken(); - _setResponse(await _client.get(url, headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - })); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - return decodedResponse['items'][0]['track']['id']; - } - - Future getTrackInfo(String id) async { - var url = Uri.https('api.spotify.com', 'v1/tracks/$id'); - var token = await _getAccessToken(); - _setResponse(await _client.get(url, headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - })); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - Map info = { - 'artist': decodedResponse['artists'][0]['name'], - 'name': decodedResponse['name'], - 'cover': decodedResponse['album']['images'][0]['url'] - }; - return info; - } - - Future _isInPlaylist(String idTrack, String idPlaylist) async { - var url = Uri.https('api.spotify.com', 'v1/playlists/$idPlaylist/tracks', - {'limit': '50', 'fields': 'items(track(id))'}); - var token = await _getAccessToken(); - _setResponse(await _client.get(url, headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - })); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - var res = decodedResponse['items'] - .where((element) => element['track']['id'] == idTrack) - .toList(); - if (res.length >= 1) { - return true; - } - return false; - } - - addToPLaylist(String idTrack) async { - var idPlaylist = await _getPlaylist(); - if (idPlaylist == null) { - idPlaylist = await _createPlaylist(); - } else { - if (await _isInPlaylist(idTrack, idPlaylist)) { - return; - } - } - var token = await _getAccessToken(); - var url = Uri.https('api.spotify.com', 'v1/playlists/$idPlaylist/tracks', - {'uris': 'spotify:track:$idTrack'}); - _setResponse(await _client.post(url, headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - })); - } - - Future _getPlaylist() async { - var url = Uri.https('api.spotify.com', 'v1/me/playlists', {'limit': '50'}); - var token = await _getAccessToken(); - _setResponse(await _client.get(url, headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - })); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - var daflplaylist = decodedResponse['items'] - .where((element) => element['name'] == _playlistName) - .toList(); - if (daflplaylist.length == 1) { - return daflplaylist[0]['uri'].substring( - 17); //17 char because format is 'spotify:playlist:MYPLAYLISTID' - } - return null; - } - - Future _createPlaylist() async { - var idUser = await MyApp.controller.currentUser.getIdSpotify(); - var token = await _getAccessToken(); - var url = Uri.https('api.spotify.com', 'v1/users/$idUser/playlists'); - _setResponse(await _client.post(url, - headers: { - 'Accept': 'application/json', - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - }, - body: jsonEncode({ - 'name': _playlistName, - 'description': - 'Retrouvez toutes vos découvertes faites sur DaflMusic 🎵', - 'public': 'true' - }))); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - var idPlaylist = decodedResponse['id']; - return idPlaylist; - } - - playTrack(String idTrack) async { - var token = await _getAccessToken(); - var url = Uri.https('api.spotify.com', 'v1/me/player/play'); - _setResponse(await _client.put(url, - headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - }, - body: jsonEncode({ - 'uris': ['spotify:track:$idTrack'] - }))); - } - - removeFromPlaylist(String idTrack) async { - var idPlaylist = await _getPlaylist(); - if (idPlaylist != null) { - if (await _isInPlaylist(idTrack, idPlaylist)) { - var token = await _getAccessToken(); - var url = - Uri.https('api.spotify.com', 'v1/playlists/$idPlaylist/tracks'); - var jsonVar = jsonEncode({ - 'tracks': [ - {'uri': 'spotify:track:$idTrack'} - ] - }); - _setResponse(await _client.delete(url, - headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - }, - body: jsonVar)); - } - } - } - - Future getIdUser() async { - var url = Uri.https('api.spotify.com', 'v1/me'); - var token = await _getAccessToken(); - _setResponse(await _client.get(url, headers: { - 'Authorization': '$_tokenType $token', - 'Content-Type': 'application/json' - })); - var decodedResponse = jsonDecode(utf8.decode(_response.bodyBytes)) as Map; - return decodedResponse['id']; - } -} diff --git a/Sources/dafl_project_flutter/lib/controller/controller.dart b/Sources/dafl_project_flutter/lib/controller/controller.dart index a0da747..8405804 100644 --- a/Sources/dafl_project_flutter/lib/controller/controller.dart +++ b/Sources/dafl_project_flutter/lib/controller/controller.dart @@ -1,45 +1,74 @@ import 'dart:convert'; +import 'package:dafl_project_flutter/model/music.dart'; +import 'package:dafl_project_flutter/services/api/api_spotify.dart'; +import 'package:dafl_project_flutter/services/position/area.dart'; import 'package:flutter/cupertino.dart'; import 'package:http/http.dart' as http; -import '../persistence/database_loader.dart'; -import '../persistence/database_saver.dart'; -import '../persistence/database_searcher.dart'; -import '../persistence/loader.dart'; -import '../persistence/saver.dart'; +import '../model/spot.dart'; import '../model/user.dart'; -import '../persistence/searcher.dart'; class Controller { - static Saver saver = DatabaseSaver(); - static Loader loader = DatabaseLoader(); - static final Searcher _searcher = DatabaseSearcher(); + ApiSpotify _api = ApiSpotify(); + late User _currentUser; + Area _area = Area(); - late BuildContext navigatorKey; + Uri getApiUrlAuthorize() { + return _api.identification.urlAuthorize; + } - late User currentUser; + String getApiRedirectUrl() { + return _api.identification.redirectUri; + } - Controller() { - currentUser = User('', ''); //TODO : remove this line + apiAuthorization(url) { + _api.apiAuthorization(url); } + String getIdSpotify() { + return _currentUser.idSpotify; + } + + Future getCompleteMusic(String id) async { + Map infos = await _api.requests.getTrackInfo(id); + return Music(id, infos['name'], infos['artist'], infos['cover']); + } + + setCurrentMusic() async { + _currentUser.currentMusic = await _api.requests.getCurrentlyPlayingTrack(); + } + + String getCurrentMusic() { + return _currentUser.currentMusic; + } + + int getIdDafl() { + return _currentUser.idDafl; + } + + List getSpots() { + return _area.spots; + } + +/* + static Saver saver = DatabaseSaver(); + static Loader loader = DatabaseLoader(); + static final Searcher _searcher = DatabaseSearcher(); + late BuildContext navigatorKey; + void save(User userToSave) { saver.save(userToSave); } load(String username, String password) async { - _changeCurrentUser(await loader.load(username, password)); - } - - _changeCurrentUser(User user) { - currentUser = user; + //TODO : call database methode + create user } - changeCurrentUsername(String newName) { - currentUser.usernameDafl = newName; + changeUsername(String newName) { + //TODO : call database method } changeCurrentPassword(String newPass) { - currentUser.passwDafl = newPass; + //TODO : call database method } Future searchByUsername(String username) async { @@ -71,4 +100,5 @@ class Controller { }), ); } + */ } diff --git a/Sources/dafl_project_flutter/lib/exceptions/api_exception.dart b/Sources/dafl_project_flutter/lib/exceptions/api_exception.dart deleted file mode 100644 index 191f176..0000000 --- a/Sources/dafl_project_flutter/lib/exceptions/api_exception.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'dart:developer' as dev; - -class ApiException implements Exception { - final String mess = 'Api exception raised,'; - - ApiException(dynamic code) { - if (code.runtimeType == String) { - dev.log('$mess state verification failed.'); - } else { - dev.log('$mess status code : $code.'); - } - } -} diff --git a/Sources/dafl_project_flutter/lib/exceptions/api_state_exception.dart b/Sources/dafl_project_flutter/lib/exceptions/api_state_exception.dart new file mode 100644 index 0000000..e3e394b --- /dev/null +++ b/Sources/dafl_project_flutter/lib/exceptions/api_state_exception.dart @@ -0,0 +1,7 @@ +import 'dart:developer' as dev; + +class ApiStateException implements Exception { + ApiStateException() { + dev.log('State verification failed.'); + } +} diff --git a/Sources/dafl_project_flutter/lib/exceptions/http_exception.dart b/Sources/dafl_project_flutter/lib/exceptions/http_exception.dart new file mode 100644 index 0000000..f2f6888 --- /dev/null +++ b/Sources/dafl_project_flutter/lib/exceptions/http_exception.dart @@ -0,0 +1,9 @@ +import 'dart:developer' as dev; + +class HttpException implements Exception { + HttpException(var value) { + dev.log('Http request failed :'); + dev.log('Status code : ${value.statusCode.toString()}'); + dev.log('Body : ${value.body.toString()}'); + } +} diff --git a/Sources/dafl_project_flutter/lib/main.dart b/Sources/dafl_project_flutter/lib/main.dart index 6f6c3fd..61fc00b 100644 --- a/Sources/dafl_project_flutter/lib/main.dart +++ b/Sources/dafl_project_flutter/lib/main.dart @@ -9,7 +9,6 @@ import 'package:provider/provider.dart'; import 'package:rive/rive.dart' as riv; import '../controller/controller.dart'; import 'model/spot.dart'; -import 'api/api.dart'; import 'dart:developer' as dev; import 'package:flutter_styled_toast/flutter_styled_toast.dart'; @@ -19,7 +18,6 @@ void main() { class MyApp extends StatelessWidget { static Controller controller = Controller(); - static Api api = Api(); const MyApp({super.key}); @@ -31,7 +29,7 @@ class MyApp extends StatelessWidget { SystemUiMode.manual, overlays: [SystemUiOverlay.top], ); - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle( statusBarIconBrightness: Brightness.dark, // For Android (dark icons) statusBarBrightness: Brightness.dark, // For iOS (dark icons) )); @@ -48,7 +46,7 @@ class MyApp extends StatelessWidget { enum CardStatus { like, disLike, discovery, message } class CardProvider extends ChangeNotifier { - final List _spotsList = MyApp.controller.currentUser.spots; + final List _spotsList = MyApp.controller.getSpots(); bool _isDragging = false; double _angle = 0; Offset _position = Offset.zero; @@ -196,7 +194,7 @@ class CardProvider extends ChangeNotifier { width: 10, ), MyApp.controller.currentUser.discovery.contains( - MyApp.controller.currentUser.spots.last.music) + MyApp.controller.getSpots().last.music) ? const Text( "Déjà dans vos discovery", style: TextStyle( @@ -220,10 +218,10 @@ class CardProvider extends ChangeNotifier { reverseCurve: Curves.linear, ); if (!MyApp.controller.currentUser.discovery - .contains(MyApp.controller.currentUser.spots.last.music)) { - MyApp.controller.currentUser.spots.last.music.defineDate(); + .contains(MyApp.controller.getSpots().last.music)) { + MyApp.controller.getSpots().last.music.defineDate(); MyApp.controller.currentUser - .addDiscovery(MyApp.controller.currentUser.spots.last.music); + .addDiscovery(MyApp.controller.getSpots().last.music); notifyListeners(); } } @@ -333,7 +331,7 @@ class CardProvider extends ChangeNotifier { child: ElevatedButton( onPressed: () { sendMessage(messageTextField.text, - MyApp.controller.currentUser.spots.last.userId); + MyApp.controller.getSpots().last.userId); }, style: ElevatedButton.styleFrom( backgroundColor: const Color(0xFF3F1DC3), @@ -364,7 +362,7 @@ class CardProvider extends ChangeNotifier { } void sendMessage(String message, String userId) { - dev.log(MyApp.controller.currentUser.spots.last.userId); + dev.log(MyApp.controller.getSpots().last.userId); } void like(context) { diff --git a/Sources/dafl_project_flutter/lib/model/music.dart b/Sources/dafl_project_flutter/lib/model/music.dart index 3cbafef..1253a09 100644 --- a/Sources/dafl_project_flutter/lib/model/music.dart +++ b/Sources/dafl_project_flutter/lib/model/music.dart @@ -1,32 +1,22 @@ -import '../exceptions/api_exception.dart'; +import '../exceptions/api_state_exception.dart'; import '../main.dart'; class Music { - late String _name; - late String _artist; - late String _linkCover; final String _id; - late DateTime date; + final String _name; + final String _artist; + final String _linkCover; - Music(this._id) { - _completeInfo(); - } + Music(this._id, this._name, this._artist, this._linkCover); + + String get id => _id; String get name => _name; + String get artist => _artist; + String get linkCover => _linkCover; - String get id => _id; - _completeInfo() async { - try { - var info = await MyApp.api.getTrackInfo(_id); - _name = info['name']; - _artist = info['artist']; - _linkCover = info['cover']; - } on ApiException { - // TODO : add notification to show that an error occured - } - } @override bool operator ==(Object other) => identical(this, other) || @@ -37,8 +27,4 @@ class Music { @override int get hashCode => name.hashCode ^ artist.hashCode; - - void defineDate() { - this.date = new DateTime.now(); - } } diff --git a/Sources/dafl_project_flutter/lib/model/user.dart b/Sources/dafl_project_flutter/lib/model/user.dart index 47c44fc..d5a1dbe 100644 --- a/Sources/dafl_project_flutter/lib/model/user.dart +++ b/Sources/dafl_project_flutter/lib/model/user.dart @@ -1,6 +1,4 @@ import 'dart:async'; -import '../../../position/location.dart'; -import '../exceptions/api_exception.dart'; import '../main.dart'; import 'music.dart'; import 'spot.dart'; @@ -17,53 +15,38 @@ class User { List spots = []; //attributes with Spotify API - String? _idSpotify; //use _getIdUser() as kind of a private getter - late Music _currentMusic; + final String _idSpotify; + + late String currentMusic; bool sortChoise = true; //constructors - User(this.usernameDafl, this.passwDafl) { - actualiseCurrentMusic(); - } + User(this.usernameDafl, this._idSpotify); - Music get currentMusic => _currentMusic; //lists - - Future getIdSpotify() async { - _idSpotify ??= await MyApp.api.getIdUser(); - return _idSpotify!; - } + String get idSpotify => _idSpotify; addDiscovery(Music music) { discovery.add(music); } - actualiseCurrentMusic() async { - try { - _currentMusic = Music(await MyApp.api.getCurrentlyPlayingTrack()); - } on ApiException { - // TODO : add notification to show that an error occurred - } - } - listSpots() { - int verif=0; + int verif = 0; Future> rep = Location.sendCurrentLocation(); //ex : dorian : 2d2s52a15d2a5 , audric : 2x5s2az3d1s5wx5s1 , lucas : s2a5d25a2a25d rep.then((Map result) { if (result.isNotEmpty) { result.forEach((key, value) { for (var element in spots) { - if (element.userId==key){ - verif=1; + if (element.userId == key) { + verif = 1; } } - if (verif==0){ + if (verif == 0) { spots.add(Spot(key, Music(value))); } - verif=0; + verif = 0; }); } }); - } } diff --git a/Sources/dafl_project_flutter/lib/persistence/saver.dart b/Sources/dafl_project_flutter/lib/persistence/saver.dart deleted file mode 100644 index da1ca5d..0000000 --- a/Sources/dafl_project_flutter/lib/persistence/saver.dart +++ /dev/null @@ -1,5 +0,0 @@ -import '../model/user.dart'; - -abstract class Saver{ - void save(User userToSave); -} \ No newline at end of file diff --git a/Sources/dafl_project_flutter/lib/api/.gitkeep b/Sources/dafl_project_flutter/lib/services/api/.gitkeep similarity index 100% rename from Sources/dafl_project_flutter/lib/api/.gitkeep rename to Sources/dafl_project_flutter/lib/services/api/.gitkeep diff --git a/Sources/dafl_project_flutter/lib/services/api/api_spotify.dart b/Sources/dafl_project_flutter/lib/services/api/api_spotify.dart new file mode 100644 index 0000000..6eeb00f --- /dev/null +++ b/Sources/dafl_project_flutter/lib/services/api/api_spotify.dart @@ -0,0 +1,20 @@ +import 'package:dafl_project_flutter/services/api/api_spotify_identification.dart'; +import 'package:dafl_project_flutter/services/api/api_spotify_requests.dart'; + +class ApiSpotify { + late ApiSpotifyIdentification _identification; + late ApiSpotifyRequests _requests; + + ApiSpotify() { + _identification = ApiSpotifyIdentification(); + } + + ApiSpotifyIdentification get identification => _identification; + + ApiSpotifyRequests get requests => _requests; + + apiAuthorization(url) async { + await _identification.setCode(url); + _requests = ApiSpotifyRequests(await _identification.createToken()); + } +} diff --git a/Sources/dafl_project_flutter/lib/services/api/api_spotify_identification.dart b/Sources/dafl_project_flutter/lib/services/api/api_spotify_identification.dart new file mode 100644 index 0000000..fc00bf6 --- /dev/null +++ b/Sources/dafl_project_flutter/lib/services/api/api_spotify_identification.dart @@ -0,0 +1,83 @@ +import 'dart:convert'; +import 'dart:math'; +import '../../exceptions/api_state_exception.dart'; +import 'package:http/http.dart' as http; +import 'package:crypto/crypto.dart'; +import '../http_response_verification.dart'; +import 'token_spotify.dart'; + +class ApiSpotifyIdentification extends HttpResponseVerification { + static const String clientId = '7ceb49d874b9404492246027e4d68cf8'; + final String _clientSecret = '98f9cb960bf54ebbb9ad306e7ff919cb'; + final String _scopes = + 'user-read-playback-state user-read-currently-playing user-read-recently-played playlist-modify-public ugc-image-upload user-modify-playback-state'; + + late String _state; + late String _codeVerifier; + late String _codeChallenge; + late String _encodedLogs; + + String? _code; + + String get redirectUri => 'https://daflmusic.000webhostapp.com/callback/'; + + Uri get urlAuthorize => Uri.https('accounts.spotify.com', 'authorize', { + 'client_id': clientId, + 'response_type': 'code', + 'redirect_uri': redirectUri, + 'state': _state, + 'scope': _scopes, + 'show_dialog': 'false', + 'code_challenge_method': 'S256', + 'code_challenge': _codeChallenge + }); + + ApiSpotifyIdentification() { + _state = _generateRandomString(16); + _codeVerifier = _generateRandomString(_generateRandomInt(43, 128)); + _codeChallenge = _generateCodeChallenge(); + _encodedLogs = base64.encode(utf8.encode("$clientId:$_clientSecret")); + } + + _generateRandomInt(int min, int max) { + return min + Random().nextInt(max - min); + } + + _generateRandomString(int length) { + const chars = + 'AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz1234567890'; + return String.fromCharCodes(Iterable.generate( + length, (_) => chars.codeUnitAt(Random().nextInt(chars.length)))); + } + + _generateCodeChallenge() { + return base64Encode(sha256.convert(utf8.encode(_codeVerifier)).bytes) + .replaceAll('+', '-') + .replaceAll('/', '_') + .replaceAll('=', ''); + } + + setCode(Uri url) async { + if (url.queryParameters['state'] != _state.toString()) { + throw ApiStateException(); + } + _code = url.queryParameters['code']; + } + + Future createToken() async { + var urlToken = Uri.https('accounts.spotify.com', 'api/token', { + 'code': _code, + 'redirect_uri': redirectUri, + 'grant_type': 'authorization_code', + 'client_id': clientId, + 'code_verifier': _codeVerifier + }); + setResponse(await http.post(urlToken, headers: { + 'Authorization': 'Basic $_encodedLogs', + 'Content-Type': 'application/x-www-form-urlencoded' + })); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + return TokenSpotify(decodedResponse['access_token'], + decodedResponse['refresh_token'], decodedResponse['expires_in']); + } +} diff --git a/Sources/dafl_project_flutter/lib/services/api/api_spotify_requests.dart b/Sources/dafl_project_flutter/lib/services/api/api_spotify_requests.dart new file mode 100644 index 0000000..d85cbad --- /dev/null +++ b/Sources/dafl_project_flutter/lib/services/api/api_spotify_requests.dart @@ -0,0 +1,177 @@ +import 'dart:convert'; +import 'package:dafl_project_flutter/services/api/token_spotify.dart'; +import 'package:http/http.dart' as http; + +import '../../main.dart'; +import '../http_response_verification.dart'; + +class ApiSpotifyRequests extends HttpResponseVerification { + final String _tokenType = 'Bearer '; + final String _playlistName = "Dafl's discovery"; + final TokenSpotify _token; + + ApiSpotifyRequests(this._token); + + Future getCurrentlyPlayingTrack() async { + var url = Uri.https('api.spotify.com', 'v1/me/player/currently-playing'); + var token = await _token.getAccessToken(); + var response = await http.get(url, headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + }); + if (response.statusCode == 204) { + return _getRecentlyPlayedTrack(); + } + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + return decodedResponse['item']['id']; + } + + Future _getRecentlyPlayedTrack() async { + var url = Uri.https( + 'api.spotify.com', 'v1/me/player/recently-played', {'limit': '1'}); + var token = await _token.getAccessToken(); + setResponse(await http.get(url, headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + })); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + return decodedResponse['items'][0]['track']['id']; + } + + Future getTrackInfo(String id) async { + var url = Uri.https('api.spotify.com', 'v1/tracks/$id'); + var token = await _token.getAccessToken(); + setResponse(await http.get(url, headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + })); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + Map infos = { + 'artist': decodedResponse['artists'][0]['name'], + 'name': decodedResponse['name'], + 'cover': decodedResponse['album']['images'][0]['url'] + }; + return infos; + } + + Future _isInPlaylist(String idTrack, String idPlaylist) async { + var url = Uri.https('api.spotify.com', 'v1/playlists/$idPlaylist/tracks', + {'limit': '50', 'fields': 'items(track(id))'}); + var token = await _token.getAccessToken(); + setResponse(await http.get(url, headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + })); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + var res = decodedResponse['items'] + .where((element) => element['track']['id'] == idTrack) + .toList(); + if (res.length >= 1) { + return true; + } + return false; + } + + addToPLaylist(String idTrack) async { + var idPlaylist = await _getPlaylist(); + if (idPlaylist == null) { + idPlaylist = await _createPlaylist(); + } else { + if (await _isInPlaylist(idTrack, idPlaylist)) { + return; + } + } + var token = await _token.getAccessToken(); + var url = Uri.https('api.spotify.com', 'v1/playlists/$idPlaylist/tracks', + {'uris': 'spotify:track:$idTrack'}); + setResponse(await http.post(url, headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + })); + } + + Future _getPlaylist() async { + var url = Uri.https('api.spotify.com', 'v1/me/playlists', {'limit': '50'}); + var token = await _token.getAccessToken(); + setResponse(await http.get(url, headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + })); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + var daflplaylist = decodedResponse['items'] + .where((element) => element['name'] == _playlistName) + .toList(); + if (daflplaylist.length == 1) { + return daflplaylist[0]['uri'].substring( + 17); //17 char because format is 'spotify:playlist:MYPLAYLISTID' + } + return null; + } + + Future _createPlaylist() async { + var idUser = MyApp.controller.getIdSpotify(); + var token = await _token.getAccessToken(); + var url = Uri.https('api.spotify.com', 'v1/users/$idUser/playlists'); + setResponse(await http.post(url, + headers: { + 'Accept': 'application/json', + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + }, + body: jsonEncode({ + 'name': _playlistName, + 'description': + 'Retrouvez toutes vos découvertes faites sur DaflMusic 🎵', + 'public': 'true' + }))); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + var idPlaylist = decodedResponse['id']; + return idPlaylist; + } + + playTrack(String idTrack) async { + var token = await _token.getAccessToken(); + var url = Uri.https('api.spotify.com', 'v1/me/player/play'); + setResponse(await http.put(url, + headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + }, + body: jsonEncode({ + 'uris': ['spotify:track:$idTrack'] + }))); + } + + removeFromPlaylist(String idTrack) async { + var idPlaylist = await _getPlaylist(); + if (idPlaylist != null) { + if (await _isInPlaylist(idTrack, idPlaylist)) { + var token = await _token.getAccessToken(); + var url = + Uri.https('api.spotify.com', 'v1/playlists/$idPlaylist/tracks'); + var jsonVar = jsonEncode({ + 'tracks': [ + {'uri': 'spotify:track:$idTrack'} + ] + }); + setResponse(await http.delete(url, + headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + }, + body: jsonVar)); + } + } + } + + Future getIdUser() async { + var url = Uri.https('api.spotify.com', 'v1/me'); + var token = await _token.getAccessToken(); + setResponse(await http.get(url, headers: { + 'Authorization': '$_tokenType $token', + 'Content-Type': 'application/json' + })); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + return decodedResponse['id']; + } +} diff --git a/Sources/dafl_project_flutter/lib/api/in_app_browser.dart b/Sources/dafl_project_flutter/lib/services/api/in_app_browser.dart similarity index 73% rename from Sources/dafl_project_flutter/lib/api/in_app_browser.dart rename to Sources/dafl_project_flutter/lib/services/api/in_app_browser.dart index 3223763..9c02c2a 100644 --- a/Sources/dafl_project_flutter/lib/api/in_app_browser.dart +++ b/Sources/dafl_project_flutter/lib/services/api/in_app_browser.dart @@ -1,8 +1,6 @@ import 'dart:io'; -import 'package:flutter/cupertino.dart'; import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import '../exceptions/api_exception.dart'; -import '../main.dart'; +import '../../main.dart'; class MyInAppBrowser extends InAppBrowser { var options = InAppBrowserClassOptions( @@ -14,7 +12,8 @@ class MyInAppBrowser extends InAppBrowser { MyInAppBrowser() { _debugBrowser(); openUrlRequest( - urlRequest: URLRequest(url: MyApp.api.urlAuthorize), options: options); + urlRequest: URLRequest(url: MyApp.controller.getApiUrlAuthorize()), + options: options); } _debugBrowser() async { @@ -26,10 +25,10 @@ class MyInAppBrowser extends InAppBrowser { @override Future onLoadStart(url) async { bool isError = false; - if (url!.origin + url.path == MyApp.api.redirectUri) { + if (url!.origin + url.path == MyApp.controller.getApiRedirectUrl()) { try { - await MyApp.api.requestUserAuthorization(url); - } on ApiException { + await MyApp.controller.apiAuthorization(url); + } catch (e) { notify(5, MyApp.controller.navigatorKey); isError = true; } finally { diff --git a/Sources/dafl_project_flutter/lib/services/api/token_spotify.dart b/Sources/dafl_project_flutter/lib/services/api/token_spotify.dart new file mode 100644 index 0000000..4f59ed0 --- /dev/null +++ b/Sources/dafl_project_flutter/lib/services/api/token_spotify.dart @@ -0,0 +1,41 @@ +import 'dart:convert'; + +import 'package:dafl_project_flutter/services/api/api_spotify_identification.dart'; + +import '../http_response_verification.dart'; +import 'package:http/http.dart' as http; + +class TokenSpotify extends HttpResponseVerification { + String _accessToken; + final String _refreshToken; + late DateTime _tokenEnd; + + TokenSpotify(this._accessToken, this._refreshToken, int expiresIn) { + _setTokenEnd(expiresIn); + } + + _setTokenEnd(int expiresIn) { + _tokenEnd = DateTime.now().add(Duration(seconds: expiresIn)); + } + + Future getAccessToken() async { + if (DateTime.now().isAfter(_tokenEnd)) { + await _actualiseToken(); + } + return _accessToken; + } + + _actualiseToken() async { + var urlToken = Uri.https('accounts.spotify.com', 'api/token', { + 'grant_type': 'refresh_token', + 'refresh_token': _refreshToken, + 'client_id': ApiSpotifyIdentification.clientId + }); + setResponse(await http.post(urlToken, headers: { + 'Content-Type': 'application/x-www-form-urlencoded' + })); + var decodedResponse = jsonDecode(utf8.decode(response.bodyBytes)) as Map; + _accessToken = decodedResponse['access_token']; + _setTokenEnd(decodedResponse['expires_in']); + } +} diff --git a/Sources/dafl_project_flutter/lib/services/http_response_verification.dart b/Sources/dafl_project_flutter/lib/services/http_response_verification.dart new file mode 100644 index 0000000..afe9668 --- /dev/null +++ b/Sources/dafl_project_flutter/lib/services/http_response_verification.dart @@ -0,0 +1,17 @@ +import 'package:flutter/cupertino.dart'; + +import '../exceptions/http_exception.dart'; +import 'package:http/http.dart' as http; + +abstract class HttpResponseVerification { + @protected + late http.Response response; + + @protected + setResponse(var value) { + if (value.statusCode >= 300) { + throw HttpException(value); + } + response = value; + } +} diff --git a/Sources/dafl_project_flutter/lib/persistence/.gitkeep b/Sources/dafl_project_flutter/lib/services/persistence/.gitkeep similarity index 100% rename from Sources/dafl_project_flutter/lib/persistence/.gitkeep rename to Sources/dafl_project_flutter/lib/services/persistence/.gitkeep diff --git a/Sources/dafl_project_flutter/lib/persistence/database_connexion.dart b/Sources/dafl_project_flutter/lib/services/persistence/database_connexion.dart similarity index 100% rename from Sources/dafl_project_flutter/lib/persistence/database_connexion.dart rename to Sources/dafl_project_flutter/lib/services/persistence/database_connexion.dart diff --git a/Sources/dafl_project_flutter/lib/persistence/database_loader.dart b/Sources/dafl_project_flutter/lib/services/persistence/database_loader.dart similarity index 96% rename from Sources/dafl_project_flutter/lib/persistence/database_loader.dart rename to Sources/dafl_project_flutter/lib/services/persistence/database_loader.dart index 848018f..7edb28c 100644 --- a/Sources/dafl_project_flutter/lib/persistence/database_loader.dart +++ b/Sources/dafl_project_flutter/lib/services/persistence/database_loader.dart @@ -1,6 +1,6 @@ import 'dart:async'; import 'loader.dart'; -import '../model/user.dart'; +import '../../model/user.dart'; import 'database_connexion.dart'; import 'dart:developer' as dev; diff --git a/Sources/dafl_project_flutter/lib/persistence/database_saver.dart b/Sources/dafl_project_flutter/lib/services/persistence/database_saver.dart similarity index 94% rename from Sources/dafl_project_flutter/lib/persistence/database_saver.dart rename to Sources/dafl_project_flutter/lib/services/persistence/database_saver.dart index 6553e79..8a6beda 100644 --- a/Sources/dafl_project_flutter/lib/persistence/database_saver.dart +++ b/Sources/dafl_project_flutter/lib/services/persistence/database_saver.dart @@ -1,6 +1,6 @@ import 'database_connexion.dart'; import 'saver.dart'; -import '../model/user.dart'; +import '../../model/user.dart'; class DatabaseSaver extends Saver { // Save user in the database diff --git a/Sources/dafl_project_flutter/lib/persistence/database_searcher.dart b/Sources/dafl_project_flutter/lib/services/persistence/database_searcher.dart similarity index 100% rename from Sources/dafl_project_flutter/lib/persistence/database_searcher.dart rename to Sources/dafl_project_flutter/lib/services/persistence/database_searcher.dart diff --git a/Sources/dafl_project_flutter/lib/persistence/loader.dart b/Sources/dafl_project_flutter/lib/services/persistence/loader.dart similarity index 76% rename from Sources/dafl_project_flutter/lib/persistence/loader.dart rename to Sources/dafl_project_flutter/lib/services/persistence/loader.dart index 9cc2eec..768426c 100644 --- a/Sources/dafl_project_flutter/lib/persistence/loader.dart +++ b/Sources/dafl_project_flutter/lib/services/persistence/loader.dart @@ -1,5 +1,5 @@ import 'dart:async'; -import '../model/user.dart'; +import '../../model/user.dart'; abstract class Loader { Future load(String username, String password); diff --git a/Sources/dafl_project_flutter/lib/services/persistence/saver.dart b/Sources/dafl_project_flutter/lib/services/persistence/saver.dart new file mode 100644 index 0000000..5c8462b --- /dev/null +++ b/Sources/dafl_project_flutter/lib/services/persistence/saver.dart @@ -0,0 +1,5 @@ +import '../../model/user.dart'; + +abstract class Saver { + void save(User userToSave); +} diff --git a/Sources/dafl_project_flutter/lib/persistence/searcher.dart b/Sources/dafl_project_flutter/lib/services/persistence/searcher.dart similarity index 100% rename from Sources/dafl_project_flutter/lib/persistence/searcher.dart rename to Sources/dafl_project_flutter/lib/services/persistence/searcher.dart diff --git a/Sources/dafl_project_flutter/lib/position/.gitkeep b/Sources/dafl_project_flutter/lib/services/position/.gitkeep similarity index 100% rename from Sources/dafl_project_flutter/lib/position/.gitkeep rename to Sources/dafl_project_flutter/lib/services/position/.gitkeep diff --git a/Sources/dafl_project_flutter/lib/position/location.dart b/Sources/dafl_project_flutter/lib/services/position/area.dart similarity index 56% rename from Sources/dafl_project_flutter/lib/position/location.dart rename to Sources/dafl_project_flutter/lib/services/position/area.dart index 1978152..fbf102a 100644 --- a/Sources/dafl_project_flutter/lib/position/location.dart +++ b/Sources/dafl_project_flutter/lib/services/position/area.dart @@ -2,42 +2,41 @@ import 'package:geolocator/geolocator.dart'; import 'package:http/http.dart' as http; import 'dart:convert'; import 'dart:async'; -import '../main.dart'; -class Location { - static Future> sendCurrentLocation() async { +import '../../main.dart'; +import '../../model/spot.dart'; + +class Area { + late List spots; + + sendCurrentLocation() async { Uri uri = Uri.parse("http://89.83.53.34/phpmyadmin/dafldev/insert.php"); LocationPermission permission; permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.deniedForever) { - //faire l'interface gra pour gérer ça - return Future.error('Location Not Available'); + //TODO : handle this case } } - String actualUser = MyApp.controller.currentUser.usernameDafl; - String actualSong = await MyApp.api.getCurrentlyPlayingTrack(); + String actualUser = MyApp.controller.getIdSpotify(); + String actualSong = await MyApp.controller.getCurrentMusic(); Position current = await Geolocator.getCurrentPosition(); await http.post(uri, body: { - "id": actualUser.toString(), + "id": actualUser, "latitude": current.latitude.toString(), "longitude": current.longitude.toString(), - "idMusic": actualSong.toString(), + "idMusic": actualSong }); - return getData(); } - static Future> getData() async { - Map spot = {}; - String actualUser = MyApp.controller.currentUser.usernameDafl; + getData() async { + String actualUser = MyApp.controller.getIdDafl().toString(); Uri uri = Uri.parse("http://89.83.53.34/phpmyadmin/dafldev/distance.php"); http.Response response = await http.post(uri, body: { "id": actualUser, }); var data = jsonDecode(response.body); - data.forEach((s)=> spot.putIfAbsent(s['user'], () => s['music'])); - return spot; + data.forEach((s) => spots.add(Spot(s['user'], s['music']))); } } - diff --git a/Sources/dafl_project_flutter/lib/services/position/location.dart b/Sources/dafl_project_flutter/lib/services/position/location.dart new file mode 100644 index 0000000..fa96c57 --- /dev/null +++ b/Sources/dafl_project_flutter/lib/services/position/location.dart @@ -0,0 +1 @@ +class Location {} diff --git a/Sources/dafl_project_flutter/lib/presentation/custom_icons_icons.dart b/Sources/dafl_project_flutter/lib/views/presentation/custom_icons_icons.dart similarity index 100% rename from Sources/dafl_project_flutter/lib/presentation/custom_icons_icons.dart rename to Sources/dafl_project_flutter/lib/views/presentation/custom_icons_icons.dart diff --git a/Sources/dafl_project_flutter/pubspec.lock b/Sources/dafl_project_flutter/pubspec.lock index 0662299..3d63a15 100644 --- a/Sources/dafl_project_flutter/pubspec.lock +++ b/Sources/dafl_project_flutter/pubspec.lock @@ -1,6 +1,20 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + _fe_analyzer_shared: + dependency: transitive + description: + name: _fe_analyzer_shared + url: "https://pub.dartlang.org" + source: hosted + version: "50.0.0" + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "5.2.0" animations: dependency: "direct main" description: @@ -78,6 +92,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.16.0" + color: + dependency: transitive + description: + name: color + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.0" convert: dependency: transitive description: @@ -106,6 +127,20 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.5" + dart_style: + dependency: transitive + description: + name: dart_style + url: "https://pub.dartlang.org" + source: hosted + version: "2.2.4" + dartx: + dependency: transitive + description: + name: dartx + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.0" fake_async: dependency: transitive description: @@ -132,6 +167,20 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_gen: + dependency: "direct main" + description: + name: flutter_gen + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0+1" + flutter_gen_core: + dependency: transitive + description: + name: flutter_gen_core + url: "https://pub.dartlang.org" + source: hosted + version: "5.1.0+1" flutter_inappwebview: dependency: "direct main" description: @@ -245,6 +294,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.1.1" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.1" graphs: dependency: transitive description: @@ -357,6 +413,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.0" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.0" page_transition: dependency: "direct main" description: @@ -462,6 +525,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "6.0.4" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" random_string: dependency: "direct main" description: @@ -530,6 +600,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "0.4.12" + time: + dependency: transitive + description: + name: time + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.3" typed_data: dependency: transitive description: @@ -558,6 +635,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.7.6" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" win32: dependency: transitive description: diff --git a/Sources/dafl_project_flutter/pubspec.yaml b/Sources/dafl_project_flutter/pubspec.yaml index 96ede70..59c174d 100644 --- a/Sources/dafl_project_flutter/pubspec.yaml +++ b/Sources/dafl_project_flutter/pubspec.yaml @@ -29,6 +29,7 @@ dependencies: home_indicator: ^2.0.2 geolocator: ^9.0.2 flutter_styled_toast: ^2.1.3 + flutter_gen: ^5.1.0+1 dev_dependencies: flutter_test: