Fakultas Ilmu Komputer UI

Commit 7a80068e authored by Fakhira Devina's avatar Fakhira Devina
Browse files

Merge branch 'PBI-2-pencarian_lokasi' into 'staging'

Pencarian Lokasi Feature

See merge request !31
parents 4a31db15 05100f0f
Pipeline #38644 failed with stages
in 27 minutes and 12 seconds
{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_maps_flutter","path":"D:\\\\Flutter\\\\flutter_windows_v1.9.1+hotfix.2-stable\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_maps_flutter-0.5.24+1\\\\","dependencies":[]},{"name":"location","path":"D:\\\\Flutter\\\\flutter_windows_v1.9.1+hotfix.2-stable\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location-2.5.3\\\\","dependencies":[]}],"android":[{"name":"flutter_plugin_android_lifecycle","path":"D:\\\\Flutter\\\\flutter_windows_v1.9.1+hotfix.2-stable\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\flutter_plugin_android_lifecycle-1.0.6\\\\","dependencies":[]},{"name":"google_maps_flutter","path":"D:\\\\Flutter\\\\flutter_windows_v1.9.1+hotfix.2-stable\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\google_maps_flutter-0.5.24+1\\\\","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"location","path":"D:\\\\Flutter\\\\flutter_windows_v1.9.1+hotfix.2-stable\\\\flutter\\\\.pub-cache\\\\hosted\\\\pub.dartlang.org\\\\location-2.5.3\\\\","dependencies":[]}],"macos":[],"linux":[],"windows":[],"web":[]},"dependencyGraph":[{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"google_maps_flutter","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"location","dependencies":[]}],"date_created":"2020-03-25 22:29:07.715265","version":"1.15.17"}
\ No newline at end of file
{"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"google_maps_flutter","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"location","dependencies":[]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]}]}
\ No newline at end of file
......@@ -289,3 +289,5 @@ modules.xml
# End of https://www.gitignore.io/api/linux,django,python,pycharm+all
tests.output
.flutter-plugins-dependencies
......@@ -22,8 +22,8 @@ Lint:
Test:
stage: test
script:
- flutter test --machine > tests.output
- flutter test --coverage
- flutter test --machine > tests.output
- lcov --summary coverage/lcov.info
- genhtml coverage/lcov.info --output=coverage
artifacts:
......
......@@ -50,4 +50,17 @@ MAPS_API_KEY=Bu***************
Run the app using the development flavor
```bash
flutter run -t lib/main_dev.dart
```
\ No newline at end of file
```
## Building Models with JsonSerializable
Jadi abis get dari API, jsonnya di map ke models biar rapih.
1. Tulis ada field apa aja dari jsonnya (bisa liat contoh yang di models/lokasi.dart)
2. bagian 'part of {nama models}.g.dart' itu harus ditulis di model yg mau dibuat. di awal emang merah, tapi biarin aja
3. kalo semua field udah di tulis, run
```bash
flutter pub run build_runner build
```
4. nanti akan ke build file {nama models}.g.dart, yang di nomor 2 merah harusnya udah gak merah lagi
## Passing Data with BLoC
Udah ada contohnya di /bloc (implementasi di screen nya ada di page/pencarian/pencarian.dart)
Bisa baca [disini]https://itnext.io/flutter-handling-your-network-api-calls-like-a-boss-936eef296547 sebagai panduannya
\ No newline at end of file
import 'dart:async';
import 'package:ppl_disabilitas/model/lokasi.dart';
import 'package:ppl_disabilitas/network/data/network_model.dart';
import 'package:ppl_disabilitas/repository/LokasiRepository.dart';
class LokasiResponseBloc {
StreamController _recentSearchController;
LokasiRepository _lokasiRepository;
StreamController _lokasiListController;
StreamSink<NetworkModel<LokasiListResponse>> get recentSearchSink =>
_recentSearchController.sink;
Stream<NetworkModel<LokasiListResponse>> get recentSearchStream =>
_recentSearchController.stream;
StreamSink<NetworkModel<LokasiListResponse>> get lokasiListSink =>
_lokasiListController.sink;
Stream<NetworkModel<LokasiListResponse>> get lokasiListStream =>
_lokasiListController.stream;
LokasiResponseBloc() {
_lokasiListController =
StreamController<NetworkModel<LokasiListResponse>>();
_recentSearchController = StreamController<NetworkModel<LokasiListResponse>>();
_lokasiRepository = LokasiRepository();
fetchLokasiList();
fetchRecentSearch();
}
fetchLokasiList() async {
lokasiListSink.add(NetworkModel.loading('Getting Locations'));
try {
LokasiListResponse lokasiListResponse =
await _lokasiRepository.fetchLokasi();
lokasiListSink.add(NetworkModel.completed(lokasiListResponse));
} catch (e) {
lokasiListSink.add(NetworkModel.error(e.toString()));
}
}
fetchRecentSearch() async {
recentSearchSink.add(NetworkModel.loading('Getting Recent Search'));
try {
LokasiListResponse recentSearchData = await _lokasiRepository.fetchRecentSearch();
recentSearchSink.add(NetworkModel.completed(recentSearchData));
} catch (e) {
recentSearchSink.add(NetworkModel.error(e.toString()));
}
}
saveRecentSearch(Lokasi search) async {
await _lokasiRepository.saveRecentSearch(search);
}
dispose() {
_recentSearchController?.close();
_lokasiListController?.close();
}
}
......@@ -5,12 +5,12 @@ final String devBaseURL = "poipole.herokuapp.com";
final String baseURL = "poipole.herokuapp.com";
String key = "";
String csrf = "";
String sessionID = "";
String sessionId = "";
setKey(String key) {
key = key;
setKey(String newKey) {
key = newKey;
}
setSessionId(String sessionId) {
sessionID = sessionId;
setSessionId(String newSessionId) {
sessionId = newSessionId;
}
\ No newline at end of file
import 'package:json_annotation/json_annotation.dart';
part 'lokasi.g.dart';
@JsonSerializable()
class LokasiListResponse {
List<Lokasi> listLokasi;
LokasiListResponse();
factory LokasiListResponse.fromJson(List json) => _$LokasiListResponseFromJson(json);
Map<String, dynamic> toJson() => _$LokasiListResponseToJson(this);
}
@JsonSerializable(nullable: true)
class Lokasi {
String nama;
double latitude;
double longitude;
String alamat;
String foto;
String telp;
Lokasi();
factory Lokasi.fromJson(Map<String, dynamic> json) => _$LokasiFromJson(json);
Map<String, dynamic> toJson() => _$LokasiToJson(this);
}
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'lokasi.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
LokasiListResponse _$LokasiListResponseFromJson(List json) {
return LokasiListResponse()
..listLokasi = json
?.map((e) =>
e == null ? null : Lokasi.fromJson(e as Map<String, dynamic>))
?.toList();
}
Map<String, dynamic> _$LokasiListResponseToJson(LokasiListResponse instance) =>
<String, dynamic>{
'listLokasi': instance.listLokasi,
};
Lokasi _$LokasiFromJson(Map<String, dynamic> json) {
return Lokasi()
..nama = json['nama'] as String
..latitude = (json['latitude'] as num)?.toDouble()
..longitude = (json['longitude'] as num)?.toDouble()
..alamat = json['alamat'] as String
..foto = json['foto'] as String
..telp = json['telp'] as String;
}
Map<String, dynamic> _$LokasiToJson(Lokasi instance) => <String, dynamic>{
'nama': instance.nama,
'latitude': instance.latitude,
'longitude': instance.longitude,
'alamat': instance.alamat,
'foto': instance.foto,
'telp': instance.telp,
};
class CustomException implements Exception {
final _message;
final _prefix;
CustomException([this._message, this._prefix]);
String toString() {
return "$_prefix$_message";
}
}
class FetchDataException extends CustomException {
FetchDataException([String message])
: super(message, "Error During Communication: ");
}
class BadRequestException extends CustomException {
BadRequestException([message]) : super(message, "Invalid Request: ");
}
class UnauthorisedException extends CustomException {
UnauthorisedException([message]) : super(message, "Unauthorised: ");
}
class InvalidInputException extends CustomException {
InvalidInputException([String message]) : super(message, "Invalid Input: ");
}
\ No newline at end of file
import 'dart:convert';
import 'dart:io';
import 'package:path_provider/path_provider.dart';
import 'package:ppl_disabilitas/config/strings.dart';
class CookiesInterface {
Future<bool> checkCookieFileAvailability({String fileName}) async {
Directory dir;
await getApplicationDocumentsDirectory().then((Directory directory) {
dir = directory;
});
File cookieFile = File("${dir.path}/$fileName.json");
bool cookiesExist = cookieFile.existsSync();
return cookiesExist;
}
Future<File> createSignInCookie({
Map<String, dynamic> responseHeaders}) async {
try {
String setCookie;
String csrfToken;
String sessionId;
String userKey;
List<String> cookiesList;
Directory dir;
await getApplicationDocumentsDirectory().then((Directory directory) {
dir = directory;
});
File cookieFile = File("${dir.path}/usercookies.json");
cookieFile.createSync();
setCookie = responseHeaders["set-cookie"];
if (setCookie != null) {
csrfToken = setCookie.split(";")[0].split("=")[1];
sessionId = setCookie.split(";")[4].split(",")[1].split("=")[1];
userKey = key;
}
cookiesList = <String>[
csrfToken,
sessionId,
userKey,
];
cookieFile.writeAsStringSync(json.encode(cookiesList));
return cookieFile;
} on Exception catch (e) {
print(e.toString());
rethrow;
}
}
Future<File> createSearchHistoryCookie({
Map<String, dynamic> recentSearch}) async {
print("recent searrch $recentSearch");
Directory dir;
List currentSearchHistory;
try {
await getApplicationDocumentsDirectory().then((Directory directory) {
dir = directory;
});
File cookieFile = File(dir.path + "/searchhistory.json");
cookieFile.createSync();
await checkCookieFileAvailability(fileName: "searchhistory").then((available) async {
if (available) {
await getCookieFile(fileName: "searchhistory").then((cookie) {
bool test = cookie == null;
print("cookie is null? $test");
if (cookie == null) {
currentSearchHistory = [];
} else {
currentSearchHistory = json.decode(cookie);
}
currentSearchHistory.insert(0, recentSearch);
});
} else {
currentSearchHistory = [];
}
await cookieFile.writeAsString(json.encode(currentSearchHistory));
});
return cookieFile;
} on Exception catch (e) {
print(e.toString());
rethrow;
}
}
Future<dynamic> getCookieFile({String fileName}) async {
Directory dir;
await getApplicationDocumentsDirectory().then((Directory directory) {
dir = directory;
});
File file = File("${dir.path}/$fileName.json");
dynamic res = file.readAsStringSync();
return res;
}
}
class NetworkModel<T> {
Status status;
T data;
String message;
NetworkModel.loading(this.message) : status = Status.LOADING;
NetworkModel.completed(this.data) : status = Status.COMPLETED;
NetworkModel.error(this.message) : status = Status.ERROR;
@override
String toString() {
return "Status : $status \n Message : $message \n Data : $data";
}
}
enum Status { LOADING, COMPLETED, ERROR }
import 'dart:convert';
import 'package:ppl_disabilitas/network/CustomException.dart';
import 'package:http/http.dart' as http;
import 'dart:io';
class NetworkInterface {
//String key = KEY;
// POST request
Future<dynamic> post({
String url, //url nya apa
dynamic bodyParams, //data apa yang mau dikasih
bool isLogin, //dia login apa ngga
}) async {
var responseJson;
Map<String, String> headersJson =
await _buildRequestHeader(isLogin); //butuh header apa ngga
try {
final response = await http.post(
"$url",
body: json.encode(bodyParams),
headers: headersJson,
);
responseJson = _response(response);
} on SocketException {
throw FetchDataException("No Internet Connection");
}
return responseJson;
}
// GET request
Future<dynamic> get({
String url,
bool isLogin,
}) async {
var responseJson;
Map<String, dynamic> headersJson = await _buildRequestHeader(isLogin);
try {
final response = await http.get(
"$url",
headers: headersJson,
);
responseJson = _response(response);
} on SocketException {
throw FetchDataException("No Internet Connection");
}
return responseJson;
}
// buildRequestHeader: untuk nentuin pake header apa aja berdasarkan login apa ngga
Future<Map<String, dynamic>> _buildRequestHeader(bool isLogin) async {
Map<String, String> headers = Map<String, String>();
headers.putIfAbsent("Content-Type", () => "application/json");
//if (isLogin) {
//List<dynamic> cookieFile = await CookiesInterface().getCookieFile(
// fileName:
// "userCookies"); //ngambil data dari yg udh disimpen di cookie
//print("cookieFile list --> ${cookieFile.toString()}");
//print("check key here >>> $key");
//setKey(cookieFile[2]);
//key = cookieFile[2];
//headers.putIfAbsent(
// "Authorization",
// () =>
// 'Token $key'); //ini kalau authorization nya ngga ada baru taro token nya
//headers.putIfAbsent("X-CSRFToken", () => cookieFile[0]); //csrf token
//headers.putIfAbsent(
// "Cookie",
// () =>
// "csrftoken=${cookieFile[0]};sessionid=${cookieFile[1]}"); //cookie file
//print("headers --> ${headers}");
//}
return headers;
}
dynamic _response(http.Response response) {
switch (response.statusCode) {
case 200:
var responseJson = json.decode(response.body.toString());
return responseJson;
case 400:
throw BadRequestException(response.body.toString());
case 401:
case 403:
throw UnauthorisedException(response.body.toString());
case 500:
default:
throw FetchDataException(
'Error occured while Communication with Server with status : ${response.statusCode}');
}
}
}
......@@ -46,6 +46,7 @@ class DashboardState extends State<Dashboard> {
}
void enableLocationService() async {
await location.changeSettings(accuracy: LocationAccuracy.HIGH);
_serviceEnabled = await location.serviceEnabled();
if (!_serviceEnabled) {
_serviceEnabled = await location.requestService();
......@@ -72,7 +73,7 @@ class DashboardState extends State<Dashboard> {
Widget build(BuildContext context) {
return Scaffold(
drawer: BisaGoDrawer(),
body: Stack(children: <Widget>[
body: Stack(key: Key("Stack"),children: <Widget>[
_buildGoogleMap(context),
InkWell(
key: Key("Navigate to Pencarian"),
......
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:ppl_disabilitas/bloc/LokasiResponseBloc.dart';
import 'package:ppl_disabilitas/config/styles.dart';
import 'package:ppl_disabilitas/model/lokasi.dart';
import 'package:ppl_disabilitas/network/data/network_model.dart';
/// Create Pencarian page widget with a state
class Pencarian extends StatefulWidget {
@override
PencarianState createState() => PencarianState();
}
/// State of Pencacrian page
class PencarianState extends State<Pencarian> {
/// Controller for textFormField
TextEditingController myController = TextEditingController();
/// Search Icon for textFormField
Icon searchIcon = Icon(Icons.search);
Widget appBarText = Text("Pencarian Lokasi");
/// Text for appbar
Widget appBarText = Text('Pencarian Lokasi');
/// List of places currently searched on the textFormField
List<Lokasi> currentSearch = [];
/// List for places from API
List<Lokasi> places = [];
/// BLoC for pencarian
LokasiResponseBloc _bloc = LokasiResponseBloc();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(DiagnosticsProperty<List>('places', places))
..add(DiagnosticsProperty<List>('currentSearch', currentSearch));
}
@override
void initState() {
myController.addListener(() {
List<Lokasi> tempList = [];
for (int i = 0; i < places.length; i++) {
if (myController.text.isNotEmpty) {
if (places[i]
.nama
.toLowerCase()
.contains(myController.text.toLowerCase())) {
tempList.add(places[i]);
}
}
}
setState(() {
currentSearch = tempList;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
......@@ -17,7 +67,7 @@ class PencarianState extends State<Pencarian> {
backgroundColor: greenPrimary,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, size: 25),
key: Key("Back Icon Key"),
key: Key('Back Icon Key'),
onPressed: () => Navigator.pop(context, 'Take me back')),
title: Container(
margin: EdgeInsets.only(top: doubleSpace, bottom: doubleSpace),
......@@ -26,7 +76,8 @@ class PencarianState extends State<Pencarian> {
borderRadius: doubleBorderRadius,
boxShadow: regularShadow),
child: TextFormField(
key: Key("Text Field Mau Kemana"),
controller: myController,
key: Key('Text Field Mau Kemana'),
decoration: InputDecoration(
contentPadding: EdgeInsets.all(0),
isDense: false,
......@@ -44,127 +95,170 @@ class PencarianState extends State<Pencarian> {
fontFamily: 'Muli',
fontWeight: FontWeight.w700),
suffixIcon: IconButton(
icon: Icon(
Icons.mic,
color: greenPrimary,
size: 25,
),
onPressed: () {})),
icon: Icon(
Icons.mic,
color: greenPrimary,
size: 25,
),
onPressed: () {},
)),
),
),
),
body: ListView(
padding: const EdgeInsets.all(8),
body: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(left: doubleSpace, top: 10),
child: Text(
'Hasil Pencarian',
style: TextStyle(
fontSize: 15,
color: Colors.black,
fontFamily: 'Muli',
),
),
),
Container(
height: 90,
color: Colors.transparent,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
CircleAvatar(
backgroundColor: greenPrimary,
child: Text('Test'),
),
Padding(
padding: EdgeInsets.all(doubleSpace),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'Margo City',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800,
color: Colors.black,
fontFamily: 'Muli',
),
),
Text(
'Jl. Margonda Raya No.358, Kemir...',
style: TextStyle(
fontSize: 15,