2021.09 - 2022.02
Flutter Dart
<aside> <img src="/icons/arrow-southeast_gray.svg" alt="/icons/arrow-southeast_gray.svg" width="40px" /> 목록으로 돌아가기
</aside>
<aside> <img src="/icons/phone_gray.svg" alt="/icons/phone_gray.svg" width="40px" /> 01. ShopMain
확실한 사업자만 검토하기 위해 전화번호로 로그인만 제공하였습니다.
APM HTTP 서버에서 배너사진, 특정 공지등을 get해서 화면에 출력했습니다. 또한 firebase messaging을 사용하여 운영팀에서 프로모션 알림을 보낼수있게 만들었습니다.
아래 사진에는 없는 내 가게 출장지역 설정을 통해 내 가게의 위치를 설정하고 출장갈수있는 반경 거리를 설정할수있었습니다. 이를 토대로 유저에게 가게가 노출되었습니다.
사용 Package
내 가게 출장지역 설정 부분 코드
![]()
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:spoon_partner/utility.dart';
import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:google_maps_webservice/places.dart';
import 'package:google_api_headers/google_api_headers.dart';
import 'package:geolocator/geolocator.dart';
import 'package:flutter_google_places_hoc081098/flutter_google_places_hoc081098.dart';
const kGoogleApiKey = 'api key';
class ShopWorkingSetting extends StatefulWidget {
final String session;
ShopWorkingSetting({required this.session});
@override
_ShopWorkingSettingState createState() => _ShopWorkingSettingState();
}
class _ShopWorkingSettingState extends State<ShopWorkingSetting> {
double lat = 36.119485;
double lon = 128.3445734;
double rad = 0;
Mode _mode = Mode.overlay;
double _currentSliderValue = 0;
late GoogleMapController mapController;
final Completer<GoogleMapController> _controller = Completer();
@override
Widget build(BuildContext context) {
return Scaffold(
resizeToAvoidBottomInset: false,
appBar: AppBar(
elevation: 0,
title: Text(
"내 가게 출장지역 설정",
style: TextStyle(fontWeight: FontWeight.bold),
),
backgroundColor: Colors.teal[300],
centerTitle: true,
),
body: Center(
child: Column(
children: [
Expanded(
child: Stack(
children: [
GoogleMap(
initialCameraPosition: CameraPosition(
target: LatLng(lat, lon),
zoom: 12,
),
onCameraMove: _onCameraMove,
myLocationButtonEnabled: false,
onMapCreated: (GoogleMapController controller) {
_controller.complete(controller);
},
circles: Set.from([
Circle(
circleId: CircleId('currentCircle'),
center: LatLng(lat, lon),
radius: rad,
fillColor: Colors.blue.shade200.withOpacity(0.5),
strokeColor: Colors.blue.shade200.withOpacity(0.1),
),
]),
),
Positioned(
top: (MediaQuery.of(context).size.height -
AppBar().preferredSize.height -
MediaQuery.of(context).padding.top -
80) /
2,
right: (MediaQuery.of(context).size.width - 40) / 2,
child: Icon(
Icons.location_on,
size: 40,
color: Colors.red,
),
),
Positioned(
bottom: 20,
right: 20,
child: Column(
children: [
FloatingActionButton(
child: Icon(
Icons.search,
size: 30,
),
backgroundColor: Colors.teal[400],
onPressed: _handlePressButton,
),
SizedBox(height: 10),
FloatingActionButton(
child: Icon(
Icons.my_location,
size: 30,
),
backgroundColor: Colors.teal[400],
onPressed: citycheck,
),
],
),
),
Positioned(
bottom: 40,
right: (MediaQuery.of(context).size.width - 180) / 2,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 200,
height: 40,
color: Colors.white,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("반경"),
Container(
width: 160,
child: Slider(
value: _currentSliderValue,
min: 0,
max: 30,
divisions: 60,
label: "반경 ${_currentSliderValue.toStringAsFixed(1)}km",
onChanged: (double value) {
setState(() {
_currentSliderValue = value;
rad = 1000 * _currentSliderValue;
});
},
),
),
],
),
),
SizedBox(height: 10),
Container(
height: 60,
width: 200,
child: RaisedButton(
color: Colors.white,
child: Text(
"이 위치로 설정하기",
style: TextStyle(
color: Colors.grey[900],
fontSize: 18,
fontWeight: FontWeight.bold,
),
),
onPressed: shopworkingpost,
),
)
],
),
),
],
),
),
],
),
),
);
}
Future<void> citycheck() async {
try {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
lat = position.latitude;
lon = position.longitude;
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newLatLngZoom(LatLng(lat, lon), 14));
} catch (e) {
showSnackbar(context, "인터넷 연결을 확인해 주십시오.\\n위치 정보 권한을 허용해 주십시오.");
}
}
Future<void> _handlePressButton() async {
try {
final p = await PlacesAutocomplete.show(
context: context,
apiKey: kGoogleApiKey,
onError: (response) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(response.errorMessage ?? 'Unknown error'),
),
);
},
mode: _mode,
language: 'kr',
components: [Component(Component.country, 'kr')],
logo: SizedBox(height: 0.1),
);
await displayPrediction(p, ScaffoldMessenger.of(context));
} catch (e) {
showSnackbar(context, "오류가 발생했습니다. 인터넷 연결을 확인해주세요.");
}
}
Future<void> displayPrediction(
Prediction? p, ScaffoldMessengerState messengerState) async {
if (p == null) return;
try {
final _places = GoogleMapsPlaces(
apiKey: kGoogleApiKey,
apiHeaders: await GoogleApiHeaders().getHeaders(),
);
final detail = await _places.getDetailsByPlaceId(p.placeId!);
final geometry = detail.result.geometry!;
final lat = geometry.location.lat;
final lon = geometry.location.lng;
setState(() {
this.lat = lat;
this.lon = lon;
});
final GoogleMapController controller = await _controller.future;
controller.animateCamera(CameraUpdate.newLatLngZoom(LatLng(lat, lon), 14));
} catch (e) {
showSnackbar(context, "오류가 발생했습니다. 인터넷 연결을 확인해주세요.");
}
}
void _onCameraMove(CameraPosition position) {
setState(() {
lat = position.target.latitude;
lon = position.target.longitude;
});
}
Future<void> shopworkingpost() async {
if (_currentSliderValue < 0 || _currentSliderValue > 30) {
showSnackbar(context, "오류가 발생했습니다. 다시 시도해주세요.");
return;
}
try {
final response = await http.post(
Uri.parse("<https://geunsujeo.com/partner/shopworkingpost.php>"),
body: {
"session": widget.session,
"lat": '$lat',
"lon": '$lon',
"distance": '$_currentSliderValue',
},
);
final jsondata = jsonDecode(response.body);
if (jsondata['status'] == "success") {
showSnackbar(context, "완료 되었습니다.");
Navigator.pop(context);
} else {
handleErrorResponse(jsondata['status']);
}
} catch (e) {
showSnackbar(context, "오류가 발생했습니다. 인터넷 연결을 확인해주세요.");
}
}
void handleErrorResponse(String status) {
if (status == "text_error") {
showSnackbar(context, "오류가 발생했습니다. 정확하게 입력했는지 확인해주세요.");
} else if (status == "login_error") {
showSnackbar(context, "로그인 에러가 발생하였습니다. 로그아웃 후 로그인하여 다시 시도해주세요.");
} else if (status == "enter_error") {
showSnackbar(context, "가게가 입점되어있지 않습니다. 입점 후 다시 시도해주세요.");
} else {
showSnackbar(context, "알 수 없는 오류가 발생했습니다. 관리자에게 문의해주세요.");
}
}
}
</aside>
<aside> <img src="/icons/phone_gray.svg" alt="/icons/phone_gray.svg" width="40px" /> 02. ShopSetting
내 가게 기본정보를 수정할수있습니다.
처음에 patner측에서 버튼을 누르면 http 서버에서 세션을 확인 후 가게정보를 반환하여 각 칸에 채워넣습니다. 이후 정보를 수정하면 세션과 데이터를 서버로 POST 하여 서버측에서 값을 확인 후 DB 정보가 수정됩니다.
사용 Package
![]()
</aside>
<aside> <img src="/icons/phone_gray.svg" alt="/icons/phone_gray.svg" width="40px" /> 03. ShopItem view, insert, edit+remove
가게 상품에 대한 정보를 입력, 수정, 제거하는 기능이 있습니다. 새로운 상품을 가게에 추가하려면 오른쪽 하단 '+' 버튼을 사용하면 됩니다. 이미 등록된 상품의 정보를 변경하려면 상품을 눌러 '수정' 및 ‘제거 ’기능을 사용할수있습니다.
가게 상품을 입력,수정,제거할때는 token을 서버측에서 재확인하였습니다.
사용 Package
![]()
![]()
</aside>