search location is live !🎉🎉🎉

LOCATION_SEARCH_LDE
Lucas Delanier 2 years ago
parent f238c1cf0f
commit ddda7ab779

@ -5,4 +5,5 @@
to allow setting breakpoints, to provide hot reload, etc. to allow setting breakpoints, to provide hot reload, etc.
--> -->
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
</manifest> </manifest>

@ -1,6 +1,7 @@
import 'package:flutter/Material.dart'; import 'package:flutter/Material.dart';
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:ionicons/ionicons.dart'; import 'package:ionicons/ionicons.dart';
import 'package:tuple/tuple.dart';
import '../values/constants.dart'; import '../values/constants.dart';
@ -15,54 +16,68 @@ class PhotoPostComponent extends StatelessWidget {
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: postbutton, borderRadius: BorderRadius.circular(8)), color: postbutton, borderRadius: BorderRadius.circular(8)),
child: Row( child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.symmetric(horizontal: 10),
mainAxisAlignment: MainAxisAlignment.center, child: Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Icon( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Ionicons.camera, children: [
size: 15, Icon(
color: Colors.white, Ionicons.camera,
), size: 15,
SizedBox( color: Colors.white,
width: 10, ),
), SizedBox(
Text( width: 10,
'Prendre un selfie', ),
style: GoogleFonts.plusJakartaSans( Expanded(
color: Colors.white, fontSize: 12), child: Text(
) 'Prendre un selfie',
]), style: GoogleFonts.plusJakartaSans(
) color: Colors.white, fontSize: 12),
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
)
]),
))
: Container( : Container(
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: fillButton, borderRadius: BorderRadius.circular(8)), color: fillButton, borderRadius: BorderRadius.circular(8)),
child: Row( child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.symmetric(horizontal: 10),
mainAxisAlignment: MainAxisAlignment.center, child: Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Text( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
"Selfie enregistré", children: [
style: GoogleFonts.plusJakartaSans( Expanded(
color: Colors.white, fontSize: 12), child: Text(
), "Selfie enregistré",
SizedBox( style: GoogleFonts.plusJakartaSans(
width: 10, color: Colors.white, fontSize: 12),
), overflow: TextOverflow.ellipsis,
Icon( maxLines: 1,
Icons.close, ),
size: 12, ),
color: Colors.white, SizedBox(
), width: 10,
]), ),
); Icon(
Icons.close,
size: 12,
color: Colors.white,
),
]),
));
} }
} }
class LocationPostComponent extends StatelessWidget { class LocationPostComponent extends StatelessWidget {
final bool empty; final bool empty;
const LocationPostComponent({Key? key, required this.empty}) final Tuple2<String, String>? location;
const LocationPostComponent(
{Key? key, required this.empty, required this.location})
: super(key: key); : super(key: key);
@override @override
@ -73,48 +88,59 @@ class LocationPostComponent extends StatelessWidget {
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: postbutton, borderRadius: BorderRadius.circular(8)), color: postbutton, borderRadius: BorderRadius.circular(8)),
child: Row( child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.symmetric(horizontal: 10),
mainAxisAlignment: MainAxisAlignment.center, child: Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Icon( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
Icons.location_on, children: [
size: 15, Icon(
color: Colors.white, Icons.location_on,
), size: 15,
SizedBox( color: Colors.white,
width: 10, ),
), SizedBox(
Text( width: 10,
'Ajouter un lieu', ),
style: GoogleFonts.plusJakartaSans( Expanded(
color: Colors.white, fontSize: 12), child: Text(
) 'Ajouter un lieu',
]), style: GoogleFonts.plusJakartaSans(
color: Colors.white, fontSize: 12),
overflow: TextOverflow.ellipsis,
),
)
])),
) )
: Container( : Container(
width: double.infinity, width: double.infinity,
padding: EdgeInsets.all(10), padding: EdgeInsets.all(10),
decoration: BoxDecoration( decoration: BoxDecoration(
color: fillButton, borderRadius: BorderRadius.circular(8)), color: fillButton, borderRadius: BorderRadius.circular(8)),
child: Row( child: Padding(
crossAxisAlignment: CrossAxisAlignment.center, padding: const EdgeInsets.symmetric(horizontal: 10),
mainAxisAlignment: MainAxisAlignment.center, child: Row(
children: [ crossAxisAlignment: CrossAxisAlignment.center,
Text( mainAxisAlignment: MainAxisAlignment.spaceEvenly,
"Lieu enregistré", children: [
style: GoogleFonts.plusJakartaSans( Expanded(
color: Colors.white, fontSize: 12), child: Text(
), '${location?.item1}, ${location?.item2}',
SizedBox( style: GoogleFonts.plusJakartaSans(
width: 10, color: Colors.white, fontSize: 12),
), overflow: TextOverflow.ellipsis,
Icon( ),
Icons.close, ),
size: 12, SizedBox(
color: Colors.white, width: 10,
), ),
]), Icon(
Icons.close,
size: 12,
color: Colors.white,
),
]),
),
); );
} }
} }

@ -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<String, String> 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),
),
])),
),
],
),
),
);
}
}

@ -11,8 +11,10 @@ import 'package:image_picker/image_picker.dart';
import 'package:insta_image_viewer/insta_image_viewer.dart'; import 'package:insta_image_viewer/insta_image_viewer.dart';
import 'package:justmusic/values/constants.dart'; import 'package:justmusic/values/constants.dart';
import 'package:text_scroll/text_scroll.dart'; import 'package:text_scroll/text_scroll.dart';
import 'package:tuple/tuple.dart';
import '../model/Music.dart'; import '../model/Music.dart';
import '../screens/search_location_screen.dart';
import 'buttonPostComponent.dart'; import 'buttonPostComponent.dart';
class EditablePostComponent extends StatefulWidget { class EditablePostComponent extends StatefulWidget {
@ -28,7 +30,27 @@ class _EditablePostComponentState extends State<EditablePostComponent>
final ImagePicker picker = ImagePicker(); final ImagePicker picker = ImagePicker();
late Animation<double> animation; late Animation<double> animation;
late AnimationController animationController; late AnimationController animationController;
late AnimationController _controller;
File? image; File? image;
Tuple2<String, String>? 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 { Future pickImage() async {
try { try {
@ -43,18 +65,34 @@ class _EditablePostComponentState extends State<EditablePostComponent>
} }
} }
@override void _selectLocation(Tuple2<String, String> location) {
void initState() { Navigator.pop(context);
animationController = AnimationController( setState(() {
vsync: this, selectedCity = location;
duration: Duration(milliseconds: 400), });
); }
animation = CurvedAnimation(
parent: animationController, void searchLocation() {
curve: Curves.easeInOutSine, 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 @override
@ -219,8 +257,14 @@ class _EditablePostComponentState extends State<EditablePostComponent>
), ),
Expanded( Expanded(
flex: 5, flex: 5,
child: LocationPostComponent( child: GestureDetector(
empty: true, onTap: () {
manageLocation();
},
child: LocationPostComponent(
empty: selectedCity == null,
location: selectedCity,
),
), ),
), ),
], ],
@ -280,4 +324,14 @@ class _EditablePostComponentState extends State<EditablePostComponent>
pickImage(); pickImage();
} }
} }
void manageLocation() {
if (selectedCity != null) {
setState(() {
selectedCity = null;
});
} else {
searchLocation();
}
}
} }

@ -3,6 +3,7 @@ import 'package:flutter/Material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:justmusic/components/back_button.dart'; import 'package:justmusic/components/back_button.dart';
import 'package:justmusic/screens/search_song_screen.dart'; import 'package:justmusic/screens/search_song_screen.dart';
import 'package:tuple/tuple.dart';
import '../components/editable_post_component.dart'; import '../components/editable_post_component.dart';
import '../components/post_button_component.dart'; import '../components/post_button_component.dart';
import '../model/Music.dart'; import '../model/Music.dart';
@ -21,6 +22,7 @@ class _PostScreenState extends State<PostScreen>
late AnimationController _controller; late AnimationController _controller;
Music? selectedMusic; Music? selectedMusic;
Tuple2<String, String>? selectedCity;
@override @override
void initState() { void initState() {
@ -39,7 +41,7 @@ class _PostScreenState extends State<PostScreen>
}); });
} }
void openDetailPost() { void openSearchSong() {
showModalBottomSheet( showModalBottomSheet(
transitionAnimationController: _controller, transitionAnimationController: _controller,
barrierColor: Colors.black.withOpacity(0.7), barrierColor: Colors.black.withOpacity(0.7),
@ -68,11 +70,11 @@ class _PostScreenState extends State<PostScreen>
resizeToAvoidBottomInset: true, resizeToAvoidBottomInset: true,
backgroundColor: bgColor, backgroundColor: bgColor,
extendBodyBehindAppBar: true, extendBodyBehindAppBar: true,
appBar: PreferredSize( appBar: const PreferredSize(
preferredSize: Size(double.infinity, 80), preferredSize: Size(double.infinity, 80),
child: SafeArea( child: SafeArea(
child: Padding( child: Padding(
padding: const EdgeInsets.only( padding: EdgeInsets.only(
left: defaultPadding, left: defaultPadding,
right: defaultPadding, right: defaultPadding,
top: defaultPadding), top: defaultPadding),
@ -104,7 +106,7 @@ class _PostScreenState extends State<PostScreen>
height: 100.h, height: 100.h,
), ),
GestureDetector( GestureDetector(
onTap: openDetailPost, onTap: openSearchSong,
child: EditablePostComponent(music: selectedMusic)), child: EditablePostComponent(music: selectedMusic)),
SizedBox( SizedBox(
height: 40.h, height: 40.h,

@ -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<SearchCityScreen> createState() => _SearchCityScreenState();
}
class _SearchCityScreenState extends State<SearchCityScreen> {
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<dynamic> 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,
),
);
}
},
),
))
],
),
),
);
}
}

@ -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<List<Tuple2<String, String>>?> 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<dynamic> cities = data['list'];
List<Tuple2<String, String>> 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 {}

@ -272,6 +272,54 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "9.2.0" 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: google_fonts:
dependency: "direct main" dependency: "direct main"
description: description:
@ -621,6 +669,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "3.1.0" 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: typed_data:
dependency: transitive dependency: transitive
description: description:

@ -58,6 +58,8 @@ dependencies:
pinch_zoom: ^1.0.0 pinch_zoom: ^1.0.0
smooth_list_view: ^1.0.4 smooth_list_view: ^1.0.4
animated_appear: ^0.0.4 animated_appear: ^0.0.4
geolocator: ^9.0.2
tuple: ^2.0.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

Loading…
Cancel
Save