From ddda7ab779de330abf8599202fcb108423f21181 Mon Sep 17 00:00:00 2001 From: Lucas Delanier Date: Sun, 30 Jul 2023 17:53:36 +0200 Subject: [PATCH] =?UTF-8?q?search=20location=20is=20live=20!=F0=9F=8E=89?= =?UTF-8?q?=F0=9F=8E=89=F0=9F=8E=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../android/app/src/debug/AndroidManifest.xml | 1 + .../lib/components/buttonPostComponent.dart | 176 ++++++++++-------- .../lib/components/city_list_component.dart | 45 +++++ .../components/editable_post_component.dart | 80 ++++++-- .../justMUSIC/lib/screens/post_screen.dart | 10 +- .../lib/screens/search_location_screen.dart | 87 +++++++++ Sources/justMUSIC/lib/services/GeoApi.dart | 60 ++++++ Sources/justMUSIC/pubspec.lock | 56 ++++++ Sources/justMUSIC/pubspec.yaml | 2 + 9 files changed, 425 insertions(+), 92 deletions(-) create mode 100644 Sources/justMUSIC/lib/components/city_list_component.dart create mode 100644 Sources/justMUSIC/lib/screens/search_location_screen.dart create mode 100644 Sources/justMUSIC/lib/services/GeoApi.dart diff --git a/Sources/justMUSIC/android/app/src/debug/AndroidManifest.xml b/Sources/justMUSIC/android/app/src/debug/AndroidManifest.xml index 44a649b..04c234b 100644 --- a/Sources/justMUSIC/android/app/src/debug/AndroidManifest.xml +++ b/Sources/justMUSIC/android/app/src/debug/AndroidManifest.xml @@ -5,4 +5,5 @@ to allow setting breakpoints, to provide hot reload, etc. --> + diff --git a/Sources/justMUSIC/lib/components/buttonPostComponent.dart b/Sources/justMUSIC/lib/components/buttonPostComponent.dart index 166f426..28c4587 100644 --- a/Sources/justMUSIC/lib/components/buttonPostComponent.dart +++ b/Sources/justMUSIC/lib/components/buttonPostComponent.dart @@ -1,6 +1,7 @@ import 'package:flutter/Material.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:ionicons/ionicons.dart'; +import 'package:tuple/tuple.dart'; import '../values/constants.dart'; @@ -15,54 +16,68 @@ class PhotoPostComponent extends StatelessWidget { padding: EdgeInsets.all(10), decoration: BoxDecoration( color: postbutton, borderRadius: BorderRadius.circular(8)), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Ionicons.camera, - size: 15, - color: Colors.white, - ), - SizedBox( - width: 10, - ), - Text( - 'Prendre un selfie', - style: GoogleFonts.plusJakartaSans( - color: Colors.white, fontSize: 12), - ) - ]), - ) + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + Ionicons.camera, + size: 15, + color: Colors.white, + ), + SizedBox( + width: 10, + ), + Expanded( + child: Text( + 'Prendre un selfie', + style: GoogleFonts.plusJakartaSans( + color: Colors.white, fontSize: 12), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ) + ]), + )) : Container( padding: EdgeInsets.all(10), decoration: BoxDecoration( color: fillButton, borderRadius: BorderRadius.circular(8)), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Selfie enregistré", - style: GoogleFonts.plusJakartaSans( - color: Colors.white, fontSize: 12), - ), - SizedBox( - width: 10, - ), - Icon( - Icons.close, - size: 12, - color: Colors.white, - ), - ]), - ); + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: Text( + "Selfie enregistré", + style: GoogleFonts.plusJakartaSans( + color: Colors.white, fontSize: 12), + overflow: TextOverflow.ellipsis, + maxLines: 1, + ), + ), + SizedBox( + width: 10, + ), + Icon( + Icons.close, + size: 12, + color: Colors.white, + ), + ]), + )); } } class LocationPostComponent extends StatelessWidget { final bool empty; - const LocationPostComponent({Key? key, required this.empty}) + final Tuple2? location; + const LocationPostComponent( + {Key? key, required this.empty, required this.location}) : super(key: key); @override @@ -73,48 +88,59 @@ class LocationPostComponent extends StatelessWidget { padding: EdgeInsets.all(10), decoration: BoxDecoration( color: postbutton, borderRadius: BorderRadius.circular(8)), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.location_on, - size: 15, - color: Colors.white, - ), - SizedBox( - width: 10, - ), - Text( - 'Ajouter un lieu', - style: GoogleFonts.plusJakartaSans( - color: Colors.white, fontSize: 12), - ) - ]), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Icon( + Icons.location_on, + size: 15, + color: Colors.white, + ), + SizedBox( + width: 10, + ), + Expanded( + child: Text( + 'Ajouter un lieu', + style: GoogleFonts.plusJakartaSans( + color: Colors.white, fontSize: 12), + overflow: TextOverflow.ellipsis, + ), + ) + ])), ) : Container( width: double.infinity, padding: EdgeInsets.all(10), decoration: BoxDecoration( color: fillButton, borderRadius: BorderRadius.circular(8)), - child: Row( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "Lieu enregistré", - style: GoogleFonts.plusJakartaSans( - color: Colors.white, fontSize: 12), - ), - SizedBox( - width: 10, - ), - Icon( - Icons.close, - size: 12, - color: Colors.white, - ), - ]), + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Expanded( + child: Text( + '${location?.item1}, ${location?.item2}', + style: GoogleFonts.plusJakartaSans( + color: Colors.white, fontSize: 12), + overflow: TextOverflow.ellipsis, + ), + ), + SizedBox( + width: 10, + ), + Icon( + Icons.close, + size: 12, + color: Colors.white, + ), + ]), + ), ); } } diff --git a/Sources/justMUSIC/lib/components/city_list_component.dart b/Sources/justMUSIC/lib/components/city_list_component.dart new file mode 100644 index 0000000..a1ef958 --- /dev/null +++ b/Sources/justMUSIC/lib/components/city_list_component.dart @@ -0,0 +1,45 @@ +import 'package:flutter/Material.dart'; +import 'package:google_fonts/google_fonts.dart'; +import 'package:justmusic/values/constants.dart'; +import 'package:tuple/tuple.dart'; + +class CityListComponent extends StatelessWidget { + final Tuple2 location; + const CityListComponent({Key? key, required this.location}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 10), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Flexible( + child: RichText( + overflow: TextOverflow.ellipsis, + maxLines: 1, + text: TextSpan(children: [ + TextSpan( + text: location.item2 + ", ", + style: GoogleFonts.plusJakartaSans( + color: grayText, + fontWeight: FontWeight.w400, + fontSize: 17), + ), + TextSpan( + text: location.item1, + style: GoogleFonts.plusJakartaSans( + color: Colors.white, + fontWeight: FontWeight.w400, + fontSize: 17), + ), + ])), + ), + ], + ), + ), + ); + } +} diff --git a/Sources/justMUSIC/lib/components/editable_post_component.dart b/Sources/justMUSIC/lib/components/editable_post_component.dart index dda4c74..f66759f 100644 --- a/Sources/justMUSIC/lib/components/editable_post_component.dart +++ b/Sources/justMUSIC/lib/components/editable_post_component.dart @@ -11,8 +11,10 @@ import 'package:image_picker/image_picker.dart'; import 'package:insta_image_viewer/insta_image_viewer.dart'; import 'package:justmusic/values/constants.dart'; import 'package:text_scroll/text_scroll.dart'; +import 'package:tuple/tuple.dart'; import '../model/Music.dart'; +import '../screens/search_location_screen.dart'; import 'buttonPostComponent.dart'; class EditablePostComponent extends StatefulWidget { @@ -28,7 +30,27 @@ class _EditablePostComponentState extends State final ImagePicker picker = ImagePicker(); late Animation animation; late AnimationController animationController; + late AnimationController _controller; File? image; + Tuple2? selectedCity; + + @override + void initState() { + _controller = AnimationController( + vsync: this, + duration: const Duration(milliseconds: 400), + ); + animationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: 400), + ); + animation = CurvedAnimation( + parent: animationController, + curve: Curves.easeInOutSine, + ); + animationController.forward(); + super.initState(); + } Future pickImage() async { try { @@ -43,18 +65,34 @@ class _EditablePostComponentState extends State } } - @override - void initState() { - animationController = AnimationController( - vsync: this, - duration: Duration(milliseconds: 400), - ); - animation = CurvedAnimation( - parent: animationController, - curve: Curves.easeInOutSine, + void _selectLocation(Tuple2 location) { + Navigator.pop(context); + setState(() { + selectedCity = location; + }); + } + + void searchLocation() { + showModalBottomSheet( + transitionAnimationController: _controller, + barrierColor: Colors.black.withOpacity(0.7), + backgroundColor: Colors.transparent, + elevation: 1, + constraints: const BoxConstraints( + maxWidth: 600, + ), + isScrollControlled: true, + context: context, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20))), + builder: ((context) { + return ClipRRect( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20), topRight: Radius.circular(20)), + child: SearchCityScreen(callback: _selectLocation)); + }), ); - animationController.forward(); - super.initState(); } @override @@ -219,8 +257,14 @@ class _EditablePostComponentState extends State ), Expanded( flex: 5, - child: LocationPostComponent( - empty: true, + child: GestureDetector( + onTap: () { + manageLocation(); + }, + child: LocationPostComponent( + empty: selectedCity == null, + location: selectedCity, + ), ), ), ], @@ -280,4 +324,14 @@ class _EditablePostComponentState extends State pickImage(); } } + + void manageLocation() { + if (selectedCity != null) { + setState(() { + selectedCity = null; + }); + } else { + searchLocation(); + } + } } diff --git a/Sources/justMUSIC/lib/screens/post_screen.dart b/Sources/justMUSIC/lib/screens/post_screen.dart index ecc1051..1f53d8b 100644 --- a/Sources/justMUSIC/lib/screens/post_screen.dart +++ b/Sources/justMUSIC/lib/screens/post_screen.dart @@ -3,6 +3,7 @@ import 'package:flutter/Material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:justmusic/components/back_button.dart'; import 'package:justmusic/screens/search_song_screen.dart'; +import 'package:tuple/tuple.dart'; import '../components/editable_post_component.dart'; import '../components/post_button_component.dart'; import '../model/Music.dart'; @@ -21,6 +22,7 @@ class _PostScreenState extends State late AnimationController _controller; Music? selectedMusic; + Tuple2? selectedCity; @override void initState() { @@ -39,7 +41,7 @@ class _PostScreenState extends State }); } - void openDetailPost() { + void openSearchSong() { showModalBottomSheet( transitionAnimationController: _controller, barrierColor: Colors.black.withOpacity(0.7), @@ -68,11 +70,11 @@ class _PostScreenState extends State resizeToAvoidBottomInset: true, backgroundColor: bgColor, extendBodyBehindAppBar: true, - appBar: PreferredSize( + appBar: const PreferredSize( preferredSize: Size(double.infinity, 80), child: SafeArea( child: Padding( - padding: const EdgeInsets.only( + padding: EdgeInsets.only( left: defaultPadding, right: defaultPadding, top: defaultPadding), @@ -104,7 +106,7 @@ class _PostScreenState extends State height: 100.h, ), GestureDetector( - onTap: openDetailPost, + onTap: openSearchSong, child: EditablePostComponent(music: selectedMusic)), SizedBox( height: 40.h, diff --git a/Sources/justMUSIC/lib/screens/search_location_screen.dart b/Sources/justMUSIC/lib/screens/search_location_screen.dart new file mode 100644 index 0000000..73f6d1a --- /dev/null +++ b/Sources/justMUSIC/lib/screens/search_location_screen.dart @@ -0,0 +1,87 @@ +import 'dart:ui'; + +import 'package:flutter/Material.dart'; + +import '../components/city_list_component.dart'; +import '../services/GeoApi.dart'; +import '../values/constants.dart'; + +class SearchCityScreen extends StatefulWidget { + final Function callback; + const SearchCityScreen({Key? key, required this.callback}) : super(key: key); + + @override + State createState() => _SearchCityScreenState(); +} + +class _SearchCityScreenState extends State { + final ScrollController _scrollController = ScrollController(); + final GeoApi api = GeoApi(); + + @override + Widget build(BuildContext context) { + double screenHeight = MediaQuery.of(context).size.height; + return BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 60.0, + sigmaY: 60.0, + ), + child: Container( + color: bgAppBar.withOpacity(0.5), + height: screenHeight - 50, + padding: const EdgeInsets.only(top: 10), + child: Column( + children: [ + Align( + child: Container( + width: 60, + height: 5, + decoration: BoxDecoration( + color: Color(0xFF3A3A3A).withOpacity(0.6), + borderRadius: BorderRadius.circular(20))), + ), + const SizedBox( + height: 10, + ), + Flexible( + child: ScrollConfiguration( + behavior: ScrollBehavior().copyWith(scrollbars: true), + child: FutureBuilder( + future: api.getNearbyCities(), + builder: + (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.hasData) { + return ListView.builder( + physics: const BouncingScrollPhysics( + decelerationRate: ScrollDecelerationRate.fast), + controller: _scrollController, + itemCount: snapshot.data.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + widget.callback(snapshot.data[index]); + }, + child: Padding( + padding: + const EdgeInsets.symmetric(horizontal: 20), + child: CityListComponent( + location: snapshot.data[index], + ), + )); + }); + } else { + return Center( + child: CircularProgressIndicator( + color: grayColor, + ), + ); + } + }, + ), + )) + ], + ), + ), + ); + } +} diff --git a/Sources/justMUSIC/lib/services/GeoApi.dart b/Sources/justMUSIC/lib/services/GeoApi.dart new file mode 100644 index 0000000..7f203bc --- /dev/null +++ b/Sources/justMUSIC/lib/services/GeoApi.dart @@ -0,0 +1,60 @@ +import 'package:geolocator/geolocator.dart'; +import 'package:http/http.dart' as http; +import 'dart:convert'; + +import 'package:tuple/tuple.dart'; + +class GeoApi { + final String apiKey = "85a2724ad38b3994c2b7ebe1d239bbff"; + Future>?> getNearbyCities() async { + try { + LocationPermission permission = await Geolocator.checkPermission(); + bool serviceEnabled; + + // Test if location services are enabled. + serviceEnabled = await Geolocator.isLocationServiceEnabled(); + if (!serviceEnabled) { + // Location services are not enabled don't continue + // accessing the position and request users of the + // App to enable the location services. + return Future.error('Location services are disabled.'); + } + + permission = await Geolocator.checkPermission(); + if (permission == LocationPermission.denied) { + permission = await Geolocator.requestPermission(); + if (permission == LocationPermission.denied) { + return Future.error('Location permissions are denied'); + } + } + + if (permission == LocationPermission.deniedForever) { + return Future.error( + 'Location permissions are permanently denied, we cannot request permissions.'); + } + + Position position = await Geolocator.getCurrentPosition( + desiredAccuracy: LocationAccuracy.high); + String apiUrl = + 'http://api.openweathermap.org/data/2.5/find?lat=${position.latitude}&lon=${position.longitude}&cnt=10&appid=$apiKey'; + var response = await http.get(Uri.parse(apiUrl)); + if (response.statusCode == 200) { + var data = json.decode(response.body); + List cities = data['list']; + List> cityInfo = cities.map((city) { + String cityName = city['name'] as String; + String countryName = city['sys']['country'] as String; + return Tuple2(cityName, countryName); + }).toList(); + return cityInfo; + } else { + print('Failed to fetch data'); + } + } catch (e) { + print('Error: $e'); + } + return null; + } +} + +class Tuple {} diff --git a/Sources/justMUSIC/pubspec.lock b/Sources/justMUSIC/pubspec.lock index dfbd0a1..7f77bef 100644 --- a/Sources/justMUSIC/pubspec.lock +++ b/Sources/justMUSIC/pubspec.lock @@ -272,6 +272,54 @@ packages: url: "https://pub.dev" source: hosted version: "9.2.0" + geolocator: + dependency: "direct main" + description: + name: geolocator + sha256: "5c23f3613f50586c0bbb2b8f970240ae66b3bd992088cf60dd5ee2e6f7dde3a8" + url: "https://pub.dev" + source: hosted + version: "9.0.2" + geolocator_android: + dependency: transitive + description: + name: geolocator_android + sha256: b06c72853c993ae533f482d81a12805d7a441f5231d9668718bc7131d7464082 + url: "https://pub.dev" + source: hosted + version: "4.2.0" + geolocator_apple: + dependency: transitive + description: + name: geolocator_apple + sha256: "36527c555f4c425f7d8fa8c7c07d67b78e3ff7590d40448051959e1860c1cfb4" + url: "https://pub.dev" + source: hosted + version: "2.2.7" + geolocator_platform_interface: + dependency: transitive + description: + name: geolocator_platform_interface + sha256: af4d69231452f9620718588f41acc4cb58312368716bfff2e92e770b46ce6386 + url: "https://pub.dev" + source: hosted + version: "4.0.7" + geolocator_web: + dependency: transitive + description: + name: geolocator_web + sha256: f68a122da48fcfff68bbc9846bb0b74ef651afe84a1b1f6ec20939de4d6860e1 + url: "https://pub.dev" + source: hosted + version: "2.1.6" + geolocator_windows: + dependency: transitive + description: + name: geolocator_windows + sha256: f5911c88e23f48b598dd506c7c19eff0e001645bdc03bb6fecb9f4549208354d + url: "https://pub.dev" + source: hosted + version: "0.1.1" google_fonts: dependency: "direct main" description: @@ -621,6 +669,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.0" + tuple: + dependency: "direct main" + description: + name: tuple + sha256: a97ce2013f240b2f3807bcbaf218765b6f301c3eff91092bcfa23a039e7dd151 + url: "https://pub.dev" + source: hosted + version: "2.0.2" typed_data: dependency: transitive description: diff --git a/Sources/justMUSIC/pubspec.yaml b/Sources/justMUSIC/pubspec.yaml index 67c874a..4afb32a 100644 --- a/Sources/justMUSIC/pubspec.yaml +++ b/Sources/justMUSIC/pubspec.yaml @@ -58,6 +58,8 @@ dependencies: pinch_zoom: ^1.0.0 smooth_list_view: ^1.0.4 animated_appear: ^0.0.4 + geolocator: ^9.0.2 + tuple: ^2.0.2 dev_dependencies: flutter_test: