Fakultas Ilmu Komputer UI

Commit fefbfb6a authored by Dzaky Noor Hasyim's avatar Dzaky Noor Hasyim
Browse files

Merge branch 'PBI-14-agenda_kegiatan' into 'development'

Pbi 14 agenda kegiatan

See merge request !81
parents 92d38a6a b1d7597c
Pipeline #82554 passed with stages
in 15 minutes and 56 seconds
3.5.2:
3.6.0:
- Tersedia nama dan foto profil pada drawer
- Tersedia daftar agenda kegiatan
- Pengguna dapat mengganti foto profil
- Pengguna dapat mengubah informasi kegiatan yang sudah ada
......@@ -11,10 +12,4 @@
- Perbaikan bugs
3.2.0:
- Tersedia fitur membagikan informasi fasilitas/kegiatan disabilitas kepada orang lain
3.1.2:
- Perbaikan masuk dengan Google
- Tersedia fitur pencarian layanan
- Tersedia pilihan kamera ketika menambahkan gambar fasilitas
- Tersedia fitur riwayat pencarian
- Tersedia fitur membagikan informasi fasilitas/kegiatan disabilitas kepada orang lain
\ No newline at end of file
import 'dart:async';
import 'package:bisaGo/network/data/network_model.dart';
import 'package:bisaGo/repository/kegiatan_repository.dart';
import 'package:get_it/get_it.dart';
class KegiatanSearchBloc {
StreamController _recentSearchController;
KegiatanRepository _kegiatanRepository;
StreamController _kegiatanSearchListController;
StreamController<NetworkModel<List<dynamic>>> get recentSearchSink =>
_recentSearchController.sink;
Stream<NetworkModel<List<dynamic>>> get recentSearchStream =>
_recentSearchController.stream;
StreamSink<NetworkModel<List<dynamic>>> get kegiatanSearchListSink =>
_kegiatanSearchListController.sink;
Stream<NetworkModel<List<dynamic>>> get layananListStream =>
_kegiatanSearchListController.stream;
KegiatanSearchBloc() {
_kegiatanSearchListController =
StreamController<NetworkModel<List<dynamic>>>();
_recentSearchController =
StreamController<NetworkModel<List<dynamic>>>();
_kegiatanRepository = GetIt.instance.get<BaseKegiatanRepository>();
}
Future<void> fetchKegiatanSearchList(String search, String sortConfig) async {
kegiatanSearchListSink.add(NetworkModel.loading('Getting Kegiatan'));
try {
final kegiatanSearchListResponse = await _kegiatanRepository.fetchKegiatanSearch(search, sortConfig);
kegiatanSearchListSink.add(NetworkModel.completed(kegiatanSearchListResponse));
} catch (e) {
kegiatanSearchListSink.add(NetworkModel.error(e.toString()));
}
}
void dispose() {
_recentSearchController?.close();
_kegiatanSearchListController?.close();
}
}
\ No newline at end of file
......@@ -42,6 +42,15 @@ class LokasiResponseBloc {
}
}
Future<Lokasi> fetchLokasiListById(String placeId) async {
lokasiListSink.add(NetworkModel.loading('Getting Location by Id'));
try {
return await _lokasiRepository.fetchLokasiById(placeId);
} catch (e) {
return Lokasi();
}
}
Future<void> fetchRecentSearch() async {
recentSearchSink.add(NetworkModel.loading('Getting Recent Search'));
try {
......
import 'dart:convert';
import 'package:bisaGo/model/user.dart';
import 'package:bisaGo/page/informasi/list_sekolah.dart';
import 'package:bisaGo/page/informasi/list_kegiatan.dart';
import 'package:bisaGo/page/profile/profile.dart';
import 'package:bisaGo/page/tentang_disabilitas/tentang_disabilitas.dart';
import 'package:flutter/material.dart';
......@@ -18,7 +18,8 @@ class BisaGoDrawer extends StatelessWidget {
final List<Map<String, dynamic>> drawerList = [
{'title': 'Beranda', 'icon': Icons.home},
{'title': 'Riwayat Pencarian', 'icon': Icons.history},
{'title': 'Layanan Disabilitas', 'icon': Icons.group},
// {'title': 'Layanan Disabilitas', 'icon': Icons.group},
{'title': 'Agenda Kegiatan', 'icon': Icons.calendar_today},
{'title': 'Tentang Disabilitas', 'icon': Icons.accessible},
{'title': 'Tentang Aplikasi', 'icon': Icons.info},
{'title': 'Login', 'icon': Icons.keyboard_backspace}
......@@ -263,8 +264,10 @@ class BisaGoDrawer extends StatelessWidget {
route = MaterialPageRoute(builder: (_) => const AboutUs());
} else if (page == 'Tentang Disabilitas') {
route = MaterialPageRoute(builder: (_) => TentangDisabilitas());
} else if (page == 'Layanan Disabilitas') {
route = MaterialPageRoute(builder: (_) => ListSekolah());
// } else if (page == 'Layanan Disabilitas') {
// route = MaterialPageRoute(builder: (_) => ListSekolah());
} else if (page == 'Agenda Kegiatan') {
route = MaterialPageRoute(builder: (_) => ListKegiatan());
}
Navigator.of(context).push(route);
}
......
......@@ -105,6 +105,12 @@ const zonaWaktu = [
'WIT',
];
const sortBy = {
'time':'Kegiatan Terdekat',
'name':'Nama Kegiatan',
'latest-added':'Kegiatan Terbaru'
};
String getTag(String tag) {
return tags[tag];
}
......@@ -56,4 +56,4 @@ class KegiatanModel {
factory KegiatanModel.fromJson(Map<String, dynamic> json) =>
_$KegiatanModelFromJson(json);
Map<String, dynamic> toJson() => _$KegiatanModelToJson(this);
}
}
\ No newline at end of file
......@@ -157,6 +157,7 @@ class _DetailPostKegiatanPageState extends State<DetailPostKegiatanPage> {
enlargeCenterPage: true,
enableInfiniteScroll: false,
initialPage: 0,
autoPlay: true),
items: widget.kegiatan.images
.map((item) => Container(
......@@ -517,8 +518,7 @@ class _DetailPostKegiatanPageState extends State<DetailPostKegiatanPage> {
),
),
],
),
),
),)
);
}
......
import 'package:bisaGo/bloc/kegiatan_search_bloc.dart';
import 'package:bisaGo/component/bisago_drawer.dart';
import 'package:bisaGo/config/styles.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:bisaGo/model/kegiatan.dart';
import 'package:bisaGo/bloc/lokasi_response_bloc.dart';
import 'package:bisaGo/network/data/network_model.dart';
import 'package:bisaGo/page/filter_fasilitas/postingan/detail_post_kegiatan.dart';
import 'package:bisaGo/config/strings.dart';
import 'package:bisaGo/utils/custom_dropdown_sort_kegiatan.dart';
import 'package:bisaGo/utils/validator.dart';
import 'package:intl/intl.dart';
class ListKegiatan extends StatefulWidget {
const ListKegiatan({Key key}) : super(key: key);
@override
_ListKegiatanState createState() => _ListKegiatanState();
}
class _ListKegiatanState extends State<ListKegiatan> {
/// Search Icon for textFormField
Icon searchIcon = const Icon(Icons.search);
KegiatanSearchBloc kegiatan_search_bloc = KegiatanSearchBloc();
LokasiResponseBloc lokasi_bloc = LokasiResponseBloc();
@override void initState() {
_sortConfig = 'time';
kegiatan_search_bloc.fetchKegiatanSearchList(_text,_sortConfig);
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
drawer: BisaGoDrawer(),
appBar: AppBar(
backgroundColor: greenPrimary,
leading: IconButton(
icon: const Icon(Icons.arrow_back_ios, size: 25),
key: const Key('Back Icon Key'),
onPressed: () => Navigator.of(context).pop()),
title: Container(
margin: const EdgeInsets.only(top: doubleSpace, bottom: doubleSpace),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: doubleBorderRadius,
boxShadow: regularShadow),
child: TextFormField(
onFieldSubmitted: (text) {
_text = text;
kegiatan_search_bloc.fetchKegiatanSearchList(_text,_sortConfig);
},
key: const Key('Text Field Cari Kegiatan'),
decoration: InputDecoration(
prefixIcon: const Icon(
Icons.search,
color: greenPrimary,
size: 25,
),
hintText: 'Cari kegiatan di sini',
),
),
),
),
body: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
const Padding(
padding: EdgeInsets.only(
top: doubleSpace, left: doubleSpace, right: doubleSpace),
child: Text(
'Agenda Kegiatan' ?? '',
style: TextStyle(
fontSize: 25,
fontFamily: 'Muli',
fontWeight: FontWeight.w800),
),
),
Container(
child: CustomDropdown(
title: 'Urutkan berdasarkan:',
required: true,
key: const Key('Dropdown Sort Kegiatan'),
validator: FieldValidator.validateDropdown,
dropdownList: sortBy,
hint: 'Kegiatan Terdekat', // hardcoded
onChanged: (value) {
setState(() {
_sortConfig=value;
});
kegiatan_search_bloc.fetchKegiatanSearchList(_text,_sortConfig);
},
),
),
//BLoC for Kegiatan Search
StreamBuilder<NetworkModel<List<dynamic>>>(
stream: kegiatan_search_bloc.layananListStream,
builder: (context, snapshot) {
if (snapshot.hasData) {
switch (snapshot.data.status) {
case Status.loading:
return const Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(greenPrimary),
),
);
break;
case Status.completed:
var layanan = snapshot.data.data;
return Column(
children: layanan.map<Widget>((each) {
return makeKegiatanWidget('kegiatan', each);
}).toList(),
);
break;
case Status.error:
return Center(child: Container(
padding: const EdgeInsets.only(top: tripleSpace),
child: Text('Tidak ada kegaitan ditemukan',
style: const TextStyle(
fontSize: 18,
color: Colors.black,
fontFamily: 'Muli',
),
textAlign: TextAlign.center,),),);
break;
}
}
return Container();
},
),
],
),
),
);
}
Widget makeKegiatanWidget(String key, KegiatanModel kegiatan) {
return InkWell(
key: Key('$key-${kegiatan.id}'),
onTap: () {
_navigateToDetailKegiatanPage(context, kegiatan);
},
child: Container(
decoration: BoxDecoration(
color: Colors.transparent,
border: Border(bottom: BorderSide(color: Colors.grey[400]))),
margin: const EdgeInsets.only(left: doubleSpace, right: doubleSpace),
height: 90,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
const CircleAvatar(
backgroundColor: greenPrimary,
child: Icon(
Icons.school,
color: Colors.white,
),
),
Expanded(
child: Container(
padding: const EdgeInsets.all(doubleSpace),
child: Row(
//crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Flexible(
child:Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
kegiatan.namaKegiatan,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800,
color: Colors.black,
fontFamily: 'Muli',
),
),
Text(
kegiatan.penyelenggara,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.left,
style: const TextStyle(
fontSize: 15,
//fontWeight: FontWeight.w800,
color: Colors.black,
fontFamily: 'Muli',
),
),
]
),
),
Text(
DateFormat('dd-MM-yyyy\nhh:mm').format(kegiatan.timeStart),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.right,
style: const TextStyle(
fontSize: 15,
color: Colors.black,
fontFamily: 'Muli',
),
),
],
),
),
),
Icon(
Icons.arrow_forward_ios,
color: Colors.grey[400],
size: 20,
)
],
),
),
);
}
void _navigateToDetailKegiatanPage(
BuildContext context, KegiatanModel kegiatan) async {
final lokasi = await lokasi_bloc.fetchLokasiListById(kegiatan.placeId);
final route = MaterialPageRoute(
builder: (_) => DetailPostKegiatanPage(
lokasi: lokasi,
kegiatan: kegiatan));
await Navigator.of(context).push(route);
}
String getTanggalFormatting(KegiatanModel kegiatan) {
return null;
}
String _text;
String _sortConfig;
}
\ No newline at end of file
......@@ -91,7 +91,7 @@ class _ListSekolahState extends State<ListSekolah> {
padding: EdgeInsets.only(
top: tripleSpace, left: doubleSpace, right: doubleSpace),
child: Text(
'Jenis Layanan',
'Agenda Kegiatan',
style: TextStyle(
fontSize: 25,
fontFamily: 'Muli',
......
......@@ -363,6 +363,7 @@ class _ProfileState extends State<Profile> {
}
Color _getFontColor(String fieldName, {Color seenColor = seenColor}) {
if (user == null) return seenColor;
if (user.canSeeHiddenFields) return seenColor;
final hiddenFields = user.hiddenFields;
if (hiddenFields.contains(fieldName)) {
......
......@@ -9,6 +9,7 @@ abstract class BaseKegiatanRepository {
Future<dynamic> updateKegiatan(
Map<String, dynamic> newKegiatanData, String placeId, int kegiatanId);
Future<KegiatanModel> fetchDetailKegiatan(String placeId, int kegiatanId);
Future<dynamic> fetchKegiatanSearch(String search, String sortConfig);
}
class KegiatanRepository implements BaseKegiatanRepository {
......@@ -30,6 +31,20 @@ class KegiatanRepository implements BaseKegiatanRepository {
return KegiatanList(allKegiatan);
}
@override
Future<dynamic> fetchKegiatanSearch(String search, String sortConfig) async {
var url = '/informasi-fasilitas/lokasi/list-kegiatan-by-$sortConfig';
if(search != null && search!=''){
url += '/$search';
}
final response = await _network.get(url: url, isLogin: false);
var list = [];
for (var each in response.entries.toList()) {
list.add(KegiatanModel.fromJson(each.value));
}
return list; // KegiatanSearchList
}
@override
Future<List<String>> fetchImages(String placeId, int id) async {
final url = '/informasi-fasilitas/lokasi/list-foto-kegiatan/$placeId/$id';
......
......@@ -11,6 +11,8 @@ import 'package:http/http.dart' as http;
abstract class BaseLokasiRepository {
Future<LokasiListResponse> fetchLokasi();
Future<Lokasi> fetchLokasiById(String placeId);
Future<LokasiListResponse> fetchRecentSearch();
Future<void> saveRecentSearch(Lokasi recentSearch);
......@@ -65,6 +67,32 @@ class LokasiRepository implements BaseLokasiRepository {
return LokasiListResponse(freqPlaces);
}
@override
Future<Lokasi> fetchLokasiById(String placeId) async {
final _places = GoogleMapsPlaces(apiKey: DotEnv().env['API_KEY']);
var details = await _places.getDetailsByPlaceId(
placeId,
fields: [
'name',
'formatted_address',
'formatted_phone_number',
'photos'
],
);
final result = details.result;
final lokasi = Lokasi()
..placeId = result.placeId
..name = result.name ?? 'INVALID'
..alamat = result.formattedAddress ?? 'LOCATION INVALID'
..noTelp = result.formattedPhoneNumber ?? '-';
final image = fetchPhoto(details.result.photos[0].photoReference, 480, 480) ?? '';
lokasi.image = image;
return lokasi;
}
@override
Future<LokasiListResponse> fetchRecentSearch() async {
final response = await CookiesInterface().getSearchHistory();
......
import 'package:bisaGo/config/styles.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class CustomDropdown extends StatefulWidget {
const CustomDropdown({
this.title,
this.hint,
this.required = false,
this.key,
this.validator,
this.onSaved,
this.dropdownList,
this.onChanged,
this.value,
}) : super(key: key);
final String title;
final String value;
@override
final Key key;
final FormFieldSetter<String> onSaved;
final String hint;
final bool required;
final FormFieldValidator<String> validator;
final Map<String, String> dropdownList;
final Function onChanged;
@override
_CustomDropdownState createState() => _CustomDropdownState();
}
class _CustomDropdownState extends State<CustomDropdown> {
final List<DropdownMenuItem<String>> _dropdownMenuItems = [];
void loadDropdownList() {
for (final key in widget.dropdownList.keys) {
_dropdownMenuItems.add(DropdownMenuItem(
key: Key('Dropdown Item ${widget.dropdownList[key]}'),
value: key,
child: Text(widget.dropdownList[key]),
));
}
}
void setDefaultValue() {
}
@override
void initState() {
super.initState();
loadDropdownList();
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.only(
top: tripleSpace,
left: doubleSpace,
right: doubleSpace,),
child: Row(
//crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
RichText(
key: Key(widget.title),
text: TextSpan(
text: widget.title,
style: TextStyle(
fontSize: widget.title == '' ? 0 : 15,
color: Colors.black,
fontFamily: 'Muli'),
),
),
// Container width and fontSize are bounded, re-adjust required after any change(s)
Container(
width: 170,
child:DropdownButtonFormField(
isExpanded: true,
onSaved: widget.onSaved,
validator: widget.validator,
style: const TextStyle(
color: Colors.black,
fontSize: 15,
fontFamily: 'Muli',
fontWeight: FontWeight.w800,
),
decoration: InputDecoration.collapsed(
hintStyle:
const TextStyle(
fontSize: 15,
fontFamily: 'Muli',
fontWeight: FontWeight.w800,),
hintText: widget.hint,
),
value: widget.value,
items: _dropdownMenuItems,
onChanged: widget.onChanged,
),),
],
),
);
}
}