Fakultas Ilmu Komputer UI

Commit 2336d428 authored by Muhammad Ariq Basyar's avatar Muhammad Ariq Basyar
Browse files

Merge branch 'PBI-13-notifikasi' into dev-ariq

parents b9ccf68b 942e7942
......@@ -83,7 +83,9 @@ dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
implementation 'com.google.firebase:firebase-analytics:17.2.2'
implementation 'com.google.firebase:firebase-analytics:'
implementation 'com.google.firebase:firebase-messaging:'
implementation 'com.google.firebase:firebase-bom:27.0.0'
}
apply plugin: 'com.google.gms.google-services'
\ No newline at end of file
......@@ -33,6 +33,10 @@
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="FLUTTER_NOTIFICATION_CLICK"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
......
import 'package:dropdown_banner/dropdown_banner.dart';
import 'package:flutter/material.dart';
import 'package:bisaGo/config/styles.dart';
import 'package:bisaGo/page/dashboard/dashboard.dart';
......@@ -7,6 +8,8 @@ class BisaGo extends StatelessWidget {
@override
Widget build(BuildContext context) {
final navigatorKey = GlobalKey<NavigatorState>();
return MaterialApp(
title: 'bisaGo',
theme: ThemeData(
......@@ -14,7 +17,10 @@ class BisaGo extends StatelessWidget {
primaryColor: greenPrimary,
backgroundColor: Colors.white,
),
home: Dashboard(),
home: DropdownBanner(
navigatorKey: navigatorKey,
child: Dashboard(),
),
);
}
}
import 'package:bisaGo/repository/cloud_messaging_repository.dart';
import 'package:get_it/get_it.dart';
import 'package:http/http.dart';
class CloudMessagingBloc {
CloudMessagingRepository _cloudMessagingRepository;
CloudMessagingBloc() {
_cloudMessagingRepository =
GetIt.instance.get<BaseCloudMessagingRepository>();
}
Future<dynamic> sendFCMToken(
String fcmToken,
String token,
) async {
try {
return await _cloudMessagingRepository.sendFCMToken(
fcmToken,
token,
);
} catch (e) {
return Response('Failed to add komentar', 400);
}
}
}
import 'package:bisaGo/repository/cloud_messaging_repository.dart';
import 'package:bisaGo/repository/kegiatan_repository.dart';
import 'package:bisaGo/repository/kegiatan_terdekat_repository.dart';
import 'package:bisaGo/repository/komentar_posting_kegiatan_repository.dart';
......@@ -26,8 +27,8 @@ class AppGetIt {
() => KomentarPostingRepository());
_getIt.registerLazySingleton<BaseKomentarPostingKegiatanRepository>(
() => KomentarPostingKegiatanRepository());
_getIt.registerLazySingleton<BaseLokasiRepository>(
() => LokasiRepository());
_getIt
.registerLazySingleton<BaseLokasiRepository>(() => LokasiRepository());
_getIt.registerLazySingleton<BaseLayananRepository>(
() => LayananRepository());
_getIt.registerLazySingleton<BaseKegiatanRepository>(
......@@ -36,5 +37,7 @@ class AppGetIt {
() => KegiatanTerdekatRepository());
_getIt.registerLazySingleton<BaseDynamicLinksServiceRepository>(
() => DynamicLinksServiceRepository());
_getIt.registerLazySingleton<BaseCloudMessagingRepository>(
() => CloudMessagingRepository());
}
}
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:bisaGo/app.dart';
import 'package:intl/date_symbol_data_local.dart';
......@@ -7,11 +9,20 @@ import 'flavor/flavor.dart';
import 'globalnetwork.dart';
import 'package:bisaGo/get_it.dart';
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
// If you're going to use other Firebase services in the background, such as Firestore,
// make sure you call `initializeApp` before using other Firebase services.
await Firebase.initializeApp();
print('Handling a background message: ${message.messageId}');
}
Future main() async {
AppGetIt().initialize();
await DotEnv().load('.env');
getDioInstance('build');
await initializeDateFormatting('id_ID', null);
FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler);
Intl.defaultLocale = 'id_ID';
dio.options.receiveTimeout = 15000;
ApiFlavor.flavor = BuildFlavor.development.toString();
......
import 'package:bisaGo/bloc/cloud_messaging_bloc.dart';
import 'package:bisaGo/bloc/lokasi_response_bloc.dart';
import 'package:bisaGo/model/komentar.dart';
import 'package:bisaGo/model/lokasi.dart';
......@@ -11,7 +12,10 @@ import 'package:bisaGo/page/filter_fasilitas/postingan/detail_post.dart';
import 'package:bisaGo/repository/komentar_repository.dart';
import 'package:bisaGo/utils/custom_dashboard_location_button.dart';
import 'package:bisaGo/utils/location_turn_on_dialog.dart';
import 'package:dropdown_banner/dropdown_banner.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_dynamic_links/firebase_dynamic_links.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:geolocator/geolocator.dart';
......@@ -21,6 +25,9 @@ import 'package:bisaGo/component/bisago_drawer.dart';
import 'package:bisaGo/config/styles.dart';
import 'package:bisaGo/page/pencarian/pencarian.dart';
import 'package:google_maps_webservice/places.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../filter_fasilitas/postingan/detail_post.dart';
class Dashboard extends StatefulWidget {
const Dashboard({Key key}) : super(key: key);
......@@ -37,6 +44,13 @@ class DashboardState extends State<Dashboard> {
LokasiResponseBloc bloc = LokasiResponseBloc();
KegiatanTerdekatBloc blocKegiatanTerdekat = KegiatanTerdekatBloc();
CloudMessagingBloc cloudMessagingBloc = CloudMessagingBloc();
FirebaseMessaging _firebaseMessaging;
bool _initialized = false;
DateTime _lastNotification;
@override
void initState() {
......@@ -45,6 +59,70 @@ class DashboardState extends State<Dashboard> {
geolocator = Geolocator()..forceAndroidLocationManager;
initDynamicLinks();
setInitialLocation();
if (!_initialized) {
_initialized = true;
_setupFirebase();
}
}
void _setupFirebase() async {
await Firebase.initializeApp();
_firebaseMessaging = FirebaseMessaging.instance;
FirebaseMessaging.onMessage.listen((RemoteMessage message) {
final now = DateTime.now();
if (_lastNotification != null &&
_lastNotification.add(Duration(seconds: 10)).isAfter(now)) return;
_lastNotification = now;
final data = message.data;
final String msg = data['message'];
DropdownBanner.showBanner(
text: msg,
color: Color(0xFF003566),
textStyle: TextStyle(
color: Colors.white,
height: 2.1,
),
duration: Duration(seconds: 8),
tapCallback: () {
if (data['type'] == 'fasilitas') {
final String placeId = data['place_id'];
final id = int.parse(data['id']);
_navigateToDetailFasilitasPage(context, placeId, id);
} else if (data['type'] == 'kegiatan') {
final String placeId = data['place_id'];
final id = int.parse(data['id']);
_navigateToDetailKegiatanPage(context, placeId, id);
}
});
});
FirebaseMessaging.onMessageOpenedApp.listen((message) {
final data = message.data;
if (data['type'] == 'fasilitas') {
final String placeId = data['place_id'];
final id = int.parse(data['id']);
_navigateToDetailFasilitasPage(context, placeId, id);
} else if (data['type'] == 'kegiatan') {
final String placeId = data['place_id'];
final id = int.parse(data['id']);
_navigateToDetailKegiatanPage(context, placeId, id);
}
});
_requestFCMToken();
}
void _requestFCMToken() async {
final fcmToken = await _firebaseMessaging.getToken();
final sharedPreferences = await SharedPreferences.getInstance();
final token = sharedPreferences.getString('token');
if (token != null) {
await cloudMessagingBloc.sendFCMToken(fcmToken, token);
}
}
void _navigateToPencarianPage(BuildContext context) {
......@@ -368,20 +446,20 @@ class DashboardState extends State<Dashboard> {
..name = namaLokasi;
final fasilitasRoute = MaterialPageRoute(
builder: (BuildContext context) => DetailPostKegiatanPage(
lokasi: lokasi,
kegiatan: KegiatanModel(
id: kegiatan.id,
placeId: lokasi.placeId,
creator: kegiatan.creator,
namaKegiatan: kegiatan.namaKegiatan,
penyelenggara: kegiatan.penyelenggara,
narahubung: kegiatan.narahubung,
deskripsi: kegiatan.deskripsi,
timeStart: kegiatan.timeStart,
timeEnd: kegiatan.timeEnd,
image: kegiatan.image,
),
));
lokasi: lokasi,
kegiatan: KegiatanModel(
id: kegiatan.id,
placeId: lokasi.placeId,
creator: kegiatan.creator,
namaKegiatan: kegiatan.namaKegiatan,
penyelenggara: kegiatan.penyelenggara,
narahubung: kegiatan.narahubung,
deskripsi: kegiatan.deskripsi,
timeStart: kegiatan.timeStart,
timeEnd: kegiatan.timeEnd,
image: kegiatan.image,
),
));
await Navigator.of(context).push(fasilitasRoute);
}
......
......@@ -17,29 +17,27 @@ class Kegiatan extends StatefulWidget {
}
class _KegiatanState extends State<Kegiatan> {
@override
Widget build(BuildContext context) {
return InkWell(
key: Key(widget.kegiatan.namaKegiatan),
onTap: () {
Navigator.of(context).pushReplacement(MaterialPageRoute(
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DetailPostKegiatanPage(
lokasi: widget.lokasi,
kegiatan: KegiatanModel(
id: widget.kegiatan.id,
placeId: widget.kegiatan.placeId,
creator: widget.kegiatan.creator,
namaKegiatan: widget.kegiatan.namaKegiatan,
penyelenggara: widget.kegiatan.penyelenggara,
narahubung: widget.kegiatan.narahubung,
deskripsi: widget.kegiatan.deskripsi,
timeStart: widget.kegiatan.timeStart,
timeEnd: widget.kegiatan.timeEnd,
image: widget.kegiatan.image,
),
)
));
lokasi: widget.lokasi,
kegiatan: KegiatanModel(
id: widget.kegiatan.id,
placeId: widget.kegiatan.placeId,
creator: widget.kegiatan.creator,
namaKegiatan: widget.kegiatan.namaKegiatan,
penyelenggara: widget.kegiatan.penyelenggara,
narahubung: widget.kegiatan.narahubung,
deskripsi: widget.kegiatan.deskripsi,
timeStart: widget.kegiatan.timeStart,
timeEnd: widget.kegiatan.timeEnd,
image: widget.kegiatan.image,
),
)));
},
child: Container(
margin: const EdgeInsets.only(bottom: regularSpace),
......@@ -72,59 +70,68 @@ class _KegiatanState extends State<Kegiatan> {
),
),
Container(
margin: const EdgeInsets.only(bottom: regularSpace),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: 160,
child: CarouselSlider(
options: CarouselOptions(
aspectRatio: 1.0,
enlargeCenterPage: true,
enableInfiniteScroll: false,
initialPage: 0,
autoPlay: true,
),
items: widget.kegiatan.image.map((item) => Container(
child: Container(
child: ClipRRect(
borderRadius: BorderRadius.all(Radius.circular(20)),
child: Stack(
children: <Widget>[
Image.network(item, fit: BoxFit.cover, width: 1000.0),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
margin: const EdgeInsets.only(bottom: regularSpace),
child: SizedBox(
width: MediaQuery.of(context).size.width,
height: 160,
child: CarouselSlider(
options: CarouselOptions(
aspectRatio: 1.0,
enlargeCenterPage: true,
enableInfiniteScroll: false,
initialPage: 0,
autoPlay: true,
),
items: widget.kegiatan.image
.map((item) => Container(
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromARGB(200, 0, 0, 0),
Color.fromARGB(0, 0, 0, 0)
],
begin: Alignment.bottomCenter,
end: Alignment.topCenter,
),
),
padding: EdgeInsets.symmetric(vertical: 10.0, horizontal: 20.0),
child: Text(
'#${widget.kegiatan.image.indexOf(item)+1}',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.bold,
),
),
child: ClipRRect(
borderRadius: BorderRadius.all(
Radius.circular(20)),
child: Stack(
children: <Widget>[
Image.network(item,
fit: BoxFit.cover,
width: 1000.0),
Positioned(
bottom: 0.0,
left: 0.0,
right: 0.0,
child: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromARGB(
200, 0, 0, 0),
Color.fromARGB(
0, 0, 0, 0)
],
begin: Alignment
.bottomCenter,
end: Alignment.topCenter,
),
),
padding: EdgeInsets.symmetric(
vertical: 10.0,
horizontal: 20.0),
child: Text(
'#${widget.kegiatan.image.indexOf(item) + 1}',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight:
FontWeight.bold,
),
),
),
),
],
)),
),
),
],
)
),
),
)).toList(),
),
)
),
))
.toList(),
),
)),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
......@@ -148,5 +155,5 @@ class _KegiatanState extends State<Kegiatan> {
],
),
));
}
}
\ No newline at end of file
}
}
......@@ -37,7 +37,7 @@ class _KomentarState extends State<Komentar> {
return InkWell(
key: Key('Fasilitas ${fasilitas[widget.komentar.tag]}'),
onTap: () {
Navigator.of(context).pushReplacement(MaterialPageRoute(
Navigator.of(context).push(MaterialPageRoute(
builder: (BuildContext context) => DetailPostPage(
lokasi: widget.lokasi,
komentar: KomentarModel(
......
import 'dart:convert';
import 'package:http/http.dart' as http;
import 'package:bisaGo/flavor/flavor.dart';
abstract class BaseCloudMessagingRepository {
Future<void> sendFCMToken(String fcmToken, String token);
}
class CloudMessagingRepository implements BaseCloudMessagingRepository {
@override
Future<bool> sendFCMToken(String fcmToken, String token) async {
try {
await http.post(
'${ApiFlavor.getBaseUrl()}/notification/',
headers: {
'Authorization': 'token $token',
'content-type': 'application/json'
},
body: json.encode({
'token': fcmToken,
'type': 'android',
}),
);
return true;
} catch (_) {
return false;
}
}
}
......@@ -55,7 +55,9 @@ dependencies:
firebase_core: ^0.7.0
firebase_core_platform_interface: ^3.0.1
firebase_dynamic_links: ^0.7.0+1
firebase_messaging: ^8.0.0-dev.15
carousel_slider: ^3.0.0
dropdown_banner: ^1.4.0
dev_dependencies:
flutter_test:
......
import 'package:bisaGo/repository/cloud_messaging_repository.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:get_it/get_it.dart';
import 'package:mockito/mockito.dart';
class MockCloudMessagingRepository extends Fake
implements CloudMessagingRepository {
@override
Future<bool> sendFCMToken(String fcmToken, String token) async {
return Future.value(true);
}
}
void main() {
setUpAll(() {
final _getIt = GetIt.instance;
_getIt.registerLazySingleton<BaseCloudMessagingRepository>(
() => MockCloudMessagingRepository());
});
testWidgets('Generate fcm token', (WidgetTester tester) async {
final result =
await MockCloudMessagingRepository().sendFCMToken('fcmToken', 'token');
expect(result, true);
});
}
import 'package:bisaGo/model/kegiatan.dart';
import 'package:bisaGo/model/lokasi.dart';
import 'package:bisaGo/page/dashboard/dashboard.dart';
import 'package:bisaGo/repository/cloud_messaging_repository.dart';
import 'package:bisaGo/repository/kegiatan_terdekat_repository.dart';
import 'package:bisaGo/repository/lokasi_repository.dart';
import 'package:flutter/material.dart';
......@@ -41,6 +42,14 @@ class MockLokasi extends Fake implements LokasiRepository {
}
}
class MockCloudMessagingRepository extends Fake
implements CloudMessagingRepository {
@override
Future<bool> sendFCMToken(String fcmToken, String token) async {
return Future.value(true);
}
}
void main() {
// final mockLokasi = {
// 'name': 'Margo City',
......@@ -68,6 +77,8 @@ void main() {
_getIt.registerLazySingleton<BaseKegiatanTerdekatRepository>(
() => MockKegiatanTerdekat());
_getIt.registerLazySingleton<BaseLokasiRepository>(() => MockLokasi());
_getIt.registerLazySingleton<BaseCloudMessagingRepository>(
() => MockCloudMessagingRepository());
});
testWidgets('Detail Post Kegiatan Page - Positive Test',
......
import 'package:bisaGo/model/kegiatan.dart';
import 'package:bisaGo/repository/cloud_messaging_repository.dart';
import 'package:bisaGo/repository/kegiatan_terdekat_repository.dart';
import 'package:bisaGo/repository/lokasi_repository.dart';
import 'package:flutter/material.dart';
......@@ -45,6 +46,14 @@ class MockKegiatanTerdekatRepository extends Fake
}
}
class MockCloudMessagingRepository extends Fake
implements CloudMessagingRepository {
@override
Future<bool> sendFCMToken(String fcmToken, String token) async {
return Future.value(true);
}
}
void main() {
group('Dashboard navigation tests', () {
NavigatorObserver mockObserver;
......@@ -74,6 +83,8 @@ void main() {
() => MockLokasiRepository());
_getIt.registerLazySingleton<BaseKegiatanTerdekatRepository>(
() => MockKegiatanTerdekatRepository());
_getIt.registerLazySingleton<BaseCloudMessagingRepository>(
() => MockCloudMessagingRepository());
});
Future<Null> _buildDashboardPage(WidgetTester tester) async {
......
......@@ -2,6 +2,7 @@ import 'package:bisaGo/model/kegiatan.dart';
import 'package:bisaGo/model/komunitas.dart';
import 'package:bisaGo/model/lokasi.dart';
import 'package:bisaGo/model/sekolah.dart';
import 'package:bisaGo/repository/cloud_messaging_repository.dart';
import 'package:bisaGo/repository/kegiatan_terdekat_repository.dart';
import 'package:bisaGo/repository/komunitas_repository.dart';
import 'package:bisaGo/repository/lokasi_repository.dart';
......@@ -88,6 +89,14 @@ class MockKegiatanTerdekatRepository extends Fake
}
}
class MockCloudMessagingRepository extends Fake
implements CloudMessagingRepository {
@override
Future<bool> sendFCMToken(String fcmToken, String token) async {
return Future.value(true);
}
}