diff --git a/.gradle/7.4.1/checksums/checksums.lock b/.gradle/7.4.1/checksums/checksums.lock index 81f15a1..a261bf1 100644 Binary files a/.gradle/7.4.1/checksums/checksums.lock and b/.gradle/7.4.1/checksums/checksums.lock differ diff --git a/.gradle/7.4.1/checksums/md5-checksums.bin b/.gradle/7.4.1/checksums/md5-checksums.bin index d7cb760..9b945e7 100644 Binary files a/.gradle/7.4.1/checksums/md5-checksums.bin and b/.gradle/7.4.1/checksums/md5-checksums.bin differ diff --git a/.gradle/7.4.1/checksums/sha1-checksums.bin b/.gradle/7.4.1/checksums/sha1-checksums.bin index 4c77484..d0fae47 100644 Binary files a/.gradle/7.4.1/checksums/sha1-checksums.bin and b/.gradle/7.4.1/checksums/sha1-checksums.bin differ diff --git a/.gradle/7.4.1/executionHistory/executionHistory.bin b/.gradle/7.4.1/executionHistory/executionHistory.bin index 00ea483..6465c1b 100644 Binary files a/.gradle/7.4.1/executionHistory/executionHistory.bin and b/.gradle/7.4.1/executionHistory/executionHistory.bin differ diff --git a/.gradle/7.4.1/executionHistory/executionHistory.lock b/.gradle/7.4.1/executionHistory/executionHistory.lock index 15c6db9..f5000cc 100644 Binary files a/.gradle/7.4.1/executionHistory/executionHistory.lock and b/.gradle/7.4.1/executionHistory/executionHistory.lock differ diff --git a/.gradle/7.4.1/fileHashes/fileHashes.bin b/.gradle/7.4.1/fileHashes/fileHashes.bin index 136478e..6cb513d 100644 Binary files a/.gradle/7.4.1/fileHashes/fileHashes.bin and b/.gradle/7.4.1/fileHashes/fileHashes.bin differ diff --git a/.gradle/7.4.1/fileHashes/fileHashes.lock b/.gradle/7.4.1/fileHashes/fileHashes.lock index 83ed753..68681db 100644 Binary files a/.gradle/7.4.1/fileHashes/fileHashes.lock and b/.gradle/7.4.1/fileHashes/fileHashes.lock differ diff --git a/.gradle/7.4.1/fileHashes/resourceHashesCache.bin b/.gradle/7.4.1/fileHashes/resourceHashesCache.bin index 245eed1..1b06e2a 100644 Binary files a/.gradle/7.4.1/fileHashes/resourceHashesCache.bin and b/.gradle/7.4.1/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index 0f5cf0a..7f04727 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/buildOutputCleanup/outputFiles.bin b/.gradle/buildOutputCleanup/outputFiles.bin index 0a4c9c1..1c3efbc 100644 Binary files a/.gradle/buildOutputCleanup/outputFiles.bin and b/.gradle/buildOutputCleanup/outputFiles.bin differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index b2e4a0f..6225e66 100644 Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ diff --git a/.idea/modules/app/app.Mealer.app.androidTest.iml b/.idea/modules/app/app.Mealer.app.androidTest.iml index 7674746..d33df22 100644 --- a/.idea/modules/app/app.Mealer.app.androidTest.iml +++ b/.idea/modules/app/app.Mealer.app.androidTest.iml @@ -14,6 +14,8 @@ + $MODULE_DIR$/../../../app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debugAndroidTest/R.jar + $MODULE_DIR$/../../../app/build/intermediates/compile_app_classes_jar/debug/classes.jar $USER_HOME$/.gradle/caches/transforms-3/28b1b539885dabedb5b279ccd2d8d374/transformed/junit-1.1.3-api.jar $USER_HOME$/.gradle/caches/transforms-3/fbf0489c44483481586dbd0b0dd34ee3/transformed/espresso-core-3.4.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/a3933a36403b543b075742c539061107/transformed/viewbinding-7.3.1-api.jar @@ -213,11 +215,11 @@ - + diff --git a/.idea/modules/app/app.Mealer.app.main.iml b/.idea/modules/app/app.Mealer.app.main.iml index 4093af8..959b05c 100644 --- a/.idea/modules/app/app.Mealer.app.main.iml +++ b/.idea/modules/app/app.Mealer.app.main.iml @@ -62,7 +62,6 @@ $USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlinx/kotlinx-coroutines-android/1.6.1/4e61fcdcc508cbaa37c4a284a50205d7c7767e37/kotlinx-coroutines-android-1.6.1.jar $USER_HOME$/.gradle/caches/modules-2/files-2.1/org.jetbrains.kotlin/kotlin-stdlib-jdk8/1.7.10/d70d7d2c56371f7aa18f32e984e3e2e998fe9081/kotlin-stdlib-jdk8-1.7.10.jar $USER_HOME$/.gradle/caches/transforms-3/92960be620479cfd6b56246f65c69605/transformed/core-ktx-1.9.0-api.jar - $USER_HOME$/.gradle/caches/transforms-3/cdaabc303198e8eb964ea116fbeadcbe/transformed/cardview-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/e63935ae0a21e7d5408a0cddc629270d/transformed/coordinatorlayout-1.1.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/ff258dc78336586ef979ce40f616d6fc/transformed/drawerlayout-1.1.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/5add3bd4a839752ebd89aca6ba55c626/transformed/dynamicanimation-1.0.0-api.jar @@ -78,13 +77,14 @@ $USER_HOME$/.gradle/caches/transforms-3/5e136a3fa3ecb30ad3e4357c4e9f4c25/transformed/legacy-support-core-utils-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/10ddaeb92f12a602f4532fd9b84e6054/transformed/loader-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/3d6a0cb9330a22a67c713e92ee321c08/transformed/core-1.9.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/cdaabc303198e8eb964ea116fbeadcbe/transformed/cardview-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/6838871df1d46483fe4fc63b24422fef/transformed/lifecycle-runtime-2.5.1-api.jar + $USER_HOME$/.gradle/caches/transforms-3/41f9ca881713e5b6a499e1bf094b62b5/transformed/versionedparcelable-1.1.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/3196f2d3741a4bd76adaf557816a8cfb/transformed/cursoradapter-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/77f4ec13539c2eea375ea17e5cee13a3/transformed/savedstate-ktx-1.1.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/10fae643d6cb385f79bc91ff7bdea367/transformed/savedstate-1.2.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/e61ae2d0a7aa4beae28519a567f4dc02/transformed/firebase-components-17.0.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/e89b3fa5d1fdc6e31a2c44d7806ce282/transformed/lifecycle-viewmodel-2.5.1-api.jar - $USER_HOME$/.gradle/caches/transforms-3/41f9ca881713e5b6a499e1bf094b62b5/transformed/versionedparcelable-1.1.1-api.jar $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection-ktx/1.1.0/f807b2f366f7b75142a67d2f3c10031065b5168/collection-ktx-1.1.0.jar $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar $USER_HOME$/.gradle/caches/transforms-3/ec68fd8c73e066a24f3ce59b2314c36e/transformed/localbroadcastmanager-1.0.0-api.jar @@ -189,11 +189,11 @@ - + diff --git a/.idea/modules/app/app.Mealer.app.unitTest.iml b/.idea/modules/app/app.Mealer.app.unitTest.iml index aef6aab..d41ad75 100644 --- a/.idea/modules/app/app.Mealer.app.unitTest.iml +++ b/.idea/modules/app/app.Mealer.app.unitTest.iml @@ -15,6 +15,7 @@ $MODULE_DIR$/../../../app/build/intermediates/compile_and_runtime_not_namespaced_r_class_jar/debug/R.jar + $MODULE_DIR$/../../../app/build/intermediates/compile_app_classes_jar/debug/classes.jar $USER_HOME$/.gradle/caches/transforms-3/a3933a36403b543b075742c539061107/transformed/viewbinding-7.3.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/0b4603c6e97d4168104b68c6ecfbbc4a/transformed/navigation-fragment-2.4.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/efe54a6814a98909d65a22d09f4a19f3/transformed/fragment-ktx-1.4.1-api.jar @@ -64,7 +65,6 @@ $USER_HOME$/.gradle/caches/modules-2/files-2.1/junit/junit/4.13.2/8ac9e16d933b6fb43bc7f576336b8f4d7eb5ba12/junit-4.13.2.jar $USER_HOME$/.gradle/caches/modules-2/files-2.1/org.hamcrest/hamcrest-core/1.3/42a25dc3219429f0e5d060061f71acb49bf010a0/hamcrest-core-1.3.jar $USER_HOME$/.gradle/caches/transforms-3/92960be620479cfd6b56246f65c69605/transformed/core-ktx-1.9.0-api.jar - $USER_HOME$/.gradle/caches/transforms-3/cdaabc303198e8eb964ea116fbeadcbe/transformed/cardview-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/e63935ae0a21e7d5408a0cddc629270d/transformed/coordinatorlayout-1.1.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/ff258dc78336586ef979ce40f616d6fc/transformed/drawerlayout-1.1.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/5add3bd4a839752ebd89aca6ba55c626/transformed/dynamicanimation-1.0.0-api.jar @@ -80,13 +80,14 @@ $USER_HOME$/.gradle/caches/transforms-3/5e136a3fa3ecb30ad3e4357c4e9f4c25/transformed/legacy-support-core-utils-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/10ddaeb92f12a602f4532fd9b84e6054/transformed/loader-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/3d6a0cb9330a22a67c713e92ee321c08/transformed/core-1.9.0-api.jar + $USER_HOME$/.gradle/caches/transforms-3/cdaabc303198e8eb964ea116fbeadcbe/transformed/cardview-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/6838871df1d46483fe4fc63b24422fef/transformed/lifecycle-runtime-2.5.1-api.jar + $USER_HOME$/.gradle/caches/transforms-3/41f9ca881713e5b6a499e1bf094b62b5/transformed/versionedparcelable-1.1.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/3196f2d3741a4bd76adaf557816a8cfb/transformed/cursoradapter-1.0.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/77f4ec13539c2eea375ea17e5cee13a3/transformed/savedstate-ktx-1.1.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/10fae643d6cb385f79bc91ff7bdea367/transformed/savedstate-1.2.0-api.jar $USER_HOME$/.gradle/caches/transforms-3/e61ae2d0a7aa4beae28519a567f4dc02/transformed/firebase-components-17.0.1-api.jar $USER_HOME$/.gradle/caches/transforms-3/e89b3fa5d1fdc6e31a2c44d7806ce282/transformed/lifecycle-viewmodel-2.5.1-api.jar - $USER_HOME$/.gradle/caches/transforms-3/41f9ca881713e5b6a499e1bf094b62b5/transformed/versionedparcelable-1.1.1-api.jar $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection-ktx/1.1.0/f807b2f366f7b75142a67d2f3c10031065b5168/collection-ktx-1.1.0.jar $USER_HOME$/.gradle/caches/modules-2/files-2.1/androidx.collection/collection/1.1.0/1f27220b47669781457de0d600849a5de0e89909/collection-1.1.0.jar $USER_HOME$/.gradle/caches/transforms-3/ec68fd8c73e066a24f3ce59b2314c36e/transformed/localbroadcastmanager-1.0.0-api.jar @@ -181,11 +182,11 @@ - + diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml index 85839f4..cb76f30 100644 --- a/.idea/navEditor.xml +++ b/.idea/navEditor.xml @@ -13,25 +13,25 @@ - + - + - + - + - + - + - - - - - - - + + + + + diff --git a/Screenshot 2022-11-18 at 13.50.40.png b/Screenshot 2022-11-18 at 13.50.40.png new file mode 100644 index 0000000..18b552f Binary files /dev/null and b/Screenshot 2022-11-18 at 13.50.40.png differ diff --git a/app/build.gradle b/app/build.gradle index c0280e8..7b70c7f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -37,8 +37,9 @@ android { } } +def core_version = "1.6.0" dependencies { - + implementation "androidx.core:core:$core_version" implementation 'androidx.core:core-ktx:1.9.0' implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.google.android.material:material:1.6.1' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7c54095..0ee7605 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,7 @@ xmlns:tools="http://schemas.android.com/tools" package="com.mealer.ui"> + + + + + + + attributes){ this.cardNumber = attributes.get("cardNumber"); this.cardExpiry = attributes.get("cardExpiry"); this.cardSecurity = attributes.get("cardSecurity"); + this.dietaryRestriction = attributes.get("dietaryRestriction"); } /** @@ -47,6 +49,7 @@ public ClientUser(String firstName, String lastName, String email, String city, this.cardNumber = cardNumber; this.cardExpiry = cardExpiry; this.cardSecurity = cardSecurity; + this.dietaryRestriction = "none"; // get database reference to the "users" tree DatabaseReference databaseReference = getReference("users"); @@ -68,4 +71,7 @@ public String getCardSecurity(){ return this.cardSecurity; } + public String getDietaryRestriction() { + return dietaryRestriction; + } } diff --git a/app/src/main/java/com/mealer/app/CookUser.java b/app/src/main/java/com/mealer/app/CookUser.java index b6cb3b5..0ca18aa 100644 --- a/app/src/main/java/com/mealer/app/CookUser.java +++ b/app/src/main/java/com/mealer/app/CookUser.java @@ -8,6 +8,7 @@ import com.google.firebase.database.DataSnapshot; import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.Exclude; import com.google.firebase.database.ValueEventListener; import com.mealer.app.menu.Menu; @@ -19,6 +20,11 @@ public class CookUser extends User implements Parcelable { private String description; private String accountStatus; private String suspensionEnd; + private String rating; + + public CookUser(){ + + } /** * cook user constructor specific for when initializing a new user object @@ -34,6 +40,11 @@ public CookUser(HashMap attributes){ this.description = attributes.get("description"); this.accountStatus = attributes.get("accountStatus"); this.suspensionEnd = attributes.get("suspensionEnd"); + try { + this.rating = attributes.get("rating"); + }catch(Exception ex){ + this.rating = "75"; + } } /** @@ -51,6 +62,7 @@ public CookUser(String firstName, String lastName, String email, this.description = description; this.accountStatus = accountStatus; this.suspensionEnd = suspensionEnd; + this.rating = "75"; // get database reference to the "users" tree DatabaseReference databaseReference = getReference("users"); @@ -71,4 +83,15 @@ public String getSuspensionEnd() { return suspensionEnd; } + public String getRating() { + return rating + "%"; + } + + @Exclude + public int getIntRating() {return Integer.parseInt(rating); } + + @Exclude + public void setRating(int rating) { + this.rating = Integer.toString(rating); + } } diff --git a/app/src/main/java/com/mealer/app/NotificationService.java b/app/src/main/java/com/mealer/app/NotificationService.java new file mode 100644 index 0000000..a5ef803 --- /dev/null +++ b/app/src/main/java/com/mealer/app/NotificationService.java @@ -0,0 +1,144 @@ +package com.mealer.app; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.Service; +import android.content.Intent; +import android.os.Handler; +import android.os.IBinder; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; + +import com.google.firebase.database.ChildEventListener; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; +import com.mealer.ui.R; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Timer; +import java.util.TimerTask; + +public class NotificationService extends Service { + + public static final String TAG = "NotificationService" ; + public static final String NOTIFICATION_CHANNEL_ID = "10001" ; + private final static String default_notification_channel_id = "default" ; + + DatabaseReference user; + String clientId; + User client; + + private final ChildEventListener childEventListener = new ChildEventListener() { + @Override + public void onChildAdded(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) { + Log.d(TAG, previousChildName + " "); + } + + @Override + public void onChildChanged(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) { + for(DataSnapshot child : snapshot.child("orders").getChildren()){ + Order order = child.getValue(Order.class); + String cook = null; + if(snapshot.getValue(CookUser.class)!=null) { + cook = Objects.requireNonNull(snapshot.getValue(CookUser.class)).getFirstName(); + } + if (order != null && order.getOrderFrom().equals(clientId)) { + order.setOrderId(child.getKey()); + if(!order.getOrderStatus().equals("pending") && order.getNotifications()==0) { + createNotification( + order.getOrderStatus(), + order.getOrderItem().getItemName(), + cook); + order.notified(user + .child(Objects.requireNonNull(snapshot.getKey())) // cookId + .child("orders") + .child(Objects.requireNonNull(child.getKey()))); + } + } + + } + } + + @Override + public void onChildRemoved(@NonNull DataSnapshot snapshot) { + } + + @Override + public void onChildMoved(@NonNull DataSnapshot snapshot, @Nullable String previousChildName) { + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + } + }; + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Log.e(TAG , "onStartCommand" ) ; + super.onStartCommand(intent , flags , startId); + + user = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("users"); + + // user.addValueEventListener(actionListener); + client = intent.getParcelableExtra("USER"); + clientId = intent.getStringExtra("ID"); + + user.addChildEventListener(childEventListener); + + return START_STICKY ; + } + + @Override + public void onCreate () { + Log.e( TAG , "onCreate"); + } + + @Override + public void onDestroy () { + Log.e(TAG, "onDestroy"); + super.onDestroy() ; + } + + private void createNotification (String status, String order, String cook) { + NotificationManager mNotificationManager = (NotificationManager) getSystemService( NOTIFICATION_SERVICE ) ; + NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(getApplicationContext() , default_notification_channel_id ) ; + mBuilder.setContentTitle( "Order " + status ) ; + if(status.equals("accepted") && cook!=null) { + mBuilder.setContentText("Your order for " + order + " was " + status + ". " + cook + + " will call to inform you when your order is ready."); + mBuilder.setTicker("Your order for " + order + " was " + status + ". " + cook + + " will call to inform you when your order is ready."); + }else{ + mBuilder.setContentText("Your order for " + order + " was " + status + "."); + mBuilder.setTicker("Your order for " + order + " was " + status + "."); + } + mBuilder.setSmallIcon(R.drawable.ic_notifications_black_24dp) ; + mBuilder.setAutoCancel( true ) ; + if (android.os.Build.VERSION. SDK_INT >= android.os.Build.VERSION_CODES. O ) { + int importance = NotificationManager. IMPORTANCE_HIGH ; + NotificationChannel notificationChannel = new NotificationChannel( NOTIFICATION_CHANNEL_ID , "NOTIFICATION_CHANNEL_NAME" , importance) ; + mBuilder.setChannelId( NOTIFICATION_CHANNEL_ID ) ; + assert mNotificationManager != null; + mNotificationManager.createNotificationChannel(notificationChannel) ; + } + assert mNotificationManager != null; + mNotificationManager.notify(( int ) System. currentTimeMillis () , mBuilder.build()) ; + } +} diff --git a/app/src/main/java/com/mealer/app/Order.java b/app/src/main/java/com/mealer/app/Order.java new file mode 100644 index 0000000..151e074 --- /dev/null +++ b/app/src/main/java/com/mealer/app/Order.java @@ -0,0 +1,141 @@ +package com.mealer.app; + +import android.os.Parcel; +import android.os.Parcelable; + +import androidx.annotation.NonNull; + +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.Exclude; +import com.google.firebase.database.FirebaseDatabase; +import com.mealer.app.menu.MenuItem; + +import java.util.HashMap; +import java.util.UUID; + +public class Order implements Parcelable { + + private DatabaseReference dbRef; + + private MenuItem orderItem; + private String orderId; + private String orderStatus; + private String orderFrom; + private int notifications; + + public Order(){} + + public Order(MenuItem item, String status, String from){ + this.orderId = UUID.randomUUID().toString(); + this.orderStatus = status; + this.orderItem = item; + this.orderFrom = from; + this.notifications = 0; + } + + public MenuItem getOrderItem() { + return orderItem; + } + + @Exclude + public String getOrderId() { + return orderId; + } + + public int getNotifications() { + return notifications; + } + + public String getOrderFrom() { + return orderFrom; + } + + public void setOrderId(String orderId) { + this.orderId = orderId; + } + + public String getOrderStatus() { + return orderStatus; + } + + public void notified(DatabaseReference ref){ + notifications = 1; + if(ref!=null){ + HashMap update = new HashMap<>(); + update.put("notifications", getNotifications()); + ref.updateChildren(update); + } + } + + public void accepted(String user){ + this.orderStatus = "accepted"; + if(user!=null) { + updateStatus(user); + } + } + + public void completed(String user){ + this.orderStatus = "completed"; + if(user!=null) { + updateStatus(user); + } + } + + public void rejected(String user){ + this.orderStatus = "rejected"; + if(user!=null) { + updateStatus(user); + } + } + + public void deleted(String user){ + this.orderStatus = "deleted"; + if(user!=null) { + updateStatus(user); + } + } + + private void updateStatus(String user){ + dbRef = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("users"); + + HashMap update = new HashMap<>(); + update.put("orderStatus", getOrderStatus()); + dbRef.child(user).child("orders").child(orderId).updateChildren(update); + } + + protected Order(Parcel in) { + orderItem = in.readParcelable(MenuItem.class.getClassLoader()); + orderId = in.readString(); + orderStatus = in.readString(); + orderFrom = in.readString(); + notifications = in.readInt(); + } + + public static final Creator CREATOR = new Creator() { + @Override + public Order createFromParcel(Parcel in) { + return new Order(in); + } + + @Override + public Order[] newArray(int size) { + return new Order[size]; + } + }; + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel parcel, int i) { + parcel.writeParcelable(orderItem, i); + parcel.writeString(orderId); + parcel.writeString(orderStatus); + parcel.writeString(orderFrom); + parcel.writeInt(notifications); + } +} diff --git a/app/src/main/java/com/mealer/app/menu/MenuItem.java b/app/src/main/java/com/mealer/app/menu/MenuItem.java index 2eb6e37..318e2a8 100644 --- a/app/src/main/java/com/mealer/app/menu/MenuItem.java +++ b/app/src/main/java/com/mealer/app/menu/MenuItem.java @@ -23,14 +23,14 @@ public MenuItem(){ } - public MenuItem (String itemName, String itemDescription, - String calories, String mainIngredients, boolean active){ + public MenuItem (String itemName, String itemDescription, String calories, + String mainIngredients, double price, boolean active){ this.itemId = UUID.randomUUID().toString(); this.itemName = itemName; this.itemDescription = itemDescription; this.calories = calories; this.mainIngredients = mainIngredients; - //this.price = price; + this.price = price; this.active = active; } @@ -40,7 +40,7 @@ protected MenuItem(Parcel in) { this.itemDescription = in.readString(); this.calories = in.readString(); this.mainIngredients = in.readString(); - //this.price = in.readDouble(); + this.price = in.readDouble(); this.active = in.readInt()==1; } @@ -69,6 +69,10 @@ public void setItemDescription(String itemDescription) { this.itemDescription = itemDescription; } + public void setPrice(double price) { + this.price = price; + } + public void setMainIngredients(String mainIngredients) { this.mainIngredients = mainIngredients; } @@ -123,7 +127,7 @@ public void writeToParcel(@NonNull Parcel parcel, int i) { parcel.writeString(itemDescription); parcel.writeString(calories); parcel.writeString(mainIngredients); - //parcel.writeDouble(price); + parcel.writeDouble(price); parcel.writeInt(active?1:0); } } diff --git a/app/src/main/java/com/mealer/ui/AdminHomePage.java b/app/src/main/java/com/mealer/ui/AdminHomePage.java index 6658629..9cb07ce 100644 --- a/app/src/main/java/com/mealer/ui/AdminHomePage.java +++ b/app/src/main/java/com/mealer/ui/AdminHomePage.java @@ -23,17 +23,18 @@ import com.mealer.ui.ui.complaint.ComplaintFragment; import com.mealer.ui.ui.complaint.ComplaintsListFragment; +import java.util.Objects; + public class AdminHomePage extends AppCompatActivity implements OnFragmentInteractionListener { private NavController navController; private ActivityAdminHomePageBinding binding; - Fragment fragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getSupportActionBar().hide(); + Objects.requireNonNull(getSupportActionBar()).hide(); binding = ActivityAdminHomePageBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -41,7 +42,7 @@ protected void onCreate(Bundle savedInstanceState) { // Passing each menu ID as a set of Ids because each // menu should be considered as top level destinations. AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder( - R.id.navigation_home, R.id.navigation_complaints, R.id.navigation_notifications, R.id.navigation_account) + R.id.navigation_complaints, R.id.navigation_account) .build(); final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() diff --git a/app/src/main/java/com/mealer/ui/ClientHomePage.java b/app/src/main/java/com/mealer/ui/ClientHomePage.java index baae6a9..6ad0448 100644 --- a/app/src/main/java/com/mealer/ui/ClientHomePage.java +++ b/app/src/main/java/com/mealer/ui/ClientHomePage.java @@ -12,15 +12,18 @@ import com.mealer.ui.databinding.ActivityClientHomePageBinding; -public class ClientHomePage extends AppCompatActivity { +import java.util.Objects; + +public class ClientHomePage extends AppCompatActivity implements OnFragmentInteractionListener { private ActivityClientHomePageBinding binding; + private NavController navController; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getSupportActionBar().hide(); + Objects.requireNonNull(getSupportActionBar()).hide(); binding = ActivityClientHomePageBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -28,15 +31,19 @@ protected void onCreate(Bundle savedInstanceState) { // Passing each menu ID as a set of Ids because each // menu should be considered as top level destinations. AppBarConfiguration appBarConfiguration = new AppBarConfiguration.Builder( - R.id.navigation_home, R.id.navigation_notifications, R.id.navigation_account) + R.id.navigation_client_home, R.id.navigation_notifications, R.id.navigation_account) .build(); final NavHostFragment navHostFragment = (NavHostFragment) getSupportFragmentManager() .findFragmentById(R.id.nav_host_fragment_activity_client_home_page); - final NavController navController = navHostFragment.getNavController(); + navController = navHostFragment.getNavController(); NavigationUI.setupWithNavController(navView, navController); NavigationUI.setupActionBarWithNavController(this, navController, appBarConfiguration); NavigationUI.setupWithNavController(binding.clientNavView, navController); } -} \ No newline at end of file + @Override + public void changeFragment(Bundle args, int id) { + navController.navigate(id, args); + } +} diff --git a/app/src/main/java/com/mealer/ui/CookHomePage.java b/app/src/main/java/com/mealer/ui/CookHomePage.java index f543c8d..a10558e 100644 --- a/app/src/main/java/com/mealer/ui/CookHomePage.java +++ b/app/src/main/java/com/mealer/ui/CookHomePage.java @@ -27,6 +27,7 @@ import com.mealer.ui.ui.menu.MenuNewFragment; import java.io.IOException; +import java.util.Objects; public class CookHomePage extends AppCompatActivity implements OnFragmentInteractionListener, UploadImage{ @@ -42,7 +43,7 @@ public class CookHomePage extends AppCompatActivity implements OnFragmentInterac protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - getSupportActionBar().hide(); + Objects.requireNonNull(getSupportActionBar()).hide(); binding = ActivityCookHomePageBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); diff --git a/app/src/main/java/com/mealer/ui/DietaryPreferences.java b/app/src/main/java/com/mealer/ui/DietaryPreferences.java index 36ca679..f242e92 100644 --- a/app/src/main/java/com/mealer/ui/DietaryPreferences.java +++ b/app/src/main/java/com/mealer/ui/DietaryPreferences.java @@ -59,7 +59,7 @@ private void setRestriction(String type){ .getReference("users"); HashMap update = new HashMap<>(); - update.put("dietary restriction", type); + update.put("dietaryRestriction", type); FirebaseUser currentFirebaseUser = this.mAuth.getCurrentUser(); if(currentFirebaseUser!=null){ diff --git a/app/src/main/java/com/mealer/ui/LoginPage.java b/app/src/main/java/com/mealer/ui/LoginPage.java index 15a1068..f44a05f 100644 --- a/app/src/main/java/com/mealer/ui/LoginPage.java +++ b/app/src/main/java/com/mealer/ui/LoginPage.java @@ -20,6 +20,7 @@ import com.mealer.app.Admin; import com.mealer.app.ClientUser; import com.mealer.app.CookUser; +import com.mealer.app.NotificationService; import com.mealer.app.User; import java.text.ParseException; @@ -47,6 +48,8 @@ public class LoginPage extends AppCompatActivity { // initializing firebase objects FirebaseAuth mAuth; DatabaseReference mDatabase; + String userId; + User user; @SuppressLint("MissingInflatedId") @Override @@ -159,12 +162,12 @@ private void getUser(FirebaseUser currentFirebaseUser){ // check if the user is banned or suspended // -1 is banned, 0 is nothing // any value >0 is the number of days the account is suspended - if("0".equals(userAttributes.get("accountStatus"))) { + if ("client".equals(userAttributes.get("userType"))) { + updateUI(currentFirebaseUser, new ClientUser(userAttributes)); + }else if("0".equals(userAttributes.get("accountStatus"))) { // check user type and update ui with new user object of that type if ("cook".equals(userAttributes.get("userType"))) { updateUI(currentFirebaseUser, new CookUser(userAttributes)); - } else if ("client".equals(userAttributes.get("userType"))) { - updateUI(currentFirebaseUser, new ClientUser(userAttributes)); } else { Log.e("signIn", "failed to detemine user type, userType: " + userAttributes.get("userType")); } @@ -248,12 +251,15 @@ private void updateUI(FirebaseUser currentFirebaseUser, User currentUser){ startActivity(getIntent()); return; } + user = currentUser; + userId = currentFirebaseUser.getUid(); Intent signIn = new Intent(this, ClientHomePage.class); if("cook".equals(currentUser.getUserType())){ signIn = new Intent(this, CookHomePage.class); } signIn.putExtra("TYPE", currentUser); startActivity(signIn); + finish(); } /** @@ -266,4 +272,12 @@ private boolean isAdmin(String email){ return email.equals(adminEmail); } + @Override + protected void onStop() { + super.onStop(); + Intent notifs = new Intent(this, NotificationService.class ); + notifs.putExtra("USER", user); + notifs.putExtra("ID", userId); + startService(notifs) ; + } } \ No newline at end of file diff --git a/app/src/main/java/com/mealer/ui/MainActivity.java b/app/src/main/java/com/mealer/ui/MainActivity.java index d93b911..4dc702f 100644 --- a/app/src/main/java/com/mealer/ui/MainActivity.java +++ b/app/src/main/java/com/mealer/ui/MainActivity.java @@ -2,20 +2,26 @@ import androidx.appcompat.app.AppCompatActivity; +import android.app.NotificationChannel; +import android.app.NotificationManager; import android.content.Intent; import android.os.Build; import android.os.Bundle; +import android.util.Log; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; +import com.mealer.app.NotificationService; import com.mealer.app.User; // GET STARTED public class MainActivity extends AppCompatActivity { + private static final String TAG = "MainActivity"; + Button signIn, accountExists; FirebaseAuth mAuth; @@ -24,6 +30,7 @@ public class MainActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_get_started_page); + createNotificationChannel(); // hide title bar getSupportActionBar().hide(); @@ -39,9 +46,28 @@ protected void onCreate(Bundle savedInstanceState) { accountExists = findViewById(R.id.accountExists); // sends to login page if user already has an account but needs to login - accountExists.setOnClickListener(x->startActivity( - new Intent(MainActivity.this, LoginPage.class) - )); + accountExists.setOnClickListener(x-> { + startActivity( + new Intent(MainActivity.this, LoginPage.class) + ); + finish(); + }); + } + + private void createNotificationChannel() { + // Create the NotificationChannel, but only on API 26+ because + // the NotificationChannel class is new and not in the support library + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + CharSequence name = getString(R.string.channel_name); + String description = getString(R.string.channel_description); + int importance = NotificationManager.IMPORTANCE_DEFAULT; + NotificationChannel channel = new NotificationChannel("lemubitA", name, importance); + channel.setDescription(description); + // Register the channel with the system; you can't change the importance + // or other notification behaviors after this + NotificationManager notificationManager = getSystemService(NotificationManager.class); + notificationManager.createNotificationChannel(channel); + } } // copied from firebase documentation @@ -55,7 +81,9 @@ public void onStart() { /* if user is logged into firebase authentication already sends user to login page where login page will detect the same thing, and create a new user object with the users attributes */ + Log.d(TAG, "Closing"); startActivity(new Intent(this, LoginPage.class)); + finish(); } } diff --git a/app/src/main/java/com/mealer/ui/ui/account/AccountPageFragment.java b/app/src/main/java/com/mealer/ui/ui/account/AccountPageFragment.java index 858850a..5d51453 100644 --- a/app/src/main/java/com/mealer/ui/ui/account/AccountPageFragment.java +++ b/app/src/main/java/com/mealer/ui/ui/account/AccountPageFragment.java @@ -1,37 +1,68 @@ package com.mealer.ui.ui.account; import android.annotation.SuppressLint; +import android.content.Context; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; +import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.ViewModelProvider; +import android.text.InputType; import android.util.Log; +import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.ArrayAdapter; import android.widget.Button; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.PopupWindow; +import android.widget.Spinner; import android.widget.TextView; import com.google.firebase.auth.FirebaseAuth; import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; import com.mealer.app.Admin; +import com.mealer.app.ClientUser; +import com.mealer.app.Complaint; +import com.mealer.app.CookUser; import com.mealer.app.User; import com.mealer.ui.LoginPage; +import com.mealer.ui.OnFragmentInteractionListener; +import com.mealer.ui.R; import com.mealer.ui.databinding.FragmentAccountPageBinding; +import java.util.HashMap; +import java.util.Objects; + public class AccountPageFragment extends Fragment { + private static final String TAG = "AccountPageFragment"; + + private static final int reload = R.id.action_navigation_account_self; + private OnFragmentInteractionListener mListener; + // initializing activity elements private TextView userType; private TextView userName; + private TextView userDescription; + private Button signOut; + private Button complain; private Admin signedIn; FragmentAccountPageBinding binding; + DatabaseReference dbRef; + DatabaseReference cRef; FirebaseAuth mAuth; @SuppressLint("SetTextI18n") @@ -44,9 +75,10 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, mAuth = FirebaseAuth.getInstance(); - Intent intent = this.getActivity().getIntent(); + Intent intent = this.requireActivity().getIntent(); userType = binding.userType; userName = binding.userName; + userDescription = binding.userCookDescription; // get current user object from intent try { @@ -57,28 +89,201 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, // get current firebase user from firebase authentication FirebaseUser currentFirebaseUser = mAuth.getCurrentUser(); + dbRef = FirebaseDatabase.getInstance().getReference("users"); + cRef = FirebaseDatabase.getInstance().getReference("complaints"); // display user type on home page - if(this.signedIn.getClass() != Admin.class){ - User currentUser = (User) this.signedIn; - userName.setText("Welcome " + currentUser.getFirstName() + " " + currentUser.getLastName() + "!"); - }else{ - userName.setText("Welcome Admin!"); - } - userType.setText("User Type: " + this.signedIn.getUserType()); - Log.d("firebase", "userType: " + this.signedIn.getUserType()); signOut = binding.signOut; signOut.setOnClickListener(c->signOut(currentFirebaseUser)); + complain = binding.complain; + complain.setVisibility(View.GONE); + + if(this.signedIn.getClass() != Admin.class && currentFirebaseUser != null){ + if(getArguments() != null) { + Bundle args = requireArguments(); + setArguments(null); + CookUser user = args.getParcelable("COOK"); + String cookId = args.getString("ID"); + userName.setText(user.getFirstName() + " " + user.getLastName()); + userType.setText("Cook Rating: " + user.getRating()); + userDescription.setText("Cook Description: \n" + user.getDescription()); + Log.d(TAG, "userType: " + this.signedIn.getUserType()); + + signOut.setText("Rate Cook"); + complain.setText("File Complaint"); + complain.setVisibility(View.VISIBLE); + if(signOut.hasOnClickListeners()){ + signOut.setOnClickListener( + onClick -> openPopup(this.getView(), user, cookId)); + } + complain.setOnClickListener( + onClick -> fileComplain(this.getView(), user, cookId)); + }else { + dbRef.child(currentFirebaseUser.getUid()).get().addOnCompleteListener(task -> { + if (task.isSuccessful()) { + DataSnapshot user = task.getResult(); + try { + CookUser currentUser = user.getValue(CookUser.class); + if(currentUser!=null) { + if(currentUser.getUserType().equals("client")){ + throw new ClassCastException(); + } + userName.setText(currentUser.getFirstName() + " " + currentUser.getLastName()); + userType.setText("Cook Rating: " + currentUser.getRating()); + userDescription.setText("Cook Description: \n" + currentUser.getDescription()); + Log.d(TAG, "userType: " + this.signedIn.getUserType()); + }else{ + Log.d(TAG, "currentUser is null"); + } + } catch (ClassCastException client) { + ClientUser currentUser = user.getValue(ClientUser.class); + if(currentUser != null) { + userName.setText(currentUser.getFirstName() + " " + currentUser.getLastName()); + userType.setText("welcome client"); + + // Centers the welcome text + LinearLayout.LayoutParams params = new LinearLayout.LayoutParams( + LinearLayout.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.MATCH_PARENT); + params.gravity = Gravity.CENTER; + userType.setLayoutParams(params); + + Log.d(TAG, "userType: " + this.signedIn.getUserType()); + }else{ + Log.d(TAG, "currentUser is null"); + } + } + } else { + Log.d(TAG, "Error getting user"); + } + }); + } + }else{ + userName.setText("Welcome Admin!"); + userType.setText("User Type: " + this.signedIn.getUserType()); + Log.d(TAG, "userType: " + this.signedIn.getUserType()); + } + return root; } + @SuppressLint({"MissingInflatedId", "ClickableViewAccessibility"}) + private void fileComplain(View view, CookUser user, String cookId) { + LayoutInflater inflater = getLayoutInflater(); + @SuppressLint("InflateParams") + View popup = inflater.inflate(R.layout.complaint_popup, null); + popup.setBackgroundColor(Color.BLACK); + + // create the popup window + int width = LinearLayout.LayoutParams.MATCH_PARENT; + int height = LinearLayout.LayoutParams.WRAP_CONTENT; + boolean focusable = true; // lets taps outside the popup also dismiss it + + final PopupWindow popupWindow = new PopupWindow(popup, width, height, focusable); + + Button submit = popup.findViewById(R.id.complaintSubmit); + EditText subject = popup.findViewById(R.id.complaintSubjectP); + EditText description = popup.findViewById(R.id.complaintDescriptionP); + + // show the popup window + // which view you pass in doesn't matter, it is only used for the window token + popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0); + + // dismiss the popup window when touched + popup.setOnTouchListener((v, event) -> { + popupWindow.dismiss(); + return true; + }); + + submit.setOnClickListener(c ->{ + Complaint complaint = new Complaint( + subject.getText().toString(), + description.getText().toString(), + cookId); + + cRef.child(complaint.getComplaintID()).setValue(complaint); + popupWindow.dismiss(); + updateUI(getArguments(), reload); + }); + } + @Override public void onDestroy() { super.onDestroy(); binding = null; } + /** + * Opens a small popup window where the admin can input the length of the cooks suspension + * @param view androidStudio view to show popup + */ + @SuppressLint({"ClickableViewAccessibility", "MissingInflatedId", "SetTextI18n"}) + public void openPopup(View view, CookUser user, String cookId){ + LayoutInflater inflater = getLayoutInflater(); + @SuppressLint("InflateParams") + View popup = inflater.inflate(R.layout.suspension_popup, null); + popup.setBackgroundColor(Color.GREEN); + + // create the popup window + int width = LinearLayout.LayoutParams.MATCH_PARENT; + int height = LinearLayout.LayoutParams.WRAP_CONTENT; + boolean focusable = true; // lets taps outside the popup also dismiss it + + final PopupWindow popupWindow = new PopupWindow(popup, width, height, focusable); + + Spinner monthDayChoice = popup.findViewById(R.id.spinner); + monthDayChoice.setVisibility(View.GONE); + + Button rate = popup.findViewById(R.id.completeSuspension); + rate.setText("Rate"); + + EditText rating = popup.findViewById(R.id.suspensionLength); + rating.setHint("Rating / 100"); + rating.setInputType(InputType.TYPE_CLASS_NUMBER); + + // show the popup window + // which view you pass in doesn't matter, it is only used for the window token + popupWindow.showAtLocation(view, Gravity.CENTER, 0, 0); + + // dismiss the popup window when touched + popup.setOnTouchListener((v, event) -> { + popupWindow.dismiss(); + return true; + }); + + rate.setOnClickListener(onClick -> { + HashMap update = new HashMap<>(); + int input = Integer.parseInt(rating.getText().toString()); + int newRating = (input + user.getIntRating())/2; // 50 == rating input + user.setRating(newRating); + update.put("rating", String.valueOf(user.getIntRating())); + dbRef.child(cookId).updateChildren(update); + popupWindow.dismiss(); + updateUI(getArguments(), reload); + }); + } + + /** + * gets the mListener object from the fragments context in order to be able to return to + * previous fragment and get the complaint info passed to this fragment + * @param context fragment context + */ + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + mListener = (OnFragmentInteractionListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + private void updateUI(Bundle args, int id) { + mListener.changeFragment(args, id); + } + + /** * signs current user that is signed in to firebase authentication out and sends to login page * @param currentFirebaseUser current user that is signed in @@ -87,6 +292,7 @@ private void signOut(FirebaseUser currentFirebaseUser){ if(currentFirebaseUser!=null){ mAuth.signOut(); } + setArguments(null); startActivity(new Intent(this.getContext(), LoginPage.class)); } } \ No newline at end of file diff --git a/app/src/main/java/com/mealer/ui/ui/menu/MenuAdapter.java b/app/src/main/java/com/mealer/ui/ui/menu/MenuAdapter.java index 423ef5b..7fa4ad7 100644 --- a/app/src/main/java/com/mealer/ui/ui/menu/MenuAdapter.java +++ b/app/src/main/java/com/mealer/ui/ui/menu/MenuAdapter.java @@ -3,6 +3,7 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.ViewGroup; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; @@ -35,6 +36,8 @@ public void onBindViewHolder(@NonNull MenuViewHolder holder, int position) { holder.name.setText(menuItemList.get(position).getItemName()); holder.calories.setText(menuItemList.get(position).getCalories()); holder.description.setText(menuItemList.get(position).getItemDescription()); + holder.price.setText(String.valueOf(menuItemList.get(position).getPrice())); + } @Override diff --git a/app/src/main/java/com/mealer/ui/ui/menu/MenuEditItemFragment.java b/app/src/main/java/com/mealer/ui/ui/menu/MenuEditItemFragment.java index ecfdb7b..48138c0 100644 --- a/app/src/main/java/com/mealer/ui/ui/menu/MenuEditItemFragment.java +++ b/app/src/main/java/com/mealer/ui/ui/menu/MenuEditItemFragment.java @@ -3,6 +3,7 @@ import android.content.Context; import android.graphics.Color; import android.os.Bundle; +import android.text.TextUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,6 +12,7 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -35,6 +37,7 @@ public class MenuEditItemFragment extends Fragment { private Button saveItem; private Button deleteItem; private EditText name; + private EditText price; private EditText description; private EditText calories; private EditText ingredients; @@ -58,6 +61,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c deleteItem = binding.delete; name = binding.itemNameText; + price = binding.itemPriceText; description = binding.itemDescriptionText; calories = binding.itemCaloriesText; ingredients = binding.itemIngredients; @@ -74,9 +78,11 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c name.setText(menuItem.getItemName()); description.setText(menuItem.getItemDescription()); calories.setText(menuItem.getCalories()); + price.setText(String.valueOf(menuItem.getPrice())); ingredients.setText(menuItem.getMainIngredients()); isActive.setChecked(menuItem.isActive()); + // on save item button set new item attrbiutes and updateMenu, then return to menuFragment saveItem.setOnClickListener(x->{ menuItem.setItemName(name.getText().toString()); @@ -84,6 +90,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c menuItem.setCalories(calories.getText().toString()); menuItem.setMainIngredients(ingredients.getText().toString()); + + + // if the state of the item active check box is different to the state of the item, // move the item to the other Map in the menu if(menuItem.isActive() != isActive.isChecked()){ @@ -92,8 +101,16 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c ValidateMenu validateMenu = new ValidateMenu(menuItem, this.getContext()); if(validateMenu.validateAll()){ - userMenu.updateMenu(); - updateUI(); + if(TextUtils.isEmpty(price.getText().toString())) { + if (this.getContext() != null) { + Toast.makeText(this.getContext(), "Price cannot be empty!", Toast.LENGTH_LONG).show(); + } + } else { + menuItem.setPrice(Double.parseDouble(price.getText().toString())); + userMenu.updateMenu(); + updateUI(); + } + } }); @@ -105,6 +122,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return root; } + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } + /** * gets the mListener object from the fragments context in order to be able to return to * previous fragment and get the complaint info passed to this fragment diff --git a/app/src/main/java/com/mealer/ui/ui/menu/MenuNewFragment.java b/app/src/main/java/com/mealer/ui/ui/menu/MenuNewFragment.java index 69cb9fd..86de666 100644 --- a/app/src/main/java/com/mealer/ui/ui/menu/MenuNewFragment.java +++ b/app/src/main/java/com/mealer/ui/ui/menu/MenuNewFragment.java @@ -63,6 +63,7 @@ public class MenuNewFragment extends Fragment { private EditText ingredients; private CheckBox isActive; private Button delete; + private EditText price; private Bundle args; @@ -81,6 +82,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c addItem = binding.addNewItem; name = binding.itemNameText; + price = binding.itemPriceText; description = binding.itemDescriptionText; calories = binding.itemCaloriesText; ingredients = binding.itemIngredients; @@ -113,6 +115,7 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c description.getText().toString(), calories.getText().toString(), ingredients.getText().toString().replace(" ", ""), + Double.parseDouble(price.getText().toString()), isActive.isChecked() ); @@ -128,6 +131,12 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c return root; } + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } + /** * gets the mListener object from the fragments context in order to be able to return to * previous fragment and get the complaint info passed to this fragment diff --git a/app/src/main/java/com/mealer/ui/ui/menu/MenuViewHolder.java b/app/src/main/java/com/mealer/ui/ui/menu/MenuViewHolder.java index 8a1fdd0..965ca19 100644 --- a/app/src/main/java/com/mealer/ui/ui/menu/MenuViewHolder.java +++ b/app/src/main/java/com/mealer/ui/ui/menu/MenuViewHolder.java @@ -14,11 +14,13 @@ public class MenuViewHolder extends RecyclerView.ViewHolder { protected TextView name; protected TextView calories; protected TextView description; + protected TextView price; public MenuViewHolder(@NonNull View itemView) { super(itemView); name = itemView.findViewById(R.id.itemName); calories = itemView.findViewById(R.id.itemCalories); description = itemView.findViewById(R.id.itemDescription); + price = itemView.findViewById(R.id.itemPriceDescription); } } diff --git a/app/src/main/java/com/mealer/ui/ui/menu/ValidateMenu.java b/app/src/main/java/com/mealer/ui/ui/menu/ValidateMenu.java index 0fcd3ab..1a3b976 100644 --- a/app/src/main/java/com/mealer/ui/ui/menu/ValidateMenu.java +++ b/app/src/main/java/com/mealer/ui/ui/menu/ValidateMenu.java @@ -1,18 +1,13 @@ package com.mealer.ui.ui.menu; import android.content.Context; +import android.text.TextUtils; import android.widget.Toast; import com.mealer.app.menu.MenuItem; public class ValidateMenu { - //TODO: validate inputs - // inputs cant be empty, - // all fields except calories must only contain a-z - // everything should be lower case - // ingredients should all be separated by semicolon (;) - // description can contain these characters: ()-;,.![]: private MenuItem menuItem; private Context context; public ValidateMenu(MenuItem menuItem, Context context) { @@ -62,6 +57,8 @@ protected boolean validateIngredients() { return true; } + + public boolean validateAll(){ return validateCalories() && validateIngredients() && validateDescription() && validateItemName(); } diff --git a/app/src/main/java/com/mealer/ui/ui/notifications/NotificationsFragment.java b/app/src/main/java/com/mealer/ui/ui/notifications/NotificationsFragment.java index 87674b7..da17530 100644 --- a/app/src/main/java/com/mealer/ui/ui/notifications/NotificationsFragment.java +++ b/app/src/main/java/com/mealer/ui/ui/notifications/NotificationsFragment.java @@ -1,7 +1,11 @@ package com.mealer.ui.ui.notifications; +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; import android.graphics.Color; import android.os.Bundle; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -9,26 +13,187 @@ import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; -import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.mealer.app.CookUser; +import com.mealer.app.Order; +import com.mealer.app.User; +import com.mealer.app.menu.MenuItem; +import com.mealer.ui.OnFragmentInteractionListener; +import com.mealer.ui.R; +import com.mealer.ui.databinding.FragmentComplaintsListBinding; import com.mealer.ui.databinding.FragmentNotificationsBinding; +import com.mealer.ui.ui.RecyclerItemClickListener; +import com.mealer.ui.ui.search.SearchAdapter; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Objects; public class NotificationsFragment extends Fragment { - private FragmentNotificationsBinding binding; + private final int navToDetails = R.id.action_navigation_notifications_to_navigation_order_item_details; + private final String TAG = "NotificationsFragment"; + + private FragmentComplaintsListBinding binding; + private OnFragmentInteractionListener mListener; + private DatabaseReference dbRef; + private Bundle args; + private FirebaseAuth mAuth; + private FirebaseUser currentUser; + + private User user; + private ArrayList orderList; + private Context context; + + private TextView title; + private RecyclerView orders; - public View onCreateView(@NonNull LayoutInflater inflater, - ViewGroup container, Bundle savedInstanceState) { - NotificationsViewModel notificationsViewModel = - new ViewModelProvider(this).get(NotificationsViewModel.class); + @SuppressLint({"SetTextI18n", "NotifyDataSetChanged"}) + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragmentNotificationsBinding.inflate(inflater, container, false); + binding = FragmentComplaintsListBinding.inflate(inflater, container, false); View root = binding.getRoot(); binding.getRoot().setBackgroundColor(Color.parseColor("#FEFAE0")); + mAuth = FirebaseAuth.getInstance(); + currentUser = mAuth.getCurrentUser(); + context = this.getContext(); + + args = getArguments(); + + title = binding.complaintsTextView; + orders = binding.complaintsView; + + orderList = new ArrayList<>(); + OrderAdapter orderAdapter = new OrderAdapter(context, orderList); + orders.setLayoutManager(new LinearLayoutManager(context)); + orders.setAdapter(orderAdapter); + orders.addOnItemTouchListener(menuItemDetails); + + Intent intent = this.requireActivity().getIntent(); + try{ + user = intent.getParcelableExtra("TYPE"); + }catch (ClassCastException cast){ + cast.printStackTrace(); + } + + dbRef = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("users"); + + if(user.getUserType().equals("cook")){ + title.setText("Order Requests"); + + dbRef.child(currentUser.getUid()).child("orders").get().addOnCompleteListener(task -> { + if(task.isSuccessful()){ + orderList.clear(); + ArrayList pending = new ArrayList<>(); + ArrayList rejected = new ArrayList<>(); + + DataSnapshot data = task.getResult(); + for(DataSnapshot child : data.getChildren()) { + Order order = child.getValue(Order.class); + if (order != null && !order.getOrderStatus().equals("deleted") && !order.getOrderStatus().equals("completed")) { + if(order.getOrderStatus().equals("rejected")){ + rejected.add(order); + }else if (order.getOrderStatus().equals("pending")){ + pending.add(order); + }else{ + orderList.add(order); + } + order.setOrderId(child.getKey()); + Log.d(TAG, "order added"); + } + } + + orderList.addAll(pending); + orderList.addAll(rejected); + orderAdapter.notifyDataSetChanged(); + }else{ + Log.e(TAG, "error"); + } + }); + }else{ + title.setText("My Orders"); + + dbRef.get().addOnCompleteListener(task -> { + if(task.isSuccessful()){ + DataSnapshot data = task.getResult(); + for(DataSnapshot child : data.getChildren()){ + try { + if ("cook".equals(Objects.requireNonNull(child.getValue(CookUser.class)).getUserType())) { + for (DataSnapshot grandChild : child.child("orders").getChildren()) { + Order order = grandChild.getValue(Order.class); + if (order != null + && !order.getOrderStatus().equals("deleted") + && !order.getOrderStatus().equals("completed") + && currentUser.getUid().equals(order.getOrderFrom())) { + orderList.add(order); + } + } + orderAdapter.notifyDataSetChanged(); + } + }catch (NullPointerException ignore){} + } + }else{ + Log.e(TAG, "client error"); + + } + }); + } + + return root; } + /** + * the onTouch listener for the activeMenu item + * listens for when an item is touched, and opens the details fragment + */ + private final RecyclerView.OnItemTouchListener menuItemDetails = + new RecyclerItemClickListener(context, this.orders, + new RecyclerItemClickListener.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + Bundle args = new Bundle(); + Order item = orderList.get(position); + args.putParcelable("ORDER", item); + updateUI(args, navToDetails); + } + + @Override + public void onLongItemClick(View view, int position) { + + } + }); + + /** + * gets the mListener object from the fragments context in order to be able to return to + * previous fragment and get the complaint info passed to this fragment + * @param context fragment context + */ + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + mListener = (OnFragmentInteractionListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + private void updateUI(Bundle args, int id) { + mListener.changeFragment(args, id); + } + @Override public void onDestroyView() { super.onDestroyView(); diff --git a/app/src/main/java/com/mealer/ui/ui/notifications/NotificationsViewModel.java b/app/src/main/java/com/mealer/ui/ui/notifications/NotificationsViewModel.java deleted file mode 100644 index 872f648..0000000 --- a/app/src/main/java/com/mealer/ui/ui/notifications/NotificationsViewModel.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.mealer.ui.ui.notifications; - -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.lifecycle.ViewModel; - -public class NotificationsViewModel extends ViewModel { - - private final MutableLiveData mText; - - public NotificationsViewModel() { - mText = new MutableLiveData<>(); - mText.setValue("This is notifications fragment"); - } - - public LiveData getText() { - return mText; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/mealer/ui/ui/notifications/OrderAdapter.java b/app/src/main/java/com/mealer/ui/ui/notifications/OrderAdapter.java new file mode 100644 index 0000000..d3ff70a --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/notifications/OrderAdapter.java @@ -0,0 +1,55 @@ +package com.mealer.ui.ui.notifications; + +import android.content.Context; +import android.graphics.Color; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.mealer.app.Order; +import com.mealer.app.menu.MenuItem; +import com.mealer.ui.R; + +import java.util.List; + +public class OrderAdapter extends RecyclerView.Adapter { + + private Context context; + private List orderList; + + public OrderAdapter(Context context, List orderList){ + this.context = context; + this.orderList = orderList; + } + + @NonNull + @Override + public OrderViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new OrderViewHolder(LayoutInflater.from(context) + .inflate(R.layout.order_item_view, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull OrderViewHolder holder, int position) { + MenuItem item = orderList.get(position).getOrderItem(); + + if(orderList.get(position).getOrderStatus().equals("accepted")){ + holder.layout.setBackgroundColor(Color.parseColor("#047F3F")); + }else if(orderList.get(position).getOrderStatus().equals("rejected")){ + holder.layout.setBackgroundColor(Color.parseColor("#9B0404")); + } + + holder.name.setText(item.getItemName()); + holder.description.setText(item.getItemDescription()); + holder.price.setText(String.valueOf(item.getPrice())); + holder.calories.setText(item.getCalories()); + holder.status.setText(orderList.get(position).getOrderStatus()); + } + + @Override + public int getItemCount() { + return orderList.size(); + } +} diff --git a/app/src/main/java/com/mealer/ui/ui/notifications/OrderDetailsFragment.java b/app/src/main/java/com/mealer/ui/ui/notifications/OrderDetailsFragment.java new file mode 100644 index 0000000..ed44126 --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/notifications/OrderDetailsFragment.java @@ -0,0 +1,211 @@ +package com.mealer.ui.ui.notifications; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.Intent; +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; + +import com.google.android.gms.tasks.Task; +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.mealer.app.ClientUser; +import com.mealer.app.Order; +import com.mealer.app.User; +import com.mealer.app.menu.MenuItem; +import com.mealer.ui.OnFragmentInteractionListener; +import com.mealer.ui.R; +import com.mealer.ui.databinding.FragmentComplaintBinding; +import com.mealer.ui.databinding.FragmentMenuItemDetailsBinding; + +import java.util.Locale; + +public class OrderDetailsFragment extends Fragment { + + private final int navToSearch = R.id.action_navigation_order_item_details_to_navigation_notifications; + private final String TAG = "OrderDetailsFragment"; + + private FragmentMenuItemDetailsBinding binding; + private OnFragmentInteractionListener mListener; + private FirebaseUser currentUser; + private Bundle args; + private Order order; + private MenuItem menuItem; + private User user; + + private TextView title; + private TextView caloriesTitle; + private EditText name; + private EditText desc; + private EditText price; + private EditText calories; + private EditText ingredients; + + private Button accept; + private Button reject; + private CheckBox remove; + + @SuppressLint("SetTextI18n") + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + binding = FragmentMenuItemDetailsBinding.inflate(inflater, container, false); + View root = binding.getRoot(); + root.setBackgroundColor(Color.parseColor("#FEFAE0")); + + currentUser = FirebaseAuth.getInstance().getCurrentUser(); + + Intent intent = this.requireActivity().getIntent(); + try{ + user = intent.getParcelableExtra("TYPE"); + }catch (ClassCastException cast){ + cast.printStackTrace(); + } + + args = getArguments(); + if(args != null) { + order = args.getParcelable("ORDER"); + menuItem = order.getOrderItem(); + } + + title = binding.menuItemInfo; + caloriesTitle = binding.itemCaloriesLabel; + + + title.setText("Order From: \n"); + DatabaseReference dbRef = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("users"); + + accept = binding.addNewItem; + accept.setText("Accept Order"); + reject = binding.delete; + reject.setText("Reject Order"); + + remove = binding.isActiveBox; + remove.setVisibility(View.GONE); + + name = binding.itemNameText; + name.setText(menuItem.getItemName()); + + price = binding.itemPriceText; + price.setText(String.valueOf(menuItem.getPrice())); + + desc = binding.itemDescriptionText; + desc.setText(menuItem.getItemDescription()); + + calories = binding.itemCaloriesText; + + ingredients = binding.itemIngredients; + ingredients.setText(menuItem.getMainIngredients()); + + + if(user.getUserType().equals("cook")){ + accept.setOnClickListener(onClick -> setOrderStatus(true)); + reject.setOnClickListener(onClick -> setOrderStatus(false)); + + if(order.getOrderStatus().equals("accepted")){ + reject.setVisibility(View.GONE); + accept.setText("Complete Order"); + accept.setOnClickListener(onCLick -> removeItem(true)); //do nothing atm + }else if(order.getOrderStatus().equals("rejected")){ + reject.setVisibility(View.GONE); + accept.setText("Delete Order"); + accept.setOnClickListener(onCLick -> removeItem(false)); + } + }else{ + title.setText("Order " + order.getOrderStatus()); + accept.setText("Return to My Orders"); + accept.setOnClickListener(onClick -> updateUI(null, navToSearch)); + calories.setText(menuItem.getCalories()); + reject.setVisibility(View.GONE); + } + + dbRef.child(order.getOrderFrom()).get().addOnCompleteListener(task -> { + if(task.isSuccessful()){ + DataSnapshot userData = task.getResult(); + ClientUser user = userData.getValue(ClientUser.class); + if (user != null) { + String fName = user.getFirstName(); + String lName = user.getLastName(); + + if(!user.getUserType().equals("cook")) { + title.setText("Order From: \n" + + fName.substring(0, 1).toUpperCase(Locale.ROOT) + fName.substring(1) + " " + + lName.substring(0, 1).toUpperCase(Locale.ROOT) + lName.substring(1)); + + caloriesTitle.setText("Dietary Restrictions"); + calories.setText(user.getDietaryRestriction()); + } + } + }else{ + Log.e(TAG, "Error"); + } + }); + + return root; + } + + private void setOrderStatus(boolean status){ + if(status){ + order.accepted(currentUser.getUid()); + }else{ + order.rejected(currentUser.getUid()); + } + + updateUI(null, navToSearch); + } + + private void removeItem(boolean status){ + if(status){ + order.completed(currentUser.getUid()); + }else{ + order.deleted(currentUser.getUid()); + } + + updateUI(null, navToSearch); + } + + /** + * gets the mListener object from the fragments context in order to be able to return to + * previous fragment and get the complaint info passed to this fragment + * @param context fragment context + */ + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + mListener = (OnFragmentInteractionListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + private void updateUI(Bundle args, int id) { + mListener.changeFragment(args, id); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + binding = null; + } +} diff --git a/app/src/main/java/com/mealer/ui/ui/notifications/OrderViewHolder.java b/app/src/main/java/com/mealer/ui/ui/notifications/OrderViewHolder.java new file mode 100644 index 0000000..22d2d27 --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/notifications/OrderViewHolder.java @@ -0,0 +1,30 @@ +package com.mealer.ui.ui.notifications; + +import android.text.Layout; +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.mealer.ui.R; + +public class OrderViewHolder extends RecyclerView.ViewHolder { + + protected TextView name; + protected TextView calories; + protected TextView description; + protected TextView price; + protected TextView status; + protected View layout; + + public OrderViewHolder(@NonNull View itemView) { + super(itemView); + name = itemView.findViewById(R.id.itemName); + calories = itemView.findViewById(R.id.itemCalories); + description = itemView.findViewById(R.id.itemDescription); + price = itemView.findViewById(R.id.itemPriceDescription); + status = itemView.findViewById(R.id.itemStatus); + layout = itemView.findViewById(R.id.orderLayout); + } +} diff --git a/app/src/main/java/com/mealer/ui/ui/search/CookListener.java b/app/src/main/java/com/mealer/ui/ui/search/CookListener.java new file mode 100644 index 0000000..af39885 --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/search/CookListener.java @@ -0,0 +1,9 @@ +package com.mealer.ui.ui.search; + +import com.mealer.app.CookUser; + +public interface CookListener { + + void onCookRecieved(CookUser cook); + +} diff --git a/app/src/main/java/com/mealer/ui/ui/search/SearchAdapter.java b/app/src/main/java/com/mealer/ui/ui/search/SearchAdapter.java new file mode 100644 index 0000000..37010db --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/search/SearchAdapter.java @@ -0,0 +1,44 @@ +package com.mealer.ui.ui.search; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.mealer.app.menu.MenuItem; +import com.mealer.ui.R; + +import java.util.List; + + +public class SearchAdapter extends RecyclerView.Adapter { + + Context context; + List menuItemList; + + public SearchAdapter(Context context, List menuItemList){ + this.context = context; + this.menuItemList = menuItemList; + } + + @NonNull + @Override + public SearchViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + return new SearchViewHolder(LayoutInflater.from(context) + .inflate(R.layout.menu_item_view, parent, false)); + } + + @Override + public void onBindViewHolder(@NonNull SearchViewHolder holder, int position) { + holder.name.setText(menuItemList.get(position).getItemName()); + holder.calories.setText(menuItemList.get(position).getCalories()); + holder.description.setText(menuItemList.get(position).getItemDescription()); + } + + @Override + public int getItemCount() { + return menuItemList.size(); + } +} diff --git a/app/src/main/java/com/mealer/ui/ui/search/SearchDetailsFragment.java b/app/src/main/java/com/mealer/ui/ui/search/SearchDetailsFragment.java new file mode 100644 index 0000000..869b702 --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/search/SearchDetailsFragment.java @@ -0,0 +1,232 @@ +package com.mealer.ui.ui.search; + +import android.annotation.SuppressLint; +import android.app.Notification; +import android.app.NotificationManager; +import android.content.Context; +import android.graphics.Color; +import android.os.Build; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Toast; + +import androidx.activity.result.ActivityResultCallback; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.RequiresApi; +import androidx.core.app.NotificationCompat; +import androidx.core.app.NotificationManagerCompat; +import androidx.fragment.app.Fragment; + +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.mealer.app.CookUser; +import com.mealer.app.Order; +import com.mealer.app.menu.MenuItem; +import com.mealer.ui.OnFragmentInteractionListener; +import com.mealer.ui.R; +import com.mealer.ui.databinding.FragmentMenuItemDetailsBinding; + +import java.util.Objects; +import java.util.UUID; + +public class SearchDetailsFragment extends Fragment { + + private final String TAG = "SearchDetailsFragment"; + private final int navToSearch = R.id.action_navigation_menu_item_details_to_navigation_client_home; + private final int navToAcc = R.id.action_navigation_menu_item_details_to_navigation_account; + + + private FragmentMenuItemDetailsBinding binding; + private OnFragmentInteractionListener mListener; + private NotificationManagerCompat manager; + private DatabaseReference cookRef; + private DatabaseReference orderRef; + private Bundle args; + private MenuItem item; + private String cookId; + + private FirebaseAuth mAuth; + + private EditText name; + private EditText desc; + private EditText calories; + private EditText ingredients; + private EditText price; + + private Button placeOrder; + private CheckBox remove; + private Button removeButton; + + @SuppressLint("SetTextI18n") + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + binding = FragmentMenuItemDetailsBinding.inflate(inflater, container, false); + View root = binding.getRoot(); + root.setBackgroundColor(Color.parseColor("#FEFAE0")); + + manager = NotificationManagerCompat.from(this.requireContext()); + mAuth = FirebaseAuth.getInstance(); + cookRef = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("users"); + + orderRef = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("orders"); + + name = binding.itemNameText; + desc = binding.itemDescriptionText; + calories = binding.itemCaloriesText; + ingredients = binding.itemIngredients; + price = binding.itemPriceText; + + args = getArguments(); + if(args!=null) { + item = args.getParcelable("ITEM"); + cookId = args.getString("COOK"); + } + + if(item!=null){ + name.setText(item.getItemName()); + desc.setText(item.getItemDescription()); + calories.setText(item.getCalories()); + ingredients.setText(item.getMainIngredients()); + price.setText(String.valueOf(item.getPrice())); + } + + name.setEnabled(false); + name.setBackgroundResource(android.R.color.transparent); + + desc.setEnabled(false); + desc.setBackgroundResource(android.R.color.transparent); + + calories.setEnabled(false); + calories.setBackgroundResource(android.R.color.transparent); + + ingredients.setEnabled(false); + ingredients.setBackgroundResource(android.R.color.transparent); + + price.setEnabled(false); + price.setBackgroundResource(android.R.color.transparent); + + placeOrder = binding.delete; + placeOrder.setText("Place Order"); + + remove = binding.isActiveBox; + remove.setVisibility(View.GONE); + + removeButton = binding.addNewItem; + removeButton.setText("View Cook Account Page"); + //removeButton.setVisibility(View.GONE); + + placeOrder.setOnClickListener(onCLick -> placeOrder(item)); + removeButton.setOnClickListener(onClick -> { + args = new Bundle(); + cookRef.child(cookId).get().addOnCompleteListener(task -> { + if(task.isSuccessful()){ + DataSnapshot data = task.getResult(); + args.putParcelable("COOK", data.getValue(CookUser.class)); + args.putString("ID", cookId); + updateUI(args, navToAcc); + } + }); + // args.putParcelable(); put cook user here / cook id + // go to cook account page + //updateUI(args, navToAcc); + }); + + return root; + } + + // Register the permissions callback, which handles the user's response to the + // system permissions dialog. Save the return value, an instance of + // ActivityResultLauncher, as an instance variable. + private final ActivityResultLauncher requestPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), isGranted -> { + if (isGranted) { + // Permission is granted. Continue the action or workflow in your + // app. + notify(manager); + Log.d(TAG, "notifs allowed"); + } else { + // Explain to the user that the feature is unavailable because the + // feature requires a permission that the user has denied. At the + // same time, respect the user's decision. Don't link to system + // settings in an effort to convince the user to change their + // decision. + Toast.makeText(this.requireContext(), "Notifications Disabled", Toast.LENGTH_LONG).show(); + Log.d(TAG, "notifs not allowed"); + } + }); + + //@RequiresApi(api = Build.VERSION_CODES.TIRAMISU) + private void placeOrder(MenuItem item){ + if(shouldShowRequestPermissionRationale(Notification.CATEGORY_EVENT)){ + Log.d(TAG, "requesting"); + } + + NotificationManagerCompat manager = NotificationManagerCompat.from(this.requireContext()); + if(manager.areNotificationsEnabled()){ + notify(manager); + }else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){ + requestPermissionLauncher.launch(android.Manifest.permission.POST_NOTIFICATIONS); + } + + if(mAuth.getCurrentUser() != null) { + Order order = new Order(item, "pending", mAuth.getCurrentUser().getUid()); + cookRef.child(cookId).child("orders").child(order.getOrderId()).setValue(order); + } + + updateUI(null, navToSearch); + } + + private void notify(NotificationManagerCompat notificationManagerCompat){ + NotificationCompat.Builder builder = new NotificationCompat.Builder(this.requireContext(), "lemubitA") + .setSmallIcon(R.drawable.ic_notifications_black_24dp) + .setContentTitle("Order Placed") + .setContentText("Your order has been placed. Please wait for the cook to action your order.") + .setPriority(NotificationCompat.PRIORITY_DEFAULT); + + notificationManagerCompat.notify(item.getItemId().hashCode(), builder.build()); + } + + /** + * gets the mListener object from the fragments context in order to be able to return to + * previous fragment and get the complaint info passed to this fragment + * @param context fragment context + */ + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + mListener = (OnFragmentInteractionListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + private void updateUI(Bundle args, int id) { + mListener.changeFragment(args, id); + } + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } +} diff --git a/app/src/main/java/com/mealer/ui/ui/search/SearchFragment.java b/app/src/main/java/com/mealer/ui/ui/search/SearchFragment.java new file mode 100644 index 0000000..e3603b2 --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/search/SearchFragment.java @@ -0,0 +1,260 @@ +package com.mealer.ui.ui.search; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.graphics.Color; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.firebase.auth.FirebaseAuth; +import com.google.firebase.auth.FirebaseUser; +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; +import com.google.firebase.database.ValueEventListener; +import com.mealer.app.CookUser; +import com.mealer.app.menu.Menu; +import com.mealer.app.menu.MenuItem; +import com.mealer.ui.OnFragmentInteractionListener; +import com.mealer.ui.databinding.FragmentClientHomeBinding; +import com.mealer.ui.ui.RecyclerItemClickListener; +import com.mealer.ui.ui.menu.MenuAdapter; +import com.mealer.ui.R; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.NoSuchElementException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +public class SearchFragment extends Fragment { + + private final String TAG = "SearchFragment"; + private final int navToDetails = R.id.action_navigation_client_home_to_navigation_menu_item_details; + + private FragmentClientHomeBinding binding; + private DatabaseReference menuData; + private DatabaseReference cookRef; + private FirebaseUser currentUser; + private OnFragmentInteractionListener mListener; + + private Context context; + + private EditText searchText; + private Button searchButton; + private RecyclerView searchResult; + + private ArrayList results; + private HashMap itemToCook; + private SearchAdapter searchAdapter; + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + + binding = FragmentClientHomeBinding.inflate(inflater, container, false); + View root = binding.getRoot(); + root.setBackgroundColor(Color.parseColor("#FEFAE0")); + + menuData = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("menus"); + cookRef = FirebaseDatabase + .getInstance("https://mealer-app-58f99-default-rtdb.firebaseio.com/") + .getReference("users"); + currentUser = FirebaseAuth.getInstance().getCurrentUser(); + + context = this.getContext(); + + searchButton = binding.searchMenu; + searchText = binding.searchText; + searchResult = binding.searchResult; + + + results = new ArrayList<>(); + searchAdapter = new SearchAdapter(context, results); + + searchResult.setLayoutManager(new LinearLayoutManager(context)); + searchResult.setAdapter(searchAdapter); + searchResult.addOnItemTouchListener(menuItemDetails); + + menuData.addValueEventListener(defaultEvent); + searchButton.setOnClickListener(onCLick -> { + if(!searchText.getText().toString().isEmpty()) { + menuData.removeEventListener(defaultEvent); + menuData.addValueEventListener(searchEvent); + }else{ + menuData.addValueEventListener(defaultEvent); + } + }); + + return root; + } + + ValueEventListener searchEvent = new ValueEventListener() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + results.clear(); + itemToCook = new HashMap<>(); + + String search = searchText.getText().toString(); + String[] keywords = search.toLowerCase(Locale.ROOT).split(" "); + + for(DataSnapshot child : snapshot.getChildren()){ + try { + getCook(child.getKey(), cook -> { + if(!"0".equals(cook.getAccountStatus())){ + Log.d(TAG, "cook suspended"); + }else{ + for (DataSnapshot grandchild : child.child("active").getChildren()) { + MenuItem item = grandchild.getValue(MenuItem.class); + for (String word : keywords) { + if (item != null && item.getItemName().toLowerCase(Locale.ROOT).contains(word)) { + itemToCook.put(item.getItemId(), child.getKey()); + results.add(item); + searchAdapter.notifyDataSetChanged(); + } + } + } + } + }); + }catch (NullPointerException nullPointerException){ + Log.e(TAG, Arrays.toString(nullPointerException.getStackTrace())); + } + } + + //searchResult.setAdapter(new SearchAdapter(context, results)); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + + } + }; + + ValueEventListener defaultEvent = new ValueEventListener() { + @SuppressLint("NotifyDataSetChanged") + @Override + public void onDataChange(@NonNull DataSnapshot snapshot) { + results.clear(); + itemToCook = new HashMap<>(); + for(DataSnapshot child : snapshot.getChildren()){ + try { + getCook(child.getKey(), cook -> { + if(!"0".equals(cook.getAccountStatus())){ + Log.d(TAG, "cook suspended"); + }else{ + try { + DataSnapshot grandchild = child.child("active").getChildren().iterator().next(); + MenuItem item = grandchild.getValue(MenuItem.class); + if(item != null) { + itemToCook.put(item.getItemId(), child.getKey()); + results.add(item); + searchAdapter.notifyDataSetChanged(); + } + } catch (NoSuchElementException noElem) { + Log.e(TAG, "no such element"); + } + } + }); + }catch (NullPointerException nullPointerException){ + Log.e(TAG, "null cook"); + } + } + + //searchResult.setAdapter(new SearchAdapter(context, results)); + } + + @Override + public void onCancelled(@NonNull DatabaseError error) { + + } + }; + + /** + * @param cookId the cook id to check if is suspended + * @param listener cookListener callback implementation + */ + private void getCook(String cookId, CookListener listener){ + //AtomicBoolean suspended = new AtomicBoolean(false); + cookRef.child(cookId).get().addOnCompleteListener(task -> { + if(task.isSuccessful()){ + CookUser cookUser = task.getResult().getValue(CookUser.class); + if(cookUser!=null && "0".equals(cookUser.getAccountStatus())){ + // suspended.set(true); + listener.onCookRecieved(cookUser); + } + }else{ + Log.d(TAG, "Error getting cook, " + task.getException()); + } + }); + //return suspended.get(); + } + + /** + * the onTouch listener for the activeMenu item + * listens for when an item is touched, and opens the details fragment + */ + private final RecyclerView.OnItemTouchListener menuItemDetails = + new RecyclerItemClickListener(context, this.searchResult, + new RecyclerItemClickListener.OnItemClickListener() { + @Override + public void onItemClick(View view, int position) { + Bundle args = new Bundle(); + MenuItem item = results.get(position); + args.putParcelable("ITEM", item); + args.putString("COOK", itemToCook.get(item.getItemId())); + updateUI(args, navToDetails); + } + + @Override + public void onLongItemClick(View view, int position) { + + } + }); + + /** + * gets the mListener object from the fragments context in order to be able to return to + * previous fragment and get the complaint info passed to this fragment + * @param context fragment context + */ + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + try { + mListener = (OnFragmentInteractionListener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + + " must implement OnFragmentInteractionListener"); + } + } + + private void updateUI(Bundle args, int id) { + mListener.changeFragment(args, id); + } + + @Override + public void onDestroy() { + super.onDestroy(); + binding = null; + } + +} diff --git a/app/src/main/java/com/mealer/ui/ui/search/SearchViewHolder.java b/app/src/main/java/com/mealer/ui/ui/search/SearchViewHolder.java new file mode 100644 index 0000000..cafbd35 --- /dev/null +++ b/app/src/main/java/com/mealer/ui/ui/search/SearchViewHolder.java @@ -0,0 +1,23 @@ +package com.mealer.ui.ui.search; + +import android.view.View; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import com.mealer.ui.R; + +public class SearchViewHolder extends RecyclerView.ViewHolder { + + protected TextView name; + protected TextView calories; + protected TextView description; + + public SearchViewHolder(@NonNull View itemView) { + super(itemView); + name = itemView.findViewById(R.id.itemName); + calories = itemView.findViewById(R.id.itemCalories); + description = itemView.findViewById(R.id.itemDescription); + } +} diff --git a/app/src/main/res/layout/activity_client_home_page.xml b/app/src/main/res/layout/activity_client_home_page.xml index a627fd6..113d7ca 100644 --- a/app/src/main/res/layout/activity_client_home_page.xml +++ b/app/src/main/res/layout/activity_client_home_page.xml @@ -22,8 +22,8 @@ -