diff --git a/app/build.gradle b/app/build.gradle index b227441..4bd256b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -45,6 +45,7 @@ dependencies { implementation 'com.google.firebase:firebase-storage:20.0.1' implementation 'com.google.android.gms:play-services-maps:18.1.0' implementation 'com.google.firebase:firebase-database:20.0.4' + implementation 'com.google.firebase:firebase-messaging:23.0.3' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a91a979..f0df2d0 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,12 +3,14 @@ xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..3e42340 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/com/capstone/foodify/shipper/API/FirebaseMessagingAPI.java b/app/src/main/java/com/capstone/foodify/shipper/API/FirebaseMessagingAPI.java new file mode 100644 index 0000000..5c05892 --- /dev/null +++ b/app/src/main/java/com/capstone/foodify/shipper/API/FirebaseMessagingAPI.java @@ -0,0 +1,28 @@ +package com.capstone.foodify.shipper.API; + +import com.capstone.foodify.shipper.Model.FirebaseMessaging.FirebaseMessaging; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; + +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; +import retrofit2.http.Body; +import retrofit2.http.Headers; +import retrofit2.http.POST; + +public interface FirebaseMessagingAPI { + Gson gson = new GsonBuilder().setDateFormat("HH:mm:ss dd-MM-yyyy").setLenient().create(); + + FirebaseMessagingAPI apiService = new Retrofit.Builder() + .baseUrl("https://fcm.googleapis.com/").addConverterFactory(GsonConverterFactory.create(gson)) + .build() + .create(FirebaseMessagingAPI.class); + + @Headers({ + "Content-Type: application/json", + "Authorization: Bearer AAAAMcAdgF0:APA91bE9OPI9SBgvFV_8KijtnWEQ5nx4PyOhrY61u8BRv5xKnPzhiqCqcLvz4WVYSgVNLHWjUiOJBaxhIiwhwB6YsAPXDn1aNfbKx-q8FvypzW7lmiKC8vOxFpYUAh8YaItk4Vf-eZ_F" + }) + @POST("fcm/send") + Call sendNotification(@Body FirebaseMessaging content); +} diff --git a/app/src/main/java/com/capstone/foodify/shipper/API/FoodApi.java b/app/src/main/java/com/capstone/foodify/shipper/API/FoodApi.java index f142d45..85415ab 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/API/FoodApi.java +++ b/app/src/main/java/com/capstone/foodify/shipper/API/FoodApi.java @@ -1,5 +1,6 @@ package com.capstone.foodify.shipper.API; +import com.capstone.foodify.shipper.Common; import com.capstone.foodify.shipper.Model.Shipper; import com.google.gson.Gson; import com.google.gson.GsonBuilder; @@ -15,7 +16,7 @@ public interface FoodApi { Gson gson = new GsonBuilder().setDateFormat("HH:mm:ss dd-MM-yyyy").setLenient().create(); FoodApi apiService = new Retrofit.Builder() - .baseUrl("http://192.168.1.183:8080/api/").addConverterFactory(GsonConverterFactory.create(gson)) + .baseUrl(Common.BASE_URL).addConverterFactory(GsonConverterFactory.create(gson)) .build() .create(FoodApi.class); diff --git a/app/src/main/java/com/capstone/foodify/shipper/API/FoodApiToken.java b/app/src/main/java/com/capstone/foodify/shipper/API/FoodApiToken.java index 1563318..0a32d76 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/API/FoodApiToken.java +++ b/app/src/main/java/com/capstone/foodify/shipper/API/FoodApiToken.java @@ -39,7 +39,7 @@ public interface FoodApiToken { FoodApiToken apiService = new Retrofit.Builder() .client(client) - .baseUrl("http://192.168.1.183:8080/api/").addConverterFactory(GsonConverterFactory.create(gson)) + .baseUrl(Common.BASE_URL).addConverterFactory(GsonConverterFactory.create(gson)) .build() .create(FoodApiToken.class); @@ -56,4 +56,6 @@ public interface FoodApiToken { @PUT("users/{userId}/orders/{orderId}/status") Call changeStatusOrder(@Path("userId") int userId, @Path("orderId") int orderId, @Query("status") String status); + @PUT("users/{userId}/update/fcm") + Call updateFCMToken(@Path("userId") int userId, @Body String token); } diff --git a/app/src/main/java/com/capstone/foodify/shipper/API/TokenFCMFirebaseAPI.java b/app/src/main/java/com/capstone/foodify/shipper/API/TokenFCMFirebaseAPI.java new file mode 100644 index 0000000..b740f2b --- /dev/null +++ b/app/src/main/java/com/capstone/foodify/shipper/API/TokenFCMFirebaseAPI.java @@ -0,0 +1,20 @@ +package com.capstone.foodify.shipper.API; + +import com.capstone.foodify.shipper.Common; + +import retrofit2.Call; +import retrofit2.Retrofit; +import retrofit2.converter.scalars.ScalarsConverterFactory; +import retrofit2.http.GET; +import retrofit2.http.Path; + +public interface TokenFCMFirebaseAPI { + + TokenFCMFirebaseAPI apiService = new Retrofit.Builder() + .baseUrl(Common.BASE_URL).addConverterFactory(ScalarsConverterFactory.create()) + .build() + .create(TokenFCMFirebaseAPI.class); + + @GET("users/{userId}/fcm") + Call getTokenFCM(@Path("userId") int userId); +} diff --git a/app/src/main/java/com/capstone/foodify/shipper/Activity/AccountAndProfileActivity.java b/app/src/main/java/com/capstone/foodify/shipper/Activity/AccountAndProfileActivity.java index 324000a..9143db5 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/Activity/AccountAndProfileActivity.java +++ b/app/src/main/java/com/capstone/foodify/shipper/Activity/AccountAndProfileActivity.java @@ -14,15 +14,12 @@ import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.CountDownTimer; import android.view.View; import android.webkit.MimeTypeMap; import android.widget.Button; import android.widget.DatePicker; import android.widget.EditText; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; import com.capstone.foodify.shipper.API.FoodApiToken; @@ -42,10 +39,6 @@ import com.google.firebase.storage.StorageReference; import com.google.firebase.storage.UploadTask; import com.makeramen.roundedimageview.RoundedImageView; import com.squareup.picasso.Picasso; -import com.thecode.aestheticdialogs.AestheticDialog; -import com.thecode.aestheticdialogs.DialogAnimation; -import com.thecode.aestheticdialogs.DialogStyle; -import com.thecode.aestheticdialogs.DialogType; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -225,7 +218,7 @@ public class AccountAndProfileActivity extends AppCompatActivity { @Override public void onFailure(Call call, Throwable t) { - Common.showErrorServerNotification(AccountAndProfileActivity.this, "Không thể cập nhật thông tin, vui lòng thử lại sau!"); + Common.showErrorDialog(AccountAndProfileActivity.this, "Không thể cập nhật thông tin, vui lòng thử lại sau!"); progressLayout.setVisibility(View.GONE); } }); diff --git a/app/src/main/java/com/capstone/foodify/shipper/Activity/MainActivity.java b/app/src/main/java/com/capstone/foodify/shipper/Activity/MainActivity.java index 5cfea16..66327e6 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/Activity/MainActivity.java +++ b/app/src/main/java/com/capstone/foodify/shipper/Activity/MainActivity.java @@ -2,12 +2,19 @@ package com.capstone.foodify.shipper.Activity; import android.Manifest; import android.annotation.SuppressLint; +import android.content.ComponentName; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.ColorStateList; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.provider.Settings; +import android.util.Log; import android.view.MenuItem; import android.view.View; import android.widget.Toast; @@ -15,18 +22,34 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatCheckBox; import androidx.core.app.ActivityCompat; +import androidx.core.app.NotificationManagerCompat; import androidx.core.content.ContextCompat; import androidx.viewpager2.widget.ViewPager2; +import com.capstone.foodify.shipper.API.FoodApiToken; import com.capstone.foodify.shipper.BuildConfig; +import com.capstone.foodify.shipper.Common; +import com.capstone.foodify.shipper.Model.CustomResponse; import com.capstone.foodify.shipper.R; import com.capstone.foodify.shipper.ViewPagerAdapter; +import com.google.android.gms.tasks.OnCompleteListener; +import com.google.android.gms.tasks.Task; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.navigation.NavigationBarView; +import com.google.firebase.messaging.FirebaseMessaging; + +import java.util.Arrays; +import java.util.List; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; public class MainActivity extends AppCompatActivity { + private static final String TAG = "MainActivity"; ViewPager2 viewPager2; ViewPagerAdapter viewPagerAdapter; BottomNavigationView bottomNavigationView; @@ -46,6 +69,9 @@ public class MainActivity extends AppCompatActivity { bottomNavigation(); checkLocationPermission(); checkBackgroundLocationPermission(); + checkNotificationPermission(); + startPowerSaverIntent(this); + getTokenFCM(); } private void bottomNavigation() { viewPagerAdapter = new ViewPagerAdapter(this); @@ -85,7 +111,71 @@ public class MainActivity extends AppCompatActivity { }); } - //Location + private void getTokenFCM(){ + FirebaseMessaging.getInstance().getToken() + .addOnCompleteListener(new OnCompleteListener() { + @Override + public void onComplete(@NonNull Task task) { + if(!task.isSuccessful()){ + Log.d(TAG, "Failed to registration token!"); + + return; + } + + String token = task.getResult(); + + Common.FCM_TOKEN_SHIPPER = token; + FoodApiToken.apiService.updateFCMToken(Common.CURRENT_USER.getId(), token).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if(response.code() != 200) + Toast.makeText(MainActivity.this, "Không thể cập nhật FCM Token. Mã lỗi: " + response.code(), Toast.LENGTH_SHORT).show(); + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(MainActivity.this, "Đã có lỗi khi kết nối đến hệ thống!", Toast.LENGTH_SHORT).show(); + } + }); + + Log.d(TAG, "Token: " + token); + } + }); + } + + //Check notification + private void checkNotificationPermission(){ + if(!NotificationManagerCompat.from(this).areNotificationsEnabled()){ + showDialogNotificationPermission(); + } + } + + private void showDialogNotificationPermission() { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + + builder.setMessage("Vui lòng bật quyền thông báo trên thiết bị của bạn để có thể cập nhật đơn một cách " + + "nhanh nhất!") + .setCancelable(false) + .setPositiveButton("Ok", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + openSettings(); + dialog.cancel(); + } + }) + .setNegativeButton("Thoát", new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + AlertDialog alertDialog = builder.create(); + alertDialog.setTitle("Thông báo!"); + alertDialog.show(); + } + + //Check location permission private void checkLocationPermission(){ if(ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED){ @@ -124,33 +214,7 @@ public class MainActivity extends AppCompatActivity { } private void requestForPermission() { - ActivityCompat.requestPermissions(this, new String[] {Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_REQUEST_CODE); - } - private void showDialogPermission() { - AlertDialog.Builder builder = new AlertDialog.Builder(this); - - builder.setMessage("Ứng dụng này cần quyền truy cập vị trí để hoạt động. Xin vui lòng cấp quyền cho ứng dụng!") - .setCancelable(false) - .setPositiveButton("Ok", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - openSettings(); - finishAffinity(); - System.exit(0); - } - }) - .setNegativeButton("Thoát", new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - finishAffinity(); - System.exit(0); - } - }); - - AlertDialog alertDialog = builder.create(); - alertDialog.setTitle("Thông báo!"); - alertDialog.show(); + ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION}, LOCATION_REQUEST_CODE); } private void openSettings() { @@ -167,4 +231,62 @@ public class MainActivity extends AppCompatActivity { } + //Check auto start permission + public static List POWER_MANAGER_INTENTS = Arrays.asList( + new Intent().setComponent(new ComponentName("com.miui.securitycenter", "com.miui.permcenter.autostart.AutoStartManagementActivity")), + new Intent().setComponent(new ComponentName("com.letv.android.letvsafe", "com.letv.android.letvsafe.AutobootManageActivity")), + new Intent().setComponent(new ComponentName("com.huawei.systemmanager", "com.huawei.systemmanager.optimize.process.ProtectActivity")), + new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.permission.startup.StartupAppListActivity")), + new Intent().setComponent(new ComponentName("com.coloros.safecenter", "com.coloros.safecenter.startupapp.StartupAppListActivity")), + new Intent().setComponent(new ComponentName("com.oppo.safe", "com.oppo.safe.permission.startup.StartupAppListActivity")), + new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.AddWhiteListActivity")), + new Intent().setComponent(new ComponentName("com.iqoo.secure", "com.iqoo.secure.ui.phoneoptimize.BgStartUpManager")), + new Intent().setComponent(new ComponentName("com.vivo.permissionmanager", "com.vivo.permissionmanager.activity.BgStartUpManagerActivity")), + new Intent().setComponent(new ComponentName("com.asus.mobilemanager", "com.asus.mobilemanager.entry.FunctionActivity")).setData(android.net.Uri.parse("mobilemanager://function/entry/AutoStart")) + ); + + + public void startPowerSaverIntent(Context context) { + SharedPreferences settings = context.getSharedPreferences("ProtectedApps", Context.MODE_PRIVATE); + boolean skipMessage = settings.getBoolean("skipProtectedAppCheck", false); + if (!skipMessage) { + final SharedPreferences.Editor editor = settings.edit(); + boolean foundCorrectIntent = false; + for (Intent intent : POWER_MANAGER_INTENTS) { + if (isCallable(context, intent)) { + foundCorrectIntent = true; + final AppCompatCheckBox dontShowAgain = new AppCompatCheckBox(context); + dontShowAgain.setText("Không hiện hộp thoại này nữa!"); + dontShowAgain.setButtonTintList(ColorStateList.valueOf(getResources().getColor(R.color.primaryColor, null))); + dontShowAgain.setOnCheckedChangeListener((buttonView, isChecked) -> { + editor.putBoolean("skipProtectedAppCheck", isChecked); + editor.apply(); + }); + + new AlertDialog.Builder(context) + .setTitle("Phát hiện máy " + Build.MANUFACTURER + "!") + .setMessage(String.format("Vì một số dòng máy Trung Quốc tự động tắt chế độ chạy nền của app %s, nên cần bạn " + + "cho phép ứng dụng luôn tự khởi chạy ở cài đặt ứng dụng để có thể không bỏ lỡ bất kỳ thông báo nào!%n", context.getString(R.string.app_name))) + .setView(dontShowAgain) + .setPositiveButton("Đi đến cài đặt", (dialog, which) -> context.startActivity(intent)) + .setNegativeButton("Đóng", null) + .show(); + break; + } + } + if (!foundCorrectIntent) { + editor.putBoolean("skipProtectedAppCheck", true); + editor.apply(); + } + } + } + + + private static boolean isCallable(Context context, Intent intent) { + List list = context.getPackageManager().queryIntentActivities(intent, + PackageManager.MATCH_DEFAULT_ONLY); + return list.size() > 0; + } + + } \ No newline at end of file diff --git a/app/src/main/java/com/capstone/foodify/shipper/Activity/OrderDetailActivity.java b/app/src/main/java/com/capstone/foodify/shipper/Activity/OrderDetailActivity.java index 54cd3f1..3ac5c63 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/Activity/OrderDetailActivity.java +++ b/app/src/main/java/com/capstone/foodify/shipper/Activity/OrderDetailActivity.java @@ -2,27 +2,20 @@ package com.capstone.foodify.shipper.Activity; import static com.capstone.foodify.shipper.Common.ACTION_START_LOCATION_SERVICE; import static com.capstone.foodify.shipper.Common.ACTION_STOP_LOCATION_SERVICE; -import static com.capstone.foodify.shipper.Common.FASTEST_UPDATE_IN_MILLISECONDS; -import static com.capstone.foodify.shipper.Common.LOCATION_REQUEST_CODE; import static com.capstone.foodify.shipper.Common.MAX_WAIT_TIME_IN_MILLISECONDS; import static com.capstone.foodify.shipper.Common.REQUEST_CHECK_SETTINGS; -import static com.capstone.foodify.shipper.Common.UPDATE_INTERVAL_IN_MILLISECONDS; -import android.Manifest; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.PendingIntent; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentSender; -import android.content.pm.PackageManager; import android.location.Location; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.provider.Settings; import android.util.Log; import android.view.View; import android.widget.Button; @@ -32,18 +25,15 @@ import android.widget.TextView; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.capstone.foodify.shipper.API.FoodApiToken; import com.capstone.foodify.shipper.API.GoogleMapApi; +import com.capstone.foodify.shipper.API.TokenFCMFirebaseAPI; import com.capstone.foodify.shipper.Adapter.OrderDetailAdapter; -import com.capstone.foodify.shipper.BuildConfig; import com.capstone.foodify.shipper.Common; import com.capstone.foodify.shipper.GoogleMap.GeofenceHelper; import com.capstone.foodify.shipper.LocationService; @@ -75,8 +65,6 @@ import com.thecode.aestheticdialogs.DialogType; import com.thecode.aestheticdialogs.OnDialogClickListener; import java.text.DecimalFormat; -import java.util.Timer; -import java.util.TimerTask; import retrofit2.Call; import retrofit2.Callback; @@ -97,7 +85,7 @@ public class OrderDetailActivity extends AppCompatActivity { LinearLayout layoutConfirmOrder; private GeofencingClient geofencingClient; private GeofenceHelper geofenceHelper; - private float GEOFENCE_RADIUS = 150; + private float GEOFENCE_RADIUS = 200; private String GEOFENCE_ID = "SOME_GEOFENCE_ID"; //Location private FusedLocationProviderClient mFusedLocationClient; @@ -117,7 +105,7 @@ public class OrderDetailActivity extends AppCompatActivity { order = (Order) getIntent().getSerializableExtra("order"); } - + //Set data order when shipper return back the app from Google Map if (order == null && Common.CURRENT_ORDER != null) { order = Common.CURRENT_ORDER; } @@ -131,6 +119,8 @@ public class OrderDetailActivity extends AppCompatActivity { initComponent(); initData(); + getFCMTokenUser(); + LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, RecyclerView.VERTICAL, false); rcv_list_order.setLayoutManager(linearLayoutManager); @@ -162,6 +152,8 @@ public class OrderDetailActivity extends AppCompatActivity { Uri.parse("google.navigation:q=" + order.getLat() + "," + order.getLng() + "&mode=l")); intent.setPackage("com.google.android.apps.maps"); startActivity(intent); + } else{ + Common.showErrorDialog(OrderDetailActivity.this, "Không thể lấy được thông tin đơn, vui lòng thử lại sau!"); } } }); @@ -216,6 +208,25 @@ public class OrderDetailActivity extends AppCompatActivity { } } + private void getFCMTokenUser(){ + TokenFCMFirebaseAPI.apiService.getTokenFCM(order.getUser().getId()).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if(response.code() == 200){ + String tempToken = response.body(); + assert tempToken != null; + Common.FCM_TOKEN_USER = tempToken.replace("\"", ""); + } else { + Toast.makeText(OrderDetailActivity.this, "Error code: " + response.code(), Toast.LENGTH_SHORT).show(); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(OrderDetailActivity.this, Common.ERROR_CONNECT_SERVER, Toast.LENGTH_SHORT).show(); + } + }); + } private void updateStatus(String status) { Common.CURRENT_LOCATION = null; changeOrderStatus(status); @@ -514,4 +525,12 @@ public class OrderDetailActivity extends AppCompatActivity { changeLayoutButton(Common.CURRENT_LOCATION.getLatitude(), Common.CURRENT_LOCATION.getLongitude()); } } + + @Override + protected void onDestroy() { + super.onDestroy(); + stopLocationService(); + Common.CURRENT_ORDER = null; + Common.FCM_TOKEN_USER = null; + } } \ No newline at end of file diff --git a/app/src/main/java/com/capstone/foodify/shipper/Activity/SignInActivity.java b/app/src/main/java/com/capstone/foodify/shipper/Activity/SignInActivity.java index b298d73..4bae66d 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/Activity/SignInActivity.java +++ b/app/src/main/java/com/capstone/foodify/shipper/Activity/SignInActivity.java @@ -5,7 +5,6 @@ import android.os.Bundle; import android.text.Editable; import android.text.TextWatcher; import android.view.View; -import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; @@ -207,7 +206,7 @@ public class SignInActivity extends AppCompatActivity { @Override public void onFailure(Call call, Throwable t) { System.out.println("ERROR: " + t); - Common.showErrorServerNotification(SignInActivity.this, "Không thể đăng nhập tài khoản! Vui lòng thử lại sau!"); + Common.showErrorDialog(SignInActivity.this, "Không thể đăng nhập tài khoản! Vui lòng thử lại sau!"); } }); } else { diff --git a/app/src/main/java/com/capstone/foodify/shipper/Common.java b/app/src/main/java/com/capstone/foodify/shipper/Common.java index 486cb3e..1d41672 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/Common.java +++ b/app/src/main/java/com/capstone/foodify/shipper/Common.java @@ -25,7 +25,10 @@ public class Common { public static Shipper CURRENT_SHIPPER = null; public static Order CURRENT_ORDER = null; public static String TOKEN = null; + public static String FCM_TOKEN_SHIPPER = null; + public static String FCM_TOKEN_USER = null; public static Location CURRENT_LOCATION = null; + public static final String BASE_URL = "http://192.168.1.183:8080/api/"; public static final String MAP_API = "AIzaSyAY14Ic32UP26Hg6GILznOfbBihiY5BUxw"; public static final String FORMAT_DATE="dd-MM-yyyy"; public static final String VALID_EMAIL_ADDRESS_REGEX = "^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$"; @@ -51,7 +54,7 @@ public class Common { return Typeface.createFromAsset(assetManager, "font/opensans.ttf"); } - public static void showErrorServerNotification(Activity activity, String message){ + public static void showErrorDialog(Activity activity, String message){ new AestheticDialog.Builder(activity, DialogStyle.RAINBOW, DialogType.ERROR) .setTitle("LỖI!") .setMessage(message) diff --git a/app/src/main/java/com/capstone/foodify/shipper/Fragment/Order/ShippingOrder.java b/app/src/main/java/com/capstone/foodify/shipper/Fragment/Order/ShippingOrder.java index 2a3c5ef..7e9bd64 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/Fragment/Order/ShippingOrder.java +++ b/app/src/main/java/com/capstone/foodify/shipper/Fragment/Order/ShippingOrder.java @@ -86,6 +86,7 @@ public class ShippingOrder extends Fragment { public void onResponse(Call call, Response response) { if(response.code() == 200){ + assert response.body() != null; listOrders.addAll(response.body().getOrders()); orderAdapter.setData(listOrders); diff --git a/app/src/main/java/com/capstone/foodify/shipper/GoogleMap/GeofenceBroadcastReceiver.java b/app/src/main/java/com/capstone/foodify/shipper/GoogleMap/GeofenceBroadcastReceiver.java index bcaf9a6..9ea5d76 100644 --- a/app/src/main/java/com/capstone/foodify/shipper/GoogleMap/GeofenceBroadcastReceiver.java +++ b/app/src/main/java/com/capstone/foodify/shipper/GoogleMap/GeofenceBroadcastReceiver.java @@ -1,28 +1,27 @@ package com.capstone.foodify.shipper.GoogleMap; import android.content.BroadcastReceiver; -import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.PackageManager; -import android.location.Location; -import android.net.Uri; import android.util.Log; -import android.widget.Toast; +import com.capstone.foodify.shipper.API.FirebaseMessagingAPI; import com.capstone.foodify.shipper.Activity.OrderDetailActivity; import com.capstone.foodify.shipper.Common; -import com.capstone.foodify.shipper.Model.Order; +import com.capstone.foodify.shipper.Model.FirebaseMessaging.FirebaseMessaging; +import com.capstone.foodify.shipper.Model.FirebaseMessaging.Notification; import com.google.android.gms.location.Geofence; import com.google.android.gms.location.GeofencingEvent; -import com.google.android.gms.location.LocationResult; import java.util.List; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + public class GeofenceBroadcastReceiver extends BroadcastReceiver { private static final String TAG = "GeofenceBroadcastReceive"; - public static final String ACTION_PROCESS_UPDATE = "com.capstone.foodify.shipper.GoogleMap.UPDATE_LOCATION"; @Override public void onReceive(Context context, Intent intent) { @@ -43,9 +42,31 @@ public class GeofenceBroadcastReceiver extends BroadcastReceiver { if(transitionType == Geofence.GEOFENCE_TRANSITION_ENTER){ notificationHelper.sendHighPriorityNotification("Đã tới khu vực giao!", "Nhấp vào đây để quay về app!", OrderDetailActivity.class); + + if(Common.FCM_TOKEN_USER != null) + sendNotification(); } + } + private void sendNotification(){ + Notification notification = new Notification("Thông báo!", "Đơn hàng đang gần đến bạn!"); + FirebaseMessaging firebaseMessaging = new FirebaseMessaging(); + firebaseMessaging.setTo(Common.FCM_TOKEN_USER); + firebaseMessaging.setNotification(notification); + FirebaseMessagingAPI.apiService.sendNotification(firebaseMessaging).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if(response.code() != 200){ + Log.d(TAG, "Error to send notification. Code: " + response.code()); + } + } + + @Override + public void onFailure(Call call, Throwable t) { + Log.e(TAG, "Error to connect FCM Messaging!"); + } + }); } } diff --git a/app/src/main/java/com/capstone/foodify/shipper/Model/FirebaseMessaging/FirebaseMessaging.java b/app/src/main/java/com/capstone/foodify/shipper/Model/FirebaseMessaging/FirebaseMessaging.java new file mode 100644 index 0000000..35f5c85 --- /dev/null +++ b/app/src/main/java/com/capstone/foodify/shipper/Model/FirebaseMessaging/FirebaseMessaging.java @@ -0,0 +1,22 @@ +package com.capstone.foodify.shipper.Model.FirebaseMessaging; + +public class FirebaseMessaging { + private String to; + private Notification notification; + + public String getTo() { + return to; + } + + public void setTo(String to) { + this.to = to; + } + + public Notification getNotification() { + return notification; + } + + public void setNotification(Notification notification) { + this.notification = notification; + } +} diff --git a/app/src/main/java/com/capstone/foodify/shipper/Model/FirebaseMessaging/Notification.java b/app/src/main/java/com/capstone/foodify/shipper/Model/FirebaseMessaging/Notification.java new file mode 100644 index 0000000..47e92c3 --- /dev/null +++ b/app/src/main/java/com/capstone/foodify/shipper/Model/FirebaseMessaging/Notification.java @@ -0,0 +1,47 @@ +package com.capstone.foodify.shipper.Model.FirebaseMessaging; + +public class Notification { + private String title; + private String body; + private String sound; + private String android_channel_id; + + public Notification(String title, String body) { + this.title = title; + this.body = body; + this.sound = "notificationsound.mp3"; + this.android_channel_id = "foodify-notification"; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getBody() { + return body; + } + + public void setBody(String body) { + this.body = body; + } + + public String getSound() { + return sound; + } + + public void setSound(String sound) { + this.sound = sound; + } + + public String getAndroid_channel_id() { + return android_channel_id; + } + + public void setAndroid_channel_id(String android_channel_id) { + this.android_channel_id = android_channel_id; + } +} diff --git a/app/src/main/java/com/capstone/foodify/shipper/NotificationService.java b/app/src/main/java/com/capstone/foodify/shipper/NotificationService.java new file mode 100644 index 0000000..65f0684 --- /dev/null +++ b/app/src/main/java/com/capstone/foodify/shipper/NotificationService.java @@ -0,0 +1,115 @@ +package com.capstone.foodify.shipper; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.media.AudioAttributes; +import android.media.RingtoneManager; +import android.net.Uri; +import android.util.Log; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.core.app.NotificationCompat; + + +import com.capstone.foodify.shipper.API.FoodApiToken; +import com.capstone.foodify.shipper.Activity.MainActivity; +import com.capstone.foodify.shipper.Model.CustomResponse; +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; + +public class NotificationService extends FirebaseMessagingService { + + private static final String TAG = "NotificationService"; + public static final String channelId = "foodify-notification"; + + @Override + public void onMessageReceived(RemoteMessage remoteMessage) { + // TODO(developer): Handle FCM messages here. + // Not getting messages here? See why this may be: https://goo.gl/39bRNJ + Log.d(TAG, "From: " + remoteMessage.getFrom()); + + // Check if message contains a notification payload. + if (remoteMessage.getNotification() != null) { + Log.d(TAG, "Message notification Body: " + remoteMessage.getNotification().getBody()); + } + + // Also if you intend on generating your own notifications as a result of a received FCM + // message, here is where that should be initiated. See sendNotification method below. + + sendNotification(remoteMessage.getNotification().getTitle() , remoteMessage.getNotification().getBody()); + } + + private void sendNotification(String title, String messageBody) { + Intent intent = new Intent(this, MainActivity.class); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0 /* Request code */, intent, + PendingIntent.FLAG_IMMUTABLE); + + Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION); + NotificationCompat.Builder notificationBuilder = + new NotificationCompat.Builder(this, channelId) + .setSmallIcon(R.mipmap.ic_launcher) + .setContentTitle(title) + .setContentText(messageBody) + .setAutoCancel(true) + .setSound(defaultSoundUri) + .setContentIntent(pendingIntent); + + NotificationManager notificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + createNotificationChannel(notificationManager); + + notificationManager.notify(0 /* ID of notification */, notificationBuilder.build()); + } + + private void createNotificationChannel(NotificationManager notificationManager) { + Uri sound = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + this.getPackageName() + "/" + R.raw.notificationsound); //Here is FILE_NAME is the name of file that you want to play + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + CharSequence name = "Notification"; + String description = "testing"; + int importance = NotificationManager.IMPORTANCE_HIGH; + AudioAttributes audioAttributes = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_ALARM) + .build(); + NotificationChannel channel = new NotificationChannel(channelId, name, importance); + channel.setDescription(description); + channel.enableLights(true); channel.enableVibration(true); + channel.setSound(sound, audioAttributes); + notificationManager.createNotificationChannel(channel); + } + + + @Override + public void onNewToken(@NonNull String token) { + super.onNewToken(token); + + if(Common.CURRENT_USER != null){ + FoodApiToken.apiService.updateFCMToken(Common.CURRENT_USER.getId(), token).enqueue(new Callback() { + @Override + public void onResponse(Call call, Response response) { + if(response.code() != 200) + Toast.makeText(NotificationService.this, "Không thể cập nhật FCM Token. Mã lỗi: " + response.code(), Toast.LENGTH_SHORT).show(); + } + + @Override + public void onFailure(Call call, Throwable t) { + Toast.makeText(NotificationService.this, "Đã có lỗi khi kết nối đến hệ thống!", Toast.LENGTH_SHORT).show(); + } + }); + } + + Log.d(TAG, "Renew token: " + token); + } +} diff --git a/app/src/main/res/drawable/logo.png b/app/src/main/res/drawable/logo.png index bd41f95..ea85045 100644 Binary files a/app/src/main/res/drawable/logo.png and b/app/src/main/res/drawable/logo.png differ diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml index 6f3b755..036d09b 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml index 6f3b755..036d09b 100644 --- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -1,6 +1,5 @@ - - - + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..6094744 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp deleted file mode 100644 index c209e78..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..8a802c2 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..2ea1ed9 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp deleted file mode 100644 index b2dfe3d..0000000 Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..bf9a1b2 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp deleted file mode 100644 index 4f0f1d6..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3e22726 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..06dddc3 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp deleted file mode 100644 index 62b611d..0000000 Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..5fd91af Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp deleted file mode 100644 index 948a307..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..07d4457 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..e574ed4 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp deleted file mode 100644 index 1b9a695..0000000 Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..879239f Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp deleted file mode 100644 index 28d4b77..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..3fca3d6 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..155ac8f Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9287f50..0000000 Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..45c4cea Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp deleted file mode 100644 index aa7d642..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..059c9d5 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..978ac9d Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp deleted file mode 100644 index 9126ae3..0000000 Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ diff --git a/app/src/main/res/raw/notificationsound.mp3 b/app/src/main/res/raw/notificationsound.mp3 new file mode 100644 index 0000000..1f2bacb Binary files /dev/null and b/app/src/main/res/raw/notificationsound.mp3 differ diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..27a641b --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #00C0FE + \ No newline at end of file diff --git a/app/src/main/res/values/splash.xml b/app/src/main/res/values/splash.xml index 858ae98..24f4fa3 100644 --- a/app/src/main/res/values/splash.xml +++ b/app/src/main/res/values/splash.xml @@ -2,7 +2,7 @@ \ No newline at end of file