Attachments to Chat #162
@@ -22,9 +22,15 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
private static final int TYPE_SENT = 1;
|
private static final int TYPE_SENT = 1;
|
||||||
private static final int TYPE_RECEIVED = 2;
|
private static final int TYPE_RECEIVED = 2;
|
||||||
|
|
||||||
|
public interface OnAttachmentClickListener {
|
||||||
|
void onAttachmentClick(Message message);
|
||||||
|
}
|
||||||
|
|
||||||
private final List<Message> messages;
|
private final List<Message> messages;
|
||||||
private Long currentUserId;
|
private Long currentUserId;
|
||||||
private String token;
|
private String token;
|
||||||
|
private String baseUrl;
|
||||||
|
private OnAttachmentClickListener attachmentClickListener;
|
||||||
|
|
||||||
public MessageAdapter(List<Message> messages, Long currentUserId) {
|
public MessageAdapter(List<Message> messages, Long currentUserId) {
|
||||||
this.messages = messages;
|
this.messages = messages;
|
||||||
@@ -40,6 +46,14 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
this.token = token;
|
this.token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setBaseUrl(String baseUrl) {
|
||||||
|
this.baseUrl = baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setOnAttachmentClickListener(OnAttachmentClickListener listener) {
|
||||||
|
this.attachmentClickListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getItemViewType(int position) {
|
public int getItemViewType(int position) {
|
||||||
Message m = messages.get(position);
|
Message m = messages.get(position);
|
||||||
@@ -64,8 +78,8 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
@Override
|
@Override
|
||||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
|
||||||
Message m = messages.get(position);
|
Message m = messages.get(position);
|
||||||
if (holder instanceof SentHolder) ((SentHolder) holder).bind(m, token);
|
if (holder instanceof SentHolder) ((SentHolder) holder).bind(m, token, baseUrl, attachmentClickListener);
|
||||||
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token);
|
if (holder instanceof ReceivedHolder) ((ReceivedHolder) holder).bind(m, token, baseUrl, attachmentClickListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public int getItemCount() { return messages.size(); }
|
@Override public int getItemCount() { return messages.size(); }
|
||||||
@@ -76,9 +90,23 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
void bind(Message m, String token) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||||
binding.tvMessageContent.setText(m.getContent());
|
// Check for Text
|
||||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token);
|
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||||
|
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvMessageContent.setText(m.getContent());
|
||||||
|
} else {
|
||||||
|
binding.tvMessageContent.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Attachment
|
||||||
|
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||||
|
|
||||||
|
View.OnClickListener click = v -> {
|
||||||
|
if (listener != null) listener.onAttachmentClick(m);
|
||||||
|
};
|
||||||
|
binding.ivAttachment.setOnClickListener(click);
|
||||||
|
binding.tvAttachmentName.setOnClickListener(click);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,22 +116,52 @@ public class MessageAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder
|
|||||||
super(binding.getRoot());
|
super(binding.getRoot());
|
||||||
this.binding = binding;
|
this.binding = binding;
|
||||||
}
|
}
|
||||||
void bind(Message m, String token) {
|
void bind(Message m, String token, String baseUrl, OnAttachmentClickListener listener) {
|
||||||
binding.tvMessageContent.setText(m.getContent());
|
// Check for Text
|
||||||
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token);
|
if (m.getContent() != null && !m.getContent().isEmpty()) {
|
||||||
|
binding.tvMessageContent.setVisibility(View.VISIBLE);
|
||||||
|
binding.tvMessageContent.setText(m.getContent());
|
||||||
|
} else {
|
||||||
|
binding.tvMessageContent.setVisibility(View.GONE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for Attachment
|
||||||
|
displayAttachment(m, binding.ivAttachment, binding.tvAttachmentName, token, baseUrl);
|
||||||
|
|
||||||
|
View.OnClickListener click = v -> {
|
||||||
|
if (listener != null) listener.onAttachmentClick(m);
|
||||||
|
};
|
||||||
|
binding.ivAttachment.setOnClickListener(click);
|
||||||
|
binding.tvAttachmentName.setOnClickListener(click);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// helper function to display the attachment to the chat bubble
|
// helper function to display the attachment to the chat bubble
|
||||||
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token) {
|
private static void displayAttachment(Message m, ImageView iv, TextView tvName, String token, String baseUrl) {
|
||||||
if (m.getAttachmentUrl() != null) {
|
// Check if there's an attachment by looking at name or mime type
|
||||||
if (m.getAttachmentType() != null && m.getAttachmentType().startsWith("image/")) {
|
if (m.getAttachmentName() != null || m.getAttachmentMimeType() != null) {
|
||||||
|
// Construct the download URL using the message ID
|
||||||
|
String url;
|
||||||
|
if (baseUrl != null) {
|
||||||
|
String cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
|
url = cleanBase + "/api/v1/chat/messages/" + m.getId() + "/attachment";
|
||||||
|
} else {
|
||||||
|
url = m.getAttachmentUrl(); // Fallback
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url == null) {
|
||||||
|
iv.setVisibility(View.GONE);
|
||||||
|
tvName.setVisibility(View.GONE);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m.getAttachmentMimeType() != null && m.getAttachmentMimeType().startsWith("image/")) {
|
||||||
iv.setVisibility(View.VISIBLE);
|
iv.setVisibility(View.VISIBLE);
|
||||||
tvName.setVisibility(View.GONE);
|
tvName.setVisibility(View.GONE);
|
||||||
|
|
||||||
Object loadTarget = m.getAttachmentUrl();
|
Object loadTarget = url;
|
||||||
if (token != null && m.getAttachmentUrl().startsWith("http")) {
|
if (token != null) {
|
||||||
loadTarget = new GlideUrl(m.getAttachmentUrl(), new LazyHeaders.Builder()
|
loadTarget = new GlideUrl(url, new LazyHeaders.Builder()
|
||||||
.addHeader("Authorization", "Bearer " + token)
|
.addHeader("Authorization", "Bearer " + token)
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -18,4 +19,7 @@ public interface CustomerApi {
|
|||||||
|
|
||||||
@GET("api/v1/customers/{customerId}")
|
@GET("api/v1/customers/{customerId}")
|
||||||
Call<CustomerDTO> getCustomerById(@Path("customerId") Long customerId);
|
Call<CustomerDTO> getCustomerById(@Path("customerId") Long customerId);
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/customers")
|
||||||
|
Call<List<DropdownDTO>> getCustomerDropdowns();
|
||||||
}
|
}
|
||||||
@@ -3,11 +3,17 @@ package com.example.petstoremobile.api;
|
|||||||
import com.example.petstoremobile.dtos.MessageDTO;
|
import com.example.petstoremobile.dtos.MessageDTO;
|
||||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.Body;
|
import retrofit2.http.Body;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Multipart;
|
||||||
import retrofit2.http.POST;
|
import retrofit2.http.POST;
|
||||||
|
import retrofit2.http.Part;
|
||||||
import retrofit2.http.Path;
|
import retrofit2.http.Path;
|
||||||
|
import retrofit2.http.Streaming;
|
||||||
|
|
||||||
//api calls to get and send messages
|
//api calls to get and send messages
|
||||||
public interface MessageApi {
|
public interface MessageApi {
|
||||||
@@ -17,4 +23,16 @@ public interface MessageApi {
|
|||||||
|
|
||||||
@POST("api/v1/chat/conversations/{id}/messages")
|
@POST("api/v1/chat/conversations/{id}/messages")
|
||||||
Call<MessageDTO> sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request);
|
Call<MessageDTO> sendMessage(@Path("id") Long conversationId, @Body SendMessageRequest request);
|
||||||
|
|
||||||
|
@Multipart
|
||||||
|
@POST("api/v1/chat/conversations/{id}/attachments")
|
||||||
|
Call<MessageDTO> sendMessageWithAttachment(
|
||||||
|
@Path("id") Long conversationId,
|
||||||
|
@Part MultipartBody.Part content,
|
||||||
|
@Part MultipartBody.Part file
|
||||||
|
);
|
||||||
|
|
||||||
|
@GET("api/v1/chat/messages/{id}/attachment")
|
||||||
|
@Streaming
|
||||||
|
Call<ResponseBody> downloadAttachment(@Path("id") Long messageId);
|
||||||
}
|
}
|
||||||
@@ -1,9 +1,12 @@
|
|||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import okhttp3.MultipartBody;
|
import okhttp3.MultipartBody;
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.Body;
|
import retrofit2.http.Body;
|
||||||
@@ -31,9 +34,16 @@ public interface PetApi {
|
|||||||
@Query("status") String status,
|
@Query("status") String status,
|
||||||
@Query("species") String species,
|
@Query("species") String species,
|
||||||
@Query("storeId") Long storeId,
|
@Query("storeId") Long storeId,
|
||||||
|
@Query("customerId") Long customerId,
|
||||||
@Query("sort") String sort
|
@Query("sort") String sort
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/customers/{customerId}/pets")
|
||||||
|
Call<List<DropdownDTO>> getCustomerPets(@Path("customerId") Long customerId);
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/adoption-pets")
|
||||||
|
Call<List<DropdownDTO>> getAdoptionPets();
|
||||||
|
|
||||||
// Get pet by id
|
// Get pet by id
|
||||||
@GET("api/v1/pets/{id}")
|
@GET("api/v1/pets/{id}")
|
||||||
Call<PetDTO> getPetById(@Path("id") Long id);
|
Call<PetDTO> getPetById(@Path("id") Long id);
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
package com.example.petstoremobile.api;
|
package com.example.petstoremobile.api;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import retrofit2.Call;
|
import retrofit2.Call;
|
||||||
import retrofit2.http.GET;
|
import retrofit2.http.GET;
|
||||||
|
import retrofit2.http.Path;
|
||||||
import retrofit2.http.Query;
|
import retrofit2.http.Query;
|
||||||
|
|
||||||
public interface StoreApi {
|
public interface StoreApi {
|
||||||
@@ -13,4 +17,10 @@ public interface StoreApi {
|
|||||||
Call<PageResponse<StoreDTO>> getAllStores(
|
Call<PageResponse<StoreDTO>> getAllStores(
|
||||||
@Query("page") int page,
|
@Query("page") int page,
|
||||||
@Query("size") int size);
|
@Query("size") int size);
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/stores")
|
||||||
|
Call<List<DropdownDTO>> getStoreDropdowns();
|
||||||
|
|
||||||
|
@GET("api/v1/dropdowns/stores/{storeId}/employees")
|
||||||
|
Call<List<DropdownDTO>> getStoreEmployees(@Path("storeId") Long storeId);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ public class NetworkModule {
|
|||||||
@Singleton
|
@Singleton
|
||||||
public static OkHttpClient provideOkHttpClient(TokenManager tokenManager) {
|
public static OkHttpClient provideOkHttpClient(TokenManager tokenManager) {
|
||||||
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
|
||||||
interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
|
interceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||||
|
|
||||||
return new OkHttpClient.Builder()
|
return new OkHttpClient.Builder()
|
||||||
.addInterceptor(interceptor)
|
.addInterceptor(interceptor)
|
||||||
@@ -191,4 +191,4 @@ public class NetworkModule {
|
|||||||
public static RefundApi provideRefundApi(Retrofit retrofit) {
|
public static RefundApi provideRefundApi(Retrofit retrofit) {
|
||||||
return retrofit.create(RefundApi.class);
|
return retrofit.create(RefundApi.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -12,6 +12,14 @@ public class ConversationDTO {
|
|||||||
|
|
||||||
public ConversationDTO() {}
|
public ConversationDTO() {}
|
||||||
|
|
||||||
|
public ConversationDTO(Long id, Long customerId, Long staffId, String lastMessage, String status) {
|
||||||
|
this.id = id;
|
||||||
|
this.customerId = customerId;
|
||||||
|
this.staffId = staffId;
|
||||||
|
this.lastMessage = lastMessage;
|
||||||
|
this.status = status;
|
||||||
|
}
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package com.example.petstoremobile.dtos;
|
||||||
|
|
||||||
|
public class DropdownDTO {
|
||||||
|
private Long id;
|
||||||
|
private String label;
|
||||||
|
|
||||||
|
public DropdownDTO() {}
|
||||||
|
|
||||||
|
public DropdownDTO(Long id, String label) {
|
||||||
|
this.id = id;
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Long getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setId(Long id) {
|
||||||
|
this.id = id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLabel() {
|
||||||
|
return label;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLabel(String label) {
|
||||||
|
this.label = label;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,8 +28,11 @@ public class MessageDTO {
|
|||||||
@SerializedName("attachmentName")
|
@SerializedName("attachmentName")
|
||||||
private String attachmentName;
|
private String attachmentName;
|
||||||
|
|
||||||
@SerializedName("attachmentType")
|
@SerializedName("attachmentMimeType")
|
||||||
private String attachmentType;
|
private String attachmentMimeType;
|
||||||
|
|
||||||
|
@SerializedName("attachmentSizeBytes")
|
||||||
|
private Long attachmentSizeBytes;
|
||||||
|
|
||||||
public MessageDTO() {}
|
public MessageDTO() {}
|
||||||
|
|
||||||
@@ -57,6 +60,9 @@ public class MessageDTO {
|
|||||||
public String getAttachmentName() { return attachmentName; }
|
public String getAttachmentName() { return attachmentName; }
|
||||||
public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; }
|
public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; }
|
||||||
|
|
||||||
public String getAttachmentType() { return attachmentType; }
|
public String getAttachmentMimeType() { return attachmentMimeType; }
|
||||||
public void setAttachmentType(String attachmentType) { this.attachmentType = attachmentType; }
|
public void setAttachmentMimeType(String attachmentMimeType) { this.attachmentMimeType = attachmentMimeType; }
|
||||||
|
|
||||||
|
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
||||||
|
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
||||||
}
|
}
|
||||||
@@ -1,15 +1,27 @@
|
|||||||
package com.example.petstoremobile.fragments;
|
package com.example.petstoremobile.fragments;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
|
import android.app.Dialog;
|
||||||
|
import android.content.ContentValues;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.database.Cursor;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.OpenableColumns;
|
import android.os.Environment;
|
||||||
|
import android.provider.MediaStore;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.*;
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.ViewGroup;
|
||||||
import android.view.inputmethod.EditorInfo;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ImageButton;
|
||||||
|
import android.widget.ImageView;
|
||||||
|
import android.widget.LinearLayout;
|
||||||
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.activity.result.ActivityResultLauncher;
|
import androidx.activity.result.ActivityResultLauncher;
|
||||||
import androidx.activity.result.contract.ActivityResultContracts;
|
import androidx.activity.result.contract.ActivityResultContracts;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -17,6 +29,7 @@ import androidx.core.view.GravityCompat;
|
|||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import com.bumptech.glide.Glide;
|
import com.bumptech.glide.Glide;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.ChatAdapter;
|
import com.example.petstoremobile.adapters.ChatAdapter;
|
||||||
@@ -25,20 +38,32 @@ import com.example.petstoremobile.api.auth.TokenManager;
|
|||||||
import com.example.petstoremobile.databinding.FragmentChatBinding;
|
import com.example.petstoremobile.databinding.FragmentChatBinding;
|
||||||
import com.example.petstoremobile.dtos.ConversationDTO;
|
import com.example.petstoremobile.dtos.ConversationDTO;
|
||||||
import com.example.petstoremobile.dtos.MessageDTO;
|
import com.example.petstoremobile.dtos.MessageDTO;
|
||||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
|
||||||
import com.example.petstoremobile.models.Chat;
|
import com.example.petstoremobile.models.Chat;
|
||||||
import com.example.petstoremobile.models.Message;
|
import com.example.petstoremobile.models.Message;
|
||||||
import com.example.petstoremobile.services.ChatNotificationService;
|
import com.example.petstoremobile.services.ChatNotificationService;
|
||||||
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.FileUtils;
|
||||||
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.viewmodels.ChatViewModel;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
import com.example.petstoremobile.viewmodels.ChatListViewModel;
|
||||||
import com.example.petstoremobile.websocket.StompChatManager;
|
import com.example.petstoremobile.websocket.StompChatManager;
|
||||||
|
|
||||||
import java.util.*;
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
import okhttp3.MediaType;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener,
|
public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickListener, StompChatManager.MessageListener,
|
||||||
@@ -47,59 +72,46 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
private static final String TAG = "ChatFragment";
|
private static final String TAG = "ChatFragment";
|
||||||
|
|
||||||
private FragmentChatBinding binding;
|
private FragmentChatBinding binding;
|
||||||
private ChatViewModel viewModel;
|
private ChatListViewModel viewModel;
|
||||||
|
|
||||||
// Adapters
|
private ChatAdapter activeChatAdapter;
|
||||||
private ChatAdapter chatAdapter;
|
private ChatAdapter closedChatAdapter;
|
||||||
private MessageAdapter messageAdapter;
|
private MessageAdapter messageAdapter;
|
||||||
|
|
||||||
// Data
|
private final List<Chat> activeChatList = new ArrayList<>();
|
||||||
private final List<Chat> chatList = new ArrayList<>();
|
private final List<Chat> closedChatList = new ArrayList<>();
|
||||||
private final List<Message> messageList = new ArrayList<>();
|
private final List<Message> messageList = new ArrayList<>();
|
||||||
private final Map<Long, String> customerNames = new HashMap<>();
|
|
||||||
private Uri pendingAttachmentUri;
|
private Uri pendingAttachmentUri;
|
||||||
|
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
|
|
||||||
// chat
|
|
||||||
private Long currentUserId;
|
|
||||||
private Long activeConversationId;
|
private Long activeConversationId;
|
||||||
private StompChatManager stompChatManager;
|
private StompChatManager stompChatManager;
|
||||||
private ActivityResultLauncher<Intent> attachmentLauncher;
|
private ActivityResultLauncher<Intent> attachmentLauncher;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the attachment launcher to handle file selection from the gallery.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ChatViewModel.class);
|
viewModel = new ViewModelProvider(requireActivity()).get(ChatListViewModel.class);
|
||||||
attachmentLauncher = registerForActivityResult(
|
attachmentLauncher = registerForActivityResult(
|
||||||
new ActivityResultContracts.StartActivityForResult(),
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
result -> {
|
result -> {
|
||||||
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) {
|
||||||
Uri uri = result.getData().getData();
|
Uri uri = result.getData().getData();
|
||||||
if (uri != null) {
|
if (uri != null) showAttachmentPreview(uri);
|
||||||
showAttachmentPreview(uri);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inflates the layout, initializes UI components, and sets up click listeners for messaging.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater,
|
public View onCreateView(@NonNull LayoutInflater inflater,
|
||||||
ViewGroup container, Bundle savedInstanceState) {
|
ViewGroup container, Bundle savedInstanceState) {
|
||||||
|
|
||||||
binding = FragmentChatBinding.inflate(inflater, container, false);
|
binding = FragmentChatBinding.inflate(inflater, container, false);
|
||||||
|
|
||||||
binding.btnHamburger.setOnClickListener(v -> binding.chatDrawerLayout.openDrawer(GravityCompat.START));
|
binding.btnHamburger.setOnClickListener(v -> binding.chatDrawerLayout.openDrawer(GravityCompat.START));
|
||||||
|
|
||||||
// Set editor action listener for message field to send when enter is pressed
|
|
||||||
binding.etMessage.setOnEditorActionListener((v, actionId, event) -> {
|
binding.etMessage.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
if (actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_NULL) {
|
if (actionId == EditorInfo.IME_ACTION_SEND || actionId == EditorInfo.IME_NULL) {
|
||||||
binding.btnSend.performClick();
|
binding.btnSend.performClick();
|
||||||
@@ -108,63 +120,208 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
//When the send button is clicked check if there is an attachment and send using the correct helper function
|
|
||||||
binding.btnSend.setOnClickListener(v -> {
|
binding.btnSend.setOnClickListener(v -> {
|
||||||
if (pendingAttachmentUri != null) {
|
if (pendingAttachmentUri != null) sendWithAttachment(pendingAttachmentUri);
|
||||||
sendWithAttachment(pendingAttachmentUri);
|
else sendMessage();
|
||||||
} else {
|
|
||||||
sendMessage();
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
//When the attachment button is clicked open the file picker
|
|
||||||
binding.btnAttach.setOnClickListener(v -> selectAttachment());
|
binding.btnAttach.setOnClickListener(v -> selectAttachment());
|
||||||
binding.btnRemoveAttachment.setOnClickListener(v -> removeAttachment());
|
binding.btnRemoveAttachment.setOnClickListener(v -> removeAttachment());
|
||||||
|
|
||||||
|
setupDrawerToggles();
|
||||||
setupRecyclerViews();
|
setupRecyclerViews();
|
||||||
|
observeViewModel();
|
||||||
loadInitialData();
|
loadInitialData();
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setupDrawerToggles() {
|
||||||
* Configures the RecyclerViews for the conversation list and the message history.
|
binding.headerActiveChats.setOnClickListener(v -> {
|
||||||
*/
|
if (binding.rvActiveChats.getVisibility() == View.VISIBLE) {
|
||||||
private void setupRecyclerViews() {
|
binding.rvActiveChats.setVisibility(View.GONE);
|
||||||
// Set up Drawer menu to select conversation
|
binding.ivActiveChevron.setImageResource(android.R.drawable.arrow_down_float);
|
||||||
chatAdapter = new ChatAdapter(chatList, this);
|
} else {
|
||||||
binding.rvChatList.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.rvActiveChats.setVisibility(View.VISIBLE);
|
||||||
binding.rvChatList.setAdapter(chatAdapter);
|
binding.ivActiveChevron.setImageResource(android.R.drawable.arrow_up_float);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.headerClosedChats.setOnClickListener(v -> {
|
||||||
|
if (binding.rvClosedChats.getVisibility() == View.VISIBLE) {
|
||||||
|
binding.rvClosedChats.setVisibility(View.GONE);
|
||||||
|
binding.ivClosedChevron.setImageResource(android.R.drawable.arrow_down_float);
|
||||||
|
} else {
|
||||||
|
binding.rvClosedChats.setVisibility(View.VISIBLE);
|
||||||
|
binding.ivClosedChevron.setImageResource(android.R.drawable.arrow_up_float);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerViews() {
|
||||||
|
activeChatAdapter = new ChatAdapter(activeChatList, this);
|
||||||
|
binding.rvActiveChats.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.rvActiveChats.setAdapter(activeChatAdapter);
|
||||||
|
|
||||||
|
closedChatAdapter = new ChatAdapter(closedChatList, this);
|
||||||
|
binding.rvClosedChats.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.rvClosedChats.setAdapter(closedChatAdapter);
|
||||||
|
|
||||||
// set up RecyclerView for selected chat to show messages
|
|
||||||
messageAdapter = new MessageAdapter(messageList, null);
|
messageAdapter = new MessageAdapter(messageList, null);
|
||||||
|
messageAdapter.setBaseUrl(baseUrl);
|
||||||
|
|
||||||
|
messageAdapter.setOnAttachmentClickListener(message -> {
|
||||||
|
if (message.getAttachmentMimeType() != null && message.getAttachmentMimeType().startsWith("image/")) {
|
||||||
|
showFullScreenImage(message);
|
||||||
|
} else {
|
||||||
|
downloadFile(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
LinearLayoutManager lm = new LinearLayoutManager(getContext());
|
LinearLayoutManager lm = new LinearLayoutManager(getContext());
|
||||||
lm.setStackFromEnd(true);
|
lm.setStackFromEnd(true);
|
||||||
binding.rvMessages.setLayoutManager(lm);
|
binding.rvMessages.setLayoutManager(lm);
|
||||||
binding.rvMessages.setAdapter(messageAdapter);
|
binding.rvMessages.setAdapter(messageAdapter);
|
||||||
setConversationActive(false);
|
setConversationActive(false, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showFullScreenImage(Message message) {
|
||||||
|
if (baseUrl == null || message.getId() == null) return;
|
||||||
|
|
||||||
|
Dialog dialog = new Dialog(requireContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen);
|
||||||
|
dialog.setContentView(R.layout.dialog_full_screen_image);
|
||||||
|
|
||||||
|
ImageView imageView = dialog.findViewById(R.id.ivFullScreen);
|
||||||
|
ImageButton closeButton = dialog.findViewById(R.id.btnClose);
|
||||||
|
ImageButton downloadButton = dialog.findViewById(R.id.btnDownload);
|
||||||
|
|
||||||
|
String cleanBase = baseUrl.endsWith("/") ? baseUrl.substring(0, baseUrl.length() - 1) : baseUrl;
|
||||||
|
String downloadUrl = cleanBase + "/api/v1/chat/messages/" + message.getId() + "/attachment";
|
||||||
|
|
||||||
|
GlideUtils.loadImageWithToken(requireContext(), imageView, downloadUrl, tokenManager.getToken(), R.drawable.placeholder);
|
||||||
|
|
||||||
|
closeButton.setOnClickListener(v -> dialog.dismiss());
|
||||||
|
downloadButton.setOnClickListener(v -> downloadFile(message));
|
||||||
|
imageView.setOnClickListener(v -> dialog.dismiss());
|
||||||
|
dialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void downloadFile(Message message) {
|
||||||
|
if (message.getId() == null) return;
|
||||||
|
|
||||||
|
DialogUtils.showConfirmDialog(requireContext(), "Download Attachment",
|
||||||
|
"Do you want to download \"" + message.getAttachmentName() + "\"?", () -> {
|
||||||
|
Toast.makeText(requireContext(), "Downloading " + message.getAttachmentName() + "...", Toast.LENGTH_SHORT).show();
|
||||||
|
|
||||||
|
viewModel.downloadAttachment(message.getId()).observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
saveFileToDownloads(resource.data, message.getAttachmentName(), message.getAttachmentMimeType());
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
Toast.makeText(requireContext(), "Download failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveFileToDownloads(ResponseBody body, String fileName, String mimeType) {
|
||||||
|
try {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(MediaStore.Downloads.DISPLAY_NAME, fileName);
|
||||||
|
values.put(MediaStore.Downloads.MIME_TYPE, mimeType);
|
||||||
|
values.put(MediaStore.Downloads.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
|
||||||
|
Uri uri = requireContext().getContentResolver().insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, values);
|
||||||
|
if (uri != null) {
|
||||||
|
try (OutputStream outputStream = requireContext().getContentResolver().openOutputStream(uri);
|
||||||
|
InputStream inputStream = body.byteStream()) {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
Toast.makeText(requireContext(), "File saved to Downloads", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
File downloadsDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS);
|
||||||
|
File file = new File(downloadsDir, fileName);
|
||||||
|
try (OutputStream outputStream = new FileOutputStream(file);
|
||||||
|
InputStream inputStream = body.byteStream()) {
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
outputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
Toast.makeText(requireContext(), "File saved to Downloads: " + file.getAbsolutePath(), Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
body.close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error saving file", e);
|
||||||
|
Toast.makeText(requireContext(), "Error saving file", Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getActiveChats().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
activeChatList.clear();
|
||||||
|
activeChatList.addAll(list);
|
||||||
|
activeChatAdapter.notifyDataSetChanged();
|
||||||
|
updateTitleAndStateIfActive(list);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getClosedChats().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
closedChatList.clear();
|
||||||
|
closedChatList.addAll(list);
|
||||||
|
closedChatAdapter.notifyDataSetChanged();
|
||||||
|
updateTitleAndStateIfActive(list);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getMessageList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
messageList.clear();
|
||||||
|
messageList.addAll(list);
|
||||||
|
messageAdapter.notifyDataSetChanged();
|
||||||
|
scrollToBottom();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), this::setLoading);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTitleAndStateIfActive(List<Chat> list) {
|
||||||
|
if (activeConversationId != null) {
|
||||||
|
for (Chat chat : list) {
|
||||||
|
if (chat.getChatId().equals(String.valueOf(activeConversationId))) {
|
||||||
|
binding.tvChatTitle.setText(chat.getCustomerName());
|
||||||
|
setConversationActive(true, chat.getStatus());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads authentication tokens and user info, then initializes the Stomp WebSocket connection.
|
|
||||||
*/
|
|
||||||
private void loadInitialData() {
|
private void loadInitialData() {
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
currentUserId = tokenManager.getUserId();
|
Long currentUserId = tokenManager.getUserId();
|
||||||
String role = tokenManager.getRole();
|
String role = tokenManager.getRole();
|
||||||
|
|
||||||
messageAdapter.setCurrentUserId(currentUserId);
|
messageAdapter.setCurrentUserId(currentUserId);
|
||||||
messageAdapter.setToken(token);
|
messageAdapter.setToken(token);
|
||||||
|
|
||||||
// if token exist then connect to websocket
|
|
||||||
if (token != null) {
|
if (token != null) {
|
||||||
stompChatManager = new StompChatManager(token, role, baseUrl);
|
stompChatManager = new StompChatManager(token, role, baseUrl);
|
||||||
stompChatManager.setMessageListener(this);
|
stompChatManager.setMessageListener(this);
|
||||||
stompChatManager.setConversationListener(this);
|
stompChatManager.setConversationListener(this);
|
||||||
stompChatManager.setConnectionListener(this);
|
stompChatManager.setConnectionListener(this);
|
||||||
stompChatManager.connect();
|
stompChatManager.connect();
|
||||||
} else {
|
|
||||||
Log.e(TAG, "No token found");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (getArguments() != null && getArguments().containsKey("conversation_id")) {
|
if (getArguments() != null && getArguments().containsKey("conversation_id")) {
|
||||||
@@ -172,141 +329,60 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
} else if (getActivity() != null && getActivity().getIntent().hasExtra("conversation_id")) {
|
} else if (getActivity() != null && getActivity().getIntent().hasExtra("conversation_id")) {
|
||||||
activeConversationId = getActivity().getIntent().getLongExtra("conversation_id", -1);
|
activeConversationId = getActivity().getIntent().getLongExtra("conversation_id", -1);
|
||||||
getActivity().getIntent().removeExtra("conversation_id");
|
getActivity().getIntent().removeExtra("conversation_id");
|
||||||
getActivity().getIntent().removeExtra("navigate_to");
|
} else {
|
||||||
|
activeConversationId = viewModel.getLastActiveConversationId();
|
||||||
}
|
}
|
||||||
|
|
||||||
loadCustomers();
|
viewModel.loadCustomers();
|
||||||
|
|
||||||
|
if (activeConversationId != null) {
|
||||||
|
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
|
||||||
|
viewModel.loadMessageHistory(activeConversationId);
|
||||||
|
} else {
|
||||||
|
setConversationActive(false, null);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a list of customers from the ViewModel to display customer names for the chat list.
|
|
||||||
*/
|
|
||||||
private void loadCustomers() {
|
|
||||||
viewModel.getAllCustomers(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
resource.data.getContent().forEach(c -> customerNames.put(c.getCustomerId(), c.getFullName()));
|
|
||||||
loadConversations();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all conversations for the current user through the ViewModel and populates the chat drawer.
|
|
||||||
*/
|
|
||||||
private void loadConversations() {
|
|
||||||
viewModel.getAllConversations().observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
chatList.clear();
|
|
||||||
for (ConversationDTO dto : resource.data) {
|
|
||||||
String name = customerNames.getOrDefault(
|
|
||||||
dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
|
||||||
chatList.add(new Chat(String.valueOf(dto.getId()),
|
|
||||||
name, dto.getLastMessage(),
|
|
||||||
dto.getCustomerId(), dto.getStaffId()));
|
|
||||||
}
|
|
||||||
chatAdapter.notifyDataSetChanged();
|
|
||||||
|
|
||||||
if (activeConversationId != null) {
|
|
||||||
setConversationActive(true);
|
|
||||||
// Update title to customer name of active conversation
|
|
||||||
for (Chat chat : chatList) {
|
|
||||||
if (chat.getChatId().equals(String.valueOf(activeConversationId))) {
|
|
||||||
binding.tvChatTitle.setText(chat.getCustomerName());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (stompChatManager != null) {
|
|
||||||
stompChatManager.subscribeToConversation(activeConversationId);
|
|
||||||
}
|
|
||||||
loadMessageHistory(activeConversationId);
|
|
||||||
} else {
|
|
||||||
messageList.clear();
|
|
||||||
messageAdapter.notifyDataSetChanged();
|
|
||||||
setConversationActive(false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles selection of a chat from the drawer, updating the UI and subscribing to the WebSocket.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onChatClick(Chat chat) {
|
public void onChatClick(Chat chat) {
|
||||||
activeConversationId = Long.parseLong(chat.getChatId());
|
activeConversationId = Long.parseLong(chat.getChatId());
|
||||||
setConversationActive(true);
|
viewModel.setLastActiveConversationId(activeConversationId);
|
||||||
|
|
||||||
|
setConversationActive(true, chat.getStatus());
|
||||||
binding.tvChatTitle.setText(chat.getCustomerName());
|
binding.tvChatTitle.setText(chat.getCustomerName());
|
||||||
binding.chatDrawerLayout.closeDrawer(GravityCompat.START);
|
binding.chatDrawerLayout.closeDrawer(GravityCompat.START);
|
||||||
|
|
||||||
if (stompChatManager != null) {
|
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
|
||||||
stompChatManager.subscribeToConversation(activeConversationId);
|
viewModel.loadMessageHistory(activeConversationId);
|
||||||
}
|
|
||||||
|
|
||||||
loadMessageHistory(activeConversationId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the full message history for a specific conversation from the ViewModel.
|
|
||||||
*/
|
|
||||||
private void loadMessageHistory(Long conversationId) {
|
|
||||||
viewModel.getMessages(conversationId).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
messageList.clear();
|
|
||||||
for (MessageDTO dto : resource.data) {
|
|
||||||
messageList.add(dtoToModel(dto));
|
|
||||||
}
|
|
||||||
messageAdapter.notifyDataSetChanged();
|
|
||||||
scrollToBottom();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a plain text message to the currently active conversation through the ViewModel.
|
|
||||||
*/
|
|
||||||
private void sendMessage() {
|
private void sendMessage() {
|
||||||
//check if a chat is selected
|
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
|
|
||||||
//get the message from text field
|
|
||||||
String text = binding.etMessage.getText().toString().trim();
|
String text = binding.etMessage.getText().toString().trim();
|
||||||
if (text.isEmpty()) return;
|
if (text.isEmpty()) return;
|
||||||
|
|
||||||
//clear text field after sending
|
|
||||||
binding.etMessage.setText("");
|
binding.etMessage.setText("");
|
||||||
|
viewModel.sendMessage(activeConversationId, text).observe(getViewLifecycleOwner(), resource -> {
|
||||||
//calls viewmodel to send the message
|
if (resource == null) return;
|
||||||
viewModel.sendMessage(activeConversationId, new SendMessageRequest(text)).observe(getViewLifecycleOwner(), resource -> {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
messageList.add(dtoToModel(resource.data));
|
viewModel.addMessageLocally(resource.data);
|
||||||
messageAdapter.notifyItemInserted(messageList.size() - 1);
|
viewModel.loadConversations();
|
||||||
scrollToBottom();
|
|
||||||
loadConversations();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Launches a file picker intent to select an attachment for the message.
|
|
||||||
*/
|
|
||||||
private void selectAttachment() {
|
private void selectAttachment() {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.setType("*/*");
|
intent.setType("*/*");
|
||||||
attachmentLauncher.launch(intent);
|
attachmentLauncher.launch(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a preview of the selected attachment in the UI.
|
|
||||||
*/
|
|
||||||
private void showAttachmentPreview(Uri uri) {
|
private void showAttachmentPreview(Uri uri) {
|
||||||
pendingAttachmentUri = uri;
|
pendingAttachmentUri = uri;
|
||||||
binding.layoutAttachmentPreview.setVisibility(View.VISIBLE);
|
binding.layoutAttachmentPreview.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
String mimeType = requireContext().getContentResolver().getType(uri);
|
String mimeType = requireContext().getContentResolver().getType(uri);
|
||||||
String fileName = getFileName(uri);
|
binding.tvPreviewName.setText(FileUtils.getFileName(requireContext(), uri));
|
||||||
binding.tvPreviewName.setText(fileName);
|
|
||||||
|
|
||||||
// If the file is an image, display a thumbnail of the image as well
|
|
||||||
if (mimeType != null && mimeType.startsWith("image/")) {
|
if (mimeType != null && mimeType.startsWith("image/")) {
|
||||||
binding.ivPreview.setVisibility(View.VISIBLE);
|
binding.ivPreview.setVisibility(View.VISIBLE);
|
||||||
Glide.with(this).load(uri).into(binding.ivPreview);
|
Glide.with(this).load(uri).into(binding.ivPreview);
|
||||||
@@ -315,183 +391,94 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears the current attachment selection and hides the preview UI.
|
|
||||||
*/
|
|
||||||
private void removeAttachment() {
|
private void removeAttachment() {
|
||||||
pendingAttachmentUri = null;
|
pendingAttachmentUri = null;
|
||||||
binding.layoutAttachmentPreview.setVisibility(View.GONE);
|
binding.layoutAttachmentPreview.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Show the display name of the file from its Uri.
|
|
||||||
*/
|
|
||||||
private String getFileName(Uri uri) {
|
|
||||||
String result = null;
|
|
||||||
if (uri.getScheme().equals("content")) {
|
|
||||||
try (Cursor cursor = requireContext().getContentResolver().query(uri, null, null, null, null)) {
|
|
||||||
if (cursor != null && cursor.moveToFirst()) {
|
|
||||||
int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
|
||||||
if (index != -1) {
|
|
||||||
result = cursor.getString(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (result == null) {
|
|
||||||
result = uri.getPath();
|
|
||||||
int cut = result.lastIndexOf('/');
|
|
||||||
if (cut != -1) {
|
|
||||||
result = result.substring(cut + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles sending a message that includes a file attachment via the ViewModel.
|
|
||||||
*/
|
|
||||||
private void sendWithAttachment(Uri uri) {
|
private void sendWithAttachment(Uri uri) {
|
||||||
if (activeConversationId == null) return;
|
if (activeConversationId == null) return;
|
||||||
|
|
||||||
|
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||||
|
if (file == null) {
|
||||||
|
Toast.makeText(requireContext(), "Failed to prepare file", Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
String text = binding.etMessage.getText().toString().trim();
|
String text = binding.etMessage.getText().toString().trim();
|
||||||
|
|
||||||
|
MultipartBody.Part contentPart = text.isEmpty()
|
||||||
|
? null
|
||||||
|
: MultipartBody.Part.createFormData("content", text);
|
||||||
|
|
||||||
|
String mimeType = requireContext().getContentResolver().getType(uri);
|
||||||
|
if (mimeType == null) mimeType = "application/octet-stream";
|
||||||
|
|
||||||
|
RequestBody filePartBody = RequestBody.create(file, MediaType.parse(mimeType));
|
||||||
|
MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", file.getName(), filePartBody);
|
||||||
|
|
||||||
binding.etMessage.setText("");
|
binding.etMessage.setText("");
|
||||||
removeAttachment();
|
removeAttachment();
|
||||||
|
|
||||||
if (!text.isEmpty()) {
|
viewModel.sendMessageWithAttachment(activeConversationId, contentPart, filePart).observe(getViewLifecycleOwner(), resource -> {
|
||||||
binding.etMessage.setText(text);
|
if (resource == null) return;
|
||||||
}
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
Toast.makeText(requireContext(), "File attachments are not supported", Toast.LENGTH_SHORT).show();
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
}
|
viewModel.addMessageLocally(resource.data);
|
||||||
|
viewModel.loadConversations();
|
||||||
/**
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
* Callback triggered when a new message is received via the WebSocket.
|
Toast.makeText(requireContext(), "Failed to send attachment: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
*/
|
}
|
||||||
@Override
|
});
|
||||||
public void onMessageReceived(MessageDTO dto) {
|
}
|
||||||
//if there is no active selected conversation or the message received is for another chat, then just update the preview of last message
|
|
||||||
if (activeConversationId == null || !activeConversationId.equals(dto.getConversationId())) {
|
@Override
|
||||||
updateConversationPreview(dto.getConversationId(), dto.getContent());
|
public void onMessageReceived(MessageDTO dto) {
|
||||||
return;
|
requireActivity().runOnUiThread(() -> {
|
||||||
}
|
if (activeConversationId != null && activeConversationId.equals(dto.getConversationId())) {
|
||||||
updateConversationPreview(dto.getConversationId(), dto.getContent());
|
if (!tokenManager.getUserId().equals(dto.getSenderId())) {
|
||||||
|
viewModel.addMessageLocally(dto);
|
||||||
if (currentUserId != null && currentUserId.equals(dto.getSenderId())) return;
|
}
|
||||||
|
}
|
||||||
//else add the message to the active chat if it's not from the current user
|
viewModel.updateConversationLocally(new ConversationDTO(dto.getConversationId(), 0L, 0L, dto.getContent(), ""));
|
||||||
messageList.add(dtoToModel(dto));
|
|
||||||
requireActivity().runOnUiThread(() -> {
|
|
||||||
messageAdapter.notifyItemInserted(messageList.size() - 1);
|
|
||||||
scrollToBottom();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when a conversation is created or updated via the WebSocket.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onConversationUpdated(ConversationDTO dto) {
|
public void onConversationUpdated(ConversationDTO dto) {
|
||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
boolean updated = false;
|
viewModel.updateConversationLocally(dto);
|
||||||
String name = customerNames.getOrDefault(
|
|
||||||
dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
|
||||||
|
|
||||||
for (int i = 0; i < chatList.size(); i++) {
|
|
||||||
Chat existing = chatList.get(i);
|
|
||||||
if (existing.getChatId().equals(String.valueOf(dto.getId()))) {
|
|
||||||
chatList.set(i, new Chat(
|
|
||||||
String.valueOf(dto.getId()),
|
|
||||||
name,
|
|
||||||
dto.getLastMessage(),
|
|
||||||
dto.getCustomerId(),
|
|
||||||
dto.getStaffId()
|
|
||||||
));
|
|
||||||
chatAdapter.notifyItemChanged(i);
|
|
||||||
updated = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!updated) {
|
|
||||||
chatList.add(0, new Chat(
|
|
||||||
String.valueOf(dto.getId()),
|
|
||||||
name,
|
|
||||||
dto.getLastMessage(),
|
|
||||||
dto.getCustomerId(),
|
|
||||||
dto.getStaffId()
|
|
||||||
));
|
|
||||||
chatAdapter.notifyItemInserted(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (activeConversationId != null && activeConversationId.equals(dto.getId())) {
|
if (activeConversationId != null && activeConversationId.equals(dto.getId())) {
|
||||||
setConversationActive(true);
|
setConversationActive(true, dto.getStatus());
|
||||||
binding.tvChatTitle.setText(name);
|
binding.tvChatTitle.setText(viewModel.getCustomerName(dto.getCustomerId()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the WebSocket connection is successfully opened.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onSocketOpened() {
|
public void onSocketOpened() {
|
||||||
if (!isAdded()) {
|
if (!isAdded()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
loadConversations();
|
viewModel.loadConversations();
|
||||||
if (activeConversationId != null) {
|
if (activeConversationId != null) viewModel.loadMessageHistory(activeConversationId);
|
||||||
loadMessageHistory(activeConversationId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when the WebSocket connection is closed.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onSocketClosed() {
|
public void onSocketClosed() {
|
||||||
if (!isAdded()) {
|
if (!isAdded()) return;
|
||||||
return;
|
requireActivity().runOnUiThread(viewModel::loadConversations);
|
||||||
}
|
|
||||||
requireActivity().runOnUiThread(this::loadConversations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Callback triggered when a WebSocket connection error occurs.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onSocketError() {
|
public void onSocketError() {
|
||||||
if (!isAdded()) {
|
if (!isAdded()) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
requireActivity().runOnUiThread(() -> {
|
requireActivity().runOnUiThread(() -> {
|
||||||
loadConversations();
|
viewModel.loadConversations();
|
||||||
if (activeConversationId != null) {
|
if (activeConversationId != null) viewModel.loadMessageHistory(activeConversationId);
|
||||||
loadMessageHistory(activeConversationId);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a MessageDTO into a Message object.
|
|
||||||
*/
|
|
||||||
private Message dtoToModel(MessageDTO dto) {
|
|
||||||
Message m = new Message();
|
|
||||||
m.setId(dto.getId());
|
|
||||||
m.setConversationId(dto.getConversationId());
|
|
||||||
m.setSenderId(dto.getSenderId());
|
|
||||||
m.setContent(dto.getContent());
|
|
||||||
m.setTimestamp(dto.getTimestamp());
|
|
||||||
m.setIsRead(dto.getIsRead());
|
|
||||||
m.setAttachmentUrl(dto.getAttachmentUrl());
|
|
||||||
m.setAttachmentName(dto.getAttachmentName());
|
|
||||||
m.setAttachmentType(dto.getAttachmentType());
|
|
||||||
return m;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Scrolls the message history RecyclerView to the most recent message.
|
|
||||||
*/
|
|
||||||
private void scrollToBottom() {
|
private void scrollToBottom() {
|
||||||
if (!messageList.isEmpty()) {
|
if (!messageList.isEmpty()) {
|
||||||
binding.rvMessages.post(() ->
|
binding.rvMessages.post(() ->
|
||||||
@@ -499,60 +486,26 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setConversationActive(boolean active, String status) {
|
||||||
* Updates the preview snippet of the last message for a specific conversation in the drawer.
|
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
|
||||||
*/
|
UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach);
|
||||||
private void updateConversationPreview(Long conversationId, String lastMessage) {
|
|
||||||
if (conversationId == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
requireActivity().runOnUiThread(() -> {
|
|
||||||
for (int i = 0; i < chatList.size(); i++) {
|
|
||||||
Chat existing = chatList.get(i);
|
|
||||||
if (existing.getChatId().equals(String.valueOf(conversationId))) {
|
|
||||||
Chat updated = new Chat(
|
|
||||||
existing.getChatId(),
|
|
||||||
existing.getCustomerName(),
|
|
||||||
lastMessage,
|
|
||||||
existing.getCustomerId(),
|
|
||||||
existing.getStaffId()
|
|
||||||
);
|
|
||||||
chatList.set(i, updated);
|
|
||||||
chatAdapter.notifyItemChanged(i);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the UI state based on whether a conversation is currently selected.
|
|
||||||
*/
|
|
||||||
private void setConversationActive(boolean active) {
|
|
||||||
binding.btnSend.setEnabled(active);
|
|
||||||
binding.etMessage.setEnabled(active);
|
|
||||||
binding.btnAttach.setEnabled(active);
|
|
||||||
if (!active) {
|
if (!active) {
|
||||||
activeConversationId = null;
|
activeConversationId = null;
|
||||||
ChatNotificationService.activeConversationIdInUi = null;
|
ChatNotificationService.activeConversationIdInUi = null;
|
||||||
removeAttachment();
|
removeAttachment();
|
||||||
if (binding != null && binding.tvChatTitle != null) binding.tvChatTitle.setText("Customer Chat");
|
if (binding != null && binding.tvChatTitle != null) binding.tvChatTitle.setText("Customer Chat");
|
||||||
if (stompChatManager != null) {
|
if (stompChatManager != null) stompChatManager.clearConversationSubscription();
|
||||||
stompChatManager.clearConversationSubscription();
|
|
||||||
}
|
|
||||||
messageList.clear();
|
messageList.clear();
|
||||||
messageAdapter.notifyDataSetChanged();
|
messageAdapter.notifyDataSetChanged();
|
||||||
binding.etMessage.setText("");
|
binding.etMessage.setText("");
|
||||||
binding.etMessage.setHint("Select a chat to start messaging");
|
binding.etMessage.setHint("Select a chat to start messaging");
|
||||||
} else {
|
} else {
|
||||||
binding.etMessage.setHint("Type a message...");
|
binding.etMessage.setHint(isClosed ? "This chat is closed" : "Type a message...");
|
||||||
ChatNotificationService.activeConversationIdInUi = activeConversationId;
|
ChatNotificationService.activeConversationIdInUi = activeConversationId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Disconnects the WebSocket manager when the fragment view is destroyed.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
|
|||||||
@@ -172,6 +172,12 @@ public class ProfileFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
@@ -184,6 +190,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
private void loadProfileData() {
|
private void loadProfileData() {
|
||||||
viewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
currentUser = resource.data;
|
currentUser = resource.data;
|
||||||
|
|
||||||
@@ -229,6 +236,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
//Call the backend to upload the avatar
|
//Call the backend to upload the avatar
|
||||||
viewModel.uploadAvatar(body).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.uploadAvatar(body).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), "Avatar updated successfully", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Avatar updated successfully", Toast.LENGTH_SHORT).show();
|
||||||
loadProfileData();
|
loadProfileData();
|
||||||
@@ -247,6 +255,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
private void deleteAvatar() {
|
private void deleteAvatar() {
|
||||||
viewModel.deleteAvatar().observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteAvatar().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
hasImage = false;
|
hasImage = false;
|
||||||
binding.imgProfile.setImageResource(R.drawable.placeholder);
|
binding.imgProfile.setImageResource(R.drawable.placeholder);
|
||||||
@@ -266,6 +275,7 @@ public class ProfileFragment extends Fragment {
|
|||||||
|
|
||||||
viewModel.updateMe(updates).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.updateMe(updates).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
currentUser = resource.data;
|
currentUser = resource.data;
|
||||||
Toast.makeText(getContext(), "Profile updated successfully", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Profile updated successfully", Toast.LENGTH_SHORT).show();
|
||||||
|
|||||||
@@ -24,9 +24,8 @@ import com.example.petstoremobile.utils.BulkDeleteHandler;
|
|||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
|
import com.example.petstoremobile.viewmodels.AdoptionListViewModel;
|
||||||
import com.example.petstoremobile.utils.EventDecorator;
|
import com.example.petstoremobile.utils.EventDecorator;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
import com.prolificinteractive.materialcalendarview.CalendarDay;
|
import com.prolificinteractive.materialcalendarview.CalendarDay;
|
||||||
import com.prolificinteractive.materialcalendarview.CalendarMode;
|
import com.prolificinteractive.materialcalendarview.CalendarMode;
|
||||||
|
|
||||||
@@ -46,28 +45,19 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
|
|
||||||
private FragmentAdoptionBinding binding;
|
private FragmentAdoptionBinding binding;
|
||||||
private List<AdoptionDTO> adoptionList = new ArrayList<>();
|
private List<AdoptionDTO> adoptionList = new ArrayList<>();
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private AdoptionAdapter adapter;
|
private AdoptionAdapter adapter;
|
||||||
private AdoptionViewModel adoptionViewModel;
|
private AdoptionListViewModel viewModel;
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
private CalendarDay selectedCalendarDay;
|
private CalendarDay selectedCalendarDay;
|
||||||
private boolean isMonthMode = false;
|
private boolean isMonthMode = false;
|
||||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its ViewModels.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
adoptionViewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AdoptionListViewModel.class);
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including RecyclerView, Search, SwipeRefresh, and Calendar.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -81,6 +71,7 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
setupCalendar();
|
setupCalendar();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
setupBulkDelete();
|
setupBulkDelete();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1));
|
binding.fabAddAdoption.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
@@ -91,6 +82,24 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getAdoptions().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
adoptionList.clear();
|
||||||
|
adoptionList.addAll(list);
|
||||||
|
updateCalendarDecorators();
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStoreAdoption, list,
|
||||||
|
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshAdoption.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -99,27 +108,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
binding.btnBulkDelete,
|
binding.btnBulkDelete,
|
||||||
adapter,
|
adapter,
|
||||||
"adoption",
|
"adoption",
|
||||||
adoptionViewModel::bulkDeleteAdoptions,
|
viewModel::bulkDeleteAdoptions,
|
||||||
this::loadAdoptions
|
this::loadAdoptions
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadAdoptions();
|
loadAdoptions();
|
||||||
loadStoreData();
|
viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the calendar display between week and month modes.
|
|
||||||
*/
|
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
isMonthMode = !isMonthMode;
|
isMonthMode = !isMonthMode;
|
||||||
binding.calendarViewAdoption.state().edit()
|
binding.calendarViewAdoption.state().edit()
|
||||||
@@ -127,35 +127,11 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
UIUtils.setupFilterToggle(binding.btnToggleFilterAdoption, binding.layoutFilterAdoption,
|
||||||
binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
|
binding.etSearchAdoption, binding.spinnerStatusAdoption, binding.spinnerStoreAdoption);
|
||||||
|
|
||||||
binding.btnToggleFilterAdoption.setOnClickListener(v -> {
|
|
||||||
boolean isVisible = binding.layoutFilterAdoption.getVisibility() == View.VISIBLE;
|
|
||||||
binding.layoutFilterAdoption.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
|
||||||
|
|
||||||
binding.btnToggleFilterAdoption.setImageResource(isVisible ?
|
|
||||||
android.R.drawable.ic_menu_search :
|
|
||||||
android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
binding.etSearchAdoption.setText("");
|
|
||||||
binding.spinnerStatusAdoption.setSelection(0);
|
|
||||||
binding.spinnerStoreAdoption.setSelection(0);
|
|
||||||
selectedCalendarDay = null;
|
|
||||||
binding.calendarViewAdoption.clearSelection();
|
|
||||||
loadAdoptions();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the date selection listener for the calendar.
|
|
||||||
*/
|
|
||||||
private void setupCalendar() {
|
private void setupCalendar() {
|
||||||
binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> {
|
binding.calendarViewAdoption.setOnDateChangedListener((widget, date, selected) -> {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
@@ -172,9 +148,6 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the calendar decorators to highlight days with adoptions.
|
|
||||||
*/
|
|
||||||
private void updateCalendarDecorators() {
|
private void updateCalendarDecorators() {
|
||||||
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
|
HashSet<CalendarDay> datesWithAdoptions = new HashSet<>();
|
||||||
for (AdoptionDTO adoption : adoptionList) {
|
for (AdoptionDTO adoption : adoptionList) {
|
||||||
@@ -195,67 +168,37 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
|
binding.calendarViewAdoption.addDecorator(new EventDecorator(Color.RED, datesWithAdoptions));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView for displaying adoptions.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new AdoptionAdapter(adoptionList, this);
|
adapter = new AdoptionAdapter(adoptionList, this);
|
||||||
binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewAdoptions.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewAdoptions.setAdapter(adapter);
|
binding.recyclerViewAdoptions.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the search bar for filtering
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchAdoption, this::loadAdoptions);
|
UIUtils.attachSearch(binding.etSearchAdoption, this::loadAdoptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the status filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Completed", "Pending", "Cancelled"};
|
String[] statuses = {"All Statuses", "Completed", "Pending", "Cancelled"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, this::loadAdoptions);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatusAdoption, statuses, this::loadAdoptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the store filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, this::loadAdoptions);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStoreAdoption, this::loadAdoptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches store data to populate the store filter.
|
|
||||||
*/
|
|
||||||
private void loadStoreData() {
|
|
||||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStoreAdoption, storeList,
|
|
||||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout to reload adoption data.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshAdoption.setOnRefreshListener(this::loadAdoptions);
|
binding.swipeRefreshAdoption.setOnRefreshListener(this::loadAdoptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the adoption list from the server through the ViewModel.
|
|
||||||
*/
|
|
||||||
private void loadAdoptions() {
|
private void loadAdoptions() {
|
||||||
String query = binding.etSearchAdoption.getText().toString().trim();
|
String query = binding.etSearchAdoption.getText().toString().trim();
|
||||||
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatusAdoption.getSelectedItem() != null ? binding.spinnerStatusAdoption.getSelectedItem().toString() : "All Statuses";
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId = null;
|
||||||
if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||||
storeId = storeList.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId();
|
if (binding.spinnerStoreAdoption.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||||
|
storeId = stores.get(binding.spinnerStoreAdoption.getSelectedItemPosition() - 1).getStoreId();
|
||||||
}
|
}
|
||||||
|
|
||||||
String selectedDateString = null;
|
String selectedDateString = null;
|
||||||
@@ -267,52 +210,18 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
if (status.equals("All Statuses")) status = null;
|
if (status.equals("All Statuses")) status = null;
|
||||||
else status = status.toUpperCase();
|
else status = status.toUpperCase();
|
||||||
|
|
||||||
adoptionViewModel.getAllAdoptions(0, 500, query, status, storeId, selectedDateString, null).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadAdoptions(true, query, status, storeId, selectedDateString, null);
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
// Check the status to see if the resource is loaded and display the data
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
// Show loading indicator
|
|
||||||
binding.swipeRefreshAdoption.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
// Hide loading indicator and display data
|
|
||||||
binding.swipeRefreshAdoption.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
adoptionList.clear();
|
|
||||||
adoptionList.addAll(resource.data.getContent());
|
|
||||||
updateCalendarDecorators();
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
// Hide loading indicator and toast error message
|
|
||||||
binding.swipeRefreshAdoption.setRefreshing(false);
|
|
||||||
Toast.makeText(getContext(), "Failed to load adoptions: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
Log.e("AdoptionFragment", "Error loading adoptions: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the adoption detail screen for a specific adoption or to create a new one.
|
|
||||||
*/
|
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
AdoptionDTO a = adoptionList.get(position);
|
AdoptionDTO a = adoptionList.get(position);
|
||||||
args.putLong("adoptionId", a.getAdoptionId());
|
args.putLong("adoptionId", a.getAdoptionId());
|
||||||
}
|
}
|
||||||
|
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_adoption_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles item click in the adoption list.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onAdoptionClick(int position) { openDetail(position); }
|
public void onAdoptionClick(int position) { openDetail(position); }
|
||||||
|
|
||||||
@@ -322,4 +231,10 @@ public class AdoptionFragment extends Fragment implements AdoptionAdapter.OnAdop
|
|||||||
bulkDeleteHandler.onSelectionChanged(selectedCount);
|
bulkDeleteHandler.onSelectionChanged(selectedCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,16 +2,14 @@ package com.example.petstoremobile.fragments.listfragments;
|
|||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import com.example.petstoremobile.databinding.FragmentAnalyticsBinding;
|
import com.example.petstoremobile.databinding.FragmentAnalyticsBinding;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
import com.example.petstoremobile.viewmodels.AnalyticsViewModel;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
@@ -21,228 +19,130 @@ import java.util.*;
|
|||||||
public class AnalyticsFragment extends Fragment {
|
public class AnalyticsFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentAnalyticsBinding binding;
|
private FragmentAnalyticsBinding binding;
|
||||||
private SaleViewModel saleViewModel;
|
private AnalyticsViewModel viewModel;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
|
binding = FragmentAnalyticsBinding.inflate(inflater, container, false);
|
||||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AnalyticsViewModel.class);
|
||||||
|
|
||||||
loadAnalytics();
|
observeViewModel();
|
||||||
|
viewModel.loadAnalytics();
|
||||||
|
|
||||||
binding.btnRefreshAnalytics.setOnClickListener(v -> loadAnalytics());
|
binding.btnRefreshAnalytics.setOnClickListener(v -> viewModel.loadAnalytics());
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerAnalytics, this);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getAnalyticsData().observe(getViewLifecycleOwner(), this::computeAndDisplay);
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
if (loading) {
|
||||||
|
binding.tvTotalRevenue.setText("Loading...");
|
||||||
|
binding.tvTotalTransactions.setText("...");
|
||||||
|
binding.tvAvgTransaction.setText("...");
|
||||||
|
binding.tvTotalItems.setText("...");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getErrorMessage().observe(getViewLifecycleOwner(), error -> {
|
||||||
|
if (error != null) showError(error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAnalytics() {
|
private void computeAndDisplay(AnalyticsViewModel.AnalyticsData data) {
|
||||||
// Clear all sections
|
if (data == null) return;
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
binding.tvTotalRevenue.setText("$" + data.totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
||||||
|
binding.tvTotalTransactions.setText(String.valueOf(data.totalTransactions));
|
||||||
|
binding.tvAvgTransaction.setText("$" + data.avgTransaction);
|
||||||
|
binding.tvTotalItems.setText(String.valueOf(data.totalItems));
|
||||||
|
|
||||||
|
// Top Revenue Products
|
||||||
binding.llTopRevenue.removeAllViews();
|
binding.llTopRevenue.removeAllViews();
|
||||||
binding.llTopQuantity.removeAllViews();
|
if (data.topRevenueProducts != null && !data.topRevenueProducts.isEmpty()) {
|
||||||
binding.llPaymentMethods.removeAllViews();
|
BigDecimal maxRevenue = data.topRevenueProducts.get(0).getValue();
|
||||||
binding.llEmployeePerformance.removeAllViews();
|
if (maxRevenue.compareTo(BigDecimal.ZERO) == 0) maxRevenue = BigDecimal.ONE;
|
||||||
binding.llDailyRevenue.removeAllViews();
|
for (Map.Entry<String, BigDecimal> e : data.topRevenueProducts) {
|
||||||
|
addBarRow(binding.llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
// Show loading
|
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
||||||
binding.tvTotalRevenue.setText("Loading...");
|
|
||||||
binding.tvTotalTransactions.setText("...");
|
|
||||||
binding.tvAvgTransaction.setText("...");
|
|
||||||
binding.tvTotalItems.setText("...");
|
|
||||||
|
|
||||||
saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc")
|
|
||||||
.observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null) {
|
|
||||||
switch (resource.status) {
|
|
||||||
case SUCCESS:
|
|
||||||
if (resource.data != null) {
|
|
||||||
computeAndDisplay(resource.data.getContent());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
Log.e("Analytics", resource.message != null ? resource.message : "Error loading sales");
|
|
||||||
showError("Failed to load sales data");
|
|
||||||
break;
|
|
||||||
case LOADING:
|
|
||||||
// Already showing loading state in UI
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void computeAndDisplay(List<SaleDTO> sales) {
|
|
||||||
// Filter out refunds for most metrics
|
|
||||||
List<SaleDTO> regularSales = new ArrayList<>();
|
|
||||||
for (SaleDTO s : sales) {
|
|
||||||
if (!Boolean.TRUE.equals(s.getIsRefund()))
|
|
||||||
regularSales.add(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
// ── Summary ──────────────────────────────────────────
|
|
||||||
BigDecimal totalRevenue = BigDecimal.ZERO;
|
|
||||||
int totalItems = 0;
|
|
||||||
|
|
||||||
for (SaleDTO s : regularSales) {
|
|
||||||
if (s.getTotalAmount() != null)
|
|
||||||
totalRevenue = totalRevenue.add(s.getTotalAmount());
|
|
||||||
if (s.getItems() != null) {
|
|
||||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
|
||||||
if (item.getQuantity() != null)
|
|
||||||
totalItems += Math.abs(item.getQuantity());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
} else {
|
||||||
|
|
||||||
int totalTx = regularSales.size();
|
|
||||||
BigDecimal avgTx = totalTx > 0
|
|
||||||
? totalRevenue.divide(BigDecimal.valueOf(totalTx), 2, RoundingMode.HALF_UP)
|
|
||||||
: BigDecimal.ZERO;
|
|
||||||
|
|
||||||
binding.tvTotalRevenue.setText("$" + totalRevenue.setScale(2, RoundingMode.HALF_UP));
|
|
||||||
binding.tvTotalTransactions.setText(String.valueOf(totalTx));
|
|
||||||
binding.tvAvgTransaction.setText("$" + avgTx);
|
|
||||||
binding.tvTotalItems.setText(String.valueOf(totalItems));
|
|
||||||
|
|
||||||
// ── Top Products by Revenue ───────────────────────────
|
|
||||||
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
|
||||||
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
for (SaleDTO s : regularSales) {
|
|
||||||
if (s.getItems() != null) {
|
|
||||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
|
||||||
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
|
||||||
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
|
||||||
BigDecimal lineTotal = item.getUnitPrice() != null
|
|
||||||
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty))
|
|
||||||
: BigDecimal.ZERO;
|
|
||||||
|
|
||||||
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
|
||||||
quantityByProduct.merge(name, qty, Integer::sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by revenue desc, take top 5
|
|
||||||
List<Map.Entry<String, BigDecimal>> topRevenue = new ArrayList<>(revenueByProduct.entrySet());
|
|
||||||
topRevenue.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
|
||||||
BigDecimal maxRevenue = topRevenue.isEmpty() ? BigDecimal.ONE : topRevenue.get(0).getValue();
|
|
||||||
|
|
||||||
binding.llTopRevenue.removeAllViews();
|
|
||||||
for (int i = 0; i < Math.min(5, topRevenue.size()); i++) {
|
|
||||||
Map.Entry<String, BigDecimal> e = topRevenue.get(i);
|
|
||||||
addBarRow(binding.llTopRevenue, e.getKey(), "$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
|
||||||
e.getValue().floatValue() / maxRevenue.floatValue(), "#ff6b35");
|
|
||||||
}
|
|
||||||
if (topRevenue.isEmpty())
|
|
||||||
addEmptyRow(binding.llTopRevenue, "No data");
|
addEmptyRow(binding.llTopRevenue, "No data");
|
||||||
|
}
|
||||||
|
|
||||||
// Sort by quantity desc, take top 5
|
// Top Quantity Products
|
||||||
List<Map.Entry<String, Integer>> topQuantity = new ArrayList<>(quantityByProduct.entrySet());
|
|
||||||
topQuantity.sort((a, b) -> b.getValue() - a.getValue());
|
|
||||||
int maxQty = topQuantity.isEmpty() ? 1 : topQuantity.get(0).getValue();
|
|
||||||
|
|
||||||
binding.llTopQuantity.removeAllViews();
|
binding.llTopQuantity.removeAllViews();
|
||||||
for (int i = 0; i < Math.min(5, topQuantity.size()); i++) {
|
if (data.topQuantityProducts != null && !data.topQuantityProducts.isEmpty()) {
|
||||||
Map.Entry<String, Integer> e = topQuantity.get(i);
|
int maxQty = data.topQuantityProducts.get(0).getValue();
|
||||||
addBarRow(binding.llTopQuantity, e.getKey(), e.getValue() + " units",
|
if (maxQty == 0) maxQty = 1;
|
||||||
(float) e.getValue() / maxQty, "#4ecdc4");
|
for (Map.Entry<String, Integer> e : data.topQuantityProducts) {
|
||||||
}
|
addBarRow(binding.llTopQuantity, e.getKey(), e.getValue() + " units",
|
||||||
if (topQuantity.isEmpty())
|
(float) e.getValue() / maxQty, "#4ecdc4");
|
||||||
addEmptyRow(binding.llTopQuantity, "No data");
|
|
||||||
|
|
||||||
// ── Payment Methods ───────────────────────────────────
|
|
||||||
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
|
||||||
for (SaleDTO s : regularSales) {
|
|
||||||
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
|
||||||
paymentCount.merge(method, 1, Integer::sum);
|
|
||||||
}
|
|
||||||
|
|
||||||
int maxPayment = paymentCount.values().stream().max(Integer::compare).orElse(1);
|
|
||||||
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
|
||||||
int ci = 0;
|
|
||||||
binding.llPaymentMethods.removeAllViews();
|
|
||||||
for (Map.Entry<String, Integer> e : paymentCount.entrySet()) {
|
|
||||||
addBarRow(binding.llPaymentMethods, e.getKey(),
|
|
||||||
e.getValue() + " transactions",
|
|
||||||
(float) e.getValue() / maxPayment,
|
|
||||||
paymentColors[ci % paymentColors.length]);
|
|
||||||
ci++;
|
|
||||||
}
|
|
||||||
if (paymentCount.isEmpty())
|
|
||||||
addEmptyRow(binding.llPaymentMethods, "No data");
|
|
||||||
|
|
||||||
// ── Employee Performance ──────────────────────────────
|
|
||||||
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
|
||||||
for (SaleDTO s : regularSales) {
|
|
||||||
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
|
||||||
if (s.getTotalAmount() != null)
|
|
||||||
employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Map.Entry<String, BigDecimal>> empList = new ArrayList<>(employeeRevenue.entrySet());
|
|
||||||
empList.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
|
||||||
BigDecimal maxEmp = empList.isEmpty() ? BigDecimal.ONE : empList.get(0).getValue();
|
|
||||||
|
|
||||||
binding.llEmployeePerformance.removeAllViews();
|
|
||||||
for (Map.Entry<String, BigDecimal> e : empList) {
|
|
||||||
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
|
||||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
|
||||||
e.getValue().floatValue() / maxEmp.floatValue(),
|
|
||||||
"#1a759f");
|
|
||||||
}
|
|
||||||
if (empList.isEmpty())
|
|
||||||
addEmptyRow(binding.llEmployeePerformance, "No data");
|
|
||||||
|
|
||||||
// ── Daily Revenue (last 7 days) ───────────────────────
|
|
||||||
Map<String, BigDecimal> dailyRevenue = new TreeMap<>();
|
|
||||||
|
|
||||||
// Initialize last 7 days
|
|
||||||
Calendar cal = Calendar.getInstance();
|
|
||||||
for (int i = 6; i >= 0; i--) {
|
|
||||||
Calendar day = Calendar.getInstance();
|
|
||||||
day.add(Calendar.DAY_OF_YEAR, -i);
|
|
||||||
String key = String.format("%04d-%02d-%02d",
|
|
||||||
day.get(Calendar.YEAR),
|
|
||||||
day.get(Calendar.MONTH) + 1,
|
|
||||||
day.get(Calendar.DAY_OF_MONTH));
|
|
||||||
dailyRevenue.put(key, BigDecimal.ZERO);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (SaleDTO s : regularSales) {
|
|
||||||
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
|
||||||
String date = s.getSaleDate().length() >= 10
|
|
||||||
? s.getSaleDate().substring(0, 10)
|
|
||||||
: s.getSaleDate();
|
|
||||||
if (dailyRevenue.containsKey(date)) {
|
|
||||||
dailyRevenue.merge(date, s.getTotalAmount(), BigDecimal::add);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
addEmptyRow(binding.llTopQuantity, "No data");
|
||||||
}
|
}
|
||||||
|
|
||||||
BigDecimal maxDaily = dailyRevenue.values().stream()
|
// Payment Methods
|
||||||
.max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
binding.llPaymentMethods.removeAllViews();
|
||||||
if (maxDaily.compareTo(BigDecimal.ZERO) == 0)
|
if (data.paymentMethodStats != null && !data.paymentMethodStats.isEmpty()) {
|
||||||
maxDaily = BigDecimal.ONE;
|
int maxPayment = data.paymentMethodStats.stream().mapToInt(Map.Entry::getValue).max().orElse(1);
|
||||||
|
String[] paymentColors = { "#1a759f", "#ff9f1c", "#577590", "#90be6d" };
|
||||||
|
int ci = 0;
|
||||||
|
for (Map.Entry<String, Integer> e : data.paymentMethodStats) {
|
||||||
|
addBarRow(binding.llPaymentMethods, e.getKey(),
|
||||||
|
e.getValue() + " transactions",
|
||||||
|
(float) e.getValue() / maxPayment,
|
||||||
|
paymentColors[ci % paymentColors.length]);
|
||||||
|
ci++;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addEmptyRow(binding.llPaymentMethods, "No data");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Employee Performance
|
||||||
|
binding.llEmployeePerformance.removeAllViews();
|
||||||
|
if (data.employeePerformance != null && !data.employeePerformance.isEmpty()) {
|
||||||
|
BigDecimal maxEmp = data.employeePerformance.get(data.employeePerformance.size() - 1).getValue();
|
||||||
|
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
||||||
|
maxEmp = data.employeePerformance.get(0).getValue();
|
||||||
|
if (maxEmp.compareTo(BigDecimal.ZERO) == 0) maxEmp = BigDecimal.ONE;
|
||||||
|
|
||||||
|
for (Map.Entry<String, BigDecimal> e : data.employeePerformance) {
|
||||||
|
addBarRow(binding.llEmployeePerformance, e.getKey(),
|
||||||
|
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
|
e.getValue().floatValue() / maxEmp.floatValue(),
|
||||||
|
"#1a759f");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
addEmptyRow(binding.llEmployeePerformance, "No data");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Daily Revenue
|
||||||
binding.llDailyRevenue.removeAllViews();
|
binding.llDailyRevenue.removeAllViews();
|
||||||
for (Map.Entry<String, BigDecimal> e : dailyRevenue.entrySet()) {
|
if (data.dailyRevenue != null && !data.dailyRevenue.isEmpty()) {
|
||||||
// Show just MM-DD
|
BigDecimal maxDaily = data.dailyRevenue.stream().map(Map.Entry::getValue).max(BigDecimal::compareTo).orElse(BigDecimal.ONE);
|
||||||
String label = e.getKey().length() >= 10
|
if (maxDaily.compareTo(BigDecimal.ZERO) == 0) maxDaily = BigDecimal.ONE;
|
||||||
? e.getKey().substring(5)
|
for (Map.Entry<String, BigDecimal> e : data.dailyRevenue) {
|
||||||
: e.getKey();
|
String label = e.getKey().length() >= 10 ? e.getKey().substring(5) : e.getKey();
|
||||||
addBarRow(binding.llDailyRevenue, label,
|
addBarRow(binding.llDailyRevenue, label,
|
||||||
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
"$" + e.getValue().setScale(2, RoundingMode.HALF_UP),
|
||||||
e.getValue().floatValue() / maxDaily.floatValue(),
|
e.getValue().floatValue() / maxDaily.floatValue(),
|
||||||
"#ff6b35");
|
"#ff6b35");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ import android.util.Log;
|
|||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
import com.example.petstoremobile.adapters.AppointmentAdapter;
|
||||||
@@ -25,10 +24,9 @@ import com.example.petstoremobile.utils.BulkDeleteHandler;
|
|||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
|
import com.example.petstoremobile.viewmodels.AppointmentListViewModel;
|
||||||
import com.example.petstoremobile.utils.EventDecorator;
|
import com.example.petstoremobile.utils.EventDecorator;
|
||||||
import com.example.petstoremobile.viewmodels.AuthViewModel;
|
import com.example.petstoremobile.viewmodels.AuthViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
import com.prolificinteractive.materialcalendarview.CalendarDay;
|
import com.prolificinteractive.materialcalendarview.CalendarDay;
|
||||||
import com.prolificinteractive.materialcalendarview.CalendarMode;
|
import com.prolificinteractive.materialcalendarview.CalendarMode;
|
||||||
|
|
||||||
@@ -48,11 +46,9 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
|
|
||||||
private FragmentAppointmentBinding binding;
|
private FragmentAppointmentBinding binding;
|
||||||
private List<AppointmentDTO> appointmentList = new ArrayList<>();
|
private List<AppointmentDTO> appointmentList = new ArrayList<>();
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
|
|
||||||
private AppointmentAdapter adapter;
|
private AppointmentAdapter adapter;
|
||||||
private AppointmentViewModel appointmentViewModel;
|
private AppointmentListViewModel viewModel;
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
private AuthViewModel authViewModel;
|
private AuthViewModel authViewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
@@ -61,20 +57,13 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
private Long currentUserId = null;
|
private Long currentUserId = null;
|
||||||
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
private final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its associated ViewModels.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
appointmentViewModel = new ViewModelProvider(this).get(AppointmentViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AppointmentListViewModel.class);
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
authViewModel = new ViewModelProvider(this).get(AuthViewModel.class);
|
authViewModel = new ViewModelProvider(this).get(AuthViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI, including RecyclerView, search, swipe-to-refresh, and calendar.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -89,6 +78,7 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
setupMyAppointmentFilter();
|
setupMyAppointmentFilter();
|
||||||
setupBulkDelete();
|
setupBulkDelete();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
binding.fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
|
binding.fabAddAppointment.setOnClickListener(v -> openAppointmentDetails(-1));
|
||||||
|
|
||||||
@@ -101,6 +91,24 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getAppointments().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
appointmentList.clear();
|
||||||
|
appointmentList.addAll(list);
|
||||||
|
updateCalendarDecorators();
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||||
|
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshAppointment.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -109,27 +117,18 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
binding.btnBulkDelete,
|
binding.btnBulkDelete,
|
||||||
adapter,
|
adapter,
|
||||||
"appointment",
|
"appointment",
|
||||||
appointmentViewModel::bulkDeleteAppointments,
|
viewModel::bulkDeleteAppointments,
|
||||||
this::loadAppointmentData
|
this::loadAppointmentData
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadAppointmentData();
|
loadAppointmentData();
|
||||||
loadStoreData();
|
viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Toggles the calendar between week and month display modes.
|
|
||||||
*/
|
|
||||||
private void toggleCalendarMode() {
|
private void toggleCalendarMode() {
|
||||||
isMonthMode = !isMonthMode;
|
isMonthMode = !isMonthMode;
|
||||||
binding.calendarView.state().edit()
|
binding.calendarView.state().edit()
|
||||||
@@ -137,18 +136,12 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
.commit();
|
.commit();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the "My Appointments" filter button.
|
|
||||||
*/
|
|
||||||
private void setupMyAppointmentFilter() {
|
private void setupMyAppointmentFilter() {
|
||||||
binding.btnMyAppointments.setOnClickListener(v -> {
|
binding.btnMyAppointments.setOnClickListener(v -> {
|
||||||
loadAppointmentData();
|
loadAppointmentData();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches current user info to get the employeeId.
|
|
||||||
*/
|
|
||||||
private void loadCurrentUserInfo() {
|
private void loadCurrentUserInfo() {
|
||||||
authViewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
authViewModel.getMe().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
@@ -157,37 +150,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchAppointment,
|
||||||
binding.spinnerStatus, binding.spinnerStore);
|
binding.spinnerStatus, binding.spinnerStore);
|
||||||
|
|
||||||
// Add additional reset logic for elements specific to this fragment
|
|
||||||
binding.btnToggleFilter.setOnClickListener(v -> {
|
|
||||||
boolean isVisible = binding.layoutFilter.getVisibility() == View.VISIBLE;
|
|
||||||
binding.layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
|
||||||
|
|
||||||
binding.btnToggleFilter.setImageResource(isVisible ?
|
|
||||||
android.R.drawable.ic_menu_search :
|
|
||||||
android.R.drawable.ic_menu_close_clear_cancel);
|
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
binding.etSearchAppointment.setText("");
|
|
||||||
binding.spinnerStatus.setSelection(0);
|
|
||||||
binding.spinnerStore.setSelection(0);
|
|
||||||
binding.btnMyAppointments.setChecked(false);
|
|
||||||
selectedCalendarDay = null;
|
|
||||||
binding.calendarView.clearSelection();
|
|
||||||
loadAppointmentData();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the date selection listener for the calendar.
|
|
||||||
*/
|
|
||||||
private void setupCalendar() {
|
private void setupCalendar() {
|
||||||
binding.calendarView.setOnDateChangedListener((widget, date, selected) -> {
|
binding.calendarView.setOnDateChangedListener((widget, date, selected) -> {
|
||||||
if (selected) {
|
if (selected) {
|
||||||
@@ -204,17 +171,11 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates calendar indicators to highlight dates that have scheduled appointments.
|
|
||||||
*/
|
|
||||||
private void updateCalendarDecorators() {
|
private void updateCalendarDecorators() {
|
||||||
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
HashSet<CalendarDay> datesWithAppointments = new HashSet<>();
|
||||||
SimpleDateFormat displayFormat = new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault());
|
|
||||||
for (AppointmentDTO appointment : appointmentList) {
|
for (AppointmentDTO appointment : appointmentList) {
|
||||||
try {
|
try {
|
||||||
//Get the appointment date
|
Date date = dateFormat.parse(appointment.getAppointmentDate());
|
||||||
Date date = displayFormat.parse(appointment.getAppointmentDate());
|
|
||||||
//if the date is not null, add it to the hashset
|
|
||||||
if (date != null) {
|
if (date != null) {
|
||||||
Calendar cal = Calendar.getInstance();
|
Calendar cal = Calendar.getInstance();
|
||||||
cal.setTime(date);
|
cal.setTime(date);
|
||||||
@@ -224,56 +185,27 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
Log.e("AppointmentFragment", "Error parsing date: " + appointment.getAppointmentDate());
|
Log.e("AppointmentFragment", "Error parsing date: " + appointment.getAppointmentDate());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//update the indicators to the calendar
|
|
||||||
binding.calendarView.removeDecorators();
|
binding.calendarView.removeDecorators();
|
||||||
binding.calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
|
binding.calendarView.addDecorator(new EventDecorator(Color.RED, datesWithAppointments));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the search bar for filtering.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchAppointment, this::loadAppointmentData);
|
UIUtils.attachSearch(binding.etSearchAppointment, this::loadAppointmentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the status filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"};
|
String[] statuses = {"All Statuses", "Booked", "Completed", "Cancelled", "Missed"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadAppointmentData);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadAppointmentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the store filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadAppointmentData);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadAppointmentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches store data to populate the store filter.
|
|
||||||
*/
|
|
||||||
private void loadStoreData() {
|
|
||||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the SwipeRefreshLayout to allow manual data refreshing.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshAppointment.setOnRefreshListener(this::loadAppointmentData);
|
binding.swipeRefreshAppointment.setOnRefreshListener(this::loadAppointmentData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the appointment detail screen for editing or creating an appointment.
|
|
||||||
*/
|
|
||||||
private void openAppointmentDetails(int position) {
|
private void openAppointmentDetails(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -283,9 +215,6 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_appointment_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles item click in the appointment list.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onAppointmentClick(int position) {
|
public void onAppointmentClick(int position) {
|
||||||
openAppointmentDetails(position);
|
openAppointmentDetails(position);
|
||||||
@@ -298,16 +227,14 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches appointment data from the server with all active filters.
|
|
||||||
*/
|
|
||||||
private void loadAppointmentData() {
|
private void loadAppointmentData() {
|
||||||
String query = binding.etSearchAppointment.getText().toString().trim();
|
String query = binding.etSearchAppointment.getText().toString().trim();
|
||||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId = null;
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||||
|
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||||
}
|
}
|
||||||
|
|
||||||
String selectedDateString = null;
|
String selectedDateString = null;
|
||||||
@@ -324,41 +251,18 @@ public class AppointmentFragment extends Fragment implements AppointmentAdapter.
|
|||||||
if (status.equals("All Statuses")) status = null;
|
if (status.equals("All Statuses")) status = null;
|
||||||
else status = status.toUpperCase();
|
else status = status.toUpperCase();
|
||||||
|
|
||||||
appointmentViewModel.getAllAppointments(0, 500, query, status, storeId, selectedDateString, employeeId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadAppointments(query, status, storeId, selectedDateString, employeeId);
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
// Check the status to see if the resource is loaded and display the data
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
// Show loading indicator
|
|
||||||
binding.swipeRefreshAppointment.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
// Hide loading indicator and display data
|
|
||||||
binding.swipeRefreshAppointment.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
appointmentList.clear();
|
|
||||||
appointmentList.addAll(resource.data.getContent());
|
|
||||||
updateCalendarDecorators();
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
// Hide loading indicator and toast error message
|
|
||||||
binding.swipeRefreshAppointment.setRefreshing(false);
|
|
||||||
Toast.makeText(getContext(), "Failed to load appointments: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
Log.e("AppointmentFragment", "Error loading appointments: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView for displaying appointments.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new AppointmentAdapter(appointmentList, this);
|
adapter = new AppointmentAdapter(appointmentList, this);
|
||||||
binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewAppointments.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewAppointments.setAdapter(adapter);
|
binding.recyclerViewAppointments.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -22,8 +21,7 @@ import com.example.petstoremobile.dtos.InventoryDTO;
|
|||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
import com.example.petstoremobile.viewmodels.InventoryListViewModel;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@@ -34,33 +32,18 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
public class InventoryFragment extends Fragment implements InventoryAdapter.OnInventoryClickListener {
|
||||||
|
|
||||||
private static final String TAG = "InventoryFragment";
|
|
||||||
private static final int PAGE_SIZE = 20;
|
|
||||||
|
|
||||||
private FragmentInventoryBinding binding;
|
private FragmentInventoryBinding binding;
|
||||||
private final List<InventoryDTO> inventoryList = new ArrayList<>();
|
private final List<InventoryDTO> inventoryList = new ArrayList<>();
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private InventoryAdapter adapter;
|
private InventoryAdapter adapter;
|
||||||
private InventoryViewModel viewModel;
|
private InventoryListViewModel viewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
// Pagination
|
|
||||||
private int currentPage = 0;
|
|
||||||
private boolean isLastPage = false;
|
|
||||||
private boolean isLoading = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its ViewModel.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
|
viewModel = new ViewModelProvider(this).get(InventoryListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including the inventory list and search.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -72,8 +55,9 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
setupBulkDelete();
|
setupBulkDelete();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
loadInventory(true);
|
loadInventory(true);
|
||||||
loadStoreData();
|
|
||||||
|
|
||||||
binding.fabAddInventory.setOnClickListener(v -> openDetail(null));
|
binding.fabAddInventory.setOnClickListener(v -> openDetail(null));
|
||||||
|
|
||||||
@@ -82,6 +66,23 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getInventory().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
inventoryList.clear();
|
||||||
|
inventoryList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||||
|
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshInventory.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -95,49 +96,30 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
viewModel.loadStores();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory, binding.spinnerStore);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchInventory, binding.spinnerStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the search bar for filtering.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true));
|
UIUtils.attachSearch(binding.etSearchInventory, () -> loadInventory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the store filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadInventory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches store data to populate the store filter.
|
|
||||||
*/
|
|
||||||
private void loadStoreData() {
|
|
||||||
viewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView with a layout manager, and adapter.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new InventoryAdapter(inventoryList, this);
|
adapter = new InventoryAdapter(inventoryList, this);
|
||||||
binding.recyclerViewInventory.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewInventory.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -146,105 +128,45 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
binding.recyclerViewInventory.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
binding.recyclerViewInventory.addOnScrollListener(new RecyclerView.OnScrollListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
|
||||||
if (dy <= 0)
|
if (dy <= 0) return;
|
||||||
return;
|
|
||||||
LinearLayoutManager lm = (LinearLayoutManager) binding.recyclerViewInventory.getLayoutManager();
|
LinearLayoutManager lm = (LinearLayoutManager) binding.recyclerViewInventory.getLayoutManager();
|
||||||
if (lm == null)
|
if (lm == null) return;
|
||||||
return;
|
|
||||||
int visible = lm.getChildCount();
|
int visible = lm.getChildCount();
|
||||||
int total = lm.getItemCount();
|
int total = lm.getItemCount();
|
||||||
int firstVis = lm.findFirstVisibleItemPosition();
|
int firstVis = lm.findFirstVisibleItemPosition();
|
||||||
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
Boolean isLoading = viewModel.getIsLoading().getValue();
|
||||||
|
if ((isLoading == null || !isLoading) && !viewModel.isLastPage() && (visible + firstVis) >= total - 3) {
|
||||||
loadInventory(false);
|
loadInventory(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout to reload the first page of inventory items.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshInventory.setOnRefreshListener(() -> loadInventory(true));
|
binding.swipeRefreshInventory.setOnRefreshListener(() -> loadInventory(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a page of inventory items from the API.
|
|
||||||
*/
|
|
||||||
private void loadInventory(boolean reset) {
|
private void loadInventory(boolean reset) {
|
||||||
if (isLoading) return;
|
|
||||||
|
|
||||||
if (reset) {
|
|
||||||
currentPage = 0;
|
|
||||||
isLastPage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Search text from input
|
|
||||||
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
|
String query = binding.etSearchInventory != null ? binding.etSearchInventory.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId = null;
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||||
|
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||||
}
|
}
|
||||||
|
|
||||||
//Load all inventory items from the backend using viewModel
|
viewModel.loadInventory(reset, query, storeId);
|
||||||
viewModel.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
// Check the status to see if the resource is loaded and display the data
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
// Show loading indicator
|
|
||||||
isLoading = true;
|
|
||||||
binding.swipeRefreshInventory.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
// Hide loading indicator and display data
|
|
||||||
isLoading = false;
|
|
||||||
binding.swipeRefreshInventory.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
if (reset) inventoryList.clear();
|
|
||||||
inventoryList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
isLastPage = resource.data.isLast();
|
|
||||||
if (!isLastPage) currentPage++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
// Hide loading indicator and toast error message
|
|
||||||
isLoading = false;
|
|
||||||
binding.swipeRefreshInventory.setRefreshing(false);
|
|
||||||
Log.e(TAG, "Error: " + resource.message);
|
|
||||||
Toast.makeText(getContext(), "Failed to load inventory: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the inventory detail screen for a specific item or to add a new one.
|
|
||||||
*/
|
|
||||||
private void openDetail(InventoryDTO inv) {
|
private void openDetail(InventoryDTO inv) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
if (inv != null) {
|
if (inv != null) {
|
||||||
args.putLong("inventoryId", inv.getInventoryId());
|
args.putLong("inventoryId", inv.getInventoryId());
|
||||||
}
|
}
|
||||||
|
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_inventory_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads inventory data when changes occur.
|
|
||||||
*/
|
|
||||||
public void onInventoryChanged() {
|
|
||||||
loadInventory(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles item click in the inventory list.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onInventoryClick(int position) {
|
public void onInventoryClick(int position) {
|
||||||
if (position >= 0 && position < inventoryList.size()) {
|
if (position >= 0 && position < inventoryList.size()) {
|
||||||
@@ -252,9 +174,6 @@ public class InventoryFragment extends Fragment implements InventoryAdapter.OnIn
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the bulk deletion UI visibility and count when items are selected or deselected.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onSelectionChanged(int selectedCount) {
|
public void onSelectionChanged(int selectedCount) {
|
||||||
if (bulkDeleteHandler != null) {
|
if (bulkDeleteHandler != null) {
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -25,8 +24,7 @@ import com.example.petstoremobile.utils.BulkDeleteHandler;
|
|||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
import com.example.petstoremobile.viewmodels.PetListViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -40,28 +38,19 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener {
|
public class PetFragment extends Fragment implements PetAdapter.OnPetClickListener {
|
||||||
private FragmentPetBinding binding;
|
private FragmentPetBinding binding;
|
||||||
private List<PetDTO> petList = new ArrayList<>();
|
private List<PetDTO> petList = new ArrayList<>();
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private PetAdapter adapter;
|
private PetAdapter adapter;
|
||||||
private PetViewModel viewModel;
|
private PetListViewModel viewModel;
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its associated ViewModels.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PetListViewModel.class);
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including RecyclerView, filters, and swipe-to-refresh.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -75,6 +64,7 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
setupBulkDelete();
|
setupBulkDelete();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
binding.fabAddPet.setOnClickListener(v -> openPetDetails());
|
binding.fabAddPet.setOnClickListener(v -> openPetDetails());
|
||||||
|
|
||||||
@@ -83,6 +73,23 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getPets().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
petList.clear();
|
||||||
|
petList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||||
|
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshPet.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -96,83 +103,62 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads data every time the fragment becomes visible.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadPetData();
|
loadPetData();
|
||||||
loadStoreData();
|
viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPet,
|
||||||
binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
|
binding.spinnerStatus, binding.spinnerSpecies, binding.spinnerStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the search bar.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchPet, this::loadPetData);
|
UIUtils.attachSearch(binding.etSearchPet, this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the status filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStatusFilter() {
|
private void setupStatusFilter() {
|
||||||
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned"};
|
String[] statuses = {"All Statuses", "Available", "Adopted", "Owned"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadPetData);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerStatus, statuses, this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the species filter spinner with species.
|
|
||||||
*/
|
|
||||||
private void setupSpeciesFilter() {
|
private void setupSpeciesFilter() {
|
||||||
String[] species = {"All Species", "Dog", "Cat", "Bird", "Rabbit", "Fish", "Hamster"};
|
String[] species = {"All Species", "Dog", "Cat", "Bird", "Rabbit", "Fish", "Hamster"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, species, this::loadPetData);
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerSpecies, species, this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the store filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadPetData);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches store data to populate the store filter.
|
|
||||||
*/
|
|
||||||
private void loadStoreData() {
|
|
||||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshPet.setOnRefreshListener(this::loadPetData);
|
binding.swipeRefreshPet.setOnRefreshListener(this::loadPetData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void loadPetData() {
|
||||||
* Navigates to the pet profile screen.
|
String query = binding.etSearchPet.getText().toString().trim();
|
||||||
*/
|
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
||||||
|
String species = binding.spinnerSpecies.getSelectedItem() != null ? binding.spinnerSpecies.getSelectedItem().toString() : "All Species";
|
||||||
|
|
||||||
|
Long storeId = null;
|
||||||
|
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||||
|
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||||
|
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadPets(query, status, species, storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new PetAdapter(petList, this);
|
||||||
|
adapter.setBaseUrl(baseUrl);
|
||||||
|
adapter.setToken(tokenManager.getToken());
|
||||||
|
binding.recyclerViewPets.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.recyclerViewPets.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
private void openPetProfile(int position) {
|
private void openPetProfile(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
PetDTO pet = petList.get(position);
|
PetDTO pet = petList.get(position);
|
||||||
@@ -180,9 +166,6 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_profile, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the pet detail screen.
|
|
||||||
*/
|
|
||||||
private void openPetDetails() {
|
private void openPetDetails() {
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail);
|
||||||
}
|
}
|
||||||
@@ -199,54 +182,9 @@ public class PetFragment extends Fragment implements PetAdapter.OnPetClickListen
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Fetches pet data from the server with all active filters.
|
public void onDestroyView() {
|
||||||
*/
|
super.onDestroyView();
|
||||||
private void loadPetData() {
|
binding = null;
|
||||||
String query = binding.etSearchPet.getText().toString().trim();
|
|
||||||
String status = binding.spinnerStatus.getSelectedItem() != null ? binding.spinnerStatus.getSelectedItem().toString() : "All Statuses";
|
|
||||||
String species = binding.spinnerSpecies.getSelectedItem() != null ? binding.spinnerSpecies.getSelectedItem().toString() : "All Species";
|
|
||||||
|
|
||||||
Long storeId = null;
|
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
|
||||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (status.equals("All Statuses")) status = null;
|
|
||||||
if (species.equals("All Species")) species = null;
|
|
||||||
|
|
||||||
viewModel.getAllPets(0, 100, query, status, species, storeId, "petName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
binding.swipeRefreshPet.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
binding.swipeRefreshPet.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
petList.clear();
|
|
||||||
petList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
binding.swipeRefreshPet.setRefreshing(false);
|
|
||||||
Toast.makeText(getContext(), "Failed to load pets: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
Log.e("PetFragment", "Error loading pets: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
|
||||||
adapter = new PetAdapter(petList, this);
|
|
||||||
adapter.setBaseUrl(baseUrl);
|
|
||||||
adapter.setToken(tokenManager.getToken());
|
|
||||||
binding.recyclerViewPets.setLayoutManager(new LinearLayoutManager(getContext()));
|
|
||||||
binding.recyclerViewPets.setAdapter(adapter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,21 +9,18 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.ProductAdapter;
|
import com.example.petstoremobile.adapters.ProductAdapter;
|
||||||
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
import com.example.petstoremobile.databinding.FragmentProductBinding;
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.ProductListViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -38,24 +35,17 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
|
|
||||||
private FragmentProductBinding binding;
|
private FragmentProductBinding binding;
|
||||||
private List<ProductDTO> productList = new ArrayList<>();
|
private List<ProductDTO> productList = new ArrayList<>();
|
||||||
private List<CategoryDTO> categoryList = new ArrayList<>();
|
|
||||||
private ProductAdapter adapter;
|
private ProductAdapter adapter;
|
||||||
private ProductViewModel viewModel;
|
private ProductListViewModel viewModel;
|
||||||
|
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its associated ProductViewModel.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -66,6 +56,7 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
setupCategoryFilter();
|
setupCategoryFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
binding.fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
binding.fabAddProduct.setOnClickListener(v -> openProductDetails(-1));
|
||||||
|
|
||||||
@@ -74,67 +65,67 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void observeViewModel() {
|
||||||
public void onDestroyView() {
|
viewModel.getProducts().observe(getViewLifecycleOwner(), list -> {
|
||||||
super.onDestroyView();
|
productList.clear();
|
||||||
binding = null;
|
productList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getCategories().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, list,
|
||||||
|
CategoryDTO::getCategoryName, "All Categories", -1L, CategoryDTO::getCategoryId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshProduct.setRefreshing(loading);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads data every time the fragment becomes visible.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadProductData();
|
loadProductData();
|
||||||
loadCategoryData();
|
viewModel.loadCategories();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter,
|
||||||
binding.etSearchProduct, binding.spinnerCategory);
|
binding.etSearchProduct, binding.spinnerCategory);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the search bar for triggering data load from backend.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchProduct, this::loadProductData);
|
UIUtils.attachSearch(binding.etSearchProduct, this::loadProductData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the category filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupCategoryFilter() {
|
private void setupCategoryFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, this::loadProductData);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerCategory, this::loadProductData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches category data to populate the category filter.
|
|
||||||
*/
|
|
||||||
private void loadCategoryData() {
|
|
||||||
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
categoryList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerCategory, categoryList,
|
|
||||||
CategoryDTO::getCategoryName, "All Categories", -1L, CategoryDTO::getCategoryId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshProduct.setOnRefreshListener(this::loadProductData);
|
binding.swipeRefreshProduct.setOnRefreshListener(this::loadProductData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void loadProductData() {
|
||||||
* Navigates to the product detail screen.
|
String query = binding.etSearchProduct.getText().toString().trim();
|
||||||
*/
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
|
Long categoryId = null;
|
||||||
|
List<CategoryDTO> categories = viewModel.getCategories().getValue();
|
||||||
|
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && categories != null && !categories.isEmpty()) {
|
||||||
|
categoryId = categories.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getCategoryId();
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loadProducts(query, categoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setupRecyclerView() {
|
||||||
|
adapter = new ProductAdapter(productList, this);
|
||||||
|
adapter.setBaseUrl(baseUrl);
|
||||||
|
binding.recyclerViewProducts.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
binding.recyclerViewProducts.setAdapter(adapter);
|
||||||
|
}
|
||||||
|
|
||||||
private void openProductDetails(int position) {
|
private void openProductDetails(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -149,51 +140,9 @@ public class ProductFragment extends Fragment implements ProductAdapter.OnProduc
|
|||||||
openProductDetails(position);
|
openProductDetails(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Fetches product data from the server with search query, category, and sorting.
|
public void onDestroyView() {
|
||||||
*/
|
super.onDestroyView();
|
||||||
private void loadProductData() {
|
binding = null;
|
||||||
String query = binding.etSearchProduct.getText().toString().trim();
|
|
||||||
if (query.isEmpty()) query = null;
|
|
||||||
|
|
||||||
Long categoryId = null;
|
|
||||||
if (binding.spinnerCategory.getSelectedItemPosition() > 0 && !categoryList.isEmpty()) {
|
|
||||||
categoryId = categoryList.get(binding.spinnerCategory.getSelectedItemPosition() - 1).getCategoryId();
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.getAllProducts(query, categoryId, 0, 100, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
binding.swipeRefreshProduct.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
binding.swipeRefreshProduct.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
productList.clear();
|
|
||||||
productList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
binding.swipeRefreshProduct.setRefreshing(false);
|
|
||||||
if (getContext() != null) {
|
|
||||||
Toast.makeText(getContext(), "Failed to load products: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
Log.e("ProductFragment", "Error loading products: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
|
||||||
adapter = new ProductAdapter(productList, this);
|
|
||||||
adapter.setBaseUrl(baseUrl);
|
|
||||||
binding.recyclerViewProducts.setLayoutManager(new LinearLayoutManager(getContext()));
|
|
||||||
binding.recyclerViewProducts.setAdapter(adapter);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -21,12 +19,9 @@ import com.example.petstoremobile.dtos.ProductDTO;
|
|||||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
|
import com.example.petstoremobile.viewmodels.ProductSupplierListViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
|
||||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -39,29 +34,17 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
|
|
||||||
private FragmentProductSupplierBinding binding;
|
private FragmentProductSupplierBinding binding;
|
||||||
private List<ProductSupplierDTO> psList = new ArrayList<>();
|
private List<ProductSupplierDTO> psList = new ArrayList<>();
|
||||||
private List<ProductDTO> productList = new ArrayList<>();
|
|
||||||
private List<SupplierDTO> supplierList = new ArrayList<>();
|
|
||||||
|
|
||||||
private ProductSupplierAdapter adapter;
|
private ProductSupplierAdapter adapter;
|
||||||
private ProductSupplierViewModel viewModel;
|
private ProductSupplierListViewModel viewModel;
|
||||||
private ProductViewModel productViewModel;
|
|
||||||
private SupplierViewModel supplierViewModel;
|
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its associated ViewModels.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductSupplierListViewModel.class);
|
||||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
|
||||||
supplierViewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including the RecyclerView, search, and swipe-to-refresh.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -74,6 +57,7 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
setupBulkDelete();
|
setupBulkDelete();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
binding.fabAddPS.setOnClickListener(v -> openDetail(-1));
|
binding.fabAddPS.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
@@ -82,6 +66,28 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getProductSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
psList.clear();
|
||||||
|
psList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getProducts().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerProduct, list,
|
||||||
|
ProductDTO::getProdName, "All Products", -1L, ProductDTO::getProdId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerSupplier, list,
|
||||||
|
SupplierDTO::getSupCompany, "All Suppliers", -1L, SupplierDTO::getSupId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshPS.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -95,136 +101,65 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
loadData();
|
||||||
|
viewModel.loadFilterData();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads data every time the fragment becomes visible.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
loadData();
|
|
||||||
loadFilterData();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPS,
|
||||||
binding.spinnerProduct, binding.spinnerSupplier);
|
binding.spinnerProduct, binding.spinnerSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView with a layout manager and adapter for product-supplier data.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new ProductSupplierAdapter(psList, this);
|
adapter = new ProductSupplierAdapter(psList, this);
|
||||||
binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewPS.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewPS.setAdapter(adapter);
|
binding.recyclerViewPS.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the search bar for filtering.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchPS, this::loadData);
|
UIUtils.attachSearch(binding.etSearchPS, this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the product filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupProductFilter() {
|
private void setupProductFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, this::loadData);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerProduct, this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the supplier filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupSupplierFilter() {
|
private void setupSupplierFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, this::loadData);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerSupplier, this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches products and suppliers to populate the filters.
|
|
||||||
*/
|
|
||||||
private void loadFilterData() {
|
|
||||||
productViewModel.getAllProducts(null, null, 0, 100, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
productList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerProduct, productList,
|
|
||||||
ProductDTO::getProdName, "All Products", -1L, ProductDTO::getProdId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
supplierViewModel.getAllSuppliers(0, 100, null, "supCompany").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
supplierList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerSupplier, supplierList,
|
|
||||||
SupplierDTO::getSupCompany, "All Suppliers", -1L, SupplierDTO::getSupId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout to allow manual reloading of product-supplier data.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshPS.setOnRefreshListener(this::loadData);
|
binding.swipeRefreshPS.setOnRefreshListener(this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches product-supplier data from the server through the ViewModel with search query and filters.
|
|
||||||
*/
|
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
String query = binding.etSearchPS.getText().toString().trim();
|
String query = binding.etSearchPS.getText().toString().trim();
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
Long productId = null;
|
Long productId = null;
|
||||||
if (binding.spinnerProduct.getSelectedItemPosition() > 0 && !productList.isEmpty()) {
|
List<ProductDTO> products = viewModel.getProducts().getValue();
|
||||||
productId = productList.get(binding.spinnerProduct.getSelectedItemPosition() - 1).getProdId();
|
if (binding.spinnerProduct.getSelectedItemPosition() > 0 && products != null && !products.isEmpty()) {
|
||||||
|
productId = products.get(binding.spinnerProduct.getSelectedItemPosition() - 1).getProdId();
|
||||||
}
|
}
|
||||||
|
|
||||||
Long supplierId = null;
|
Long supplierId = null;
|
||||||
if (binding.spinnerSupplier.getSelectedItemPosition() > 0 && !supplierList.isEmpty()) {
|
List<SupplierDTO> suppliers = viewModel.getSuppliers().getValue();
|
||||||
supplierId = supplierList.get(binding.spinnerSupplier.getSelectedItemPosition() - 1).getSupId();
|
if (binding.spinnerSupplier.getSelectedItemPosition() > 0 && suppliers != null && !suppliers.isEmpty()) {
|
||||||
|
supplierId = suppliers.get(binding.spinnerSupplier.getSelectedItemPosition() - 1).getSupId();
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName").observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadProductSuppliers(query, productId, supplierId);
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
// Check the status to see if the resource is loaded and display the data
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
// Show loading indicator
|
|
||||||
binding.swipeRefreshPS.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
// Hide loading indicator and display data
|
|
||||||
binding.swipeRefreshPS.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
psList.clear();
|
|
||||||
psList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
// Hide loading indicator and toast error message
|
|
||||||
binding.swipeRefreshPS.setRefreshing(false);
|
|
||||||
Toast.makeText(getContext(), "Failed to load: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
Log.e("PSFragment", "Error loading: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the product-supplier detail screen for a specific item or to add a new record.
|
|
||||||
*/
|
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
@@ -235,9 +170,6 @@ public class ProductSupplierFragment extends Fragment
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_product_supplier_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles item click in the product-supplier list.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onProductSupplierClick(int position) { openDetail(position); }
|
public void onProductSupplierClick(int position) { openDetail(position); }
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,9 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@@ -19,11 +17,9 @@ import com.example.petstoremobile.adapters.PurchaseOrderAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
import com.example.petstoremobile.databinding.FragmentPurchaseOrderBinding;
|
||||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
|
import com.example.petstoremobile.viewmodels.PurchaseOrderListViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -36,24 +32,15 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
|
|
||||||
private FragmentPurchaseOrderBinding binding;
|
private FragmentPurchaseOrderBinding binding;
|
||||||
private List<PurchaseOrderDTO> poList = new ArrayList<>();
|
private List<PurchaseOrderDTO> poList = new ArrayList<>();
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private PurchaseOrderAdapter adapter;
|
private PurchaseOrderAdapter adapter;
|
||||||
private PurchaseOrderViewModel viewModel;
|
private PurchaseOrderListViewModel viewModel;
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its associated ViewModels.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PurchaseOrderListViewModel.class);
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including RecyclerView, filters, and swipe-to-refresh.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -64,121 +51,72 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
setupStoreFilter();
|
setupStoreFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburgerPO, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburgerPO, this);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void observeViewModel() {
|
||||||
public void onDestroyView() {
|
viewModel.getPurchaseOrders().observe(getViewLifecycleOwner(), list -> {
|
||||||
super.onDestroyView();
|
poList.clear();
|
||||||
binding = null;
|
poList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||||
|
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshPO.setRefreshing(loading);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Reloads data every time the fragment becomes visible.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadData();
|
loadData();
|
||||||
loadStoreData();
|
viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO, binding.spinnerStore);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchPO, binding.spinnerStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the search bar for filtering.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchPO, this::loadData);
|
UIUtils.attachSearch(binding.etSearchPO, this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the store filter spinner.
|
|
||||||
*/
|
|
||||||
private void setupStoreFilter() {
|
private void setupStoreFilter() {
|
||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadData);
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches store data to populate the store filter.
|
|
||||||
*/
|
|
||||||
private void loadStoreData() {
|
|
||||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "All Stores", -1L, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView with a layout manager and adapter.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new PurchaseOrderAdapter(poList, this);
|
adapter = new PurchaseOrderAdapter(poList, this);
|
||||||
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewPO.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewPO.setAdapter(adapter);
|
binding.recyclerViewPO.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout to allow manual reloading of purchase order data.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshPO.setOnRefreshListener(this::loadData);
|
binding.swipeRefreshPO.setOnRefreshListener(this::loadData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches purchase order data from the server with active filters and updates the UI.
|
|
||||||
*/
|
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
String query = binding.etSearchPO != null ? binding.etSearchPO.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId = null;
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||||
|
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadPurchaseOrders(query, storeId);
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
// Check the status to see if the resource is loaded and display the data
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
// Show loading indicator
|
|
||||||
binding.swipeRefreshPO.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
// Hide loading indicator and display data
|
|
||||||
binding.swipeRefreshPO.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
poList.clear();
|
|
||||||
poList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
// Hide loading indicator and toast error message
|
|
||||||
binding.swipeRefreshPO.setRefreshing(false);
|
|
||||||
Toast.makeText(getContext(), "Failed to load purchase orders: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
Log.e("POFragment", "Error loading purchase orders: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the purchase order detail screen for a specific record.
|
|
||||||
*/
|
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
PurchaseOrderDTO po = poList.get(position);
|
PurchaseOrderDTO po = poList.get(position);
|
||||||
@@ -186,11 +124,14 @@ public class PurchaseOrderFragment extends Fragment
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_purchase_order_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles item click in the purchase order list.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onPurchaseOrderClick(int position) {
|
public void onPurchaseOrderClick(int position) {
|
||||||
openDetail(position);
|
openDetail(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
import androidx.recyclerview.widget.RecyclerView;
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -19,11 +18,9 @@ import com.example.petstoremobile.adapters.SaleAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
import com.example.petstoremobile.databinding.FragmentSaleBinding;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
import com.example.petstoremobile.viewmodels.SaleListViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -33,20 +30,10 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickListener {
|
||||||
|
|
||||||
private static final String TAG = "SaleFragment";
|
|
||||||
private static final int PAGE_SIZE = 200;
|
|
||||||
|
|
||||||
private FragmentSaleBinding binding;
|
private FragmentSaleBinding binding;
|
||||||
private final List<SaleDTO> saleList = new ArrayList<>();
|
private final List<SaleDTO> saleList = new ArrayList<>();
|
||||||
private final List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private SaleAdapter adapter;
|
private SaleAdapter adapter;
|
||||||
private SaleViewModel saleViewModel;
|
private SaleListViewModel viewModel;
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
|
|
||||||
// Pagination
|
|
||||||
private int currentPage = 0;
|
|
||||||
private boolean isLastPage = false;
|
|
||||||
private boolean isLoading = false;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
@@ -58,8 +45,7 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
viewModel = new ViewModelProvider(this).get(SaleListViewModel.class);
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
|
|
||||||
setupRecyclerView();
|
setupRecyclerView();
|
||||||
setupSearch();
|
setupSearch();
|
||||||
@@ -67,6 +53,8 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
setupPaymentMethodFilter();
|
setupPaymentMethodFilter();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
loadSales(true);
|
loadSales(true);
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
@@ -78,10 +66,27 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
|
NavHostFragment.findNavController(this).navigate(R.id.nav_refund));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getSales().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
saleList.clear();
|
||||||
|
saleList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getStores().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, list,
|
||||||
|
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshSale.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
loadStoreData();
|
viewModel.loadStores();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
@@ -93,28 +98,11 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
SpinnerUtils.setupFilterSpinner(binding.spinnerStore, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadStoreData() {
|
|
||||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList.clear();
|
|
||||||
storeList.addAll(resource.data.getContent());
|
|
||||||
SpinnerUtils.populateWhiteSpinner(requireContext(), binding.spinnerStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "Stores", null, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupPaymentMethodFilter() {
|
private void setupPaymentMethodFilter() {
|
||||||
String[] paymentMethods = {"Payments", "Cash", "Card"};
|
String[] paymentMethods = {"Payments", "Cash", "Card"};
|
||||||
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
SpinnerUtils.setupStringFilterSpinner(requireContext(), binding.spinnerPaymentMethod, paymentMethods, () -> loadSales(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onDestroyView() {
|
|
||||||
super.onDestroyView();
|
|
||||||
binding = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new SaleAdapter(saleList, this);
|
adapter = new SaleAdapter(saleList, this);
|
||||||
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewSales.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -129,7 +117,8 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
int visible = lm.getChildCount();
|
int visible = lm.getChildCount();
|
||||||
int total = lm.getItemCount();
|
int total = lm.getItemCount();
|
||||||
int firstVis = lm.findFirstVisibleItemPosition();
|
int firstVis = lm.findFirstVisibleItemPosition();
|
||||||
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
Boolean isLoading = viewModel.getIsLoading().getValue();
|
||||||
|
if ((isLoading == null || !isLoading) && !viewModel.isLastPage() && (visible + firstVis) >= total - 3) {
|
||||||
loadSales(false);
|
loadSales(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,13 +135,6 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void loadSales(boolean reset) {
|
private void loadSales(boolean reset) {
|
||||||
if (isLoading) return;
|
|
||||||
|
|
||||||
if (reset) {
|
|
||||||
currentPage = 0;
|
|
||||||
isLastPage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : "";
|
String query = binding.etSearchSale != null ? binding.etSearchSale.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
|
||||||
@@ -162,39 +144,12 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
}
|
}
|
||||||
|
|
||||||
Long storeId = null;
|
Long storeId = null;
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() > 0 && !storeList.isEmpty()) {
|
List<StoreDTO> stores = viewModel.getStores().getValue();
|
||||||
storeId = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
if (binding.spinnerStore.getSelectedItemPosition() > 0 && stores != null && !stores.isEmpty()) {
|
||||||
|
storeId = stores.get(binding.spinnerStore.getSelectedItemPosition() - 1).getStoreId();
|
||||||
}
|
}
|
||||||
|
|
||||||
saleViewModel.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadSales(reset, query, paymentMethod, storeId);
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
isLoading = true;
|
|
||||||
binding.swipeRefreshSale.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
isLoading = false;
|
|
||||||
binding.swipeRefreshSale.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
if (reset) saleList.clear();
|
|
||||||
saleList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
isLastPage = resource.data.isLast();
|
|
||||||
if (!isLastPage) currentPage++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
isLoading = false;
|
|
||||||
binding.swipeRefreshSale.setRefreshing(false);
|
|
||||||
Log.e(TAG, "Error loading sales: " + resource.message);
|
|
||||||
if (getContext() != null) {
|
|
||||||
Toast.makeText(getContext(), "Failed to load sales: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -210,4 +165,10 @@ public class SaleFragment extends Fragment implements SaleAdapter.OnSaleClickLis
|
|||||||
}
|
}
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_sale_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroyView() {
|
||||||
|
super.onDestroyView();
|
||||||
|
binding = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
@@ -21,7 +20,7 @@ import com.example.petstoremobile.databinding.FragmentServiceBinding;
|
|||||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
import com.example.petstoremobile.viewmodels.ServiceListViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -34,32 +33,18 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class ServiceFragment extends Fragment implements ServiceAdapter.OnServiceClickListener {
|
public class ServiceFragment extends Fragment implements ServiceAdapter.OnServiceClickListener {
|
||||||
|
|
||||||
private static final String TAG = "ServiceFragment";
|
|
||||||
private static final int PAGE_SIZE = 20;
|
|
||||||
|
|
||||||
private FragmentServiceBinding binding;
|
private FragmentServiceBinding binding;
|
||||||
private final List<ServiceDTO> serviceList = new ArrayList<>();
|
private final List<ServiceDTO> serviceList = new ArrayList<>();
|
||||||
private ServiceAdapter adapter;
|
private ServiceAdapter adapter;
|
||||||
private ServiceViewModel viewModel;
|
private ServiceListViewModel viewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
// Pagination
|
|
||||||
private int currentPage = 0;
|
|
||||||
private boolean isLastPage = false;
|
|
||||||
private boolean isLoading = false;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its associated ViewModel.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ServiceListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -70,15 +55,27 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
setupBulkDelete();
|
setupBulkDelete();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
loadServices(true);
|
loadServices(true);
|
||||||
|
|
||||||
binding.fabAddService.setOnClickListener(v -> openDetail(null));
|
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getServices().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
serviceList.clear();
|
||||||
|
serviceList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshService.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -98,23 +95,14 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchService);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the search bar for filtering.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true));
|
UIUtils.attachSearch(binding.etSearchService, () -> loadServices(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView with a layout manager and adapter.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new ServiceAdapter(serviceList, this);
|
adapter = new ServiceAdapter(serviceList, this);
|
||||||
binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewServices.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
@@ -129,66 +117,24 @@ public class ServiceFragment extends Fragment implements ServiceAdapter.OnServic
|
|||||||
int visible = lm.getChildCount();
|
int visible = lm.getChildCount();
|
||||||
int total = lm.getItemCount();
|
int total = lm.getItemCount();
|
||||||
int firstVis = lm.findFirstVisibleItemPosition();
|
int firstVis = lm.findFirstVisibleItemPosition();
|
||||||
if (!isLoading && !isLastPage && (visible + firstVis) >= total - 3) {
|
Boolean isLoading = viewModel.getIsLoading().getValue();
|
||||||
|
if ((isLoading == null || !isLoading) && !viewModel.isLastPage() && (visible + firstVis) >= total - 3) {
|
||||||
loadServices(false);
|
loadServices(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true));
|
binding.swipeRefreshService.setOnRefreshListener(() -> loadServices(true));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a page of services from the API.
|
|
||||||
*/
|
|
||||||
private void loadServices(boolean reset) {
|
private void loadServices(boolean reset) {
|
||||||
if (isLoading) return;
|
|
||||||
|
|
||||||
if (reset) {
|
|
||||||
currentPage = 0;
|
|
||||||
isLastPage = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
String query = binding.etSearchService.getText().toString().trim();
|
String query = binding.etSearchService.getText().toString().trim();
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
viewModel.loadServices(reset, query);
|
||||||
viewModel.getAllServices(currentPage, PAGE_SIZE, query, "serviceName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
isLoading = true;
|
|
||||||
binding.swipeRefreshService.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
isLoading = false;
|
|
||||||
binding.swipeRefreshService.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
if (reset) serviceList.clear();
|
|
||||||
serviceList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
isLastPage = resource.data.isLast();
|
|
||||||
if (!isLastPage) currentPage++;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
isLoading = false;
|
|
||||||
binding.swipeRefreshService.setRefreshing(false);
|
|
||||||
Log.e(TAG, "Error: " + resource.message);
|
|
||||||
Toast.makeText(getContext(), "Failed to load services: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the service detail screen.
|
|
||||||
*/
|
|
||||||
private void openDetail(ServiceDTO service) {
|
private void openDetail(ServiceDTO service) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (service != null) {
|
if (service != null) {
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments;
|
package com.example.petstoremobile.fragments.listfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@@ -14,7 +13,7 @@ import com.example.petstoremobile.adapters.EmployeeAdapter;
|
|||||||
import com.example.petstoremobile.databinding.FragmentStaffBinding;
|
import com.example.petstoremobile.databinding.FragmentStaffBinding;
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.EmployeeViewModel;
|
import com.example.petstoremobile.viewmodels.StaffListViewModel;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
@@ -22,21 +21,22 @@ import java.util.*;
|
|||||||
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
|
public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmployeeClickListener {
|
||||||
|
|
||||||
private FragmentStaffBinding binding;
|
private FragmentStaffBinding binding;
|
||||||
private EmployeeViewModel employeeViewModel;
|
private StaffListViewModel viewModel;
|
||||||
private List<EmployeeDTO> employeeList = new ArrayList<>();
|
private List<EmployeeDTO> staffList = new ArrayList<>();
|
||||||
private List<EmployeeDTO> filteredList = new ArrayList<>();
|
|
||||||
private EmployeeAdapter adapter;
|
private EmployeeAdapter adapter;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
binding = FragmentStaffBinding.inflate(inflater, container, false);
|
binding = FragmentStaffBinding.inflate(inflater, container, false);
|
||||||
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
|
viewModel = new ViewModelProvider(this).get(StaffListViewModel.class);
|
||||||
|
|
||||||
setupRecyclerView();
|
setupRecyclerView();
|
||||||
setupSearch();
|
setupSearch();
|
||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
loadStaff();
|
observeViewModel();
|
||||||
|
|
||||||
|
viewModel.loadStaff();
|
||||||
|
|
||||||
binding.fabAddStaff.setOnClickListener(v -> openDetail(-1));
|
binding.fabAddStaff.setOnClickListener(v -> openDetail(-1));
|
||||||
|
|
||||||
@@ -46,70 +46,36 @@ public class StaffFragment extends Fragment implements EmployeeAdapter.OnEmploye
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getFilteredEmployees().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
staffList.clear();
|
||||||
|
staffList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshStaff.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new EmployeeAdapter(filteredList, this);
|
adapter = new EmployeeAdapter(staffList, this);
|
||||||
binding.recyclerViewStaff.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewStaff.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
binding.recyclerViewStaff.setAdapter(adapter);
|
binding.recyclerViewStaff.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchStaff, () -> filter(binding.etSearchStaff.getText().toString()));
|
UIUtils.attachSearch(binding.etSearchStaff, () -> viewModel.filter(binding.etSearchStaff.getText().toString()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshStaff.setOnRefreshListener(this::loadStaff);
|
binding.swipeRefreshStaff.setOnRefreshListener(viewModel::loadStaff);
|
||||||
}
|
|
||||||
|
|
||||||
private void filter(String query) {
|
|
||||||
filteredList.clear();
|
|
||||||
if (query.isEmpty()) {
|
|
||||||
filteredList.addAll(employeeList);
|
|
||||||
} else {
|
|
||||||
String lower = query.toLowerCase();
|
|
||||||
for (EmployeeDTO e : employeeList) {
|
|
||||||
if ((e.getFullName() != null && e.getFullName().toLowerCase().contains(lower))
|
|
||||||
|| (e.getUsername() != null && e.getUsername().toLowerCase().contains(lower))
|
|
||||||
|| (e.getEmail() != null && e.getEmail().toLowerCase().contains(lower))
|
|
||||||
|| (e.getPhone() != null && e.getPhone().toLowerCase().contains(lower))) {
|
|
||||||
filteredList.add(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void loadStaff() {
|
|
||||||
binding.swipeRefreshStaff.setRefreshing(true);
|
|
||||||
employeeViewModel.getAllEmployees(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null) {
|
|
||||||
switch (resource.status) {
|
|
||||||
case SUCCESS:
|
|
||||||
binding.swipeRefreshStaff.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
employeeList.clear();
|
|
||||||
employeeList.addAll(resource.data.getContent());
|
|
||||||
filter(binding != null ? binding.etSearchStaff.getText().toString() : "");
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
binding.swipeRefreshStaff.setRefreshing(false);
|
|
||||||
if (getContext() != null) {
|
|
||||||
Toast.makeText(getContext(), resource.message != null ? resource.message : "Failed to load staff",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case LOADING:
|
|
||||||
binding.swipeRefreshStaff.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void openDetail(int position) {
|
private void openDetail(int position) {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
EmployeeDTO e = filteredList.get(position);
|
EmployeeDTO e = staffList.get(position);
|
||||||
args.putLong("employeeId", e.getEmployeeId());
|
args.putLong("employeeId", e.getEmployeeId());
|
||||||
args.putString("username", e.getUsername() != null ? e.getUsername() : "");
|
args.putString("username", e.getUsername() != null ? e.getUsername() : "");
|
||||||
args.putString("firstName", e.getFirstName() != null ? e.getFirstName() : "");
|
args.putString("firstName", e.getFirstName() != null ? e.getFirstName() : "");
|
||||||
|
|||||||
@@ -9,20 +9,17 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager;
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
|
||||||
import android.util.Log;
|
|
||||||
import android.view.LayoutInflater;
|
import android.view.LayoutInflater;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.adapters.SupplierAdapter;
|
import com.example.petstoremobile.adapters.SupplierAdapter;
|
||||||
import com.example.petstoremobile.databinding.FragmentSupplierBinding;
|
import com.example.petstoremobile.databinding.FragmentSupplierBinding;
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
import com.example.petstoremobile.utils.BulkDeleteHandler;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
import com.example.petstoremobile.viewmodels.SupplierListViewModel;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -35,21 +32,15 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
private FragmentSupplierBinding binding;
|
private FragmentSupplierBinding binding;
|
||||||
private List<SupplierDTO> supplierList = new ArrayList<>();
|
private List<SupplierDTO> supplierList = new ArrayList<>();
|
||||||
private SupplierAdapter adapter;
|
private SupplierAdapter adapter;
|
||||||
private SupplierViewModel viewModel;
|
private SupplierListViewModel viewModel;
|
||||||
private BulkDeleteHandler bulkDeleteHandler;
|
private BulkDeleteHandler bulkDeleteHandler;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the fragment and its associated SupplierViewModel.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
viewModel = new ViewModelProvider(this).get(SupplierListViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the fragment's UI components, including RecyclerView, search, and swipe-to-refresh.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -60,9 +51,10 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
setupSwipeRefresh();
|
setupSwipeRefresh();
|
||||||
setupFilterToggle();
|
setupFilterToggle();
|
||||||
setupBulkDelete();
|
setupBulkDelete();
|
||||||
|
observeViewModel();
|
||||||
|
|
||||||
loadSupplierData();
|
loadSupplierData();
|
||||||
|
|
||||||
//Add button to opens the add dialog
|
|
||||||
binding.fabAddSupplier.setOnClickListener(v -> openSupplierDetails(-1));
|
binding.fabAddSupplier.setOnClickListener(v -> openSupplierDetails(-1));
|
||||||
|
|
||||||
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
UIUtils.setupHamburgerMenu(binding.btnHamburger, this);
|
||||||
@@ -70,6 +62,18 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getSuppliers().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
supplierList.clear();
|
||||||
|
supplierList.addAll(list);
|
||||||
|
adapter.notifyDataSetChanged();
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getIsLoading().observe(getViewLifecycleOwner(), loading -> {
|
||||||
|
binding.swipeRefreshSupplier.setRefreshing(loading);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void setupBulkDelete() {
|
private void setupBulkDelete() {
|
||||||
bulkDeleteHandler = new BulkDeleteHandler(
|
bulkDeleteHandler = new BulkDeleteHandler(
|
||||||
this,
|
this,
|
||||||
@@ -89,47 +93,27 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the filter toggle button to show/hide the filter layout.
|
|
||||||
*/
|
|
||||||
private void setupFilterToggle() {
|
private void setupFilterToggle() {
|
||||||
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
|
UIUtils.setupFilterToggle(binding.btnToggleFilter, binding.layoutFilter, binding.etSearchSupplier);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the search bar for filtering.
|
|
||||||
*/
|
|
||||||
private void setupSearch() {
|
private void setupSearch() {
|
||||||
UIUtils.attachSearch(binding.etSearchSupplier, this::loadSupplierData);
|
UIUtils.attachSearch(binding.etSearchSupplier, this::loadSupplierData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up the SwipeRefreshLayout to allow manual reloading of supplier data.
|
|
||||||
*/
|
|
||||||
private void setupSwipeRefresh() {
|
private void setupSwipeRefresh() {
|
||||||
binding.swipeRefreshSupplier.setOnRefreshListener(this::loadSupplierData);
|
binding.swipeRefreshSupplier.setOnRefreshListener(this::loadSupplierData);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates to the supplier detail screen for editing an existing record or adding a new one.
|
|
||||||
*/
|
|
||||||
private void openSupplierDetails(int position) {
|
private void openSupplierDetails(int position) {
|
||||||
//Make a bundle to pass data to the detail fragment
|
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
|
|
||||||
//if editing a supplier, add the supplier id to the bundle
|
|
||||||
if (position != -1) {
|
if (position != -1) {
|
||||||
SupplierDTO supplier = supplierList.get(position);
|
SupplierDTO supplier = supplierList.get(position);
|
||||||
args.putLong("supId", supplier.getSupId());
|
args.putLong("supId", supplier.getSupId());
|
||||||
}
|
}
|
||||||
|
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_supplier_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_supplier_detail, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles item click in the supplier list.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onSupplierClick(int position) {
|
public void onSupplierClick(int position) {
|
||||||
openSupplierDetails(position);
|
openSupplierDetails(position);
|
||||||
@@ -142,47 +126,12 @@ public class SupplierFragment extends Fragment implements SupplierAdapter.OnSupp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all supplier data from the server through the ViewModel and updates the UI.
|
|
||||||
*/
|
|
||||||
private void loadSupplierData() {
|
private void loadSupplierData() {
|
||||||
String query = binding.etSearchSupplier != null ? binding.etSearchSupplier.getText().toString().trim() : "";
|
String query = binding.etSearchSupplier != null ? binding.etSearchSupplier.getText().toString().trim() : "";
|
||||||
if (query.isEmpty()) query = null;
|
if (query.isEmpty()) query = null;
|
||||||
|
viewModel.loadSuppliers(query);
|
||||||
//Load suppliers from the backend with query and default sort
|
|
||||||
viewModel.getAllSuppliers(0, 100, query, "supCompany").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource == null) return;
|
|
||||||
|
|
||||||
// Check the status to see if the resource is loaded and display the data
|
|
||||||
switch (resource.status) {
|
|
||||||
case LOADING:
|
|
||||||
// Show loading indicator
|
|
||||||
binding.swipeRefreshSupplier.setRefreshing(true);
|
|
||||||
break;
|
|
||||||
case SUCCESS:
|
|
||||||
// Hide loading indicator and display data
|
|
||||||
binding.swipeRefreshSupplier.setRefreshing(false);
|
|
||||||
if (resource.data != null) {
|
|
||||||
supplierList.clear();
|
|
||||||
supplierList.addAll(resource.data.getContent());
|
|
||||||
adapter.notifyDataSetChanged();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
// Hide loading indicator and toast error message
|
|
||||||
binding.swipeRefreshSupplier.setRefreshing(false);
|
|
||||||
if (getContext() != null) {
|
|
||||||
Toast.makeText(getContext(), "Failed to load suppliers: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
Log.e("SupplierFragment", "Error loading suppliers: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the RecyclerView with a layout manager and adapter for displaying suppliers.
|
|
||||||
*/
|
|
||||||
private void setupRecyclerView() {
|
private void setupRecyclerView() {
|
||||||
adapter = new SupplierAdapter(supplierList, this);
|
adapter = new SupplierAdapter(supplierList, this);
|
||||||
binding.recyclerViewSuppliers.setLayoutManager(new LinearLayoutManager(getContext()));
|
binding.recyclerViewSuppliers.setLayoutManager(new LinearLayoutManager(getContext()));
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.app.DatePickerDialog;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
@@ -12,14 +11,13 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
|
|
||||||
import com.example.petstoremobile.databinding.FragmentAdoptionDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentAdoptionDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AdoptionViewModel;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.CustomerViewModel;
|
import com.example.petstoremobile.viewmodels.AdoptionDetailViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
import com.example.petstoremobile.viewmodels.UserViewModel;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -33,35 +31,19 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class AdoptionDetailFragment extends Fragment {
|
public class AdoptionDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentAdoptionDetailBinding binding;
|
private FragmentAdoptionDetailBinding binding;
|
||||||
|
private AdoptionDetailViewModel viewModel;
|
||||||
|
|
||||||
private long adoptionId = -1;
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private long preselectedPetId = -1;
|
private long preselectedPetId = -1;
|
||||||
private long preselectedCustomerId = -1;
|
private long preselectedCustomerId = -1;
|
||||||
private long preselectedStoreId = -1;
|
private long preselectedStoreId = -1;
|
||||||
private long preselectedEmployeeId = -1;
|
private long preselectedEmployeeId = -1;
|
||||||
|
|
||||||
private List<PetDTO> petList = new ArrayList<>();
|
|
||||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private List<UserDTO> employeeList = new ArrayList<>();
|
|
||||||
|
|
||||||
private final String[] STATUSES = {"Pending", "Completed", "Cancelled"};
|
private final String[] STATUSES = {"Pending", "Completed", "Cancelled"};
|
||||||
|
|
||||||
private AdoptionViewModel adoptionViewModel;
|
|
||||||
private PetViewModel petViewModel;
|
|
||||||
private CustomerViewModel customerViewModel;
|
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
private UserViewModel userViewModel;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
adoptionViewModel = new ViewModelProvider(this).get(AdoptionViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AdoptionDetailViewModel.class);
|
||||||
petViewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
|
||||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -76,6 +58,7 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setupSpinners();
|
setupSpinners();
|
||||||
setupDatePicker();
|
setupDatePicker();
|
||||||
|
observeViewModel();
|
||||||
loadSpinnersData();
|
loadSpinnersData();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
@@ -84,155 +67,146 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete());
|
binding.btnDeleteAdoption.setOnClickListener(v -> confirmDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getPetList().observe(getViewLifecycleOwner(), list -> refreshPetSpinner());
|
||||||
|
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> refreshCustomerSpinner());
|
||||||
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
||||||
|
viewModel.getEmployeeList().observe(getViewLifecycleOwner(), list -> refreshEmployeeSpinner());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the spinner for adoption status.
|
|
||||||
*/
|
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, STATUSES);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAdoptionStatus, STATUSES);
|
||||||
|
|
||||||
|
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
||||||
|
|
||||||
|
binding.spinnerAdoptionCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (position > 0) {
|
||||||
|
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
|
||||||
|
} else {
|
||||||
|
if (!viewModel.isEditing()) {
|
||||||
|
binding.spinnerAdoptionPet.setSelection(0);
|
||||||
|
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.spinnerAdoptionStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
if (position > 0 && viewModel.getStoreList().getValue() != null && position <= viewModel.getStoreList().getValue().size()) {
|
||||||
|
DropdownDTO selectedStore = viewModel.getStoreList().getValue().get(position - 1);
|
||||||
|
loadEmployees(selectedStore.getId());
|
||||||
|
} else {
|
||||||
|
viewModel.setEmployeeList(new ArrayList<>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the date picker dialog for the adoption date field.
|
|
||||||
*/
|
|
||||||
private void setupDatePicker() {
|
private void setupDatePicker() {
|
||||||
binding.etAdoptionDate.setOnClickListener(v -> {
|
binding.etAdoptionDate.setOnClickListener(v -> UIUtils.showDatePicker(requireContext(), binding.etAdoptionDate, null));
|
||||||
Calendar c = Calendar.getInstance();
|
|
||||||
new DatePickerDialog(requireContext(),
|
|
||||||
(dp, y, m, d) -> binding.etAdoptionDate.setText(
|
|
||||||
String.format("%04d-%02d-%02d", y, m + 1, d)),
|
|
||||||
c.get(Calendar.YEAR),
|
|
||||||
c.get(Calendar.MONTH),
|
|
||||||
c.get(Calendar.DAY_OF_MONTH)).show();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches required data for spinners from the backend.
|
|
||||||
*/
|
|
||||||
private void loadSpinnersData() {
|
private void loadSpinnersData() {
|
||||||
loadPets();
|
viewModel.loadPets().observe(getViewLifecycleOwner(), resource -> {
|
||||||
loadCustomers();
|
if (resource == null) return;
|
||||||
loadStores();
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
loadEmployees();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of pets from the API.
|
|
||||||
*/
|
|
||||||
private void loadPets() {
|
|
||||||
petViewModel.getAllPets(0, 200, null, null, null, null, "petName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
petList = resource.data.getContent();
|
viewModel.setPetList(resource.data);
|
||||||
refreshPetSpinner();
|
}
|
||||||
|
});
|
||||||
|
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setCustomerList(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setStoreList(resource.data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the pet selection spinner with data.
|
|
||||||
*/
|
|
||||||
private void refreshPetSpinner() {
|
private void refreshPetSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, petList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionPet, viewModel.getPetList().getValue(),
|
||||||
PetDTO::getPetName, "-- Select Pet --",
|
DropdownDTO::getLabel, "-- Select Pet --",
|
||||||
preselectedPetId, PetDTO::getPetId);
|
preselectedPetId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of customers from the API.
|
|
||||||
*/
|
|
||||||
private void loadCustomers() {
|
|
||||||
customerViewModel.getAllCustomers(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
customerList = resource.data.getContent();
|
|
||||||
refreshCustomerSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the customer selection spinner with data.
|
|
||||||
*/
|
|
||||||
private void refreshCustomerSpinner() {
|
private void refreshCustomerSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, customerList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionCustomer, viewModel.getCustomerList().getValue(),
|
||||||
item -> item.getFirstName() + " " + item.getLastName(),
|
DropdownDTO::getLabel, "-- Select Customer --",
|
||||||
"-- Select Customer --",
|
preselectedCustomerId, DropdownDTO::getId);
|
||||||
preselectedCustomerId, CustomerDTO::getCustomerId);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of stores from the API.
|
|
||||||
*/
|
|
||||||
private void loadStores() {
|
|
||||||
storeViewModel.getAllStores(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
refreshStoreSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the store selection spinner with data.
|
|
||||||
*/
|
|
||||||
private void refreshStoreSpinner() {
|
private void refreshStoreSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, storeList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionStore, viewModel.getStoreList().getValue(),
|
||||||
StoreDTO::getStoreName, "-- Select Store --",
|
DropdownDTO::getLabel, "-- Select Store --",
|
||||||
preselectedStoreId, StoreDTO::getStoreId);
|
preselectedStoreId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void loadEmployees(Long storeId) {
|
||||||
* Loads the list of employees from the API.
|
viewModel.loadEmployees(storeId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
*/
|
if (resource == null) return;
|
||||||
private void loadEmployees() {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
userViewModel.getUsers("STAFF", 0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
employeeList = resource.data.getContent();
|
viewModel.setEmployeeList(resource.data);
|
||||||
refreshEmployeeSpinner();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the employee selection spinner with data.
|
|
||||||
*/
|
|
||||||
private void refreshEmployeeSpinner() {
|
private void refreshEmployeeSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, employeeList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerAdoptionEmployee, viewModel.getEmployeeList().getValue(),
|
||||||
UserDTO::getFullName, "-- Select Staff --",
|
DropdownDTO::getLabel, "-- Select Staff --",
|
||||||
preselectedEmployeeId, UserDTO::getId);
|
preselectedEmployeeId, DropdownDTO::getId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles arguments to determine if the fragment is in edit or add mode.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("adoptionId")) {
|
if (a != null && a.containsKey("adoptionId")) {
|
||||||
isEditing = true;
|
long adoptionId = a.getLong("adoptionId");
|
||||||
adoptionId = a.getLong("adoptionId");
|
viewModel.setAdoptionId(adoptionId);
|
||||||
binding.tvAdoptionMode.setText("Edit Adoption");
|
binding.tvAdoptionMode.setText("Edit Adoption");
|
||||||
binding.tvAdoptionId.setText("ID: " + adoptionId);
|
binding.tvAdoptionId.setText(DateTimeUtils.formatId(adoptionId));
|
||||||
binding.tvAdoptionId.setVisibility(View.VISIBLE);
|
binding.tvAdoptionId.setVisibility(View.VISIBLE);
|
||||||
binding.btnDeleteAdoption.setVisibility(View.VISIBLE);
|
binding.btnDeleteAdoption.setVisibility(View.VISIBLE);
|
||||||
loadAdoptionData();
|
loadAdoptionData();
|
||||||
} else {
|
} else {
|
||||||
|
viewModel.setAdoptionId(-1);
|
||||||
binding.tvAdoptionMode.setText("Add Adoption");
|
binding.tvAdoptionMode.setText("Add Adoption");
|
||||||
binding.btnDeleteAdoption.setVisibility(View.GONE);
|
binding.btnDeleteAdoption.setVisibility(View.GONE);
|
||||||
binding.tvAdoptionId.setVisibility(View.GONE);
|
binding.tvAdoptionId.setVisibility(View.GONE);
|
||||||
|
UIUtils.setViewsEnabled(false, binding.spinnerAdoptionPet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches specific adoption details from the backend using the ID.
|
|
||||||
*/
|
|
||||||
private void loadAdoptionData() {
|
private void loadAdoptionData() {
|
||||||
adoptionViewModel.getAdoptionById(adoptionId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadAdoption().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
AdoptionDTO a = resource.data;
|
AdoptionDTO a = resource.data;
|
||||||
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
||||||
@@ -247,90 +221,68 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
refreshPetSpinner();
|
refreshPetSpinner();
|
||||||
refreshCustomerSpinner();
|
refreshCustomerSpinner();
|
||||||
refreshStoreSpinner();
|
refreshStoreSpinner();
|
||||||
refreshEmployeeSpinner();
|
|
||||||
|
if (preselectedCustomerId != -1) {
|
||||||
|
UIUtils.setViewsEnabled(true, binding.spinnerAdoptionPet);
|
||||||
|
}
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Failed to load adoption: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Failed to load adoption: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates input and saves the adoption request to the backend.
|
|
||||||
*/
|
|
||||||
private void saveAdoption() {
|
private void saveAdoption() {
|
||||||
if (binding.spinnerAdoptionCustomer.getSelectedItemPosition() == 0) {
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionCustomer, "Customer")) return;
|
||||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionPet, "Pet")) return;
|
||||||
}
|
if (!InputValidator.isSpinnerSelected(binding.spinnerAdoptionStore, "Store")) return;
|
||||||
if (binding.spinnerAdoptionPet.getSelectedItemPosition() == 0) {
|
if (!InputValidator.isNotEmpty(binding.etAdoptionDate, "Adoption Date")) return;
|
||||||
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
if (binding.spinnerAdoptionStore.getSelectedItemPosition() == 0) {
|
|
||||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
String date = binding.etAdoptionDate.getText().toString().trim();
|
|
||||||
if (date.isEmpty()) {
|
|
||||||
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal fee = BigDecimal.ZERO;
|
BigDecimal fee = BigDecimal.ZERO;
|
||||||
String feeStr = binding.etAdoptionFee.getText().toString().trim();
|
String feeStr = binding.etAdoptionFee.getText().toString().trim();
|
||||||
if (!feeStr.isEmpty()) {
|
if (!feeStr.isEmpty()) {
|
||||||
try {
|
if (!InputValidator.isPositiveDecimal(binding.etAdoptionFee, "Adoption Fee")) return;
|
||||||
fee = new BigDecimal(feeStr);
|
fee = new BigDecimal(feeStr);
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
Toast.makeText(getContext(), "Invalid fee format", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CustomerDTO customer = customerList.get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
DropdownDTO customer = viewModel.getCustomerList().getValue().get(binding.spinnerAdoptionCustomer.getSelectedItemPosition() - 1);
|
||||||
PetDTO pet = petList.get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
|
DropdownDTO pet = viewModel.getPetList().getValue().get(binding.spinnerAdoptionPet.getSelectedItemPosition() - 1);
|
||||||
StoreDTO store = storeList.get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerAdoptionStore.getSelectedItemPosition() - 1);
|
||||||
|
|
||||||
Long employeeId = null;
|
Long employeeId = null;
|
||||||
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0) {
|
if (binding.spinnerAdoptionEmployee.getSelectedItemPosition() > 0) {
|
||||||
employeeId = employeeList.get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
|
employeeId = viewModel.getEmployeeList().getValue().get(binding.spinnerAdoptionEmployee.getSelectedItemPosition() - 1).getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String adoptionDate = binding.etAdoptionDate.getText().toString().trim();
|
||||||
String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()];
|
String status = STATUSES[binding.spinnerAdoptionStatus.getSelectedItemPosition()];
|
||||||
|
|
||||||
AdoptionDTO dto = new AdoptionDTO(
|
AdoptionDTO dto = new AdoptionDTO(
|
||||||
pet.getPetId(),
|
pet.getId(),
|
||||||
customer.getCustomerId(),
|
customer.getId(),
|
||||||
employeeId,
|
employeeId,
|
||||||
store.getStoreId(),
|
store.getId(),
|
||||||
date,
|
adoptionDate,
|
||||||
status,
|
status,
|
||||||
fee
|
fee
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isEditing) {
|
viewModel.saveAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
adoptionViewModel.updateAdoption(adoptionId, dto).observe(getViewLifecycleOwner(), resource -> {
|
if (resource == null) return;
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
navigateBack();
|
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
navigateBack();
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
}
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
adoptionViewModel.createAdoption(dto).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
|
||||||
Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show();
|
|
||||||
navigateBack();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a confirmation dialog before deleting an adoption request.
|
|
||||||
*/
|
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Adoption Record", () ->
|
||||||
adoptionViewModel.deleteAdoption(adoptionId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteAdoption().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
@@ -340,9 +292,6 @@ public class AdoptionDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the previous fragment.
|
|
||||||
*/
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.app.DatePickerDialog;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.view.LayoutInflater;
|
||||||
import android.view.*;
|
import android.view.View;
|
||||||
import android.widget.*;
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.AdapterView;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
@@ -12,18 +14,16 @@ import androidx.lifecycle.ViewModelProvider;
|
|||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
import com.example.petstoremobile.databinding.FragmentAppointmentDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentAppointmentDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.viewmodels.AppointmentViewModel;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.CustomerViewModel;
|
import com.example.petstoremobile.viewmodels.AppointmentDetailViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
|
||||||
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
import com.example.petstoremobile.viewmodels.UserViewModel;
|
|
||||||
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@@ -35,39 +35,22 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private FragmentAppointmentDetailBinding binding;
|
private FragmentAppointmentDetailBinding binding;
|
||||||
|
|
||||||
private long appointmentId = -1;
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private long preselectedPetId = -1;
|
private long preselectedPetId = -1;
|
||||||
private long preselectedServiceId = -1;
|
private long preselectedServiceId = -1;
|
||||||
private long preselectedCustomerId = -1;
|
private long preselectedCustomerId = -1;
|
||||||
private long preselectedStoreId = -1;
|
private long preselectedStoreId = -1;
|
||||||
private long preselectedStaffId = -1;
|
private long preselectedStaffId = -1;
|
||||||
|
|
||||||
private List<PetDTO> petList = new ArrayList<>();
|
private final Integer[] HOURS = {9, 10, 11, 12, 13, 14, 15, 16, 17};
|
||||||
private List<ServiceDTO> serviceList = new ArrayList<>();
|
private final Integer[] MINUTES = {0, 15, 30, 45};
|
||||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private List<UserDTO> staffList = new ArrayList<>();
|
|
||||||
|
|
||||||
private final Integer[] HOURS = {9,10,11,12,13,14,15,16,17};
|
private AppointmentDetailViewModel viewModel;
|
||||||
private final Integer[] MINUTES = {0,15,30,45};
|
private boolean isUpdatingUI = false;
|
||||||
|
|
||||||
private AppointmentViewModel appointmentViewModel;
|
|
||||||
private PetViewModel petViewModel;
|
|
||||||
private ServiceViewModel serviceViewModel;
|
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
private CustomerViewModel customerViewModel;
|
|
||||||
private UserViewModel userViewModel;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
appointmentViewModel = new ViewModelProvider(this).get(AppointmentViewModel.class);
|
viewModel = new ViewModelProvider(this).get(AppointmentDetailViewModel.class);
|
||||||
petViewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
|
||||||
serviceViewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
|
||||||
userViewModel = new ViewModelProvider(this).get(UserViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -81,7 +64,8 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
setupSpinners();
|
setupSpinners();
|
||||||
setupDatePicker();
|
setupDatePicker();
|
||||||
loadSpinnersData();
|
observeViewModel();
|
||||||
|
viewModel.loadInitialFormData();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
binding.btnApptBack.setOnClickListener(v -> navigateBack());
|
binding.btnApptBack.setOnClickListener(v -> navigateBack());
|
||||||
@@ -95,365 +79,214 @@ public class AppointmentDetailFragment extends Fragment {
|
|||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the adapters for spinners.
|
|
||||||
*/
|
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus,
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, new String[]{});
|
||||||
new String[]{"Booked", "Completed", "Cancelled", "Missed"});
|
|
||||||
|
|
||||||
String[] hours = new String[HOURS.length];
|
String[] hours = new String[HOURS.length];
|
||||||
for (int i = 0; i < HOURS.length; i++)
|
for (int i = 0; i < HOURS.length; i++)
|
||||||
hours[i] = String.format("%02d:00", HOURS[i]);
|
hours[i] = DateTimeUtils.formatTime(HOURS[i], 0);
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerHour, hours);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerHour, hours);
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00","15","30","45"});
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerMinute, new String[]{"00", "15", "30", "45"});
|
||||||
|
|
||||||
|
UIUtils.setViewsEnabled(false, binding.spinnerPet, binding.spinnerStaff);
|
||||||
|
|
||||||
|
binding.spinnerCustomer.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
viewModel.onCustomerSelected(position);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
binding.spinnerStore.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
viewModel.onStoreSelected(position);
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
|
});
|
||||||
|
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerService, p -> viewModel.onServiceSelected(p));
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerPet, p -> viewModel.onPetSelected(p));
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerStaff, p -> viewModel.onStaffSelected(p));
|
||||||
|
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerHour, p -> notifyDateTimeStatusChange());
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerMinute, p -> notifyDateTimeStatusChange());
|
||||||
|
SpinnerUtils.setOnIndexSelectedListener(binding.spinnerAppointmentStatus, p -> notifyDateTimeStatusChange());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures the date picker dialog for the appointment date field.
|
|
||||||
*/
|
|
||||||
private void setupDatePicker() {
|
private void setupDatePicker() {
|
||||||
binding.etAppointmentDate.setOnClickListener(v -> {
|
binding.etAppointmentDate.setOnClickListener(v ->
|
||||||
Calendar c = Calendar.getInstance();
|
UIUtils.showDatePicker(requireContext(), binding.etAppointmentDate, this::notifyDateTimeStatusChange));
|
||||||
DatePickerDialog d = new DatePickerDialog(requireContext(),
|
|
||||||
(dp,y,m,d1) -> binding.etAppointmentDate.setText(
|
|
||||||
String.format("%04d-%02d-%02d", y, m+1, d1)),
|
|
||||||
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
|
||||||
c.get(Calendar.DAY_OF_MONTH));
|
|
||||||
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
|
||||||
d.show();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void observeViewModel() {
|
||||||
* Fetches all required data for spinners from the backend.
|
viewModel.getViewState().observe(getViewLifecycleOwner(), this::applyViewState);
|
||||||
*/
|
|
||||||
private void loadSpinnersData() {
|
viewModel.getCustomers().observe(getViewLifecycleOwner(), list ->
|
||||||
loadPets();
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, list, DropdownDTO::getLabel, "-- Select Customer --", preselectedCustomerId, DropdownDTO::getId));
|
||||||
loadServices();
|
|
||||||
loadCustomers();
|
viewModel.getStores().observe(getViewLifecycleOwner(), list ->
|
||||||
loadStores();
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, list, DropdownDTO::getLabel, "-- Select Store --", preselectedStoreId, DropdownDTO::getId));
|
||||||
loadStaff();
|
|
||||||
|
viewModel.getServices().observe(getViewLifecycleOwner(), list ->
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, list, ServiceDTO::getServiceName, "-- Select Service --", preselectedServiceId, ServiceDTO::getServiceId));
|
||||||
|
|
||||||
|
viewModel.getCustomerPets().observe(getViewLifecycleOwner(), list ->
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, list, DropdownDTO::getLabel, "-- Select Pet --", preselectedPetId, DropdownDTO::getId));
|
||||||
|
|
||||||
|
viewModel.getStoreEmployees().observe(getViewLifecycleOwner(), list ->
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, list, DropdownDTO::getLabel, "-- Select Staff --", preselectedStaffId, DropdownDTO::getId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void setLoading(boolean loading) {
|
||||||
* Loads the list of pets from the ViewModel.
|
if (binding != null && binding.progressBar != null) {
|
||||||
*/
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
private void loadPets() {
|
}
|
||||||
petViewModel.getAllPets(0, 200, null, null, null, null, "petName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
petList = resource.data.getContent();
|
|
||||||
refreshPetSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void applyViewState(AppointmentDetailViewModel.ViewState state) {
|
||||||
* Populates the pet selection spinner.
|
isUpdatingUI = true;
|
||||||
*/
|
|
||||||
private void refreshPetSpinner() {
|
binding.tvApptMode.setText(state.isEditing ? "Edit Appointment" : "Add Appointment");
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPet, petList,
|
binding.tvAppointmentId.setText(DateTimeUtils.formatId(viewModel.getAppointmentId()));
|
||||||
PetDTO::getPetName, "-- Select Pet --",
|
binding.tvAppointmentId.setVisibility(state.isEditing ? View.VISIBLE : View.GONE);
|
||||||
preselectedPetId, PetDTO::getPetId);
|
binding.btnDeleteAppointment.setVisibility(state.isDeleteVisible ? View.VISIBLE : View.GONE);
|
||||||
|
binding.btnSaveAppointment.setVisibility(state.isSaveVisible ? View.VISIBLE : View.GONE);
|
||||||
|
|
||||||
|
UIUtils.setFieldEnabled(state.isCustomerEnabled, binding.spinnerCustomer, binding.tvLabelCustomer);
|
||||||
|
UIUtils.setFieldEnabled(state.isStoreEnabled, binding.spinnerStore, binding.tvLabelStore);
|
||||||
|
UIUtils.setFieldEnabled(state.isPetEnabled, binding.spinnerPet, binding.tvLabelPet);
|
||||||
|
UIUtils.setFieldEnabled(state.isServiceEnabled, binding.spinnerService, binding.tvLabelService);
|
||||||
|
UIUtils.setFieldEnabled(state.isStaffEnabled, binding.spinnerStaff, binding.tvLabelStaff);
|
||||||
|
UIUtils.setFieldEnabled(state.isDateEnabled, binding.etAppointmentDate, binding.tvLabelDate);
|
||||||
|
UIUtils.setFieldEnabled(state.isTimeEnabled, binding.spinnerHour, binding.tvLabelTime);
|
||||||
|
UIUtils.setViewsEnabled(state.isTimeEnabled, binding.spinnerMinute);
|
||||||
|
UIUtils.setViewsEnabled(state.isStatusEnabled, binding.spinnerAppointmentStatus);
|
||||||
|
|
||||||
|
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
|
||||||
|
String current = selected != null ? selected.toString() : "";
|
||||||
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerAppointmentStatus, state.availableStatuses);
|
||||||
|
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, current);
|
||||||
|
|
||||||
|
isUpdatingUI = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void notifyDateTimeStatusChange() {
|
||||||
* Loads the list of services from the API.
|
if (isUpdatingUI) return;
|
||||||
*/
|
|
||||||
private void loadServices() {
|
String date = binding.etAppointmentDate.getText().toString();
|
||||||
serviceViewModel.getAllServices(0, 200, null, "serviceName").observe(getViewLifecycleOwner(), resource -> {
|
String time = buildTimeString();
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
Object selected = binding.spinnerAppointmentStatus.getSelectedItem();
|
||||||
serviceList = resource.data.getContent();
|
String status = selected != null ? selected.toString() : "";
|
||||||
refreshServiceSpinner();
|
viewModel.onDateOrTimeChanged(date, time, status);
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the service selection spinner.
|
|
||||||
*/
|
|
||||||
private void refreshServiceSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerService, serviceList,
|
|
||||||
ServiceDTO::getServiceName, "-- Select Service --",
|
|
||||||
preselectedServiceId, ServiceDTO::getServiceId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of customers from the API.
|
|
||||||
*/
|
|
||||||
private void loadCustomers() {
|
|
||||||
customerViewModel.getAllCustomers(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
customerList = resource.data.getContent();
|
|
||||||
refreshCustomerSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the customer selection spinner.
|
|
||||||
*/
|
|
||||||
private void refreshCustomerSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerCustomer, customerList,
|
|
||||||
item -> item.getFirstName() + " " + item.getLastName(),
|
|
||||||
"-- Select Customer --",
|
|
||||||
preselectedCustomerId, CustomerDTO::getCustomerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of stores from the API.
|
|
||||||
*/
|
|
||||||
private void loadStores() {
|
|
||||||
storeViewModel.getAllStores(0, 50).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
refreshStoreSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the store selection spinner.
|
|
||||||
*/
|
|
||||||
private void refreshStoreSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "-- Select Store --",
|
|
||||||
preselectedStoreId, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of staff from the API.
|
|
||||||
*/
|
|
||||||
private void loadStaff() {
|
|
||||||
userViewModel.getUsers("STAFF", 0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
staffList = resource.data.getContent();
|
|
||||||
refreshStaffSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Populates the staff selection spinner.
|
|
||||||
*/
|
|
||||||
private void refreshStaffSpinner() {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerStaff, staffList,
|
|
||||||
UserDTO::getFullName, "-- Select Staff --",
|
|
||||||
preselectedStaffId, UserDTO::getId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles arguments to determine if the fragment is in edit or add mode.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("appointmentId")) {
|
if (a != null && a.containsKey("appointmentId")) {
|
||||||
isEditing = true;
|
viewModel.setAppointmentId(a.getLong("appointmentId"));
|
||||||
appointmentId = a.getLong("appointmentId");
|
|
||||||
binding.tvApptMode.setText("Edit Appointment");
|
|
||||||
binding.tvAppointmentId.setText("ID: " + appointmentId);
|
|
||||||
binding.tvAppointmentId.setVisibility(View.VISIBLE);
|
|
||||||
binding.btnDeleteAppointment.setVisibility(View.VISIBLE);
|
|
||||||
loadAppointmentData();
|
loadAppointmentData();
|
||||||
} else {
|
} else {
|
||||||
binding.tvApptMode.setText("Add Appointment");
|
viewModel.setAppointmentId(-1);
|
||||||
binding.btnDeleteAppointment.setVisibility(View.GONE);
|
|
||||||
binding.tvAppointmentId.setVisibility(View.GONE);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches specific appointment details from the backend using the ID.
|
|
||||||
*/
|
|
||||||
private void loadAppointmentData() {
|
private void loadAppointmentData() {
|
||||||
appointmentViewModel.getAppointmentById(appointmentId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadAppointment().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
AppointmentDTO a = resource.data;
|
AppointmentDTO a = resource.data;
|
||||||
preselectedPetId = (a.getPetId() != null) ? a.getPetId() : -1;
|
preselectedPetId = a.getPetId() != null ? a.getPetId() : -1;
|
||||||
preselectedServiceId = (a.getServiceId() != null) ? a.getServiceId() : -1;
|
preselectedServiceId = a.getServiceId() != null ? a.getServiceId() : -1;
|
||||||
preselectedCustomerId = (a.getCustomerId() != null) ? a.getCustomerId() : -1;
|
preselectedCustomerId = a.getCustomerId() != null ? a.getCustomerId() : -1;
|
||||||
preselectedStoreId = (a.getStoreId() != null) ? a.getStoreId() : -1;
|
preselectedStoreId = a.getStoreId() != null ? a.getStoreId() : -1;
|
||||||
preselectedStaffId = (a.getEmployeeId() != null) ? a.getEmployeeId() : -1;
|
preselectedStaffId = a.getEmployeeId() != null ? a.getEmployeeId() : -1;
|
||||||
|
|
||||||
binding.etAppointmentDate.setText(a.getAppointmentDate());
|
binding.etAppointmentDate.setText(a.getAppointmentDate());
|
||||||
|
parseAndSetTimeSpinners(a.getAppointmentTime() != null ? a.getAppointmentTime() : "09:00");
|
||||||
// Pre-fill time spinners
|
|
||||||
String time = a.getAppointmentTime() != null ? a.getAppointmentTime() : "09:00";
|
|
||||||
if (time.length() > 5) time = time.substring(0, 5);
|
|
||||||
String[] parts = time.split(":");
|
|
||||||
if (parts.length == 2) {
|
|
||||||
try {
|
|
||||||
int hour = Integer.parseInt(parts[0]);
|
|
||||||
int min = Integer.parseInt(parts[1]);
|
|
||||||
for (int i = 0; i < HOURS.length; i++)
|
|
||||||
if (HOURS[i] == hour) { binding.spinnerHour.setSelection(i); break; }
|
|
||||||
for (int i = 0; i < MINUTES.length; i++)
|
|
||||||
if (MINUTES[i] == min) { binding.spinnerMinute.setSelection(i); break; }
|
|
||||||
} catch (NumberFormatException ignored) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Match Title labels with backend values
|
|
||||||
String status = a.getAppointmentStatus();
|
String status = a.getAppointmentStatus();
|
||||||
if (status != null && !status.isEmpty()) {
|
if (status != null && !status.isEmpty()) {
|
||||||
String formattedStatus = status.substring(0, 1).toUpperCase() + status.substring(1).toLowerCase();
|
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, DateTimeUtils.formatStatusFromBackend(status));
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerAppointmentStatus, formattedStatus);
|
|
||||||
}
|
}
|
||||||
|
notifyDateTimeStatusChange();
|
||||||
refreshPetSpinner();
|
|
||||||
refreshServiceSpinner();
|
|
||||||
refreshCustomerSpinner();
|
|
||||||
refreshStoreSpinner();
|
|
||||||
refreshStaffSpinner();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Failed to load appointment: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates input and saves the appointment to the backend.
|
|
||||||
*/
|
|
||||||
private void saveAppointment() {
|
private void saveAppointment() {
|
||||||
if (binding.spinnerCustomer.getSelectedItemPosition() == 0) {
|
if (!validateRequiredFields()) return;
|
||||||
Toast.makeText(getContext(), "Select a customer", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
if (binding.spinnerStore.getSelectedItemPosition() == 0) {
|
|
||||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
if (binding.spinnerPet.getSelectedItemPosition() == 0) {
|
|
||||||
Toast.makeText(getContext(), "Select a pet", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
if (binding.spinnerService.getSelectedItemPosition() == 0) {
|
|
||||||
Toast.makeText(getContext(), "Select a service", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
String date = binding.etAppointmentDate.getText().toString().trim();
|
String date = binding.etAppointmentDate.getText().toString().trim();
|
||||||
if (date.isEmpty()) {
|
String time = buildTimeString();
|
||||||
Toast.makeText(getContext(), "Select a date", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
CustomerDTO customer = customerList.get(binding.spinnerCustomer.getSelectedItemPosition() - 1);
|
|
||||||
StoreDTO store = storeList.get(binding.spinnerStore.getSelectedItemPosition() - 1);
|
|
||||||
PetDTO pet = petList.get(binding.spinnerPet.getSelectedItemPosition() - 1);
|
|
||||||
ServiceDTO service = serviceList.get(binding.spinnerService.getSelectedItemPosition() - 1);
|
|
||||||
|
|
||||||
Long employeeId = null;
|
|
||||||
if (binding.spinnerStaff.getSelectedItemPosition() > 0) {
|
|
||||||
employeeId = staffList.get(binding.spinnerStaff.getSelectedItemPosition() - 1).getId();
|
|
||||||
}
|
|
||||||
|
|
||||||
String time = String.format("%02d:%02d",
|
|
||||||
HOURS[binding.spinnerHour.getSelectedItemPosition()],
|
|
||||||
MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
|
||||||
|
|
||||||
// Get status and convert to uppercase for backend
|
|
||||||
String status = binding.spinnerAppointmentStatus.getSelectedItem().toString().toUpperCase();
|
String status = binding.spinnerAppointmentStatus.getSelectedItem().toString().toUpperCase();
|
||||||
|
|
||||||
|
if (!viewModel.isValidFutureBooking(status, date, time)) {
|
||||||
// Validate future date+time if status is BOOKED
|
DialogUtils.showInfoDialog(requireContext(), "Invalid Time", "Booked appointments must be in the future.");
|
||||||
if ("BOOKED".equalsIgnoreCase(status)) {
|
return;
|
||||||
try {
|
|
||||||
String[] dateParts = date.split("-");
|
|
||||||
String[] timeParts = time.split(":");
|
|
||||||
Calendar selected = Calendar.getInstance();
|
|
||||||
selected.set(
|
|
||||||
Integer.parseInt(dateParts[0]),
|
|
||||||
Integer.parseInt(dateParts[1]) - 1,
|
|
||||||
Integer.parseInt(dateParts[2]),
|
|
||||||
Integer.parseInt(timeParts[0]),
|
|
||||||
Integer.parseInt(timeParts[1]),
|
|
||||||
0
|
|
||||||
);
|
|
||||||
if (selected.before(Calendar.getInstance())) {
|
|
||||||
DialogUtils.showInfoDialog(requireContext(), "Invalid Time",
|
|
||||||
"Booked appointments must be in the future. " +
|
|
||||||
"Please select a future date and time.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("APPT_SAVE", "Date parse error: " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build DTO with all required IDs
|
viewModel.saveAppointment(date, time, status).observe(getViewLifecycleOwner(), resource -> {
|
||||||
AppointmentDTO dto = new AppointmentDTO(
|
if (resource == null) return;
|
||||||
customer.getCustomerId(),
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
store.getStoreId(),
|
|
||||||
service.getServiceId(),
|
|
||||||
employeeId,
|
|
||||||
date,
|
|
||||||
time,
|
|
||||||
status,
|
|
||||||
pet.getPetId()
|
|
||||||
);
|
|
||||||
|
|
||||||
androidx.lifecycle.Observer<Resource<AppointmentDTO>> observer = resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), isEditing ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
|
AppointmentDetailViewModel.ViewState state = viewModel.getViewState().getValue();
|
||||||
|
String message = (state != null && state.isEditing) ? "Updated" : "Saved";
|
||||||
|
Toast.makeText(getContext(), message, Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
handleSaveError(resource.message);
|
handleSaveError(resource.message);
|
||||||
}
|
}
|
||||||
};
|
});
|
||||||
|
}
|
||||||
if (isEditing) {
|
|
||||||
appointmentViewModel.updateAppointment(appointmentId, dto).observe(getViewLifecycleOwner(), observer);
|
private boolean validateRequiredFields() {
|
||||||
} else {
|
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Customer")) return false;
|
||||||
appointmentViewModel.createAppointment(dto).observe(getViewLifecycleOwner(), observer);
|
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return false;
|
||||||
}
|
if (!InputValidator.isSpinnerSelected(binding.spinnerPet, "Pet")) return false;
|
||||||
|
if (!InputValidator.isSpinnerSelected(binding.spinnerService, "Service")) return false;
|
||||||
|
if (!InputValidator.isNotEmpty(binding.etAppointmentDate, "Appointment Date")) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildTimeString() {
|
||||||
|
return DateTimeUtils.formatTime(HOURS[binding.spinnerHour.getSelectedItemPosition()], MINUTES[binding.spinnerMinute.getSelectedItemPosition()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles errors that occur during the saving process.
|
|
||||||
*/
|
|
||||||
private void handleSaveError(String errorMessage) {
|
private void handleSaveError(String errorMessage) {
|
||||||
if (errorMessage != null) {
|
if (errorMessage != null && errorMessage.toLowerCase().contains("not available")) showNoAvailabilityDialog();
|
||||||
Log.e("APPT_SAVE", "Error: " + errorMessage);
|
else Toast.makeText(getContext(), errorMessage != null ? errorMessage : "Error saving", Toast.LENGTH_SHORT).show();
|
||||||
if (errorMessage.toLowerCase().contains("future")) {
|
|
||||||
DialogUtils.showInfoDialog(requireContext(), "Invalid Date/Time",
|
|
||||||
"Booked appointments must be scheduled in the future.");
|
|
||||||
} else if (errorMessage.toLowerCase().contains("not available")) {
|
|
||||||
showNoAvailabilityDialog();
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), errorMessage, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), "Something went wrong", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a specialized dialog when a time slot is not available.
|
|
||||||
*/
|
|
||||||
private void showNoAvailabilityDialog() {
|
private void showNoAvailabilityDialog() {
|
||||||
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
.setTitle("No Availability")
|
.setTitle("No Availability")
|
||||||
.setMessage("This time slot is already booked. Please choose a different time or date.")
|
.setMessage("This time slot is already booked.")
|
||||||
.setPositiveButton("Change Time", (d, w) -> d.dismiss())
|
.setPositiveButton("Change Time", (d, w) -> d.dismiss())
|
||||||
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack())
|
.setNegativeButton("Cancel Booking", (d, w) -> navigateBack()).show();
|
||||||
.setCancelable(false)
|
|
||||||
.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a confirmation dialog and handles the deletion of an appointment.
|
|
||||||
*/
|
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Appointment", () ->
|
||||||
appointmentViewModel.deleteAppointment(appointmentId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteAppointment().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource == null) return;
|
||||||
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
navigateBack();
|
if (resource.status == Resource.Status.SUCCESS) navigateBack();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Delete failed", Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the previous screen.
|
|
||||||
*/
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void parseAndSetTimeSpinners(String time) {
|
||||||
|
int[] parsedTime = DateTimeUtils.parseTimeString(time);
|
||||||
|
if (parsedTime == null) return;
|
||||||
|
SpinnerUtils.setSelectionByValueArray(binding.spinnerHour, HOURS, parsedTime[0]);
|
||||||
|
SpinnerUtils.setSelectionByValueArray(binding.spinnerMinute, MINUTES, parsedTime[1]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,24 +8,20 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
|
|
||||||
import com.example.petstoremobile.databinding.FragmentInventoryDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentInventoryDetailBinding;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.viewmodels.InventoryViewModel;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.InventoryDetailViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@@ -36,33 +32,17 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class InventoryDetailFragment extends Fragment {
|
public class InventoryDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentInventoryDetailBinding binding;
|
private FragmentInventoryDetailBinding binding;
|
||||||
|
private InventoryDetailViewModel viewModel;
|
||||||
|
|
||||||
private InventoryViewModel inventoryViewModel;
|
|
||||||
private ProductViewModel productViewModel;
|
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private long inventoryId = -1;
|
|
||||||
private long preselectedStoreId = -1;
|
private long preselectedStoreId = -1;
|
||||||
private long preselectedProductId = -1;
|
private long preselectedProductId = -1;
|
||||||
|
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private List<ProductDTO> productList = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the view models.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
inventoryViewModel = new ViewModelProvider(this).get(InventoryViewModel.class);
|
viewModel = new ViewModelProvider(this).get(InventoryDetailViewModel.class);
|
||||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inflates the layout.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -70,13 +50,11 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up UI components after the view is created.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
|
observeViewModel();
|
||||||
loadSpinnersData();
|
loadSpinnersData();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
@@ -85,64 +63,57 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
|
binding.btnDeleteInventory.setOnClickListener(v -> confirmDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> refreshStoreSpinner());
|
||||||
|
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches required data for spinners from the backend.
|
|
||||||
*/
|
|
||||||
private void loadSpinnersData() {
|
private void loadSpinnersData() {
|
||||||
loadStores();
|
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||||
loadProducts();
|
if (resource == null) return;
|
||||||
}
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of stores for the spinner.
|
|
||||||
*/
|
|
||||||
private void loadStores() {
|
|
||||||
storeViewModel.getAllStores(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
storeList = resource.data.getContent();
|
viewModel.setStoreList(resource.data);
|
||||||
refreshStoreSpinner();
|
}
|
||||||
|
});
|
||||||
|
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setProductList(resource.data.getContent());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshStoreSpinner() {
|
private void refreshStoreSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, storeList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryStore, viewModel.getStoreList().getValue(),
|
||||||
StoreDTO::getStoreName, "-- Select Store --",
|
DropdownDTO::getLabel, "-- Select Store --",
|
||||||
preselectedStoreId, StoreDTO::getStoreId);
|
preselectedStoreId, DropdownDTO::getId);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of products for the spinner.
|
|
||||||
*/
|
|
||||||
private void loadProducts() {
|
|
||||||
productViewModel.getAllProducts(null, null, 0, 500, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
productList = resource.data.getContent();
|
|
||||||
refreshProductSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshProductSpinner() {
|
private void refreshProductSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, productList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerInventoryProduct, viewModel.getProductList().getValue(),
|
||||||
ProductDTO::getProdName, "-- Select Product --",
|
ProductDTO::getProdName, "-- Select Product --",
|
||||||
preselectedProductId, ProductDTO::getProdId);
|
preselectedProductId, ProductDTO::getProdId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles fragment arguments to determine if we are in edit or add mode.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args != null && args.containsKey("inventoryId")) {
|
if (args != null && args.containsKey("inventoryId")) {
|
||||||
isEditing = true;
|
long inventoryId = args.getLong("inventoryId");
|
||||||
inventoryId = args.getLong("inventoryId");
|
viewModel.setInventoryId(inventoryId);
|
||||||
|
|
||||||
binding.tvInventoryMode.setText("Edit Inventory");
|
binding.tvInventoryMode.setText("Edit Inventory");
|
||||||
binding.tvInventoryId.setText("Inventory ID: " + inventoryId);
|
binding.tvInventoryId.setText("Inventory ID: " + inventoryId);
|
||||||
@@ -152,7 +123,7 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
|
|
||||||
loadInventoryData();
|
loadInventoryData();
|
||||||
} else {
|
} else {
|
||||||
isEditing = false;
|
viewModel.setInventoryId(-1);
|
||||||
binding.tvInventoryMode.setText("Add Inventory");
|
binding.tvInventoryMode.setText("Add Inventory");
|
||||||
binding.tvInventoryId.setVisibility(View.GONE);
|
binding.tvInventoryId.setVisibility(View.GONE);
|
||||||
binding.btnDeleteInventory.setVisibility(View.GONE);
|
binding.btnDeleteInventory.setVisibility(View.GONE);
|
||||||
@@ -160,12 +131,10 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads existing inventory data from the backend.
|
|
||||||
*/
|
|
||||||
private void loadInventoryData() {
|
private void loadInventoryData() {
|
||||||
inventoryViewModel.getInventoryById(inventoryId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadInventory().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
InventoryDTO inv = resource.data;
|
InventoryDTO inv = resource.data;
|
||||||
binding.etQuantity.setText(String.valueOf(inv.getQuantity()));
|
binding.etQuantity.setText(String.valueOf(inv.getQuantity()));
|
||||||
@@ -180,95 +149,59 @@ public class InventoryDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates input and saves the current inventory item details to the backend.
|
|
||||||
*/
|
|
||||||
private void saveInventory() {
|
private void saveInventory() {
|
||||||
if (binding.spinnerInventoryStore.getSelectedItemPosition() == 0) {
|
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryStore, "Store")) return;
|
||||||
Toast.makeText(getContext(), "Please select a store", Toast.LENGTH_SHORT).show();
|
if (!InputValidator.isSpinnerSelected(binding.spinnerInventoryProduct, "Product")) return;
|
||||||
return;
|
if (!InputValidator.isPositiveInteger(binding.etQuantity, "Quantity")) return;
|
||||||
}
|
|
||||||
if (binding.spinnerInventoryProduct.getSelectedItemPosition() == 0) {
|
|
||||||
Toast.makeText(getContext(), "Please select a product", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!InputValidator.isNotEmpty(binding.etQuantity, "Quantity") ||
|
|
||||||
!InputValidator.isPositiveInteger(binding.etQuantity, "Quantity")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
|
int quantity = Integer.parseInt(binding.etQuantity.getText().toString().trim());
|
||||||
StoreDTO store = storeList.get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
|
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerInventoryStore.getSelectedItemPosition() - 1);
|
||||||
ProductDTO product = productList.get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerInventoryProduct.getSelectedItemPosition() - 1);
|
||||||
|
|
||||||
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getStoreId(), quantity);
|
InventoryDTO request = new InventoryDTO(product.getProdId(), store.getId(), quantity);
|
||||||
setButtonsEnabled(false);
|
setButtonsEnabled(false);
|
||||||
|
|
||||||
if (isEditing) {
|
viewModel.saveInventory(request).observe(getViewLifecycleOwner(), resource -> {
|
||||||
inventoryViewModel.updateInventory(inventoryId, request).observe(getViewLifecycleOwner(), resource -> {
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status != Resource.Status.LOADING) {
|
||||||
setButtonsEnabled(true);
|
setButtonsEnabled(true);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), "Inventory updated", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), viewModel.isEditing() ? "Inventory updated" : "Inventory created", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
|
||||||
} else {
|
|
||||||
inventoryViewModel.createInventory(request).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
setButtonsEnabled(true);
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
|
||||||
Toast.makeText(getContext(), "Inventory created", Toast.LENGTH_SHORT).show();
|
|
||||||
navigateBack();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a confirmation dialog before deleting an inventory item.
|
|
||||||
*/
|
|
||||||
private void confirmDelete() {
|
|
||||||
new AlertDialog.Builder(requireContext())
|
|
||||||
.setTitle("Delete inventory item?")
|
|
||||||
.setMessage("This cannot be undone.")
|
|
||||||
.setPositiveButton("Delete", (d, w) -> deleteInventory())
|
|
||||||
.setNegativeButton("Cancel", null)
|
|
||||||
.show();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a request to the API to delete the inventory item.
|
|
||||||
*/
|
|
||||||
private void deleteInventory() {
|
|
||||||
setButtonsEnabled(false);
|
|
||||||
inventoryViewModel.deleteInventory(inventoryId).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
setButtonsEnabled(true);
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
|
||||||
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
|
||||||
navigateBack();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void confirmDelete() {
|
||||||
* Navigates back to the previous fragment.
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Inventory Item", this::deleteInventory);
|
||||||
*/
|
}
|
||||||
|
|
||||||
|
private void deleteInventory() {
|
||||||
|
setButtonsEnabled(false);
|
||||||
|
viewModel.deleteInventory().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status != Resource.Status.LOADING) {
|
||||||
|
setButtonsEnabled(true);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
|
Toast.makeText(getContext(), "Inventory deleted", Toast.LENGTH_SHORT).show();
|
||||||
|
navigateBack();
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Enables or disables action buttons.
|
|
||||||
*/
|
|
||||||
private void setButtonsEnabled(boolean enabled) {
|
private void setButtonsEnabled(boolean enabled) {
|
||||||
binding.btnSaveInventory.setEnabled(enabled);
|
UIUtils.setViewsEnabled(enabled, binding.btnSaveInventory, binding.btnDeleteInventory, binding.btnInventoryBack);
|
||||||
binding.btnDeleteInventory.setEnabled(enabled);
|
|
||||||
binding.btnInventoryBack.setEnabled(enabled);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,20 +18,17 @@ import android.widget.Toast;
|
|||||||
|
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentPetDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import com.example.petstoremobile.utils.ActivityLogger;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.viewmodels.CustomerViewModel;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
import com.example.petstoremobile.viewmodels.PetDetailViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.StoreViewModel;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
@@ -43,23 +40,15 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class PetDetailFragment extends Fragment {
|
public class PetDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentPetDetailBinding binding;
|
private FragmentPetDetailBinding binding;
|
||||||
private long petId;
|
private PetDetailViewModel viewModel;
|
||||||
private boolean isEditing = false;
|
|
||||||
|
|
||||||
private PetViewModel viewModel;
|
|
||||||
private CustomerViewModel customerViewModel;
|
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private Long selectedCustomerId = null;
|
private Long selectedCustomerId = null;
|
||||||
private Long selectedStoreId = null;
|
private Long selectedStoreId = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PetDetailViewModel.class);
|
||||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -74,34 +63,54 @@ public class PetDetailFragment extends Fragment {
|
|||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
setupSpinner();
|
setupSpinner();
|
||||||
loadCustomers();
|
observeViewModel();
|
||||||
loadStores();
|
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
//set button click listeners
|
|
||||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||||
binding.btnSavePet.setOnClickListener(v -> savePet());
|
binding.btnSavePet.setOnClickListener(v -> savePet());
|
||||||
binding.btnDeletePet.setOnClickListener(v -> deletePet());
|
binding.btnDeletePet.setOnClickListener(v -> deletePet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> updateCustomerSpinnerSelection());
|
||||||
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> updateStoreSpinnerSelection());
|
||||||
|
|
||||||
|
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setCustomerList(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setStoreList(resource.data);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the saving of pet data (adding/updating).
|
|
||||||
*/
|
|
||||||
private void savePet() {
|
private void savePet() {
|
||||||
// Validates all fields using InputValidator
|
|
||||||
if (!InputValidator.isNotEmpty(binding.etPetName, "Pet Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etPetName, "Pet Name")) return;
|
||||||
if (!InputValidator.isNotEmpty(binding.etPetSpecies, "Species")) return;
|
if (!InputValidator.isNotEmpty(binding.etPetSpecies, "Species")) return;
|
||||||
if (!InputValidator.isNotEmpty(binding.etPetBreed, "Breed")) return;
|
if (!InputValidator.isNotEmpty(binding.etPetBreed, "Breed")) return;
|
||||||
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
if (!InputValidator.isPositiveInteger(binding.etPetAge, "Age")) return;
|
||||||
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
if (!InputValidator.isPositiveDecimal(binding.etPetPrice, "Price")) return;
|
||||||
|
|
||||||
//get all the values from the fields
|
|
||||||
String name = binding.etPetName.getText().toString().trim();
|
String name = binding.etPetName.getText().toString().trim();
|
||||||
String species = binding.etPetSpecies.getText().toString().trim();
|
String species = binding.etPetSpecies.getText().toString().trim();
|
||||||
String breed = binding.etPetBreed.getText().toString().trim();
|
String breed = binding.etPetBreed.getText().toString().trim();
|
||||||
@@ -109,37 +118,27 @@ public class PetDetailFragment extends Fragment {
|
|||||||
double price = Double.parseDouble(binding.etPetPrice.getText().toString().trim());
|
double price = Double.parseDouble(binding.etPetPrice.getText().toString().trim());
|
||||||
String status = binding.spinnerPetStatus.getSelectedItem().toString();
|
String status = binding.spinnerPetStatus.getSelectedItem().toString();
|
||||||
|
|
||||||
// Get selected customer
|
|
||||||
Long customerId = null;
|
Long customerId = null;
|
||||||
int customerPos = binding.spinnerCustomer.getSelectedItemPosition();
|
if (binding.spinnerCustomer.getSelectedItemPosition() > 0) {
|
||||||
if (customerPos > 0) { // 0 means no customer for pet
|
customerId = viewModel.getCustomerList().getValue().get(binding.spinnerCustomer.getSelectedItemPosition() - 1).getId();
|
||||||
customerId = customerList.get(customerPos - 1).getCustomerId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get selected store
|
|
||||||
Long storeId = null;
|
Long storeId = null;
|
||||||
int storePos = binding.spinnerStore.getSelectedItemPosition();
|
if (binding.spinnerStore.getSelectedItemPosition() > 0) {
|
||||||
if (storePos > 0) {
|
storeId = viewModel.getStoreList().getValue().get(binding.spinnerStore.getSelectedItemPosition() - 1).getId();
|
||||||
storeId = storeList.get(storePos - 1).getStoreId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation: If status is Available, a store must be selected
|
|
||||||
if ("Available".equalsIgnoreCase(status)) {
|
if ("Available".equalsIgnoreCase(status)) {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation: If status is Owned, an owner must be selected
|
|
||||||
if ("Owned".equalsIgnoreCase(status)) {
|
if ("Owned".equalsIgnoreCase(status)) {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validation: If status is Adopted, an owner and store must be selected
|
|
||||||
if ("Adopted".equalsIgnoreCase(status)) {
|
if ("Adopted".equalsIgnoreCase(status)) {
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerCustomer, "Owner")) return;
|
||||||
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerStore, "Store")) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//create a pet object to send to the API
|
|
||||||
PetDTO petDTO = new PetDTO();
|
PetDTO petDTO = new PetDTO();
|
||||||
petDTO.setPetName(name);
|
petDTO.setPetName(name);
|
||||||
petDTO.setPetSpecies(species);
|
petDTO.setPetSpecies(species);
|
||||||
@@ -150,107 +149,74 @@ public class PetDetailFragment extends Fragment {
|
|||||||
petDTO.setCustomerId(customerId);
|
petDTO.setCustomerId(customerId);
|
||||||
petDTO.setStoreId(storeId);
|
petDTO.setStoreId(storeId);
|
||||||
|
|
||||||
//check if the pet is being edited or added
|
viewModel.savePet(petDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (isEditing) {
|
if (resource == null) return;
|
||||||
// Update existing pet
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
petDTO.setPetId(petId);
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
viewModel.updatePet(petId, petDTO).observe(getViewLifecycleOwner(), resource -> {
|
if (viewModel.isEditing()) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) viewModel.getPetId());
|
||||||
ActivityLogger.logChange(requireContext(), "Pet", "UPDATED", (int) petId);
|
|
||||||
Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Pet updated successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateToPetList();
|
} else {
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Add new pet
|
|
||||||
viewModel.createPet(petDTO).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
|
||||||
ActivityLogger.log(requireContext(), "Added new Pet: " + name);
|
ActivityLogger.log(requireContext(), "Added new Pet: " + name);
|
||||||
Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Pet added successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateToPetList();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
});
|
navigateToPetList();
|
||||||
}
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a confirmation dialog and handles the deletion of a pet.
|
|
||||||
*/
|
|
||||||
private void deletePet() {
|
private void deletePet() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Pet", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Pet", () -> {
|
||||||
viewModel.deletePet(petId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deletePet().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) petId);
|
ActivityLogger.logChange(requireContext(), "Pet", "DELETED", (int) viewModel.getPetId());
|
||||||
Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Pet deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateToPetList();
|
navigateToPetList();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}));
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the pet list screen.
|
|
||||||
*/
|
|
||||||
private void navigateToPetList() {
|
private void navigateToPetList() {
|
||||||
NavHostFragment.findNavController(this).popBackStack(R.id.nav_pet, false);
|
NavHostFragment.findNavController(this).popBackStack(R.id.nav_pet, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the previous screen.
|
|
||||||
*/
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
// Pet is being edited if the bundle contains a petId
|
|
||||||
if (getArguments() != null && getArguments().containsKey("petId")) {
|
if (getArguments() != null && getArguments().containsKey("petId")) {
|
||||||
// Get pet data from arguments and populate fields
|
long petId = getArguments().getLong("petId");
|
||||||
isEditing = true;
|
viewModel.setPetId(petId);
|
||||||
petId = getArguments().getLong("petId");
|
|
||||||
binding.tvMode.setText("Edit Pet");
|
binding.tvMode.setText("Edit Pet");
|
||||||
binding.tvPetId.setText("ID: " + petId);
|
binding.tvPetId.setText(DateTimeUtils.formatId(petId));
|
||||||
binding.tvPetId.setVisibility(View.VISIBLE);
|
binding.tvPetId.setVisibility(View.VISIBLE);
|
||||||
binding.btnDeletePet.setVisibility(View.VISIBLE);
|
binding.btnDeletePet.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Disable species and breed fields in edit mode
|
UIUtils.setViewsEnabled(false, binding.etPetSpecies, binding.etPetBreed);
|
||||||
binding.etPetSpecies.setEnabled(false);
|
|
||||||
binding.etPetBreed.setEnabled(false);
|
|
||||||
binding.etPetSpecies.setAlpha(0.5f);
|
|
||||||
binding.etPetBreed.setAlpha(0.5f);
|
|
||||||
|
|
||||||
loadPetData();
|
loadPetData();
|
||||||
} else {
|
} else {
|
||||||
// Pet is being added
|
viewModel.setPetId(-1);
|
||||||
// Set default values for add a new pet
|
|
||||||
isEditing = false;
|
|
||||||
binding.tvMode.setText("Add Pet");
|
binding.tvMode.setText("Add Pet");
|
||||||
binding.tvPetId.setVisibility(View.GONE);
|
binding.tvPetId.setVisibility(View.GONE);
|
||||||
binding.btnDeletePet.setVisibility(View.GONE);
|
binding.btnDeletePet.setVisibility(View.GONE);
|
||||||
binding.btnSavePet.setText("Add");
|
binding.btnSavePet.setText("Add");
|
||||||
|
|
||||||
// Enable species and breed fields in edit mode
|
UIUtils.setViewsEnabled(true, binding.etPetSpecies, binding.etPetBreed);
|
||||||
binding.etPetSpecies.setEnabled(true);
|
|
||||||
binding.etPetBreed.setEnabled(true);
|
|
||||||
binding.etPetSpecies.setAlpha(1.0f);
|
|
||||||
binding.etPetBreed.setAlpha(1.0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches specific pet details from the backend using the ID.
|
|
||||||
*/
|
|
||||||
private void loadPetData() {
|
private void loadPetData() {
|
||||||
viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadPet().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
PetDTO p = resource.data;
|
PetDTO p = resource.data;
|
||||||
binding.etPetName.setText(p.getPetName());
|
binding.etPetName.setText(p.getPetName());
|
||||||
@@ -273,63 +239,30 @@ public class PetDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the list of customers and populates the spinner.
|
|
||||||
*/
|
|
||||||
private void loadCustomers() {
|
|
||||||
customerViewModel.getAllCustomers(0, 1000).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
customerList = resource.data.getContent();
|
|
||||||
updateCustomerSpinnerSelection();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches the list of stores and populates the spinner.
|
|
||||||
*/
|
|
||||||
private void loadStores() {
|
|
||||||
storeViewModel.getAllStores(0, 1000).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
updateStoreSpinnerSelection();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the customer spinner with the current list and sets the selection if needed.
|
|
||||||
*/
|
|
||||||
private void updateCustomerSpinnerSelection() {
|
private void updateCustomerSpinnerSelection() {
|
||||||
SpinnerUtils.populateSpinner(
|
SpinnerUtils.populateSpinner(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
binding.spinnerCustomer,
|
binding.spinnerCustomer,
|
||||||
customerList,
|
viewModel.getCustomerList().getValue(),
|
||||||
CustomerDTO::getFullName,
|
DropdownDTO::getLabel,
|
||||||
"No Owner",
|
"No Owner",
|
||||||
selectedCustomerId,
|
selectedCustomerId,
|
||||||
CustomerDTO::getCustomerId
|
DropdownDTO::getId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the store spinner with the current list and sets the selection if needed.
|
|
||||||
*/
|
|
||||||
private void updateStoreSpinnerSelection() {
|
private void updateStoreSpinnerSelection() {
|
||||||
SpinnerUtils.populateSpinner(
|
SpinnerUtils.populateSpinner(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
binding.spinnerStore,
|
binding.spinnerStore,
|
||||||
storeList,
|
viewModel.getStoreList().getValue(),
|
||||||
StoreDTO::getStoreName,
|
DropdownDTO::getLabel,
|
||||||
"None",
|
"None",
|
||||||
selectedStoreId,
|
selectedStoreId,
|
||||||
StoreDTO::getStoreId
|
DropdownDTO::getId
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the spinner for pet status selection.
|
|
||||||
*/
|
|
||||||
private void setupSpinner() {
|
private void setupSpinner() {
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus,
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPetStatus,
|
||||||
new String[]{"Available", "Adopted", "Owned"});
|
new String[]{"Available", "Adopted", "Owned"});
|
||||||
@@ -339,28 +272,21 @@ public class PetDetailFragment extends Fragment {
|
|||||||
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
String status = parent.getItemAtPosition(position).toString();
|
String status = parent.getItemAtPosition(position).toString();
|
||||||
|
|
||||||
// Clear any existing error icons when status changes
|
|
||||||
clearSpinnerError(binding.spinnerCustomer);
|
clearSpinnerError(binding.spinnerCustomer);
|
||||||
clearSpinnerError(binding.spinnerStore);
|
clearSpinnerError(binding.spinnerStore);
|
||||||
|
|
||||||
//Disable the customer spinner if the status is "Available"
|
|
||||||
if ("Available".equalsIgnoreCase(status)) {
|
if ("Available".equalsIgnoreCase(status)) {
|
||||||
binding.spinnerCustomer.setSelection(0);
|
binding.spinnerCustomer.setSelection(0);
|
||||||
binding.spinnerCustomer.setEnabled(false);
|
UIUtils.setViewsEnabled(false, binding.spinnerCustomer);
|
||||||
binding.spinnerCustomer.setAlpha(0.5f);
|
|
||||||
} else {
|
} else {
|
||||||
binding.spinnerCustomer.setEnabled(true);
|
UIUtils.setViewsEnabled(true, binding.spinnerCustomer);
|
||||||
binding.spinnerCustomer.setAlpha(1.0f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Disable the store spinner if the status is "Owned"
|
|
||||||
if ("Owned".equalsIgnoreCase(status)) {
|
if ("Owned".equalsIgnoreCase(status)) {
|
||||||
binding.spinnerStore.setSelection(0);
|
binding.spinnerStore.setSelection(0);
|
||||||
binding.spinnerStore.setEnabled(false);
|
UIUtils.setViewsEnabled(false, binding.spinnerStore);
|
||||||
binding.spinnerStore.setAlpha(0.5f);
|
|
||||||
} else {
|
} else {
|
||||||
binding.spinnerStore.setEnabled(true);
|
UIUtils.setViewsEnabled(true, binding.spinnerStore);
|
||||||
binding.spinnerStore.setAlpha(1.0f);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -370,9 +296,6 @@ public class PetDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Clears error messages from a Spinner's selected view.
|
|
||||||
*/
|
|
||||||
private void clearSpinnerError(Spinner spinner) {
|
private void clearSpinnerError(Spinner spinner) {
|
||||||
View selectedView = spinner.getSelectedView();
|
View selectedView = spinner.getSelectedView();
|
||||||
if (selectedView instanceof TextView) {
|
if (selectedView instanceof TextView) {
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import com.example.petstoremobile.api.*;
|
|||||||
import com.example.petstoremobile.api.auth.TokenManager;
|
import com.example.petstoremobile.api.auth.TokenManager;
|
||||||
import com.example.petstoremobile.databinding.FragmentProductDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentProductDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.ProductDetailViewModel;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.FileUtils;
|
import com.example.petstoremobile.utils.FileUtils;
|
||||||
import com.example.petstoremobile.utils.GlideUtils;
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
@@ -31,7 +32,6 @@ import java.math.BigDecimal;
|
|||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|
||||||
import javax.inject.Named;
|
import javax.inject.Named;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
@@ -46,29 +46,22 @@ import okhttp3.RequestBody;
|
|||||||
public class ProductDetailFragment extends Fragment {
|
public class ProductDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentProductDetailBinding binding;
|
private FragmentProductDetailBinding binding;
|
||||||
|
private ProductDetailViewModel viewModel;
|
||||||
|
private ImagePickerHelper imagePickerHelper;
|
||||||
|
|
||||||
private long prodId = -1;
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private long preselectedCategoryId = -1;
|
private long preselectedCategoryId = -1;
|
||||||
private boolean hasImage = false;
|
private boolean hasImage = false;
|
||||||
private boolean isImageChanged = false;
|
private boolean isImageChanged = false;
|
||||||
private boolean isImageRemoved = false;
|
private boolean isImageRemoved = false;
|
||||||
|
|
||||||
private List<CategoryDTO> categoryList = new ArrayList<>();
|
|
||||||
private Uri photoUri;
|
private Uri photoUri;
|
||||||
private ProductViewModel viewModel;
|
|
||||||
private ImagePickerHelper imagePickerHelper;
|
|
||||||
|
|
||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes activity launchers and the ImagePickerHelper.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductDetailViewModel.class);
|
||||||
|
|
||||||
imagePickerHelper = new ImagePickerHelper(this, "product_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
imagePickerHelper = new ImagePickerHelper(this, "product_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -95,9 +88,6 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inflates the layout.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
@@ -105,14 +95,11 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets up UI components and listeners after the view is created.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
loadCategories();
|
observeViewModel();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
binding.btnProductBack.setOnClickListener(v -> navigateBack());
|
binding.btnProductBack.setOnClickListener(v -> navigateBack());
|
||||||
@@ -121,41 +108,49 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
|
binding.ivProductImage.setOnClickListener(v -> imagePickerHelper.showImagePickerDialog("Select Product Image", hasImage));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getCategoryList().observe(getViewLifecycleOwner(), list -> updateCategorySpinner());
|
||||||
|
|
||||||
|
viewModel.loadCategories().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setCategoryList(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateCategorySpinner() {
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, viewModel.getCategoryList().getValue(),
|
||||||
|
CategoryDTO::getCategoryName, "-- Select Category --",
|
||||||
|
preselectedCategoryId, CategoryDTO::getCategoryId);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches all product categories for the selection spinner.
|
|
||||||
*/
|
|
||||||
private void loadCategories() {
|
|
||||||
viewModel.getAllCategories(0, 100).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
categoryList = resource.data.getContent();
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList,
|
|
||||||
CategoryDTO::getCategoryName, "-- Select Category --",
|
|
||||||
preselectedCategoryId, CategoryDTO::getCategoryId);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if the fragment was opened with existing product data for editing.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("prodId")) {
|
if (a != null && a.containsKey("prodId")) {
|
||||||
isEditing = true;
|
long prodId = a.getLong("prodId");
|
||||||
prodId = a.getLong("prodId");
|
viewModel.setProdId(prodId);
|
||||||
binding.tvProductMode.setText("Edit Product");
|
binding.tvProductMode.setText("Edit Product");
|
||||||
binding.tvProductId.setText("ID: " + prodId);
|
binding.tvProductId.setText(DateTimeUtils.formatId(prodId));
|
||||||
binding.tvProductId.setVisibility(View.VISIBLE);
|
binding.tvProductId.setVisibility(View.VISIBLE);
|
||||||
binding.btnDeleteProduct.setVisibility(View.VISIBLE);
|
binding.btnDeleteProduct.setVisibility(View.VISIBLE);
|
||||||
loadProductData();
|
loadProductData();
|
||||||
loadProductImage();
|
loadProductImage();
|
||||||
} else {
|
} else {
|
||||||
|
viewModel.setProdId(-1);
|
||||||
binding.tvProductMode.setText("Add Product");
|
binding.tvProductMode.setText("Add Product");
|
||||||
binding.btnDeleteProduct.setVisibility(View.GONE);
|
binding.btnDeleteProduct.setVisibility(View.GONE);
|
||||||
binding.tvProductId.setVisibility(View.GONE);
|
binding.tvProductId.setVisibility(View.GONE);
|
||||||
@@ -163,36 +158,25 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the product data from the backend.
|
|
||||||
*/
|
|
||||||
private void loadProductData() {
|
private void loadProductData() {
|
||||||
viewModel.getProductById(prodId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadProduct().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
ProductDTO p = resource.data;
|
ProductDTO p = resource.data;
|
||||||
binding.etProductName.setText(p.getProdName());
|
binding.etProductName.setText(p.getProdName());
|
||||||
binding.etProductDesc.setText(p.getProdDesc());
|
binding.etProductDesc.setText(p.getProdDesc());
|
||||||
binding.etProductPrice.setText(p.getProdPrice() != null ? p.getProdPrice().toString() : "");
|
binding.etProductPrice.setText(p.getProdPrice() != null ? p.getProdPrice().toString() : "");
|
||||||
preselectedCategoryId = p.getCategoryId() != null ? p.getCategoryId() : -1;
|
preselectedCategoryId = p.getCategoryId() != null ? p.getCategoryId() : -1;
|
||||||
|
updateCategorySpinner();
|
||||||
// Refresh spinner selection once data is loaded
|
|
||||||
if (!categoryList.isEmpty()) {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerProductCategory, categoryList,
|
|
||||||
CategoryDTO::getCategoryName, "-- Select Category --",
|
|
||||||
preselectedCategoryId, CategoryDTO::getCategoryId);
|
|
||||||
}
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Failed to load product: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Failed to load product: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the product image from the backend.
|
|
||||||
*/
|
|
||||||
private void loadProductImage() {
|
private void loadProductImage() {
|
||||||
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, prodId);
|
String imageUrl = baseUrl + String.format(Locale.US, ProductApi.PRODUCT_IMAGE_PATH, viewModel.getProdId());
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
|
|
||||||
GlideUtils.loadImageWithToken(requireContext(), binding.ivProductImage, imageUrl, token, R.drawable.placeholder2, new GlideUtils.ImageLoadListener() {
|
GlideUtils.loadImageWithToken(requireContext(), binding.ivProductImage, imageUrl, token, R.drawable.placeholder2, new GlideUtils.ImageLoadListener() {
|
||||||
@@ -208,13 +192,12 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Performs image related actions (upload/delete) after product details are saved.
|
|
||||||
*/
|
|
||||||
private void performPendingImageActions(String successMsg) {
|
private void performPendingImageActions(String successMsg) {
|
||||||
if (isImageRemoved) {
|
if (isImageRemoved) {
|
||||||
viewModel.deleteProductImage(prodId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteProductImage().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status != Resource.Status.LOADING) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
@@ -231,9 +214,6 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads the selected image file to the server.
|
|
||||||
*/
|
|
||||||
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
|
private void uploadProductImageAndNavigate(Uri uri, String successMsg) {
|
||||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
@@ -245,8 +225,10 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
|
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
|
||||||
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
||||||
|
|
||||||
viewModel.uploadProductImage(prodId, body).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.uploadProductImage(body).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status != Resource.Status.LOADING) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), successMsg, Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
} else {
|
||||||
@@ -257,69 +239,47 @@ public class ProductDetailFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates input fields and saves product information to the backend.
|
|
||||||
*/
|
|
||||||
private void saveProduct() {
|
private void saveProduct() {
|
||||||
if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etProductName, "Product Name")) return;
|
||||||
|
if (!InputValidator.isSpinnerSelected(binding.spinnerProductCategory, "Category")) return;
|
||||||
if (binding.spinnerProductCategory.getSelectedItemPosition() == 0) {
|
if (!InputValidator.isPositiveDecimal(binding.etProductPrice, "Price")) return;
|
||||||
Toast.makeText(getContext(), "Select a category", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!InputValidator.isNotEmpty(binding.etProductPrice, "Price") ||
|
|
||||||
!InputValidator.isPositiveDecimal(binding.etProductPrice, "Price")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String name = binding.etProductName.getText().toString().trim();
|
String name = binding.etProductName.getText().toString().trim();
|
||||||
String desc = binding.etProductDesc.getText().toString().trim();
|
String desc = binding.etProductDesc.getText().toString().trim();
|
||||||
BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim());
|
BigDecimal price = new BigDecimal(binding.etProductPrice.getText().toString().trim());
|
||||||
|
|
||||||
CategoryDTO category = categoryList.get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
CategoryDTO category = viewModel.getCategoryList().getValue().get(binding.spinnerProductCategory.getSelectedItemPosition() - 1);
|
||||||
ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price);
|
ProductDTO dto = new ProductDTO(name, category.getCategoryId(), desc, price);
|
||||||
|
|
||||||
if (isEditing) {
|
viewModel.saveProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
viewModel.updateProduct(prodId, dto).observe(getViewLifecycleOwner(), resource -> {
|
if (resource == null) return;
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status != Resource.Status.LOADING) {
|
||||||
performPendingImageActions("Updated");
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
} else {
|
if (resource.data != null) {
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
viewModel.setProdId(resource.data.getProdId());
|
||||||
}
|
}
|
||||||
|
performPendingImageActions(viewModel.isEditing() ? "Updated" : "Saved");
|
||||||
|
} else {
|
||||||
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
viewModel.createProduct(dto).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
prodId = resource.data.getProdId();
|
|
||||||
performPendingImageActions("Saved");
|
|
||||||
} else {
|
|
||||||
Toast.makeText(getContext(), "Error saving: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a confirmation dialog before deleting the product.
|
|
||||||
*/
|
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product", () ->
|
||||||
viewModel.deleteProduct(prodId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteProduct().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS) {
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the previous fragment.
|
|
||||||
*/
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,9 +15,8 @@ import com.example.petstoremobile.utils.DialogUtils;
|
|||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductSupplierViewModel;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.ProductViewModel;
|
import com.example.petstoremobile.viewmodels.ProductSupplierDetailViewModel;
|
||||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -31,26 +30,15 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class ProductSupplierDetailFragment extends Fragment {
|
public class ProductSupplierDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentProductSupplierDetailBinding binding;
|
private FragmentProductSupplierDetailBinding binding;
|
||||||
|
private ProductSupplierDetailViewModel viewModel;
|
||||||
|
|
||||||
private boolean isEditing = false;
|
|
||||||
private long editProductId = -1;
|
|
||||||
private long editSupplierId = -1;
|
|
||||||
private long preselectedProductId = -1;
|
private long preselectedProductId = -1;
|
||||||
private long preselectedSupplierId = -1;
|
private long preselectedSupplierId = -1;
|
||||||
|
|
||||||
private List<ProductDTO> productList = new ArrayList<>();
|
|
||||||
private List<SupplierDTO> supplierList = new ArrayList<>();
|
|
||||||
|
|
||||||
private ProductSupplierViewModel psViewModel;
|
|
||||||
private ProductViewModel productViewModel;
|
|
||||||
private SupplierViewModel supplierViewModel;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
psViewModel = new ViewModelProvider(this).get(ProductSupplierViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ProductSupplierDetailViewModel.class);
|
||||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
|
||||||
supplierViewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -63,6 +51,7 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
@Override
|
@Override
|
||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
observeViewModel();
|
||||||
loadSpinnersData();
|
loadSpinnersData();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
@@ -71,128 +60,97 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
binding.btnDeletePS.setOnClickListener(v -> confirmDelete());
|
binding.btnDeletePS.setOnClickListener(v -> confirmDelete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> refreshProductSpinner());
|
||||||
|
viewModel.getSupplierList().observe(getViewLifecycleOwner(), list -> refreshSupplierSpinner());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches products and suppliers to populate the spinners.
|
|
||||||
*/
|
|
||||||
private void loadSpinnersData() {
|
private void loadSpinnersData() {
|
||||||
loadProducts();
|
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
|
||||||
loadSuppliers();
|
if (resource == null) return;
|
||||||
}
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of products from the API.
|
|
||||||
*/
|
|
||||||
private void loadProducts() {
|
|
||||||
productViewModel.getAllProducts(null, null, 0, 200, "prodName").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
productList = resource.data.getContent();
|
viewModel.setProductList(resource.data.getContent());
|
||||||
refreshProductSpinner();
|
}
|
||||||
|
});
|
||||||
|
viewModel.loadSuppliers().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
viewModel.setSupplierList(resource.data.getContent());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void refreshProductSpinner() {
|
private void refreshProductSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, productList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSProduct, viewModel.getProductList().getValue(),
|
||||||
ProductDTO::getProdName, "-- Select Product --",
|
ProductDTO::getProdName, "-- Select Product --",
|
||||||
preselectedProductId, ProductDTO::getProdId);
|
preselectedProductId, ProductDTO::getProdId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads the list of suppliers from the API.
|
|
||||||
*/
|
|
||||||
private void loadSuppliers() {
|
|
||||||
supplierViewModel.getAllSuppliers(0, 200, null, "supCompany").observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
supplierList = resource.data.getContent();
|
|
||||||
refreshSupplierSpinner();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private void refreshSupplierSpinner() {
|
private void refreshSupplierSpinner() {
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, supplierList,
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerPSSupplier, viewModel.getSupplierList().getValue(),
|
||||||
SupplierDTO::getSupCompany, "-- Select Supplier --",
|
SupplierDTO::getSupCompany, "-- Select Supplier --",
|
||||||
preselectedSupplierId, SupplierDTO::getSupId);
|
preselectedSupplierId, SupplierDTO::getSupId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles arguments to determine if the fragment is in edit or add mode.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("productId") && a.containsKey("supplierId")) {
|
if (a != null && a.containsKey("productId") && a.containsKey("supplierId")) {
|
||||||
isEditing = true;
|
long productId = a.getLong("productId");
|
||||||
editProductId = a.getLong("productId");
|
long supplierId = a.getLong("supplierId");
|
||||||
editSupplierId = a.getLong("supplierId");
|
viewModel.setEditMode(productId, supplierId);
|
||||||
preselectedProductId = editProductId;
|
preselectedProductId = productId;
|
||||||
preselectedSupplierId = editSupplierId;
|
preselectedSupplierId = supplierId;
|
||||||
|
|
||||||
binding.tvPSMode.setText("Edit Product Supplier");
|
binding.tvPSMode.setText("Edit Product Supplier");
|
||||||
binding.btnDeletePS.setVisibility(View.VISIBLE);
|
binding.btnDeletePS.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
binding.tvPSMode.setText("Add Product Supplier");
|
binding.tvPSMode.setText("Add Product Supplier");
|
||||||
binding.btnDeletePS.setVisibility(View.GONE);
|
binding.btnDeletePS.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validates input and saves the product-supplier to the backend.
|
|
||||||
*/
|
|
||||||
private void save() {
|
private void save() {
|
||||||
if (binding.spinnerPSProduct.getSelectedItemPosition() == 0) {
|
if (!InputValidator.isSpinnerSelected(binding.spinnerPSProduct, "Product")) return;
|
||||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show(); return;
|
if (!InputValidator.isSpinnerSelected(binding.spinnerPSSupplier, "Supplier")) return;
|
||||||
}
|
if (!InputValidator.isPositiveDecimal(binding.etPSCost, "Cost")) return;
|
||||||
if (binding.spinnerPSSupplier.getSelectedItemPosition() == 0) {
|
|
||||||
Toast.makeText(getContext(), "Select a supplier", Toast.LENGTH_SHORT).show(); return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!InputValidator.isNotEmpty(binding.etPSCost, "Cost") ||
|
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerPSProduct.getSelectedItemPosition() - 1);
|
||||||
!InputValidator.isPositiveDecimal(binding.etPSCost, "Cost")) {
|
SupplierDTO supplier = viewModel.getSupplierList().getValue().get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProductDTO product = productList.get(binding.spinnerPSProduct.getSelectedItemPosition() - 1);
|
|
||||||
SupplierDTO supplier = supplierList.get(binding.spinnerPSSupplier.getSelectedItemPosition() - 1);
|
|
||||||
BigDecimal cost = new BigDecimal(binding.etPSCost.getText().toString().trim());
|
BigDecimal cost = new BigDecimal(binding.etPSCost.getText().toString().trim());
|
||||||
|
|
||||||
ProductSupplierDTO dto = new ProductSupplierDTO(
|
ProductSupplierDTO dto = new ProductSupplierDTO(product.getProdId(), supplier.getSupId(), cost);
|
||||||
product.getProdId(), supplier.getSupId(), cost);
|
|
||||||
|
|
||||||
if (isEditing) {
|
viewModel.saveProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
psViewModel.updateProductSupplier(editProductId, editSupplierId, dto).observe(getViewLifecycleOwner(), resource -> {
|
if (resource == null) return;
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
Toast.makeText(getContext(), "Updated", Toast.LENGTH_SHORT).show();
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
navigateBack();
|
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated" : "Saved", Toast.LENGTH_SHORT).show();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
navigateBack();
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
}
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
psViewModel.createProductSupplier(dto).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
|
||||||
Toast.makeText(getContext(), "Saved", Toast.LENGTH_SHORT).show();
|
|
||||||
navigateBack();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Shows a confirmation dialog before deleting a product-supplier relationship.
|
|
||||||
*/
|
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product Supplier", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Product Supplier Relationship", () ->
|
||||||
psViewModel.deleteProductSupplier(editProductId, editSupplierId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteProductSupplier().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Deleted", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
@@ -202,9 +160,6 @@ public class ProductSupplierDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the previous screen.
|
|
||||||
*/
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
import com.example.petstoremobile.databinding.FragmentPurchaseOrderDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentPurchaseOrderDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.viewmodels.PurchaseOrderViewModel;
|
import com.example.petstoremobile.viewmodels.PurchaseOrderDetailViewModel;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@@ -25,13 +25,13 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class PurchaseOrderDetailFragment extends Fragment {
|
public class PurchaseOrderDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentPurchaseOrderDetailBinding binding;
|
private FragmentPurchaseOrderDetailBinding binding;
|
||||||
private PurchaseOrderViewModel viewModel;
|
private PurchaseOrderDetailViewModel viewModel;
|
||||||
private long purchaseOrderId;
|
private long purchaseOrderId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PurchaseOrderViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PurchaseOrderDetailViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -66,9 +66,16 @@ public class PurchaseOrderDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadPurchaseOrderData() {
|
private void loadPurchaseOrderData() {
|
||||||
viewModel.getPurchaseOrderById(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadPurchaseOrder(purchaseOrderId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
PurchaseOrderDTO po = resource.data;
|
PurchaseOrderDTO po = resource.data;
|
||||||
binding.tvPODetailId.setText("PO #" + po.getPurchaseOrderId());
|
binding.tvPODetailId.setText("PO #" + po.getPurchaseOrderId());
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.app.AlertDialog;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
@@ -12,7 +11,10 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.databinding.FragmentRefundBinding;
|
import com.example.petstoremobile.databinding.FragmentRefundBinding;
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
import com.example.petstoremobile.viewmodels.SaleViewModel;
|
import com.example.petstoremobile.viewmodels.RefundViewModel;
|
||||||
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
@@ -22,53 +24,23 @@ import java.util.*;
|
|||||||
public class RefundFragment extends Fragment {
|
public class RefundFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentRefundBinding binding;
|
private FragmentRefundBinding binding;
|
||||||
private SaleViewModel saleViewModel;
|
private RefundViewModel viewModel;
|
||||||
private SaleDTO currentSale;
|
|
||||||
private List<SaleDTO> allSales = new ArrayList<>();
|
|
||||||
|
|
||||||
// Items available to refund (after accounting for previous refunds)
|
|
||||||
private List<RefundItem> availableItems = new ArrayList<>();
|
|
||||||
// Items user has added to refund cart
|
|
||||||
private List<RefundItem> refundCart = new ArrayList<>();
|
|
||||||
|
|
||||||
private final String[] PAYMENT_METHODS = {"Cash", "Card"};
|
private final String[] PAYMENT_METHODS = {"Cash", "Card"};
|
||||||
|
|
||||||
// Inner class to track refund items
|
|
||||||
static class RefundItem {
|
|
||||||
long prodId;
|
|
||||||
String productName;
|
|
||||||
int quantity;
|
|
||||||
BigDecimal unitPrice;
|
|
||||||
|
|
||||||
RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) {
|
|
||||||
this.prodId = prodId;
|
|
||||||
this.productName = productName;
|
|
||||||
this.quantity = quantity;
|
|
||||||
this.unitPrice = unitPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
BigDecimal getTotal() {
|
|
||||||
return unitPrice != null
|
|
||||||
? unitPrice.multiply(BigDecimal.valueOf(quantity))
|
|
||||||
: BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
binding = FragmentRefundBinding.inflate(inflater, container, false);
|
binding = FragmentRefundBinding.inflate(inflater, container, false);
|
||||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
viewModel = new ViewModelProvider(this).get(RefundViewModel.class);
|
||||||
|
|
||||||
setupSpinner();
|
setupSpinner();
|
||||||
|
observeViewModel();
|
||||||
loadAllSales();
|
loadAllSales();
|
||||||
|
|
||||||
// Pre-fill sale ID if passed from SaleFragment
|
|
||||||
Bundle args = getArguments();
|
Bundle args = getArguments();
|
||||||
if (args != null && args.containsKey("saleId")) {
|
if (args != null && args.containsKey("saleId")) {
|
||||||
long saleId = args.getLong("saleId");
|
binding.etRefundSaleId.setText(String.valueOf(args.getLong("saleId")));
|
||||||
binding.etRefundSaleId.setText(String.valueOf(saleId));
|
|
||||||
// Auto-load after sales are fetched
|
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.btnLoadSale.setOnClickListener(v -> loadSale());
|
binding.btnLoadSale.setOnClickListener(v -> loadSale());
|
||||||
@@ -79,31 +51,36 @@ public class RefundFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void setupSpinner() {
|
private void setupSpinner() {
|
||||||
binding.spinnerRefundPayment.setAdapter(new ArrayAdapter<>(requireContext(),
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerRefundPayment, PAYMENT_METHODS);
|
||||||
android.R.layout.simple_spinner_item, PAYMENT_METHODS));
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getAvailableItems().observe(getViewLifecycleOwner(), items -> renderOriginalItems());
|
||||||
|
viewModel.getRefundCart().observe(getViewLifecycleOwner(), cart -> {
|
||||||
|
renderRefundCart();
|
||||||
|
updateRefundTotal();
|
||||||
|
renderOriginalItems(); // Re-render to reflect quantities in cart
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadAllSales() {
|
private void loadAllSales() {
|
||||||
saleViewModel.getAllSales(0, 1000, null, null, null, "saleDate,desc")
|
viewModel.loadAllSales().observe(getViewLifecycleOwner(), resource -> {
|
||||||
.observe(getViewLifecycleOwner(), resource -> {
|
if (resource == null) return;
|
||||||
if (resource != null) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
switch (resource.status) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
case SUCCESS:
|
viewModel.setAllSales(resource.data.getContent());
|
||||||
if (resource.data != null) {
|
Bundle args = getArguments();
|
||||||
allSales = resource.data.getContent();
|
if (args != null && args.containsKey("saleId")) {
|
||||||
// Auto-load if saleId was pre-filled
|
loadSale();
|
||||||
Bundle args = getArguments();
|
}
|
||||||
if (args != null && args.containsKey("saleId")) {
|
}
|
||||||
loadSale();
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
Log.e("Refund", "Failed to load sales: " + resource.message);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSale() {
|
private void loadSale() {
|
||||||
@@ -120,11 +97,12 @@ public class RefundFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find sale in loaded list
|
|
||||||
SaleDTO found = null;
|
SaleDTO found = null;
|
||||||
for (SaleDTO s : allSales) {
|
if (viewModel.getAllSalesList() != null) {
|
||||||
if (s.getSaleId() != null && s.getSaleId() == saleId) {
|
for (SaleDTO s : viewModel.getAllSalesList()) {
|
||||||
found = s; break;
|
if (s.getSaleId() != null && s.getSaleId() == saleId) {
|
||||||
|
found = s; break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,9 +117,9 @@ public class RefundFragment extends Fragment {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
currentSale = found;
|
viewModel.setCurrentSale(found);
|
||||||
|
SaleDTO currentSale = viewModel.getCurrentSale();
|
||||||
|
|
||||||
// Show sale info
|
|
||||||
binding.tvSaleInfo.setVisibility(View.VISIBLE);
|
binding.tvSaleInfo.setVisibility(View.VISIBLE);
|
||||||
binding.tvSaleInfo.setText("Sale #" + currentSale.getSaleId()
|
binding.tvSaleInfo.setText("Sale #" + currentSale.getSaleId()
|
||||||
+ " | " + (currentSale.getSaleDate() != null
|
+ " | " + (currentSale.getSaleDate() != null
|
||||||
@@ -151,94 +129,44 @@ public class RefundFragment extends Fragment {
|
|||||||
+ " | Total: $" + currentSale.getTotalAmount()
|
+ " | Total: $" + currentSale.getTotalAmount()
|
||||||
+ " | Payment: " + currentSale.getPaymentMethod());
|
+ " | Payment: " + currentSale.getPaymentMethod());
|
||||||
|
|
||||||
// Pre-select payment method
|
|
||||||
if (currentSale.getPaymentMethod() != null) {
|
if (currentSale.getPaymentMethod() != null) {
|
||||||
for (int i = 0; i < PAYMENT_METHODS.length; i++) {
|
SpinnerUtils.setSelectionByValue(binding.spinnerRefundPayment, currentSale.getPaymentMethod());
|
||||||
if (PAYMENT_METHODS[i].equalsIgnoreCase(currentSale.getPaymentMethod())) {
|
|
||||||
binding.spinnerRefundPayment.setSelection(i); break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build refundable items accounting for previous refunds
|
if (viewModel.getAvailableItems().getValue() == null || viewModel.getAvailableItems().getValue().isEmpty()) {
|
||||||
buildRefundableItems();
|
Toast.makeText(getContext(), "This sale has no remaining refundable items", Toast.LENGTH_LONG).show();
|
||||||
|
|
||||||
if (availableItems.isEmpty()) {
|
|
||||||
Toast.makeText(getContext(),
|
|
||||||
"This sale has no remaining refundable items", Toast.LENGTH_LONG).show();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset refund cart
|
|
||||||
refundCart.clear();
|
|
||||||
|
|
||||||
// Show cards
|
|
||||||
binding.cardOriginalItems.setVisibility(View.VISIBLE);
|
binding.cardOriginalItems.setVisibility(View.VISIBLE);
|
||||||
binding.cardRefundItems.setVisibility(View.VISIBLE);
|
binding.cardRefundItems.setVisibility(View.VISIBLE);
|
||||||
binding.cardPayment.setVisibility(View.VISIBLE);
|
binding.cardPayment.setVisibility(View.VISIBLE);
|
||||||
binding.btnProcessRefund.setVisibility(View.VISIBLE);
|
binding.btnProcessRefund.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
renderOriginalItems();
|
|
||||||
renderRefundCart();
|
|
||||||
updateRefundTotal();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void buildRefundableItems() {
|
|
||||||
availableItems.clear();
|
|
||||||
if (currentSale.getItems() == null) return;
|
|
||||||
|
|
||||||
// Find all previous refunds for this sale
|
|
||||||
Map<Long, Integer> alreadyRefunded = new HashMap<>();
|
|
||||||
for (SaleDTO s : allSales) {
|
|
||||||
if (Boolean.TRUE.equals(s.getIsRefund())
|
|
||||||
&& currentSale.getSaleId().equals(s.getOriginalSaleId())
|
|
||||||
&& s.getItems() != null) {
|
|
||||||
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
|
||||||
if (item.getProdId() != null && item.getQuantity() != null) {
|
|
||||||
alreadyRefunded.merge(item.getProdId(),
|
|
||||||
Math.abs(item.getQuantity()), Integer::sum);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build available items
|
|
||||||
for (SaleDTO.SaleItemDTO item : currentSale.getItems()) {
|
|
||||||
if (item.getProdId() == null || item.getQuantity() == null) continue;
|
|
||||||
int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0);
|
|
||||||
int remaining = item.getQuantity() - refunded;
|
|
||||||
if (remaining > 0) {
|
|
||||||
availableItems.add(new RefundItem(
|
|
||||||
item.getProdId(),
|
|
||||||
item.getProductName() != null ? item.getProductName() : "Unknown",
|
|
||||||
remaining,
|
|
||||||
item.getUnitPrice()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void renderOriginalItems() {
|
private void renderOriginalItems() {
|
||||||
binding.llOriginalItems.removeAllViews();
|
binding.llOriginalItems.removeAllViews();
|
||||||
|
List<RefundViewModel.RefundItem> available = viewModel.getAvailableItems().getValue();
|
||||||
|
if (available == null) return;
|
||||||
|
|
||||||
// Header
|
|
||||||
addTableHeader(binding.llOriginalItems);
|
addTableHeader(binding.llOriginalItems);
|
||||||
|
|
||||||
for (RefundItem item : availableItems) {
|
for (RefundViewModel.RefundItem item : available) {
|
||||||
// Calculate pending in cart
|
int inCart = 0;
|
||||||
int pendingQty = 0;
|
if (viewModel.getRefundCart().getValue() != null) {
|
||||||
for (RefundItem r : refundCart) {
|
for (RefundViewModel.RefundItem r : viewModel.getRefundCart().getValue()) {
|
||||||
if (r.prodId == item.prodId) { pendingQty = r.quantity; break; }
|
if (r.prodId == item.prodId) { inCart = r.quantity; break; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
int displayQty = item.quantity - pendingQty;
|
int displayQty = item.quantity - inCart;
|
||||||
if (displayQty <= 0) continue;
|
if (displayQty <= 0) continue;
|
||||||
|
|
||||||
LinearLayout row = buildItemRow(
|
LinearLayout row = buildItemRow(
|
||||||
item.productName,
|
item.productName,
|
||||||
displayQty,
|
displayQty,
|
||||||
item.unitPrice,
|
item.unitPrice,
|
||||||
true, // show add button
|
true,
|
||||||
() -> showQuantityDialog(item)
|
() -> showQuantityDialog(item, displayQty)
|
||||||
);
|
);
|
||||||
binding.llOriginalItems.addView(row);
|
binding.llOriginalItems.addView(row);
|
||||||
}
|
}
|
||||||
@@ -246,8 +174,9 @@ public class RefundFragment extends Fragment {
|
|||||||
|
|
||||||
private void renderRefundCart() {
|
private void renderRefundCart() {
|
||||||
binding.llRefundItems.removeAllViews();
|
binding.llRefundItems.removeAllViews();
|
||||||
|
List<RefundViewModel.RefundItem> cart = viewModel.getRefundCart().getValue();
|
||||||
|
|
||||||
if (refundCart.isEmpty()) {
|
if (cart == null || cart.isEmpty()) {
|
||||||
TextView empty = new TextView(getContext());
|
TextView empty = new TextView(getContext());
|
||||||
empty.setText("No items added to refund yet");
|
empty.setText("No items added to refund yet");
|
||||||
empty.setTextColor(0xFF888780);
|
empty.setTextColor(0xFF888780);
|
||||||
@@ -258,18 +187,13 @@ public class RefundFragment extends Fragment {
|
|||||||
|
|
||||||
addTableHeader(binding.llRefundItems);
|
addTableHeader(binding.llRefundItems);
|
||||||
|
|
||||||
for (RefundItem item : refundCart) {
|
for (RefundViewModel.RefundItem item : cart) {
|
||||||
LinearLayout row = buildItemRow(
|
LinearLayout row = buildItemRow(
|
||||||
item.productName,
|
item.productName,
|
||||||
item.quantity,
|
item.quantity,
|
||||||
item.unitPrice,
|
item.unitPrice,
|
||||||
false, // show remove button
|
false,
|
||||||
() -> {
|
() -> viewModel.removeFromCart(item)
|
||||||
refundCart.remove(item);
|
|
||||||
renderOriginalItems();
|
|
||||||
renderRefundCart();
|
|
||||||
updateRefundTotal();
|
|
||||||
}
|
|
||||||
);
|
);
|
||||||
binding.llRefundItems.addView(row);
|
binding.llRefundItems.addView(row);
|
||||||
}
|
}
|
||||||
@@ -342,146 +266,79 @@ public class RefundFragment extends Fragment {
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showQuantityDialog(RefundItem item) {
|
private void showQuantityDialog(RefundViewModel.RefundItem item, int available) {
|
||||||
// Calculate how many are already in cart
|
|
||||||
int inCart = 0;
|
|
||||||
for (RefundItem r : refundCart) {
|
|
||||||
if (r.prodId == item.prodId) { inCart = r.quantity; break; }
|
|
||||||
}
|
|
||||||
int available = item.quantity - inCart;
|
|
||||||
if (available <= 0) {
|
|
||||||
Toast.makeText(getContext(), "All units already added to refund",
|
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build dialog
|
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext());
|
|
||||||
builder.setTitle("Refund Quantity");
|
|
||||||
builder.setMessage("Product: " + item.productName
|
|
||||||
+ "\nAvailable: " + available);
|
|
||||||
|
|
||||||
EditText input = new EditText(getContext());
|
EditText input = new EditText(getContext());
|
||||||
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
|
input.setInputType(android.text.InputType.TYPE_CLASS_NUMBER);
|
||||||
input.setText(String.valueOf(available));
|
input.setText(String.valueOf(available));
|
||||||
input.setSelectAllOnFocus(true);
|
input.setSelectAllOnFocus(true);
|
||||||
builder.setView(input);
|
input.setPadding(40, 40, 40, 40);
|
||||||
|
|
||||||
builder.setPositiveButton("Add to Refund", (d, w) -> {
|
new androidx.appcompat.app.AlertDialog.Builder(requireContext())
|
||||||
String val = input.getText().toString().trim();
|
.setTitle("Refund Quantity")
|
||||||
if (val.isEmpty()) return;
|
.setMessage("Product: " + item.productName + "\nAvailable: " + available)
|
||||||
int qty;
|
.setView(input)
|
||||||
try { qty = Integer.parseInt(val); }
|
.setPositiveButton("Add to Refund", (d, w) -> {
|
||||||
catch (Exception e) {
|
String val = input.getText().toString().trim();
|
||||||
Toast.makeText(getContext(), "Invalid quantity", Toast.LENGTH_SHORT).show();
|
if (val.isEmpty()) return;
|
||||||
return;
|
int qty;
|
||||||
}
|
try { qty = Integer.parseInt(val); }
|
||||||
if (qty <= 0) {
|
catch (Exception e) {
|
||||||
Toast.makeText(getContext(), "Quantity must be at least 1",
|
Toast.makeText(getContext(), "Invalid quantity", Toast.LENGTH_SHORT).show();
|
||||||
Toast.LENGTH_SHORT).show();
|
return;
|
||||||
return;
|
}
|
||||||
}
|
if (qty <= 0) {
|
||||||
if (qty > available) {
|
Toast.makeText(getContext(), "Quantity must be at least 1", Toast.LENGTH_SHORT).show();
|
||||||
Toast.makeText(getContext(), "Cannot exceed " + available,
|
return;
|
||||||
Toast.LENGTH_SHORT).show();
|
}
|
||||||
return;
|
if (qty > available) {
|
||||||
}
|
Toast.makeText(getContext(), "Cannot exceed " + available, Toast.LENGTH_SHORT).show();
|
||||||
|
return;
|
||||||
// Add or merge into cart
|
}
|
||||||
boolean merged = false;
|
viewModel.addToCart(item, qty);
|
||||||
for (int i = 0; i < refundCart.size(); i++) {
|
})
|
||||||
if (refundCart.get(i).prodId == item.prodId) {
|
.setNegativeButton("Cancel", null)
|
||||||
RefundItem existing = refundCart.get(i);
|
.show();
|
||||||
refundCart.set(i, new RefundItem(existing.prodId,
|
|
||||||
existing.productName,
|
|
||||||
existing.quantity + qty,
|
|
||||||
existing.unitPrice));
|
|
||||||
merged = true; break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!merged) {
|
|
||||||
refundCart.add(new RefundItem(item.prodId, item.productName,
|
|
||||||
qty, item.unitPrice));
|
|
||||||
}
|
|
||||||
|
|
||||||
renderOriginalItems();
|
|
||||||
renderRefundCart();
|
|
||||||
updateRefundTotal();
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.setNegativeButton("Cancel", null);
|
|
||||||
builder.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRefundTotal() {
|
private void updateRefundTotal() {
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
List<RefundViewModel.RefundItem> cart = viewModel.getRefundCart().getValue();
|
||||||
|
if (cart != null) {
|
||||||
|
for (RefundViewModel.RefundItem item : cart) total = total.add(item.getTotal());
|
||||||
|
}
|
||||||
binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
binding.tvRefundTotal.setText("Refund Total: $" + total.setScale(2, RoundingMode.HALF_UP));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void processRefund() {
|
private void processRefund() {
|
||||||
if (currentSale == null) {
|
if (viewModel.getCurrentSale() == null) {
|
||||||
Toast.makeText(getContext(), "Load a sale first", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Load a sale first", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (refundCart.isEmpty()) {
|
if (viewModel.getRefundCart().getValue() == null || viewModel.getRefundCart().getValue().isEmpty()) {
|
||||||
Toast.makeText(getContext(), "Add at least one item to refund",
|
Toast.makeText(getContext(), "Add at least one item to refund", Toast.LENGTH_SHORT).show();
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()];
|
String payment = PAYMENT_METHODS[binding.spinnerRefundPayment.getSelectedItemPosition()];
|
||||||
|
|
||||||
// Confirm dialog
|
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
for (RefundItem item : refundCart) total = total.add(item.getTotal());
|
for (RefundViewModel.RefundItem item : viewModel.getRefundCart().getValue()) total = total.add(item.getTotal());
|
||||||
final BigDecimal finalTotal = total;
|
final BigDecimal finalTotal = total;
|
||||||
|
|
||||||
new AlertDialog.Builder(requireContext())
|
DialogUtils.showConfirmDialog(requireContext(), "Confirm Refund",
|
||||||
.setTitle("Confirm Refund")
|
"Process refund for Sale #" + viewModel.getCurrentSale().getSaleId()
|
||||||
.setMessage("Process refund for Sale #" + currentSale.getSaleId()
|
+ "?\nRefund amount: $" + finalTotal.setScale(2, RoundingMode.HALF_UP),
|
||||||
+ "?\nRefund amount: $" + finalTotal.setScale(2, RoundingMode.HALF_UP))
|
() -> submitRefund(payment));
|
||||||
.setPositiveButton("Yes", (d, w) -> submitRefund(payment))
|
|
||||||
.setNegativeButton("No", null)
|
|
||||||
.show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void submitRefund(String payment) {
|
private void submitRefund(String payment) {
|
||||||
// Build sale items list
|
viewModel.submitRefund(payment).observe(getViewLifecycleOwner(), resource -> {
|
||||||
List<SaleDTO.SaleItemDTO> items = new ArrayList<>();
|
|
||||||
for (RefundItem item : refundCart) {
|
|
||||||
// Backend expects negative quantity for refunds
|
|
||||||
items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity));
|
|
||||||
}
|
|
||||||
|
|
||||||
SaleDTO dto = new SaleDTO(
|
|
||||||
currentSale.getStoreId(),
|
|
||||||
payment,
|
|
||||||
items,
|
|
||||||
true, // isRefund = true
|
|
||||||
currentSale.getSaleId(), // originalSaleId
|
|
||||||
null // no customer needed
|
|
||||||
);
|
|
||||||
|
|
||||||
Log.d("REFUND", "Submitting refund for saleId=" + currentSale.getSaleId()
|
|
||||||
+ " items=" + items.size());
|
|
||||||
|
|
||||||
saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
switch (resource.status) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
case SUCCESS:
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
if (resource.data != null) {
|
Toast.makeText(getContext(), "Refund processed successfully!", Toast.LENGTH_LONG).show();
|
||||||
Toast.makeText(getContext(),
|
navigateBack();
|
||||||
"Refund #" + resource.data.getSaleId() + " processed successfully!",
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
Toast.LENGTH_LONG).show();
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||||
navigateBack();
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
Log.e("REFUND", "Error: " + resource.message);
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message,
|
|
||||||
Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,10 +11,12 @@ import androidx.navigation.fragment.NavHostFragment;
|
|||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.databinding.FragmentSaleDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentSaleDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.*;
|
import com.example.petstoremobile.dtos.*;
|
||||||
import com.example.petstoremobile.viewmodels.*;
|
import com.example.petstoremobile.viewmodels.SaleDetailViewModel;
|
||||||
import com.example.petstoremobile.utils.SpinnerUtils;
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -23,18 +25,7 @@ import java.util.*;
|
|||||||
public class SaleDetailFragment extends Fragment {
|
public class SaleDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentSaleDetailBinding binding;
|
private FragmentSaleDetailBinding binding;
|
||||||
private SaleViewModel saleViewModel;
|
private SaleDetailViewModel viewModel;
|
||||||
private StoreViewModel storeViewModel;
|
|
||||||
private CustomerViewModel customerViewModel;
|
|
||||||
private ProductViewModel productViewModel;
|
|
||||||
|
|
||||||
private boolean viewOnly = false;
|
|
||||||
private long saleId = -1;
|
|
||||||
|
|
||||||
private List<StoreDTO> storeList = new ArrayList<>();
|
|
||||||
private List<CustomerDTO> customerList = new ArrayList<>();
|
|
||||||
private List<ProductDTO> productList = new ArrayList<>();
|
|
||||||
private List<SaleDTO.SaleItemDTO> cartItems = new ArrayList<>();
|
|
||||||
|
|
||||||
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
|
private final String[] PAYMENT_METHODS = { "Cash", "Card"};
|
||||||
|
|
||||||
@@ -42,17 +33,14 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
binding = FragmentSaleDetailBinding.inflate(inflater, container, false);
|
binding = FragmentSaleDetailBinding.inflate(inflater, container, false);
|
||||||
|
viewModel = new ViewModelProvider(this).get(SaleDetailViewModel.class);
|
||||||
saleViewModel = new ViewModelProvider(this).get(SaleViewModel.class);
|
|
||||||
storeViewModel = new ViewModelProvider(this).get(StoreViewModel.class);
|
|
||||||
customerViewModel = new ViewModelProvider(this).get(CustomerViewModel.class);
|
|
||||||
productViewModel = new ViewModelProvider(this).get(ProductViewModel.class);
|
|
||||||
|
|
||||||
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPaymentMethod, PAYMENT_METHODS);
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerPaymentMethod, PAYMENT_METHODS);
|
||||||
|
|
||||||
|
observeViewModel();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
if (!viewOnly) {
|
if (!viewModel.isViewOnly()) {
|
||||||
loadData();
|
loadData();
|
||||||
setupAddItem();
|
setupAddItem();
|
||||||
}
|
}
|
||||||
@@ -64,32 +52,55 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void observeViewModel() {
|
||||||
|
viewModel.getStoreList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, list,
|
||||||
|
DropdownDTO::getLabel, "-- Select Store --", -1L, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getCustomerList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, list,
|
||||||
|
DropdownDTO::getLabel, "-- No Customer --", -1L, DropdownDTO::getId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getProductList().observe(getViewLifecycleOwner(), list -> {
|
||||||
|
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, list,
|
||||||
|
ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId);
|
||||||
|
});
|
||||||
|
|
||||||
|
viewModel.getCartItems().observe(getViewLifecycleOwner(), items -> {
|
||||||
|
renderCartItems();
|
||||||
|
updateTotal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.containsKey("saleId")) {
|
if (a != null && a.containsKey("saleId")) {
|
||||||
saleId = a.getLong("saleId");
|
long saleId = a.getLong("saleId");
|
||||||
viewOnly = a.getBoolean("viewOnly", false);
|
boolean viewOnly = a.getBoolean("viewOnly", false);
|
||||||
|
viewModel.setSaleId(saleId, viewOnly);
|
||||||
|
|
||||||
binding.tvSaleMode.setText("Sale #" + saleId);
|
binding.tvSaleMode.setText("Sale #" + saleId);
|
||||||
binding.tvSaleDetailId.setText("ID: " + saleId);
|
binding.tvSaleDetailId.setText("ID: " + saleId);
|
||||||
|
|
||||||
// Show refund button for existing non-refund sales
|
|
||||||
if (!a.getBoolean("isRefund", false)) {
|
if (!a.getBoolean("isRefund", false)) {
|
||||||
binding.btnRefundSale.setVisibility(View.VISIBLE);
|
binding.btnRefundSale.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hide save and input controls for view only
|
|
||||||
if (viewOnly) {
|
if (viewOnly) {
|
||||||
binding.btnSaveSale.setVisibility(View.GONE);
|
binding.btnSaveSale.setVisibility(View.GONE);
|
||||||
binding.spinnerSaleStore.setEnabled(false);
|
UIUtils.setViewsEnabled(false,
|
||||||
binding.spinnerSaleCustomer.setEnabled(false);
|
binding.spinnerSaleStore,
|
||||||
binding.spinnerPaymentMethod.setEnabled(false);
|
binding.spinnerSaleCustomer,
|
||||||
|
binding.spinnerPaymentMethod);
|
||||||
binding.llAddItemRow.setVisibility(View.GONE);
|
binding.llAddItemRow.setVisibility(View.GONE);
|
||||||
binding.llExtraInfo.setVisibility(View.VISIBLE);
|
binding.llExtraInfo.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load sale details
|
|
||||||
loadSaleDetails();
|
loadSaleDetails();
|
||||||
} else {
|
} else {
|
||||||
|
viewModel.setSaleId(-1, false);
|
||||||
binding.tvSaleMode.setText("New Sale");
|
binding.tvSaleMode.setText("New Sale");
|
||||||
binding.tvSaleDetailId.setVisibility(View.GONE);
|
binding.tvSaleDetailId.setVisibility(View.GONE);
|
||||||
binding.btnRefundSale.setVisibility(View.GONE);
|
binding.btnRefundSale.setVisibility(View.GONE);
|
||||||
@@ -97,89 +108,62 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void loadData() {
|
private void loadData() {
|
||||||
loadStores();
|
viewModel.loadStores().observe(getViewLifecycleOwner(), resource -> {
|
||||||
loadCustomers();
|
if (resource == null) return;
|
||||||
loadProducts();
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
}
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setStoreList(resource.data);
|
||||||
|
|
||||||
private void loadStores() {
|
|
||||||
storeViewModel.getAllStores(0, 50).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
storeList = resource.data.getContent();
|
|
||||||
if (binding != null) {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "-- Select Store --", -1L, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
} else if (storeList.isEmpty()) {
|
|
||||||
storeList = Collections.singletonList(new StoreDTO(1L, "Downtown Branch"));
|
|
||||||
if (binding != null) {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleStore, storeList,
|
|
||||||
StoreDTO::getStoreName, "-- Select Store --", -1L, StoreDTO::getStoreId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
viewModel.loadCustomers().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
private void loadCustomers() {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
customerViewModel.getAllCustomers(0, 200).observe(getViewLifecycleOwner(), resource -> {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setCustomerList(resource.data);
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
customerList = resource.data.getContent();
|
|
||||||
if (binding != null) {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleCustomer, customerList,
|
|
||||||
CustomerDTO::getFullName, "-- No Customer --", -1L, CustomerDTO::getCustomerId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
viewModel.loadProducts().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
private void loadProducts() {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
productViewModel.getAllProducts(null, null, 0, 200, null).observe(getViewLifecycleOwner(), resource -> {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) viewModel.setProductList(resource.data.getContent());
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
|
||||||
productList = resource.data.getContent();
|
|
||||||
if (binding != null) {
|
|
||||||
SpinnerUtils.populateSpinner(requireContext(), binding.spinnerSaleProduct, productList,
|
|
||||||
ProductDTO::getProdName, "Select Product", -1L, ProductDTO::getProdId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void loadSaleDetails() {
|
private void loadSaleDetails() {
|
||||||
saleViewModel.getSaleById(saleId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadSaleDetails().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
SaleDTO sale = resource.data;
|
SaleDTO sale = resource.data;
|
||||||
if (binding != null) {
|
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
|
||||||
binding.tvSaleDetailTotal.setText("Total: $" + sale.getTotalAmount());
|
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
|
||||||
binding.tvSaleSubtotal.setText("$" + (sale.getSubtotalAmount() != null ? sale.getSubtotalAmount() : sale.getTotalAmount()));
|
|
||||||
|
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
if (sale.getCouponDiscountAmount() != null && sale.getCouponDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
||||||
binding.llCouponDiscount.setVisibility(View.VISIBLE);
|
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
|
||||||
binding.tvSaleCouponDiscount.setText("-$" + sale.getCouponDiscountAmount());
|
} else {
|
||||||
} else {
|
binding.llCouponDiscount.setVisibility(View.GONE);
|
||||||
binding.llCouponDiscount.setVisibility(View.GONE);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
if (sale.getEmployeeDiscountAmount() != null && sale.getEmployeeDiscountAmount().compareTo(BigDecimal.ZERO) > 0) {
|
||||||
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
|
binding.llEmployeeDiscount.setVisibility(View.VISIBLE);
|
||||||
binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount());
|
binding.tvSaleEmployeeDiscount.setText("-$" + sale.getEmployeeDiscountAmount());
|
||||||
} else {
|
} else {
|
||||||
binding.llEmployeeDiscount.setVisibility(View.GONE);
|
binding.llEmployeeDiscount.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
binding.tvSaleChannel.setText(sale.getChannel() != null ? sale.getChannel() : "—");
|
||||||
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
binding.tvSalePoints.setText(String.valueOf(sale.getPointsEarned() != null ? sale.getPointsEarned() : 0));
|
||||||
|
|
||||||
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
|
SpinnerUtils.setSelectionByValue(binding.spinnerPaymentMethod, sale.getPaymentMethod());
|
||||||
|
|
||||||
// Display items
|
if (sale.getItems() != null) {
|
||||||
if (sale.getItems() != null) {
|
binding.llSaleItems.removeAllViews();
|
||||||
binding.llSaleItems.removeAllViews();
|
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||||
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
addItemRow(item.getProductName(), Math.abs(item.getQuantity()), item.getUnitPrice());
|
||||||
addItemRow(item.getProductName(),
|
|
||||||
Math.abs(item.getQuantity()),
|
|
||||||
item.getUnitPrice());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,41 +172,44 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
|
|
||||||
private void setupAddItem() {
|
private void setupAddItem() {
|
||||||
binding.btnAddItem.setOnClickListener(v -> {
|
binding.btnAddItem.setOnClickListener(v -> {
|
||||||
if (binding.spinnerSaleProduct.getSelectedItemPosition() == 0) {
|
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleProduct, "Product")) return;
|
||||||
Toast.makeText(getContext(), "Select a product", Toast.LENGTH_SHORT).show();
|
if (!InputValidator.isPositiveInteger(binding.etSaleQuantity, "Quantity")) return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
String qtyStr = binding.etSaleQuantity.getText().toString().trim();
|
|
||||||
if (qtyStr.isEmpty()) {
|
|
||||||
binding.etSaleQuantity.setError("Enter quantity");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int qty;
|
|
||||||
try {
|
|
||||||
qty = Integer.parseInt(qtyStr);
|
|
||||||
} catch (Exception e) {
|
|
||||||
binding.etSaleQuantity.setError("Invalid quantity");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ProductDTO product = productList.get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1);
|
int qty = Integer.parseInt(binding.etSaleQuantity.getText().toString().trim());
|
||||||
|
ProductDTO product = viewModel.getProductList().getValue().get(binding.spinnerSaleProduct.getSelectedItemPosition() - 1);
|
||||||
|
|
||||||
// Check if product already in cart
|
for (SaleDTO.SaleItemDTO existing : viewModel.getCartItems().getValue()) {
|
||||||
for (SaleDTO.SaleItemDTO existing : cartItems) {
|
|
||||||
if (existing.getProdId().equals(product.getProdId())) {
|
if (existing.getProdId().equals(product.getProdId())) {
|
||||||
Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Product already added", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
SaleDTO.SaleItemDTO item = new SaleDTO.SaleItemDTO(product.getProdId(), qty);
|
viewModel.addToCart(new SaleDTO.SaleItemDTO(product.getProdId(), qty));
|
||||||
cartItems.add(item);
|
|
||||||
addItemRow(product.getProdName(), qty, product.getProdPrice());
|
|
||||||
updateTotal();
|
|
||||||
binding.etSaleQuantity.setText("");
|
binding.etSaleQuantity.setText("");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void renderCartItems() {
|
||||||
|
binding.llSaleItems.removeAllViews();
|
||||||
|
List<SaleDTO.SaleItemDTO> items = viewModel.getCartItems().getValue();
|
||||||
|
List<ProductDTO> products = viewModel.getProductList().getValue();
|
||||||
|
if (items == null || products == null) return;
|
||||||
|
|
||||||
|
for (SaleDTO.SaleItemDTO item : items) {
|
||||||
|
String name = "Unknown";
|
||||||
|
BigDecimal price = BigDecimal.ZERO;
|
||||||
|
for (ProductDTO p : products) {
|
||||||
|
if (p.getProdId().equals(item.getProdId())) {
|
||||||
|
name = p.getProdName();
|
||||||
|
price = p.getProdPrice();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addItemRow(name, item.getQuantity(), price);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void addItemRow(String name, int qty, BigDecimal price) {
|
private void addItemRow(String name, int qty, BigDecimal price) {
|
||||||
if (getContext() == null) return;
|
if (getContext() == null) return;
|
||||||
LinearLayout row = new LinearLayout(getContext());
|
LinearLayout row = new LinearLayout(getContext());
|
||||||
@@ -230,18 +217,15 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
row.setPadding(0, 8, 0, 8);
|
row.setPadding(0, 8, 0, 8);
|
||||||
|
|
||||||
TextView tvName = new TextView(getContext());
|
TextView tvName = new TextView(getContext());
|
||||||
tvName.setLayoutParams(new LinearLayout.LayoutParams(
|
tvName.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
||||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 2f));
|
|
||||||
tvName.setText(name);
|
tvName.setText(name);
|
||||||
|
|
||||||
TextView tvQty = new TextView(getContext());
|
TextView tvQty = new TextView(getContext());
|
||||||
tvQty.setLayoutParams(new LinearLayout.LayoutParams(
|
tvQty.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
|
||||||
tvQty.setText("x" + qty);
|
tvQty.setText("x" + qty);
|
||||||
|
|
||||||
TextView tvPrice = new TextView(getContext());
|
TextView tvPrice = new TextView(getContext());
|
||||||
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(
|
tvPrice.setLayoutParams(new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
||||||
0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f));
|
|
||||||
tvPrice.setText(price != null ? "$" + price : "");
|
tvPrice.setText(price != null ? "$" + price : "");
|
||||||
|
|
||||||
row.addView(tvName);
|
row.addView(tvName);
|
||||||
@@ -251,59 +235,37 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateTotal() {
|
private void updateTotal() {
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
BigDecimal total = viewModel.calculateSubtotal();
|
||||||
for (SaleDTO.SaleItemDTO item : cartItems) {
|
|
||||||
for (ProductDTO p : productList) {
|
|
||||||
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
|
|
||||||
total = total.add(p.getProdPrice()
|
|
||||||
.multiply(BigDecimal.valueOf(item.getQuantity())));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
binding.tvSaleSubtotal.setText("$" + total);
|
binding.tvSaleSubtotal.setText("$" + total);
|
||||||
binding.tvSaleDetailTotal.setText("Total: $" + total);
|
binding.tvSaleDetailTotal.setText("Total: $" + total);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveSale() {
|
private void saveSale() {
|
||||||
if (binding.spinnerSaleStore.getSelectedItemPosition() == 0) {
|
if (!InputValidator.isSpinnerSelected(binding.spinnerSaleStore, "Store")) return;
|
||||||
Toast.makeText(getContext(), "Select a store", Toast.LENGTH_SHORT).show();
|
|
||||||
return;
|
if (viewModel.getCartItems().getValue() == null || viewModel.getCartItems().getValue().isEmpty()) {
|
||||||
}
|
|
||||||
if (cartItems.isEmpty()) {
|
|
||||||
Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Add at least one item", Toast.LENGTH_SHORT).show();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
StoreDTO store = storeList.get(binding.spinnerSaleStore.getSelectedItemPosition() - 1);
|
DropdownDTO store = viewModel.getStoreList().getValue().get(binding.spinnerSaleStore.getSelectedItemPosition() - 1);
|
||||||
String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()];
|
String payment = PAYMENT_METHODS[binding.spinnerPaymentMethod.getSelectedItemPosition()];
|
||||||
|
|
||||||
// Optional customer
|
|
||||||
Long customerId = null;
|
Long customerId = null;
|
||||||
if (binding.spinnerSaleCustomer.getSelectedItemPosition() > 0) {
|
if (binding.spinnerSaleCustomer.getSelectedItemPosition() > 0) {
|
||||||
customerId = customerList.get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1)
|
customerId = viewModel.getCustomerList().getValue().get(binding.spinnerSaleCustomer.getSelectedItemPosition() - 1).getId();
|
||||||
.getCustomerId();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SaleDTO dto = new SaleDTO(
|
SaleDTO dto = new SaleDTO(store.getId(), payment, viewModel.getCartItems().getValue(), false, null, customerId);
|
||||||
store.getStoreId(),
|
|
||||||
payment,
|
|
||||||
cartItems,
|
|
||||||
false,
|
|
||||||
null,
|
|
||||||
customerId);
|
|
||||||
|
|
||||||
saleViewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.createSale(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null) {
|
if (resource != null) {
|
||||||
switch (resource.status) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
case SUCCESS:
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Sale saved!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
break;
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
case ERROR:
|
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
||||||
Log.e("SALE_SAVE", "Error: " + resource.message);
|
|
||||||
DialogUtils.showInfoDialog(requireContext(), "Save Error", resource.message);
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -313,7 +275,7 @@ public class SaleDetailFragment extends Fragment {
|
|||||||
DialogUtils.showConfirmDialog(requireContext(), "Process Refund",
|
DialogUtils.showConfirmDialog(requireContext(), "Process Refund",
|
||||||
"Are you sure you want to process a refund for this sale?", () -> {
|
"Are you sure you want to process a refund for this sale?", () -> {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putLong("saleId", saleId);
|
args.putLong("saleId", viewModel.getSaleId());
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_refund, args);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,10 +17,11 @@ import com.example.petstoremobile.R;
|
|||||||
import com.example.petstoremobile.databinding.FragmentServiceDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentServiceDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
import com.example.petstoremobile.utils.ActivityLogger;
|
import com.example.petstoremobile.utils.ActivityLogger;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
import com.example.petstoremobile.utils.DialogUtils;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.viewmodels.ServiceViewModel;
|
import com.example.petstoremobile.viewmodels.ServiceDetailViewModel;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@@ -31,15 +32,12 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class ServiceDetailFragment extends Fragment {
|
public class ServiceDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentServiceDetailBinding binding;
|
private FragmentServiceDetailBinding binding;
|
||||||
private long serviceId;
|
private ServiceDetailViewModel viewModel;
|
||||||
private boolean isEditing = false;
|
|
||||||
|
|
||||||
private ServiceViewModel viewModel;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(ServiceViewModel.class);
|
viewModel = new ViewModelProvider(this).get(ServiceDetailViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,78 +51,67 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
//get controls from layout and display the view depending on the mode
|
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
//set button click listeners
|
|
||||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||||
binding.btnSaveService.setOnClickListener(v -> saveService());
|
binding.btnSaveService.setOnClickListener(v -> saveService());
|
||||||
binding.btnDeleteService.setOnClickListener(v -> deleteService());
|
binding.btnDeleteService.setOnClickListener(v -> deleteService());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the saving of service data (adding or updating).
|
|
||||||
*/
|
|
||||||
private void saveService() {
|
private void saveService() {
|
||||||
// Validates all fields using InputValidator
|
|
||||||
if (!InputValidator.isNotEmpty(binding.etServiceName, "Service Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etServiceName, "Service Name")) return;
|
||||||
if (!InputValidator.isNotEmpty(binding.etServiceDesc, "Description")) return;
|
if (!InputValidator.isNotEmpty(binding.etServiceDesc, "Description")) return;
|
||||||
if (!InputValidator.isPositiveInteger(binding.etServiceDuration, "Duration")) return;
|
if (!InputValidator.isPositiveInteger(binding.etServiceDuration, "Duration")) return;
|
||||||
if (!InputValidator.isPositiveDecimal(binding.etServicePrice, "Price")) return;
|
if (!InputValidator.isPositiveDecimal(binding.etServicePrice, "Price")) return;
|
||||||
|
|
||||||
//get all the values from the fields
|
|
||||||
String name = binding.etServiceName.getText().toString().trim();
|
String name = binding.etServiceName.getText().toString().trim();
|
||||||
String desc = binding.etServiceDesc.getText().toString().trim();
|
String desc = binding.etServiceDesc.getText().toString().trim();
|
||||||
int duration = Integer.parseInt(binding.etServiceDuration.getText().toString().trim());
|
int duration = Integer.parseInt(binding.etServiceDuration.getText().toString().trim());
|
||||||
double price = Double.parseDouble(binding.etServicePrice.getText().toString().trim());
|
double price = Double.parseDouble(binding.etServicePrice.getText().toString().trim());
|
||||||
|
|
||||||
//create a service object to send to the API
|
|
||||||
ServiceDTO serviceDTO = new ServiceDTO();
|
ServiceDTO serviceDTO = new ServiceDTO();
|
||||||
serviceDTO.setServiceName(name);
|
serviceDTO.setServiceName(name);
|
||||||
serviceDTO.setServiceDesc(desc);
|
serviceDTO.setServiceDesc(desc);
|
||||||
serviceDTO.setServiceDuration(duration);
|
serviceDTO.setServiceDuration(duration);
|
||||||
serviceDTO.setServicePrice(price);
|
serviceDTO.setServicePrice(price);
|
||||||
|
|
||||||
//check if the service is being edited or added
|
viewModel.saveService(serviceDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (isEditing) {
|
if (resource == null) return;
|
||||||
// Update existing service
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
serviceDTO.setServiceId(serviceId);
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
viewModel.updateService(serviceId, serviceDTO).observe(getViewLifecycleOwner(), resource -> {
|
if (viewModel.isEditing()) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) viewModel.getServiceId());
|
||||||
ActivityLogger.logChange(requireContext(), "Service", "UPDATED", (int) serviceId);
|
|
||||||
Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Service updated successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
} else {
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
viewModel.createService(serviceDTO).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
|
||||||
ActivityLogger.log(requireContext(), "Added new Service: " + name);
|
ActivityLogger.log(requireContext(), "Added new Service: " + name);
|
||||||
Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Service added successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
});
|
navigateBack();
|
||||||
}
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a confirmation dialog and handles the deletion of a service.
|
|
||||||
*/
|
|
||||||
private void deleteService() {
|
private void deleteService() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Service", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Service", () ->
|
||||||
viewModel.deleteService(serviceId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteService().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) serviceId);
|
ActivityLogger.logChange(requireContext(), "Service", "DELETED", (int) viewModel.getServiceId());
|
||||||
Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Service deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
@@ -133,30 +120,20 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the previous screen.
|
|
||||||
*/
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
// Service is being edited if the bundle contains a serviceId
|
|
||||||
if (getArguments() != null && getArguments().containsKey("serviceId")) {
|
if (getArguments() != null && getArguments().containsKey("serviceId")) {
|
||||||
// Get service data from arguments and populate fields
|
long serviceId = getArguments().getLong("serviceId");
|
||||||
isEditing = true;
|
viewModel.setServiceId(serviceId);
|
||||||
serviceId = getArguments().getLong("serviceId");
|
|
||||||
binding.tvMode.setText("Edit Service");
|
binding.tvMode.setText("Edit Service");
|
||||||
binding.tvServiceId.setText("ID: " + serviceId);
|
binding.tvServiceId.setText(DateTimeUtils.formatId(serviceId));
|
||||||
binding.btnDeleteService.setVisibility(View.VISIBLE);
|
binding.btnDeleteService.setVisibility(View.VISIBLE);
|
||||||
loadServiceData();
|
loadServiceData();
|
||||||
} else {
|
} else {
|
||||||
// Service is being added
|
viewModel.setServiceId(-1);
|
||||||
// Set default values for add a new service
|
|
||||||
isEditing = false;
|
|
||||||
binding.tvMode.setText("Add Service");
|
binding.tvMode.setText("Add Service");
|
||||||
binding.tvServiceId.setVisibility(View.GONE);
|
binding.tvServiceId.setVisibility(View.GONE);
|
||||||
binding.btnDeleteService.setVisibility(View.GONE);
|
binding.btnDeleteService.setVisibility(View.GONE);
|
||||||
@@ -164,12 +141,10 @@ public class ServiceDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches specific service details from the backend using the ID.
|
|
||||||
*/
|
|
||||||
private void loadServiceData() {
|
private void loadServiceData() {
|
||||||
viewModel.getServiceById(serviceId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadService().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
ServiceDTO s = resource.data;
|
ServiceDTO s = resource.data;
|
||||||
binding.etServiceName.setText(s.getServiceName());
|
binding.etServiceName.setText(s.getServiceName());
|
||||||
|
|||||||
@@ -1,27 +1,28 @@
|
|||||||
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
package com.example.petstoremobile.fragments.listfragments.detailfragments;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.*;
|
import android.view.*;
|
||||||
import android.widget.*;
|
import android.widget.*;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.lifecycle.ViewModelProvider;
|
import androidx.lifecycle.ViewModelProvider;
|
||||||
import androidx.navigation.fragment.NavHostFragment;
|
import androidx.navigation.fragment.NavHostFragment;
|
||||||
import com.example.petstoremobile.R;
|
import com.example.petstoremobile.R;
|
||||||
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
|
import com.example.petstoremobile.databinding.FragmentStaffDetailBinding;
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
import com.example.petstoremobile.viewmodels.EmployeeViewModel;
|
import com.example.petstoremobile.utils.DialogUtils;
|
||||||
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
|
import com.example.petstoremobile.utils.SpinnerUtils;
|
||||||
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
|
import com.example.petstoremobile.viewmodels.StaffDetailViewModel;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
public class StaffDetailFragment extends Fragment {
|
public class StaffDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentStaffDetailBinding binding;
|
private FragmentStaffDetailBinding binding;
|
||||||
private EmployeeViewModel employeeViewModel;
|
private StaffDetailViewModel viewModel;
|
||||||
private long employeeId = -1;
|
|
||||||
private boolean isEditing = false;
|
|
||||||
|
|
||||||
private final String[] ROLES = {"STAFF", "ADMIN"};
|
private final String[] ROLES = {"STAFF", "ADMIN"};
|
||||||
private final String[] STATUSES = {"Active", "Inactive"};
|
private final String[] STATUSES = {"Active", "Inactive"};
|
||||||
@@ -30,7 +31,7 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
binding = FragmentStaffDetailBinding.inflate(inflater, container, false);
|
binding = FragmentStaffDetailBinding.inflate(inflater, container, false);
|
||||||
employeeViewModel = new ViewModelProvider(this).get(EmployeeViewModel.class);
|
viewModel = new ViewModelProvider(this).get(StaffDetailViewModel.class);
|
||||||
|
|
||||||
setupSpinners();
|
setupSpinners();
|
||||||
handleArguments();
|
handleArguments();
|
||||||
@@ -38,21 +39,22 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
binding.btnStaffBack.setOnClickListener(v -> navigateBack());
|
binding.btnStaffBack.setOnClickListener(v -> navigateBack());
|
||||||
binding.btnSaveStaff.setOnClickListener(v -> save());
|
binding.btnSaveStaff.setOnClickListener(v -> save());
|
||||||
binding.btnDeleteStaff.setOnClickListener(v -> confirmDelete());
|
binding.btnDeleteStaff.setOnClickListener(v -> confirmDelete());
|
||||||
|
|
||||||
|
UIUtils.formatPhoneInput(binding.etStaffPhone);
|
||||||
|
|
||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupSpinners() {
|
private void setupSpinners() {
|
||||||
binding.spinnerStaffRole.setAdapter(new ArrayAdapter<>(requireContext(),
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffRole, ROLES);
|
||||||
android.R.layout.simple_spinner_item, ROLES));
|
SpinnerUtils.setupStringSpinner(requireContext(), binding.spinnerStaffStatus, STATUSES);
|
||||||
binding.spinnerStaffStatus.setAdapter(new ArrayAdapter<>(requireContext(),
|
|
||||||
android.R.layout.simple_spinner_item, STATUSES));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
Bundle a = getArguments();
|
Bundle a = getArguments();
|
||||||
if (a != null && a.getBoolean("isEditing", false)) {
|
if (a != null && a.getBoolean("isEditing", false)) {
|
||||||
isEditing = true;
|
long employeeId = a.getLong("employeeId", -1);
|
||||||
employeeId = a.getLong("employeeId", -1);
|
viewModel.setEmployeeId(employeeId, true);
|
||||||
|
|
||||||
binding.tvStaffMode.setText("Edit Staff Account");
|
binding.tvStaffMode.setText("Edit Staff Account");
|
||||||
binding.tvStaffId.setText("ID: " + employeeId);
|
binding.tvStaffId.setText("ID: " + employeeId);
|
||||||
@@ -64,52 +66,50 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
binding.etStaffPhone.setText(a.getString("phone", ""));
|
binding.etStaffPhone.setText(a.getString("phone", ""));
|
||||||
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
|
binding.btnDeleteStaff.setVisibility(View.VISIBLE);
|
||||||
|
|
||||||
// Pre-fill role
|
SpinnerUtils.setSelectionByValue(binding.spinnerStaffRole, a.getString("role", "STAFF"));
|
||||||
String role = a.getString("role", "STAFF");
|
binding.spinnerStaffStatus.setSelection(a.getBoolean("active", true) ? 0 : 1);
|
||||||
for (int i = 0; i < ROLES.length; i++) {
|
|
||||||
if (ROLES[i].equals(role)) {
|
|
||||||
binding.spinnerStaffRole.setSelection(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Pre-fill status
|
|
||||||
boolean active = a.getBoolean("active", true);
|
|
||||||
binding.spinnerStaffStatus.setSelection(active ? 0 : 1);
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
isEditing = false;
|
viewModel.setEmployeeId(-1, false);
|
||||||
employeeId = -1;
|
|
||||||
binding.tvStaffMode.setText("Add Staff Account");
|
binding.tvStaffMode.setText("Add Staff Account");
|
||||||
binding.btnDeleteStaff.setVisibility(View.GONE);
|
binding.btnDeleteStaff.setVisibility(View.GONE);
|
||||||
binding.tvStaffId.setVisibility(View.GONE);
|
binding.tvStaffId.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void save() {
|
private void save() {
|
||||||
String username = binding.etStaffUsername.getText() != null ? binding.etStaffUsername.getText().toString().trim() : "";
|
if (!InputValidator.isNotEmpty(binding.etStaffUsername, "Username")) return;
|
||||||
String password = binding.etStaffPassword.getText() != null ? binding.etStaffPassword.getText().toString().trim() : "";
|
|
||||||
String firstName = binding.etStaffFirstName.getText() != null ? binding.etStaffFirstName.getText().toString().trim() : "";
|
if (!viewModel.isEditing()) {
|
||||||
String lastName = binding.etStaffLastName.getText() != null ? binding.etStaffLastName.getText().toString().trim() : "";
|
if (!InputValidator.isNotEmpty(binding.etStaffPassword, "Password")) return;
|
||||||
String email = binding.etStaffEmail.getText() != null ? binding.etStaffEmail.getText().toString().trim() : "";
|
String pass = binding.etStaffPassword.getText().toString();
|
||||||
String phone = binding.etStaffPhone.getText() != null ? binding.etStaffPhone.getText().toString().trim() : "";
|
if (pass.length() < 6) {
|
||||||
|
binding.etStaffPassword.setError("At least 6 characters");
|
||||||
|
binding.etStaffPassword.requestFocus();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InputValidator.isNotEmpty(binding.etStaffFirstName, "First Name")) return;
|
||||||
|
if (!InputValidator.isNotEmpty(binding.etStaffLastName, "Last Name")) return;
|
||||||
|
if (!InputValidator.isValidEmail(binding.etStaffEmail)) return;
|
||||||
|
if (!InputValidator.isValidPhone(binding.etStaffPhone)) return;
|
||||||
|
|
||||||
|
String username = binding.etStaffUsername.getText().toString().trim();
|
||||||
|
String password = binding.etStaffPassword.getText().toString().trim();
|
||||||
|
String firstName = binding.etStaffFirstName.getText().toString().trim();
|
||||||
|
String lastName = binding.etStaffLastName.getText().toString().trim();
|
||||||
|
String email = binding.etStaffEmail.getText().toString().trim();
|
||||||
|
String phone = binding.etStaffPhone.getText().toString().trim();
|
||||||
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
|
String role = ROLES[binding.spinnerStaffRole.getSelectedItemPosition()];
|
||||||
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
|
boolean active = binding.spinnerStaffStatus.getSelectedItemPosition() == 0;
|
||||||
|
|
||||||
// Validation
|
|
||||||
if (username.isEmpty()) { binding.etStaffUsername.setError("Required"); return; }
|
|
||||||
if (!isEditing && password.isEmpty()) {
|
|
||||||
binding.etStaffPassword.setError("Required for new account"); return;
|
|
||||||
}
|
|
||||||
if (!isEditing && password.length() < 6) {
|
|
||||||
binding.etStaffPassword.setError("At least 6 characters"); return;
|
|
||||||
}
|
|
||||||
if (firstName.isEmpty()) { binding.etStaffFirstName.setError("Required"); return; }
|
|
||||||
if (lastName.isEmpty()) { binding.etStaffLastName.setError("Required"); return; }
|
|
||||||
if (email.isEmpty()) { binding.etStaffEmail.setError("Required"); return; }
|
|
||||||
if (phone.isEmpty()) { binding.etStaffPhone.setError("Required"); return; }
|
|
||||||
|
|
||||||
EmployeeDTO dto = new EmployeeDTO(
|
EmployeeDTO dto = new EmployeeDTO(
|
||||||
username,
|
username,
|
||||||
password.isEmpty() ? null : password,
|
password.isEmpty() ? null : password,
|
||||||
@@ -121,56 +121,32 @@ public class StaffDetailFragment extends Fragment {
|
|||||||
active
|
active
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isEditing && employeeId > 0) {
|
viewModel.saveEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
||||||
employeeViewModel.updateEmployee(employeeId, dto).observe(getViewLifecycleOwner(), resource -> {
|
if (resource != null) {
|
||||||
if (resource != null) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
switch (resource.status) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
case SUCCESS:
|
Toast.makeText(getContext(), viewModel.isEditing() ? "Updated successfully" : "Staff account created", Toast.LENGTH_SHORT).show();
|
||||||
Toast.makeText(getContext(), "Updated successfully", Toast.LENGTH_SHORT).show();
|
navigateBack();
|
||||||
navigateBack();
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
break;
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
||||||
case ERROR:
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
}
|
||||||
} else {
|
});
|
||||||
employeeViewModel.createEmployee(dto).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource != null) {
|
|
||||||
switch (resource.status) {
|
|
||||||
case SUCCESS:
|
|
||||||
Toast.makeText(getContext(), "Staff account created", Toast.LENGTH_SHORT).show();
|
|
||||||
navigateBack();
|
|
||||||
break;
|
|
||||||
case ERROR:
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_LONG).show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void confirmDelete() {
|
private void confirmDelete() {
|
||||||
new AlertDialog.Builder(requireContext())
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Staff Account", () ->
|
||||||
.setTitle("Delete Staff Account?")
|
viewModel.deleteEmployee().observe(getViewLifecycleOwner(), resource -> {
|
||||||
.setMessage("This will permanently delete this staff account.")
|
if (resource != null) {
|
||||||
.setPositiveButton("Yes", (d, w) ->
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
employeeViewModel.deleteEmployee(employeeId).observe(getViewLifecycleOwner(), resource -> {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
if (resource != null) {
|
navigateBack();
|
||||||
switch (resource.status) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
case SUCCESS:
|
Toast.makeText(getContext(), "Delete failed: " + resource.message,
|
||||||
navigateBack();
|
Toast.LENGTH_SHORT).show();
|
||||||
break;
|
}
|
||||||
case ERROR:
|
}
|
||||||
Toast.makeText(getContext(), "Delete failed: " + resource.message,
|
}));
|
||||||
Toast.LENGTH_SHORT).show();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
.setNegativeButton("No", null).show();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ import com.example.petstoremobile.utils.DialogUtils;
|
|||||||
import com.example.petstoremobile.utils.InputValidator;
|
import com.example.petstoremobile.utils.InputValidator;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.utils.UIUtils;
|
import com.example.petstoremobile.utils.UIUtils;
|
||||||
import com.example.petstoremobile.viewmodels.SupplierViewModel;
|
import com.example.petstoremobile.viewmodels.SupplierDetailViewModel;
|
||||||
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint;
|
import dagger.hilt.android.AndroidEntryPoint;
|
||||||
|
|
||||||
@@ -31,15 +31,12 @@ import dagger.hilt.android.AndroidEntryPoint;
|
|||||||
public class SupplierDetailFragment extends Fragment {
|
public class SupplierDetailFragment extends Fragment {
|
||||||
|
|
||||||
private FragmentSupplierDetailBinding binding;
|
private FragmentSupplierDetailBinding binding;
|
||||||
private long supId;
|
private SupplierDetailViewModel viewModel;
|
||||||
private boolean isEditing = false;
|
|
||||||
|
|
||||||
private SupplierViewModel viewModel;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(SupplierViewModel.class);
|
viewModel = new ViewModelProvider(this).get(SupplierDetailViewModel.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -53,42 +50,39 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
|
||||||
super.onViewCreated(view, savedInstanceState);
|
super.onViewCreated(view, savedInstanceState);
|
||||||
|
|
||||||
// Add phone number formatting (CA) and limit length to 14 characters
|
|
||||||
UIUtils.formatPhoneInput(binding.etSupPhone);
|
UIUtils.formatPhoneInput(binding.etSupPhone);
|
||||||
|
|
||||||
handleArguments();
|
handleArguments();
|
||||||
|
|
||||||
//set button click listeners
|
|
||||||
binding.btnBack.setOnClickListener(v -> navigateBack());
|
binding.btnBack.setOnClickListener(v -> navigateBack());
|
||||||
binding.btnSaveSupplier.setOnClickListener(v -> saveSupplier());
|
binding.btnSaveSupplier.setOnClickListener(v -> saveSupplier());
|
||||||
binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier());
|
binding.btnDeleteSupplier.setOnClickListener(v -> deleteSupplier());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles the saving of supplier data (adding or updating).
|
|
||||||
*/
|
|
||||||
private void saveSupplier() {
|
private void saveSupplier() {
|
||||||
// Validates all fields using InputValidator
|
|
||||||
if (!InputValidator.isNotEmpty(binding.etSupCompany, "Company Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etSupCompany, "Company Name")) return;
|
||||||
if (!InputValidator.isNotEmpty(binding.etSupContactFirstName, "First Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etSupContactFirstName, "First Name")) return;
|
||||||
if (!InputValidator.isNotEmpty(binding.etSupContactLastName, "Last Name")) return;
|
if (!InputValidator.isNotEmpty(binding.etSupContactLastName, "Last Name")) return;
|
||||||
if (!InputValidator.isValidEmail(binding.etSupEmail)) return;
|
if (!InputValidator.isValidEmail(binding.etSupEmail)) return;
|
||||||
if (!InputValidator.isValidPhone(binding.etSupPhone)) return;
|
if (!InputValidator.isValidPhone(binding.etSupPhone)) return;
|
||||||
|
|
||||||
//get all the values from the fields
|
|
||||||
String company = binding.etSupCompany.getText().toString().trim();
|
String company = binding.etSupCompany.getText().toString().trim();
|
||||||
String firstName = binding.etSupContactFirstName.getText().toString().trim();
|
String firstName = binding.etSupContactFirstName.getText().toString().trim();
|
||||||
String lastName = binding.etSupContactLastName.getText().toString().trim();
|
String lastName = binding.etSupContactLastName.getText().toString().trim();
|
||||||
String email = binding.etSupEmail.getText().toString().trim();
|
String email = binding.etSupEmail.getText().toString().trim();
|
||||||
String phone = binding.etSupPhone.getText().toString().trim();
|
String phone = binding.etSupPhone.getText().toString().trim();
|
||||||
|
|
||||||
//create a supplier object to send to the API
|
|
||||||
SupplierDTO supplierDTO = new SupplierDTO();
|
SupplierDTO supplierDTO = new SupplierDTO();
|
||||||
supplierDTO.setSupCompany(company);
|
supplierDTO.setSupCompany(company);
|
||||||
supplierDTO.setSupContactFirstName(firstName);
|
supplierDTO.setSupContactFirstName(firstName);
|
||||||
@@ -96,41 +90,31 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
supplierDTO.setSupEmail(email);
|
supplierDTO.setSupEmail(email);
|
||||||
supplierDTO.setSupPhone(phone);
|
supplierDTO.setSupPhone(phone);
|
||||||
|
|
||||||
//check if the supplier is being edited or added
|
viewModel.saveSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (isEditing) {
|
if (resource == null) return;
|
||||||
// Update existing supplier
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
supplierDTO.setSupId(supId);
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
viewModel.updateSupplier(supId, supplierDTO).observe(getViewLifecycleOwner(), resource -> {
|
if (viewModel.isEditing()) {
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) viewModel.getSupId());
|
||||||
ActivityLogger.logChange(requireContext(), "Supplier", "UPDATED", (int) supId);
|
|
||||||
Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Supplier updated successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
} else {
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
// Add new supplier
|
|
||||||
viewModel.createSupplier(supplierDTO).observe(getViewLifecycleOwner(), resource -> {
|
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
|
||||||
ActivityLogger.log(requireContext(), "Added new Supplier: " + company);
|
ActivityLogger.log(requireContext(), "Added new Supplier: " + company);
|
||||||
Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Supplier added successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
|
||||||
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
|
||||||
}
|
}
|
||||||
});
|
navigateBack();
|
||||||
}
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
Toast.makeText(getContext(), "Error: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Displays a confirmation dialog and handles the deletion of a supplier.
|
|
||||||
*/
|
|
||||||
private void deleteSupplier() {
|
private void deleteSupplier() {
|
||||||
DialogUtils.showDeleteConfirmDialog(requireContext(), "Supplier", () ->
|
DialogUtils.showDeleteConfirmDialog(requireContext(), "Supplier", () ->
|
||||||
viewModel.deleteSupplier(supId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deleteSupplier().observe(getViewLifecycleOwner(), resource -> {
|
||||||
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) supId);
|
ActivityLogger.logChange(requireContext(), "Supplier", "DELETED", (int) viewModel.getSupId());
|
||||||
Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show();
|
Toast.makeText(getContext(), "Supplier deleted successfully!", Toast.LENGTH_SHORT).show();
|
||||||
navigateBack();
|
navigateBack();
|
||||||
} else if (resource.status == Resource.Status.ERROR) {
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
@@ -139,31 +123,21 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Navigates back to the previous screen.
|
|
||||||
*/
|
|
||||||
private void navigateBack() {
|
private void navigateBack() {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles arguments passed to the fragment to determine if it's in edit or add mode.
|
|
||||||
*/
|
|
||||||
private void handleArguments() {
|
private void handleArguments() {
|
||||||
// Supplier is being edited if the bundle contains a supId
|
|
||||||
if (getArguments() != null && getArguments().containsKey("supId")) {
|
if (getArguments() != null && getArguments().containsKey("supId")) {
|
||||||
// Get supplier data from arguments and populate fields
|
long supId = getArguments().getLong("supId");
|
||||||
isEditing = true;
|
viewModel.setSupId(supId);
|
||||||
supId = getArguments().getLong("supId");
|
|
||||||
binding.tvMode.setText("Edit Supplier");
|
binding.tvMode.setText("Edit Supplier");
|
||||||
binding.tvSupId.setText("ID: " + supId);
|
binding.tvSupId.setText("ID: " + supId);
|
||||||
binding.tvSupId.setVisibility(View.VISIBLE);
|
binding.tvSupId.setVisibility(View.VISIBLE);
|
||||||
binding.btnDeleteSupplier.setVisibility(View.VISIBLE);
|
binding.btnDeleteSupplier.setVisibility(View.VISIBLE);
|
||||||
loadSupplierData();
|
loadSupplierData();
|
||||||
} else {
|
} else {
|
||||||
// Supplier is being added
|
viewModel.setSupId(-1);
|
||||||
// Set default values for add a new supplier
|
|
||||||
isEditing = false;
|
|
||||||
binding.tvMode.setText("Add Supplier");
|
binding.tvMode.setText("Add Supplier");
|
||||||
binding.tvSupId.setVisibility(View.GONE);
|
binding.tvSupId.setVisibility(View.GONE);
|
||||||
binding.btnDeleteSupplier.setVisibility(View.GONE);
|
binding.btnDeleteSupplier.setVisibility(View.GONE);
|
||||||
@@ -171,12 +145,10 @@ public class SupplierDetailFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches specific supplier details from the backend using the ID.
|
|
||||||
*/
|
|
||||||
private void loadSupplierData() {
|
private void loadSupplierData() {
|
||||||
viewModel.getSupplierById(supId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.loadSupplier().observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
SupplierDTO s = resource.data;
|
SupplierDTO s = resource.data;
|
||||||
binding.etSupCompany.setText(s.getSupCompany());
|
binding.etSupCompany.setText(s.getSupCompany());
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import com.example.petstoremobile.utils.FileUtils;
|
|||||||
import com.example.petstoremobile.utils.GlideUtils;
|
import com.example.petstoremobile.utils.GlideUtils;
|
||||||
import com.example.petstoremobile.utils.ImagePickerHelper;
|
import com.example.petstoremobile.utils.ImagePickerHelper;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
import com.example.petstoremobile.viewmodels.PetViewModel;
|
import com.example.petstoremobile.viewmodels.PetProfileViewModel;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@@ -46,17 +46,13 @@ public class PetProfileFragment extends Fragment {
|
|||||||
@Inject @Named("baseUrl") String baseUrl;
|
@Inject @Named("baseUrl") String baseUrl;
|
||||||
@Inject TokenManager tokenManager;
|
@Inject TokenManager tokenManager;
|
||||||
|
|
||||||
private PetViewModel viewModel;
|
private PetProfileViewModel viewModel;
|
||||||
private ImagePickerHelper imagePickerHelper;
|
private ImagePickerHelper imagePickerHelper;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes activity launchers for gallery, camera, and permissions.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
viewModel = new ViewModelProvider(this).get(PetViewModel.class);
|
viewModel = new ViewModelProvider(this).get(PetProfileViewModel.class);
|
||||||
|
|
||||||
imagePickerHelper = new ImagePickerHelper(this, "pet_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
imagePickerHelper = new ImagePickerHelper(this, "pet_photo.jpg", new ImagePickerHelper.ImagePickerListener() {
|
||||||
@Override
|
@Override
|
||||||
@@ -71,34 +67,27 @@ public class PetProfileFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Inflates the layout using view binding, initializes views, and sets up click listeners.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container,
|
||||||
Bundle savedInstanceState) {
|
Bundle savedInstanceState) {
|
||||||
binding = FragmentPetProfileBinding.inflate(inflater, container, false);
|
binding = FragmentPetProfileBinding.inflate(inflater, container, false);
|
||||||
|
|
||||||
// Set pet details to display
|
|
||||||
if (getArguments() != null) {
|
if (getArguments() != null) {
|
||||||
petId = getArguments().getLong("petId");
|
petId = getArguments().getLong("petId");
|
||||||
loadPetData();
|
loadPetData();
|
||||||
loadPetImage((int) petId);
|
loadPetImage((int) petId);
|
||||||
}
|
}
|
||||||
|
|
||||||
//set button click listeners
|
|
||||||
binding.btnBack.setOnClickListener(v -> {
|
binding.btnBack.setOnClickListener(v -> {
|
||||||
NavHostFragment.findNavController(this).popBackStack();
|
NavHostFragment.findNavController(this).popBackStack();
|
||||||
});
|
});
|
||||||
|
|
||||||
//Make the edit button go to the pet detail view
|
|
||||||
binding.btnEditPet.setOnClickListener(v -> {
|
binding.btnEditPet.setOnClickListener(v -> {
|
||||||
Bundle args = new Bundle();
|
Bundle args = new Bundle();
|
||||||
args.putLong("petId", petId);
|
args.putLong("petId", petId);
|
||||||
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail, args);
|
NavHostFragment.findNavController(this).navigate(R.id.nav_pet_detail, args);
|
||||||
});
|
});
|
||||||
|
|
||||||
//Make change photo button ask user to select a new photo
|
|
||||||
binding.btnChangePhoto.setOnClickListener(v -> {
|
binding.btnChangePhoto.setOnClickListener(v -> {
|
||||||
imagePickerHelper.showImagePickerDialog("Change Pet Photo", hasImage);
|
imagePickerHelper.showImagePickerDialog("Change Pet Photo", hasImage);
|
||||||
});
|
});
|
||||||
@@ -106,18 +95,22 @@ public class PetProfileFragment extends Fragment {
|
|||||||
return binding.getRoot();
|
return binding.getRoot();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setLoading(boolean loading) {
|
||||||
|
if (binding != null && binding.progressBar != null) {
|
||||||
|
binding.progressBar.setVisibility(loading ? View.VISIBLE : View.GONE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDestroyView() {
|
public void onDestroyView() {
|
||||||
super.onDestroyView();
|
super.onDestroyView();
|
||||||
binding = null;
|
binding = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches current pet data from the backend and updates the UI.
|
|
||||||
*/
|
|
||||||
private void loadPetData() {
|
private void loadPetData() {
|
||||||
viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.getPetById(petId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource == null) return;
|
if (resource == null) return;
|
||||||
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
PetDTO pet = resource.data;
|
PetDTO pet = resource.data;
|
||||||
binding.tvPetName.setText(pet.getPetName());
|
binding.tvPetName.setText(pet.getPetName());
|
||||||
@@ -133,7 +126,6 @@ public class PetProfileFragment extends Fragment {
|
|||||||
|
|
||||||
String status = pet.getPetStatus();
|
String status = pet.getPetStatus();
|
||||||
|
|
||||||
// Display owner name only if the pet is Adopted or Owned
|
|
||||||
if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status)) {
|
if ("Adopted".equalsIgnoreCase(status) || "Owned".equalsIgnoreCase(status)) {
|
||||||
binding.layoutPetOwner.setVisibility(View.VISIBLE);
|
binding.layoutPetOwner.setVisibility(View.VISIBLE);
|
||||||
if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) {
|
if (pet.getCustomerName() != null && !pet.getCustomerName().isEmpty()) {
|
||||||
@@ -145,7 +137,6 @@ public class PetProfileFragment extends Fragment {
|
|||||||
binding.layoutPetOwner.setVisibility(View.GONE);
|
binding.layoutPetOwner.setVisibility(View.GONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Display store name only if the pet is Adopted or Available
|
|
||||||
if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) {
|
if ("Available".equalsIgnoreCase(status) || "Adopted".equalsIgnoreCase(status)) {
|
||||||
binding.layoutPetStore.setVisibility(View.VISIBLE);
|
binding.layoutPetStore.setVisibility(View.VISIBLE);
|
||||||
if (pet.getStoreName() != null && !pet.getStoreName().isEmpty()) {
|
if (pet.getStoreName() != null && !pet.getStoreName().isEmpty()) {
|
||||||
@@ -162,9 +153,6 @@ public class PetProfileFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches and displays the pet\'s image from the server.
|
|
||||||
*/
|
|
||||||
private void loadPetImage(int petId) {
|
private void loadPetImage(int petId) {
|
||||||
String imageUrl = baseUrl + String.format(Locale.US, PetApi.PET_IMAGE_PATH, petId);
|
String imageUrl = baseUrl + String.format(Locale.US, PetApi.PET_IMAGE_PATH, petId);
|
||||||
String token = tokenManager.getToken();
|
String token = tokenManager.getToken();
|
||||||
@@ -182,27 +170,22 @@ public class PetProfileFragment extends Fragment {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads a selected or captured image a pet photo through the ViewModel.
|
|
||||||
*/
|
|
||||||
private void uploadPetImage(Uri uri) {
|
private void uploadPetImage(Uri uri) {
|
||||||
try {
|
try {
|
||||||
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
File file = FileUtils.getFileFromUri(requireContext(), uri);
|
||||||
if (file == null) return;
|
if (file == null) return;
|
||||||
|
|
||||||
// Create RequestBody for file upload
|
|
||||||
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
|
RequestBody requestFile = RequestBody.create(file, MediaType.parse(requireContext().getContentResolver().getType(uri)));
|
||||||
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
MultipartBody.Part body = MultipartBody.Part.createFormData("image", file.getName(), requestFile);
|
||||||
|
|
||||||
// Use ViewModel to upload image
|
|
||||||
viewModel.uploadPetImage(petId, body).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.uploadPetImage(petId, body).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource == null) return;
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
Toast.makeText(getContext(), "Pet photo updated successfully", Toast.LENGTH_SHORT).show();
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
loadPetImage((int) petId);
|
Toast.makeText(getContext(), "Pet photo updated successfully", Toast.LENGTH_SHORT).show();
|
||||||
} else {
|
loadPetImage((int) petId);
|
||||||
Toast.makeText(getContext(), "Upload failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
}
|
Toast.makeText(getContext(), "Upload failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -210,19 +193,16 @@ public class PetProfileFragment extends Fragment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a request to the ViewModel to remove the current pet photo.
|
|
||||||
*/
|
|
||||||
private void deletePetImage() {
|
private void deletePetImage() {
|
||||||
viewModel.deletePetImage(petId).observe(getViewLifecycleOwner(), resource -> {
|
viewModel.deletePetImage(petId).observe(getViewLifecycleOwner(), resource -> {
|
||||||
if (resource != null && resource.status != Resource.Status.LOADING) {
|
if (resource == null) return;
|
||||||
if (resource.status == Resource.Status.SUCCESS) {
|
setLoading(resource.status == Resource.Status.LOADING);
|
||||||
Toast.makeText(getContext(), "Pet photo removed", Toast.LENGTH_SHORT).show();
|
if (resource.status == Resource.Status.SUCCESS) {
|
||||||
hasImage = false;
|
Toast.makeText(getContext(), "Pet photo removed", Toast.LENGTH_SHORT).show();
|
||||||
binding.imgPet.setImageResource(R.drawable.placeholder);
|
hasImage = false;
|
||||||
} else {
|
binding.imgPet.setImageResource(R.drawable.placeholder);
|
||||||
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
}
|
Toast.makeText(getContext(), "Delete failed: " + resource.message, Toast.LENGTH_SHORT).show();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,13 +6,15 @@ public class Chat {
|
|||||||
private String lastMessage;
|
private String lastMessage;
|
||||||
private Long customerId;
|
private Long customerId;
|
||||||
private Long staffId;
|
private Long staffId;
|
||||||
|
private String status;
|
||||||
|
|
||||||
public Chat(String chatId, String customerName, String lastMessage, Long customerId, Long staffId) {
|
public Chat(String chatId, String customerName, String lastMessage, Long customerId, Long staffId, String status) {
|
||||||
this.chatId = chatId;
|
this.chatId = chatId;
|
||||||
this.customerName = customerName;
|
this.customerName = customerName;
|
||||||
this.lastMessage = lastMessage;
|
this.lastMessage = lastMessage;
|
||||||
this.customerId = customerId;
|
this.customerId = customerId;
|
||||||
this.staffId = staffId;
|
this.staffId = staffId;
|
||||||
|
this.status = status;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getChatId() {
|
public String getChatId() {
|
||||||
@@ -34,4 +36,8 @@ public class Chat {
|
|||||||
public Long getStaffId() {
|
public Long getStaffId() {
|
||||||
return staffId;
|
return staffId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getStatus() {
|
||||||
|
return status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -9,7 +9,8 @@ public class Message {
|
|||||||
private Boolean isRead;
|
private Boolean isRead;
|
||||||
private String attachmentUrl;
|
private String attachmentUrl;
|
||||||
private String attachmentName;
|
private String attachmentName;
|
||||||
private String attachmentType;
|
private String attachmentMimeType;
|
||||||
|
private Long attachmentSizeBytes;
|
||||||
|
|
||||||
public Message() {}
|
public Message() {}
|
||||||
|
|
||||||
@@ -43,6 +44,9 @@ public class Message {
|
|||||||
public String getAttachmentName() { return attachmentName; }
|
public String getAttachmentName() { return attachmentName; }
|
||||||
public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; }
|
public void setAttachmentName(String attachmentName) { this.attachmentName = attachmentName; }
|
||||||
|
|
||||||
public String getAttachmentType() { return attachmentType; }
|
public String getAttachmentMimeType() { return attachmentMimeType; }
|
||||||
public void setAttachmentType(String attachmentType) { this.attachmentType = attachmentType; }
|
public void setAttachmentMimeType(String attachmentMimeType) { this.attachmentMimeType = attachmentMimeType; }
|
||||||
|
|
||||||
|
public Long getAttachmentSizeBytes() { return attachmentSizeBytes; }
|
||||||
|
public void setAttachmentSizeBytes(Long attachmentSizeBytes) { this.attachmentSizeBytes = attachmentSizeBytes; }
|
||||||
}
|
}
|
||||||
@@ -17,6 +17,10 @@ import java.util.List;
|
|||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repository for handling chat-related data operations.
|
* Repository for handling chat-related data operations.
|
||||||
*/
|
*/
|
||||||
@@ -55,6 +59,20 @@ public class ChatRepository extends BaseRepository {
|
|||||||
return executeCall(messageApi.sendMessage(conversationId, request));
|
return executeCall(messageApi.sendMessage(conversationId, request));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message with an attachment.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, MultipartBody.Part content, MultipartBody.Part attachment) {
|
||||||
|
return executeCall(messageApi.sendMessageWithAttachment(conversationId, content, attachment));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Downloads an attachment for a specific message.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<ResponseBody>> downloadAttachment(Long messageId) {
|
||||||
|
return executeCall(messageApi.downloadAttachment(messageId));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches a paginated list of customers.
|
* Fetches a paginated list of customers.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -4,9 +4,12 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.example.petstoremobile.api.CustomerApi;
|
import com.example.petstoremobile.api.CustomerApi;
|
||||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@@ -33,4 +36,11 @@ public class CustomerRepository extends BaseRepository {
|
|||||||
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
||||||
return executeCall(customerApi.getCustomerById(id));
|
return executeCall(customerApi.getCustomerById(id));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of customer dropdowns from the API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getCustomerDropdowns() {
|
||||||
|
return executeCall(customerApi.getCustomerDropdowns());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,10 +4,13 @@ import androidx.lifecycle.LiveData;
|
|||||||
|
|
||||||
import com.example.petstoremobile.api.PetApi;
|
import com.example.petstoremobile.api.PetApi;
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@@ -26,8 +29,22 @@ public class PetRepository extends BaseRepository {
|
|||||||
/**
|
/**
|
||||||
* Retrieves a paginated list of pets from the API with optional filters.
|
* Retrieves a paginated list of pets from the API with optional filters.
|
||||||
*/
|
*/
|
||||||
public LiveData<Resource<PageResponse<PetDTO>>> getAllPets(int page, int size, String query, String status, String species, Long storeId, String sort) {
|
public LiveData<Resource<PageResponse<PetDTO>>> getAllPets(int page, int size, String query, String status, String species, Long storeId, Long customerId, String sort) {
|
||||||
return executeCall(petApi.getAllPets(page, size, query, status, species, storeId, sort));
|
return executeCall(petApi.getAllPets(page, size, query, status, species, storeId, customerId, sort));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of pets for a specific customer from the dropdowns API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getCustomerPets(Long customerId) {
|
||||||
|
return executeCall(petApi.getCustomerPets(customerId));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of pets available for adoption from the dropdowns API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getAdoptionPets() {
|
||||||
|
return executeCall(petApi.getAdoptionPets());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -3,10 +3,13 @@ package com.example.petstoremobile.repositories;
|
|||||||
import androidx.lifecycle.LiveData;
|
import androidx.lifecycle.LiveData;
|
||||||
|
|
||||||
import com.example.petstoremobile.api.StoreApi;
|
import com.example.petstoremobile.api.StoreApi;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
import com.example.petstoremobile.utils.Resource;
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Singleton;
|
import javax.inject.Singleton;
|
||||||
|
|
||||||
@@ -26,4 +29,18 @@ public class StoreRepository extends BaseRepository {
|
|||||||
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
||||||
return executeCall(storeApi.getAllStores(page, size));
|
return executeCall(storeApi.getAllStores(page, size));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of store dropdowns from the API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getStoreDropdowns() {
|
||||||
|
return executeCall(storeApi.getStoreDropdowns());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a list of employees for a specific store from the dropdowns API.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> getStoreEmployees(Long storeId) {
|
||||||
|
return executeCall(storeApi.getStoreEmployees(storeId));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,113 @@
|
|||||||
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility class for date and time operations.
|
||||||
|
*/
|
||||||
|
public class DateTimeUtils {
|
||||||
|
|
||||||
|
private static final String TAG = "DateTimeUtils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats status from backend format to UI format
|
||||||
|
* (backend is using all caps so we lower case them with this function)
|
||||||
|
*/
|
||||||
|
public static String formatStatusFromBackend(String status) {
|
||||||
|
if (status == null || status.isEmpty()) return status;
|
||||||
|
return status.substring(0, 1).toUpperCase() + status.substring(1).toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a date and time string to a Calendar object.
|
||||||
|
* format: date = "YYYY-MM-DD", time = "HH:MM"
|
||||||
|
*/
|
||||||
|
public static Calendar parseDateTimeToCalendar(String date, String time) throws Exception {
|
||||||
|
Calendar calendar = Calendar.getInstance();
|
||||||
|
String[] dateParts = date.split("-");
|
||||||
|
String[] timeParts = time.split(":");
|
||||||
|
|
||||||
|
calendar.set(
|
||||||
|
Integer.parseInt(dateParts[0]),
|
||||||
|
Integer.parseInt(dateParts[1]) - 1,
|
||||||
|
Integer.parseInt(dateParts[2]),
|
||||||
|
Integer.parseInt(timeParts[0]),
|
||||||
|
Integer.parseInt(timeParts[1]),
|
||||||
|
0
|
||||||
|
);
|
||||||
|
return calendar;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given date is in the past.
|
||||||
|
* format: date = "YYYY-MM-DD"
|
||||||
|
*/
|
||||||
|
public static boolean isDateInPast(String date) {
|
||||||
|
if (date == null || date.isEmpty()) return false;
|
||||||
|
try {
|
||||||
|
Calendar selected = Calendar.getInstance();
|
||||||
|
String[] dateParts = date.split("-");
|
||||||
|
selected.set(
|
||||||
|
Integer.parseInt(dateParts[0]),
|
||||||
|
Integer.parseInt(dateParts[1]) - 1,
|
||||||
|
Integer.parseInt(dateParts[2]),
|
||||||
|
0, 0, 0
|
||||||
|
);
|
||||||
|
return selected.before(Calendar.getInstance());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error parsing date: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if a given date and time are in the past.
|
||||||
|
* format: date = "YYYY-MM-DD", time = "HH:MM"
|
||||||
|
*/
|
||||||
|
public static boolean isDateTimeInPast(String date, String time) {
|
||||||
|
if (date == null || date.isEmpty() || time == null || time.isEmpty()) return false;
|
||||||
|
try {
|
||||||
|
Calendar selected = parseDateTimeToCalendar(date, time);
|
||||||
|
return selected.before(Calendar.getInstance());
|
||||||
|
} catch (Exception e) {
|
||||||
|
Log.e(TAG, "Error parsing date/time: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a time string and returns hour and minute values.
|
||||||
|
*/
|
||||||
|
public static int[] parseTimeString(String time) {
|
||||||
|
if (time == null || time.isEmpty()) return null;
|
||||||
|
if (time.length() > 5) time = time.substring(0, 5);
|
||||||
|
|
||||||
|
String[] parts = time.split(":");
|
||||||
|
if (parts.length != 2) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
int hour = Integer.parseInt(parts[0]);
|
||||||
|
int min = Integer.parseInt(parts[1]);
|
||||||
|
return new int[]{hour, min};
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an hour and minute into an HH:mm string.
|
||||||
|
*/
|
||||||
|
public static String formatTime(int hour, int minute) {
|
||||||
|
return String.format(Locale.getDefault(), "%02d:%02d", hour, minute);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Formats an ID for display (e.g., "ID: 123").
|
||||||
|
*/
|
||||||
|
public static String formatId(long id) {
|
||||||
|
return String.format(Locale.getDefault(), "ID: %d", id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
package com.example.petstoremobile.utils;
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.provider.OpenableColumns;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
@@ -9,8 +11,11 @@ import java.io.InputStream;
|
|||||||
public class FileUtils {
|
public class FileUtils {
|
||||||
public static File getFileFromUri(Context context, Uri uri) {
|
public static File getFileFromUri(Context context, Uri uri) {
|
||||||
try {
|
try {
|
||||||
|
String fileName = getFileName(context, uri);
|
||||||
|
if (fileName == null) fileName = "upload_" + System.currentTimeMillis();
|
||||||
|
|
||||||
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
InputStream inputStream = context.getContentResolver().openInputStream(uri);
|
||||||
File tempFile = new File(context.getCacheDir(), "upload_image_" + System.currentTimeMillis() + ".jpg");
|
File tempFile = new File(context.getCacheDir(), fileName);
|
||||||
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
FileOutputStream outputStream = new FileOutputStream(tempFile);
|
||||||
byte[] buffer = new byte[1024];
|
byte[] buffer = new byte[1024];
|
||||||
int length;
|
int length;
|
||||||
@@ -24,4 +29,22 @@ public class FileUtils {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static String getFileName(Context context, Uri uri) {
|
||||||
|
String result = null;
|
||||||
|
if (uri.getScheme().equals("content")) {
|
||||||
|
try (Cursor cursor = context.getContentResolver().query(uri, null, null, null, null)) {
|
||||||
|
if (cursor != null && cursor.moveToFirst()) {
|
||||||
|
int index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
|
||||||
|
if (index != -1) result = cursor.getString(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (result == null) {
|
||||||
|
result = uri.getPath();
|
||||||
|
int cut = result.lastIndexOf('/');
|
||||||
|
if (cut != -1) result = result.substring(cut + 1);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,8 +10,10 @@ import com.example.petstoremobile.adapters.BlackTextArrayAdapter;
|
|||||||
import com.example.petstoremobile.adapters.WhiteTextArrayAdapter;
|
import com.example.petstoremobile.adapters.WhiteTextArrayAdapter;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Consumer;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,6 +52,12 @@ public class SpinnerUtils {
|
|||||||
names.add(nameExtractor.apply(item));
|
names.add(nameExtractor.apply(item));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Only update adapter if contents changed to remove infinite loop when spinner is opened
|
||||||
|
if (isAdapterDataSame(spinner, names)) {
|
||||||
|
setSelectedId(spinner, data, defaultText, preselectedId, idExtractor);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
ArrayAdapter<String> adapter;
|
ArrayAdapter<String> adapter;
|
||||||
if (useWhiteText) {
|
if (useWhiteText) {
|
||||||
adapter = new WhiteTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, names);
|
adapter = new WhiteTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, names);
|
||||||
@@ -60,26 +68,41 @@ public class SpinnerUtils {
|
|||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
spinner.setAdapter(adapter);
|
spinner.setAdapter(adapter);
|
||||||
|
|
||||||
|
setSelectedId(spinner, data, defaultText, preselectedId, idExtractor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static <T> void setSelectedId(Spinner spinner, List<T> data, String defaultText, Long preselectedId, Function<T, Long> idExtractor) {
|
||||||
if (preselectedId != null && preselectedId != -1) {
|
if (preselectedId != null && preselectedId != -1) {
|
||||||
int offset = (defaultText != null) ? 1 : 0;
|
int offset = (defaultText != null) ? 1 : 0;
|
||||||
for (int i = 0; i < data.size(); i++) {
|
for (int i = 0; i < data.size(); i++) {
|
||||||
Long currentId = idExtractor.apply(data.get(i));
|
Long currentId = idExtractor.apply(data.get(i));
|
||||||
if (Objects.equals(currentId, preselectedId)) {
|
if (Objects.equals(currentId, preselectedId)) {
|
||||||
spinner.setSelection(i + offset);
|
if (spinner.getSelectedItemPosition() != i + offset) {
|
||||||
|
spinner.setSelection(i + offset);
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the adapter data is the same as the new data.
|
||||||
|
*/
|
||||||
|
private static boolean isAdapterDataSame(Spinner spinner, List<String> newNames) {
|
||||||
|
if (spinner.getAdapter() == null) return false;
|
||||||
|
if (spinner.getAdapter().getCount() != newNames.size()) return false;
|
||||||
|
for (int i = 0; i < newNames.size(); i++) {
|
||||||
|
if (!Objects.equals(spinner.getAdapter().getItem(i), newNames.get(i))) return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a simple string spinner for filtering with a callback.
|
* Sets up a simple string spinner for filtering with a callback.
|
||||||
*/
|
*/
|
||||||
public static void setupStringFilterSpinner(Context context, Spinner spinner, String[] items, Runnable onSelectionChanged) {
|
public static void setupStringFilterSpinner(Context context, Spinner spinner, String[] items, Runnable onSelectionChanged) {
|
||||||
WhiteTextArrayAdapter<String> adapter = new WhiteTextArrayAdapter<>(context,
|
updateStringSpinnerIfChanged(context, spinner, items, true);
|
||||||
android.R.layout.simple_spinner_item, items);
|
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
|
||||||
spinner.setAdapter(adapter);
|
|
||||||
setupFilterSpinner(spinner, onSelectionChanged);
|
setupFilterSpinner(spinner, onSelectionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -96,6 +119,13 @@ public class SpinnerUtils {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a listener that provides the selected index to a consumer.
|
||||||
|
*/
|
||||||
|
public static void setOnIndexSelectedListener(Spinner spinner, Consumer<Integer> callback) {
|
||||||
|
spinner.setOnItemSelectedListener(new OnIndexSelected(callback));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the selection of a spinner based on a string value.
|
* Sets the selection of a spinner based on a string value.
|
||||||
*/
|
*/
|
||||||
@@ -103,18 +133,65 @@ public class SpinnerUtils {
|
|||||||
if (value == null || spinner.getAdapter() == null) return;
|
if (value == null || spinner.getAdapter() == null) return;
|
||||||
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinner.getAdapter();
|
ArrayAdapter<String> adapter = (ArrayAdapter<String>) spinner.getAdapter();
|
||||||
int pos = adapter.getPosition(value);
|
int pos = adapter.getPosition(value);
|
||||||
if (pos >= 0) {
|
if (pos >= 0 && spinner.getSelectedItemPosition() != pos) {
|
||||||
spinner.setSelection(pos);
|
spinner.setSelection(pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the selection of a spinner based on a value within an array.
|
||||||
|
*/
|
||||||
|
public static <T> void setSelectionByValueArray(Spinner spinner, T[] array, T value) {
|
||||||
|
if (spinner == null || array == null || value == null) return;
|
||||||
|
for (int i = 0; i < array.length; i++) {
|
||||||
|
if (Objects.equals(array[i], value)) {
|
||||||
|
if (spinner.getSelectedItemPosition() != i) {
|
||||||
|
spinner.setSelection(i);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures a simple string array spinner.
|
* Configures a simple string array spinner.
|
||||||
*/
|
*/
|
||||||
public static void setupStringSpinner(Context context, Spinner spinner, String[] items) {
|
public static void setupStringSpinner(Context context, Spinner spinner, String[] items) {
|
||||||
BlackTextArrayAdapter<String> adapter = new BlackTextArrayAdapter<>(context,
|
updateStringSpinnerIfChanged(context, spinner, items, false);
|
||||||
android.R.layout.simple_spinner_item, items);
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates a string spinner only if the items have changed.
|
||||||
|
*/
|
||||||
|
public static void updateStringSpinnerIfChanged(Context context, Spinner spinner, String[] items, boolean useWhiteText) {
|
||||||
|
if (isAdapterDataSame(spinner, Arrays.asList(items))) return;
|
||||||
|
|
||||||
|
ArrayAdapter<String> adapter;
|
||||||
|
if (useWhiteText) {
|
||||||
|
adapter = new WhiteTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, items);
|
||||||
|
} else {
|
||||||
|
adapter = new BlackTextArrayAdapter<>(context, android.R.layout.simple_spinner_item, items);
|
||||||
|
}
|
||||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
spinner.setAdapter(adapter);
|
spinner.setAdapter(adapter);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper listener to get selected index from a spinner.
|
||||||
|
*/
|
||||||
|
public static class OnIndexSelected implements AdapterView.OnItemSelectedListener {
|
||||||
|
private final Consumer<Integer> callback;
|
||||||
|
|
||||||
|
public OnIndexSelected(Consumer<Integer> callback) {
|
||||||
|
this.callback = callback;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
|
||||||
|
callback.accept(position);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onNothingSelected(AdapterView<?> parent) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
package com.example.petstoremobile.utils;
|
package com.example.petstoremobile.utils;
|
||||||
|
|
||||||
|
import android.app.DatePickerDialog;
|
||||||
|
import android.content.Context;
|
||||||
import android.telephony.PhoneNumberFormattingTextWatcher;
|
import android.telephony.PhoneNumberFormattingTextWatcher;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.InputFilter;
|
import android.text.InputFilter;
|
||||||
@@ -8,10 +10,14 @@ import android.view.View;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import com.example.petstoremobile.fragments.ListFragment;
|
import com.example.petstoremobile.fragments.ListFragment;
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility class for shared UI component logic and formatting.
|
* Utility class for shared UI component logic and formatting.
|
||||||
*/
|
*/
|
||||||
@@ -25,24 +31,16 @@ public class UIUtils {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets up a toggle for a filter layout, including icon changes and field resets.
|
* Sets up a toggle for a filter layout, including icon changes.
|
||||||
*/
|
*/
|
||||||
public static void setupFilterToggle(ImageButton btnToggle, View layoutFilter, EditText etSearch, Spinner... spinners) {
|
public static void setupFilterToggle(ImageButton btnToggle, View layoutFilter, EditText etSearch, Spinner... spinners) {
|
||||||
btnToggle.setOnClickListener(v -> {
|
btnToggle.setOnClickListener(v -> {
|
||||||
boolean isVisible = layoutFilter.getVisibility() == View.VISIBLE;
|
boolean isVisible = layoutFilter.getVisibility() == View.VISIBLE;
|
||||||
layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
layoutFilter.setVisibility(isVisible ? View.GONE : View.VISIBLE);
|
||||||
|
|
||||||
// Use Android default icons or app-specific ones if available
|
|
||||||
btnToggle.setImageResource(isVisible ?
|
btnToggle.setImageResource(isVisible ?
|
||||||
android.R.drawable.ic_menu_search :
|
android.R.drawable.ic_menu_search :
|
||||||
android.R.drawable.ic_menu_close_clear_cancel);
|
android.R.drawable.ic_menu_close_clear_cancel);
|
||||||
|
|
||||||
if (isVisible) {
|
|
||||||
if (etSearch != null) etSearch.setText("");
|
|
||||||
for (Spinner spinner : spinners) {
|
|
||||||
if (spinner != null) spinner.setSelection(0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,4 +71,80 @@ public class UIUtils {
|
|||||||
@Override public void afterTextChanged(Editable s) {}
|
@Override public void afterTextChanged(Editable s) {}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the enabled state and alpha for multiple views, only if changed.
|
||||||
|
*/
|
||||||
|
public static void setViewsEnabled(boolean enabled, View... views) {
|
||||||
|
for (View v : views) {
|
||||||
|
if (v != null) {
|
||||||
|
if (v.isEnabled() != enabled) {
|
||||||
|
v.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
float targetAlpha = enabled ? 1.0f : 0.5f;
|
||||||
|
if (Math.abs(v.getAlpha() - targetAlpha) > 0.01f) {
|
||||||
|
v.setAlpha(targetAlpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets enabled state for a field and updates alpha for both the field and its label, only if changed.
|
||||||
|
*/
|
||||||
|
public static void setFieldEnabled(boolean enabled, View field, View label) {
|
||||||
|
if (field != null) {
|
||||||
|
if (field.isEnabled() != enabled) {
|
||||||
|
field.setEnabled(enabled);
|
||||||
|
}
|
||||||
|
float targetAlpha = enabled ? 1.0f : 0.5f;
|
||||||
|
if (Math.abs(field.getAlpha() - targetAlpha) > 0.01f) {
|
||||||
|
field.setAlpha(targetAlpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (label != null) {
|
||||||
|
float targetAlpha = enabled ? 1.0f : 0.5f;
|
||||||
|
if (Math.abs(label.getAlpha() - targetAlpha) > 0.01f) {
|
||||||
|
label.setAlpha(targetAlpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the alpha for multiple views, only if changed.
|
||||||
|
*/
|
||||||
|
public static void setViewsAlpha(float alpha, View... views) {
|
||||||
|
for (View v : views) {
|
||||||
|
if (v != null && Math.abs(v.getAlpha() - alpha) > 0.01f) {
|
||||||
|
v.setAlpha(alpha);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a DatePickerDialog and sets the result to an EditText.
|
||||||
|
*/
|
||||||
|
public static void showDatePicker(Context context, EditText editText, Runnable onDateSet) {
|
||||||
|
Calendar c = Calendar.getInstance();
|
||||||
|
DatePickerDialog d = new DatePickerDialog(context,
|
||||||
|
(dp, y, m, d1) -> {
|
||||||
|
String selectedDate = String.format(Locale.getDefault(), "%04d-%02d-%02d", y, m + 1, d1);
|
||||||
|
editText.setText(selectedDate);
|
||||||
|
if (onDateSet != null) onDateSet.run();
|
||||||
|
},
|
||||||
|
c.get(Calendar.YEAR), c.get(Calendar.MONTH),
|
||||||
|
c.get(Calendar.DAY_OF_MONTH));
|
||||||
|
d.getDatePicker().setMinDate(System.currentTimeMillis() - 1000);
|
||||||
|
d.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Displays a toast and returns false. Useful for validation chains.
|
||||||
|
*/
|
||||||
|
public static boolean showToast(Context context, String msg) {
|
||||||
|
if (context != null) {
|
||||||
|
Toast.makeText(context, msg, Toast.LENGTH_SHORT).show();
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
|
import com.example.petstoremobile.repositories.AdoptionRepository;
|
||||||
|
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||||
|
import com.example.petstoremobile.repositories.PetRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class AdoptionDetailViewModel extends ViewModel {
|
||||||
|
private final AdoptionRepository adoptionRepository;
|
||||||
|
private final PetRepository petRepository;
|
||||||
|
private final CustomerRepository customerRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private long adoptionId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> petList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> employeeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AdoptionDetailViewModel(AdoptionRepository adoptionRepository, PetRepository petRepository,
|
||||||
|
CustomerRepository customerRepository, StoreRepository storeRepository) {
|
||||||
|
this.adoptionRepository = adoptionRepository;
|
||||||
|
this.petRepository = petRepository;
|
||||||
|
this.customerRepository = customerRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAdoptionId(long id) {
|
||||||
|
this.adoptionId = id;
|
||||||
|
this.isEditing = id != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getAdoptionId() {
|
||||||
|
return adoptionId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<AdoptionDTO>> loadAdoption() {
|
||||||
|
return adoptionRepository.getAdoptionById(adoptionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadPets() {
|
||||||
|
return petRepository.getAdoptionPets();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
||||||
|
return customerRepository.getCustomerDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||||
|
return storeRepository.getStoreDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadEmployees(Long storeId) {
|
||||||
|
return storeRepository.getStoreEmployees(storeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<AdoptionDTO>> saveAdoption(AdoptionDTO dto) {
|
||||||
|
if (isEditing) {
|
||||||
|
return adoptionRepository.updateAdoption(adoptionId, dto);
|
||||||
|
} else {
|
||||||
|
return adoptionRepository.createAdoption(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteAdoption() {
|
||||||
|
return adoptionRepository.deleteAdoption(adoptionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPetList(List<DropdownDTO> list) { petList.setValue(list); }
|
||||||
|
public LiveData<List<DropdownDTO>> getPetList() { return petList; }
|
||||||
|
|
||||||
|
public void setCustomerList(List<DropdownDTO> list) { customerList.setValue(list); }
|
||||||
|
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
|
||||||
|
|
||||||
|
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||||
|
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||||
|
|
||||||
|
public void setEmployeeList(List<DropdownDTO> list) { employeeList.setValue(list); }
|
||||||
|
public LiveData<List<DropdownDTO>> getEmployeeList() { return employeeList; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.AdoptionDTO;
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
import com.example.petstoremobile.repositories.AdoptionRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class AdoptionListViewModel extends ViewModel {
|
||||||
|
private final AdoptionRepository adoptionRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<AdoptionDTO>> adoptions = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
private int currentPage = 0;
|
||||||
|
private boolean isLastPage = false;
|
||||||
|
private static final int PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AdoptionListViewModel(AdoptionRepository adoptionRepository, StoreRepository storeRepository) {
|
||||||
|
this.adoptionRepository = adoptionRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<AdoptionDTO>> getAdoptions() { return adoptions; }
|
||||||
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
public boolean isLastPage() { return isLastPage; }
|
||||||
|
|
||||||
|
public void loadAdoptions(boolean reset, String query, String status, Long storeId, String date, Long employeeId) {
|
||||||
|
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
currentPage = 0;
|
||||||
|
isLastPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("All Statuses".equals(status)) status = null;
|
||||||
|
|
||||||
|
isLoading.setValue(true);
|
||||||
|
adoptionRepository.getAllAdoptions(currentPage, PAGE_SIZE, query, status, storeId, date, employeeId).observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
List<AdoptionDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(adoptions.getValue());
|
||||||
|
currentList.addAll(resource.data.getContent());
|
||||||
|
adoptions.setValue(currentList);
|
||||||
|
isLastPage = resource.data.isLast();
|
||||||
|
if (!isLastPage) currentPage++;
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadStores() {
|
||||||
|
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
stores.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteAdoptions(List<String> ids) {
|
||||||
|
return adoptionRepository.bulkDeleteAdoptions(new BulkDeleteRequest(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.AdoptionDTO;
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.repositories.AdoptionRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class AdoptionViewModel extends ViewModel {
|
|
||||||
private final AdoptionRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public AdoptionViewModel(AdoptionRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all adoptions with filters.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<AdoptionDTO>>> getAllAdoptions(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
|
|
||||||
return repository.getAllAdoptions(page, size, query, status, storeId, date, employeeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single adoption by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<AdoptionDTO>> getAdoptionById(Long id) {
|
|
||||||
return repository.getAdoptionById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new adoption record.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<AdoptionDTO>> createAdoption(AdoptionDTO adoption) {
|
|
||||||
return repository.createAdoption(adoption);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing adoption record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<AdoptionDTO>> updateAdoption(Long id, AdoptionDTO adoption) {
|
|
||||||
return repository.updateAdoption(id, adoption);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an adoption record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteAdoption(Long id) {
|
|
||||||
return repository.deleteAdoption(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes multiple adoption records.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> bulkDeleteAdoptions(List<String> ids) {
|
|
||||||
return repository.bulkDeleteAdoptions(new BulkDeleteRequest(ids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
|
import com.example.petstoremobile.repositories.SaleRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TreeMap;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class AnalyticsViewModel extends ViewModel {
|
||||||
|
private final SaleRepository saleRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<AnalyticsData> analyticsData = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
private final MutableLiveData<String> errorMessage = new MutableLiveData<>();
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AnalyticsViewModel(SaleRepository saleRepository) {
|
||||||
|
this.saleRepository = saleRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<AnalyticsData> getAnalyticsData() { return analyticsData; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
public LiveData<String> getErrorMessage() { return errorMessage; }
|
||||||
|
|
||||||
|
public void loadAnalytics() {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
errorMessage.setValue(null);
|
||||||
|
saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
computeAnalytics(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
errorMessage.setValue(resource.message);
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void computeAnalytics(List<SaleDTO> sales) {
|
||||||
|
List<SaleDTO> regularSales = new ArrayList<>();
|
||||||
|
for (SaleDTO s : sales) {
|
||||||
|
if (!Boolean.TRUE.equals(s.getIsRefund()))
|
||||||
|
regularSales.add(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnalyticsData data = new AnalyticsData();
|
||||||
|
|
||||||
|
// Summary
|
||||||
|
BigDecimal totalRevenue = BigDecimal.ZERO;
|
||||||
|
int totalItems = 0;
|
||||||
|
for (SaleDTO s : regularSales) {
|
||||||
|
if (s.getTotalAmount() != null) totalRevenue = totalRevenue.add(s.getTotalAmount());
|
||||||
|
if (s.getItems() != null) {
|
||||||
|
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||||
|
if (item.getQuantity() != null) totalItems += Math.abs(item.getQuantity());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.totalRevenue = totalRevenue;
|
||||||
|
data.totalTransactions = regularSales.size();
|
||||||
|
data.avgTransaction = data.totalTransactions > 0
|
||||||
|
? totalRevenue.divide(BigDecimal.valueOf(data.totalTransactions), 2, RoundingMode.HALF_UP)
|
||||||
|
: BigDecimal.ZERO;
|
||||||
|
data.totalItems = totalItems;
|
||||||
|
|
||||||
|
// Product Maps
|
||||||
|
Map<String, BigDecimal> revenueByProduct = new LinkedHashMap<>();
|
||||||
|
Map<String, Integer> quantityByProduct = new LinkedHashMap<>();
|
||||||
|
Map<String, Integer> paymentCount = new LinkedHashMap<>();
|
||||||
|
Map<String, BigDecimal> employeeRevenue = new LinkedHashMap<>();
|
||||||
|
|
||||||
|
for (SaleDTO s : regularSales) {
|
||||||
|
// Payments
|
||||||
|
String method = s.getPaymentMethod() != null ? s.getPaymentMethod() : "Unknown";
|
||||||
|
paymentCount.merge(method, 1, Integer::sum);
|
||||||
|
|
||||||
|
// Employee
|
||||||
|
String emp = s.getEmployeeName() != null ? s.getEmployeeName() : "Unknown";
|
||||||
|
if (s.getTotalAmount() != null) employeeRevenue.merge(emp, s.getTotalAmount(), BigDecimal::add);
|
||||||
|
|
||||||
|
// Items
|
||||||
|
if (s.getItems() != null) {
|
||||||
|
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||||
|
String name = item.getProductName() != null ? item.getProductName() : "Unknown";
|
||||||
|
int qty = item.getQuantity() != null ? Math.abs(item.getQuantity()) : 0;
|
||||||
|
BigDecimal lineTotal = item.getUnitPrice() != null
|
||||||
|
? item.getUnitPrice().multiply(BigDecimal.valueOf(qty))
|
||||||
|
: BigDecimal.ZERO;
|
||||||
|
revenueByProduct.merge(name, lineTotal, BigDecimal::add);
|
||||||
|
quantityByProduct.merge(name, qty, Integer::sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort Top Revenue
|
||||||
|
data.topRevenueProducts = new ArrayList<>(revenueByProduct.entrySet());
|
||||||
|
data.topRevenueProducts.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||||
|
if (data.topRevenueProducts.size() > 5) data.topRevenueProducts = data.topRevenueProducts.subList(0, 5);
|
||||||
|
|
||||||
|
// Sort Top Quantity
|
||||||
|
data.topQuantityProducts = new ArrayList<>(quantityByProduct.entrySet());
|
||||||
|
data.topQuantityProducts.sort((a, b) -> b.getValue() - a.getValue());
|
||||||
|
if (data.topQuantityProducts.size() > 5) data.topQuantityProducts = data.topQuantityProducts.subList(0, 5);
|
||||||
|
|
||||||
|
// Payment Stats
|
||||||
|
data.paymentMethodStats = new ArrayList<>(paymentCount.entrySet());
|
||||||
|
|
||||||
|
// Employee Performance
|
||||||
|
data.employeePerformance = new ArrayList<>(employeeRevenue.entrySet());
|
||||||
|
data.employeePerformance.sort((a, b) -> b.getValue().compareTo(a.getValue()));
|
||||||
|
|
||||||
|
// Daily Revenue (last 7 days)
|
||||||
|
Map<String, BigDecimal> dailyMap = new TreeMap<>();
|
||||||
|
for (int i = 6; i >= 0; i--) {
|
||||||
|
Calendar day = Calendar.getInstance();
|
||||||
|
day.add(Calendar.DAY_OF_YEAR, -i);
|
||||||
|
String key = String.format("%04d-%02d-%02d",
|
||||||
|
day.get(Calendar.YEAR), day.get(Calendar.MONTH) + 1, day.get(Calendar.DAY_OF_MONTH));
|
||||||
|
dailyMap.put(key, BigDecimal.ZERO);
|
||||||
|
}
|
||||||
|
for (SaleDTO s : regularSales) {
|
||||||
|
if (s.getSaleDate() != null && s.getTotalAmount() != null) {
|
||||||
|
String date = s.getSaleDate().length() >= 10 ? s.getSaleDate().substring(0, 10) : s.getSaleDate();
|
||||||
|
if (dailyMap.containsKey(date)) dailyMap.merge(date, s.getTotalAmount(), BigDecimal::add);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
data.dailyRevenue = new ArrayList<>(dailyMap.entrySet());
|
||||||
|
|
||||||
|
analyticsData.setValue(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class AnalyticsData {
|
||||||
|
public BigDecimal totalRevenue;
|
||||||
|
public int totalTransactions;
|
||||||
|
public BigDecimal avgTransaction;
|
||||||
|
public int totalItems;
|
||||||
|
public List<Map.Entry<String, BigDecimal>> topRevenueProducts;
|
||||||
|
public List<Map.Entry<String, Integer>> topQuantityProducts;
|
||||||
|
public List<Map.Entry<String, Integer>> paymentMethodStats;
|
||||||
|
public List<Map.Entry<String, BigDecimal>> employeePerformance;
|
||||||
|
public List<Map.Entry<String, BigDecimal>> dailyRevenue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,402 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
|
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||||
|
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||||
|
import com.example.petstoremobile.repositories.PetRepository;
|
||||||
|
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.DateTimeUtils;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel for managing appointment details and form state.
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
public class AppointmentDetailViewModel extends ViewModel {
|
||||||
|
private final AppointmentRepository repository;
|
||||||
|
private final CustomerRepository customerRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
private final PetRepository petRepository;
|
||||||
|
private final ServiceRepository serviceRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> customers = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> stores = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<ServiceDTO>> services = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> customerPets = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> storeEmployees = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<ViewState> viewState = new MutableLiveData<>(new ViewState());
|
||||||
|
|
||||||
|
private long appointmentId = -1;
|
||||||
|
private Long currentCustomerId;
|
||||||
|
private Long currentStoreId;
|
||||||
|
private Long currentPetId;
|
||||||
|
private Long currentServiceId;
|
||||||
|
private Long currentStaffId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor for AppointmentDetailViewModel.
|
||||||
|
*/
|
||||||
|
@Inject
|
||||||
|
public AppointmentDetailViewModel(
|
||||||
|
AppointmentRepository repository,
|
||||||
|
CustomerRepository customerRepository,
|
||||||
|
StoreRepository storeRepository,
|
||||||
|
PetRepository petRepository,
|
||||||
|
ServiceRepository serviceRepository) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.customerRepository = customerRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
this.petRepository = petRepository;
|
||||||
|
this.serviceRepository = serviceRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial Data Loading
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads initial dropdown data for customers, stores, and services.
|
||||||
|
*/
|
||||||
|
public void loadInitialFormData() {
|
||||||
|
customerRepository.getCustomerDropdowns().observeForever(r -> {
|
||||||
|
if (r.status == Resource.Status.SUCCESS) customers.setValue(r.data);
|
||||||
|
});
|
||||||
|
storeRepository.getStoreDropdowns().observeForever(r -> {
|
||||||
|
if (r.status == Resource.Status.SUCCESS) stores.setValue(r.data);
|
||||||
|
});
|
||||||
|
serviceRepository.getAllServices(0, 200, null, "serviceName").observeForever(r -> {
|
||||||
|
if (r.status == Resource.Status.SUCCESS && r.data != null) services.setValue(r.data.getContent());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// LiveData Getters
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LiveData for the list of customers.
|
||||||
|
*/
|
||||||
|
public LiveData<List<DropdownDTO>> getCustomers() { return customers; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LiveData for the list of stores.
|
||||||
|
*/
|
||||||
|
public LiveData<List<DropdownDTO>> getStores() { return stores; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LiveData for the list of services.
|
||||||
|
*/
|
||||||
|
public LiveData<List<ServiceDTO>> getServices() { return services; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LiveData for the list of pets for the current customer.
|
||||||
|
*/
|
||||||
|
public LiveData<List<DropdownDTO>> getCustomerPets() { return customerPets; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LiveData for the list of employees for the current store.
|
||||||
|
*/
|
||||||
|
public LiveData<List<DropdownDTO>> getStoreEmployees() { return storeEmployees; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the LiveData for the view state.
|
||||||
|
*/
|
||||||
|
public LiveData<ViewState> getViewState() { return viewState; }
|
||||||
|
|
||||||
|
//State Getters
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the current appointment ID.
|
||||||
|
*/
|
||||||
|
public long getAppointmentId() { return appointmentId; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the current appointment ID and updates the mode.
|
||||||
|
*/
|
||||||
|
public void setAppointmentId(long id) {
|
||||||
|
this.appointmentId = id;
|
||||||
|
initMode(id != -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Selection Handlers for spinners
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles customer selection and loads their pets.
|
||||||
|
*/
|
||||||
|
public void onCustomerSelected(int position) {
|
||||||
|
List<DropdownDTO> list = customers.getValue();
|
||||||
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
|
currentCustomerId = list.get(position - 1).getId();
|
||||||
|
loadPetsForCustomer(currentCustomerId);
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.selectedCustomerId = currentCustomerId;
|
||||||
|
s.isPetEnabled = !s.isEditing;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
currentCustomerId = null;
|
||||||
|
customerPets.setValue(new ArrayList<>());
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.selectedCustomerId = null;
|
||||||
|
s.isPetEnabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles store selection and loads its employees.
|
||||||
|
*/
|
||||||
|
public void onStoreSelected(int position) {
|
||||||
|
List<DropdownDTO> list = stores.getValue();
|
||||||
|
if (position > 0 && list != null && position <= list.size()) {
|
||||||
|
currentStoreId = list.get(position - 1).getId();
|
||||||
|
loadEmployeesForStore(currentStoreId);
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.selectedStoreId = currentStoreId;
|
||||||
|
s.isStaffEnabled = !s.isPast;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
currentStoreId = null;
|
||||||
|
storeEmployees.setValue(new ArrayList<>());
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.selectedStoreId = null;
|
||||||
|
s.isStaffEnabled = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles service selection.
|
||||||
|
*/
|
||||||
|
public void onServiceSelected(int position) {
|
||||||
|
List<ServiceDTO> list = services.getValue();
|
||||||
|
currentServiceId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getServiceId() : null;
|
||||||
|
updateViewState(s -> s.selectedServiceId = currentServiceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles pet selection.
|
||||||
|
*/
|
||||||
|
public void onPetSelected(int position) {
|
||||||
|
List<DropdownDTO> list = customerPets.getValue();
|
||||||
|
currentPetId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||||
|
updateViewState(s -> s.selectedPetId = currentPetId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles staff selection.
|
||||||
|
*/
|
||||||
|
public void onStaffSelected(int position) {
|
||||||
|
List<DropdownDTO> list = storeEmployees.getValue();
|
||||||
|
currentStaffId = (position > 0 && list != null && position <= list.size()) ? list.get(position - 1).getId() : null;
|
||||||
|
updateViewState(s -> s.selectedStaffId = currentStaffId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of pets for a specific customer.
|
||||||
|
*/
|
||||||
|
private void loadPetsForCustomer(Long customerId) {
|
||||||
|
petRepository.getCustomerPets(customerId).observeForever(r -> {
|
||||||
|
if (r.status == Resource.Status.SUCCESS) customerPets.setValue(r.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the list of employees for a specific store.
|
||||||
|
*/
|
||||||
|
private void loadEmployeesForStore(Long storeId) {
|
||||||
|
storeRepository.getStoreEmployees(storeId).observeForever(r -> {
|
||||||
|
if (r.status == Resource.Status.SUCCESS) storeEmployees.setValue(r.data);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Appointment Detail CRUD
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches appointment details and populates internal state.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<AppointmentDTO>> loadAppointment() {
|
||||||
|
MutableLiveData<Resource<AppointmentDTO>> result = new MutableLiveData<>();
|
||||||
|
repository.getAppointmentById(appointmentId).observeForever(resource -> {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
AppointmentDTO a = resource.data;
|
||||||
|
currentCustomerId = a.getCustomerId();
|
||||||
|
currentStoreId = a.getStoreId();
|
||||||
|
currentPetId = a.getPetId();
|
||||||
|
currentServiceId = a.getServiceId();
|
||||||
|
currentStaffId = a.getEmployeeId();
|
||||||
|
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.selectedCustomerId = currentCustomerId;
|
||||||
|
s.selectedStoreId = currentStoreId;
|
||||||
|
s.selectedPetId = currentPetId;
|
||||||
|
s.selectedServiceId = currentServiceId;
|
||||||
|
s.selectedStaffId = currentStaffId;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (currentCustomerId != null) loadPetsForCustomer(currentCustomerId);
|
||||||
|
if (currentStoreId != null) loadEmployeesForStore(currentStoreId);
|
||||||
|
}
|
||||||
|
result.setValue(resource);
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the appointment by building the DTO from tracked state.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<AppointmentDTO>> saveAppointment(String date, String time, String status) {
|
||||||
|
AppointmentDTO dto = new AppointmentDTO(currentCustomerId, currentStoreId, currentServiceId, currentStaffId, date, time, status, currentPetId);
|
||||||
|
if (appointmentId != -1) {
|
||||||
|
return repository.updateAppointment(appointmentId, dto);
|
||||||
|
} else {
|
||||||
|
return repository.createAppointment(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the current appointment.
|
||||||
|
*/
|
||||||
|
public LiveData<Resource<Void>> deleteAppointment() {
|
||||||
|
return repository.deleteAppointment(appointmentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UI Logic
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the UI state when date, time, or status changes.
|
||||||
|
*/
|
||||||
|
public void onDateOrTimeChanged(String date, String time, String currentStatus) {
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.availableStatuses = calculateAvailableStatuses(s.isEditing, date, time, currentStatus);
|
||||||
|
boolean isPast = DateTimeUtils.isDateTimeInPast(date, time);
|
||||||
|
boolean isCancelled = "Cancelled".equalsIgnoreCase(currentStatus);
|
||||||
|
|
||||||
|
if (isCancelled) {
|
||||||
|
s.isPast = true;
|
||||||
|
setAllFieldsEnabled(s, false);
|
||||||
|
s.isStatusEnabled = false;
|
||||||
|
s.isSaveVisible = false;
|
||||||
|
} else if (isPast) {
|
||||||
|
s.isPast = true;
|
||||||
|
setAllFieldsEnabled(s, false);
|
||||||
|
s.isStatusEnabled = true;
|
||||||
|
} else {
|
||||||
|
s.isPast = false;
|
||||||
|
if (!s.isEditing) {
|
||||||
|
s.isCustomerEnabled = true;
|
||||||
|
s.isStoreEnabled = true;
|
||||||
|
s.isServiceEnabled = true;
|
||||||
|
s.isPetEnabled = currentCustomerId != null;
|
||||||
|
}
|
||||||
|
s.isDateEnabled = true;
|
||||||
|
s.isTimeEnabled = true;
|
||||||
|
s.isStatusEnabled = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates available appointment statuses based on the current context.
|
||||||
|
*/
|
||||||
|
private String[] calculateAvailableStatuses(boolean isEditing, String date, String currentTime, String currentStatus) {
|
||||||
|
if (!isEditing) return new String[]{"Booked"};
|
||||||
|
if (date == null || date.isEmpty()) return new String[]{};
|
||||||
|
if ("Cancelled".equalsIgnoreCase(currentStatus)) return new String[]{"Cancelled"};
|
||||||
|
if (DateTimeUtils.isDateTimeInPast(date, currentTime)) return new String[]{"Completed", "Missed"};
|
||||||
|
return new String[]{"Booked", "Cancelled"};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to enable or disable all fields.
|
||||||
|
*/
|
||||||
|
private void setAllFieldsEnabled(ViewState s, boolean enabled) {
|
||||||
|
s.isCustomerEnabled = enabled;
|
||||||
|
s.isStoreEnabled = enabled;
|
||||||
|
s.isPetEnabled = enabled;
|
||||||
|
s.isServiceEnabled = enabled;
|
||||||
|
s.isStaffEnabled = enabled;
|
||||||
|
s.isDateEnabled = enabled;
|
||||||
|
s.isTimeEnabled = enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the UI mode (Create vs Edit).
|
||||||
|
*/
|
||||||
|
public void initMode(boolean isEditing) {
|
||||||
|
updateViewState(s -> {
|
||||||
|
s.isEditing = isEditing;
|
||||||
|
s.isDeleteVisible = isEditing;
|
||||||
|
if (isEditing) {
|
||||||
|
s.isCustomerEnabled = false;
|
||||||
|
s.isStoreEnabled = false;
|
||||||
|
s.isPetEnabled = false;
|
||||||
|
s.isServiceEnabled = false;
|
||||||
|
} else {
|
||||||
|
s.isCustomerEnabled = true;
|
||||||
|
s.isStoreEnabled = true;
|
||||||
|
s.isServiceEnabled = true;
|
||||||
|
s.isPetEnabled = false; // until customer selected
|
||||||
|
s.isStaffEnabled = false; // until store selected
|
||||||
|
s.availableStatuses = new String[]{"Booked"};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates if a booking is in the future.
|
||||||
|
*/
|
||||||
|
public boolean isValidFutureBooking(String status, String date, String time) {
|
||||||
|
return !"BOOKED".equalsIgnoreCase(status) || !DateTimeUtils.isDateTimeInPast(date, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to update the view state and notify observers.
|
||||||
|
*/
|
||||||
|
private void updateViewState(Action<ViewState> action) {
|
||||||
|
ViewState current = viewState.getValue();
|
||||||
|
if (current != null) {
|
||||||
|
action.run(current);
|
||||||
|
viewState.setValue(current);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private interface Action<T> {
|
||||||
|
void run(T t);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A Class to show the states of Appointment Detail Fragment.
|
||||||
|
*/
|
||||||
|
public static class ViewState {
|
||||||
|
public boolean isPast = false;
|
||||||
|
public boolean isEditing = false;
|
||||||
|
public boolean isSaveVisible = true;
|
||||||
|
public boolean isDeleteVisible = false;
|
||||||
|
public boolean isCustomerEnabled = true;
|
||||||
|
public boolean isStoreEnabled = true;
|
||||||
|
public boolean isPetEnabled = false;
|
||||||
|
public boolean isServiceEnabled = true;
|
||||||
|
public boolean isStaffEnabled = false;
|
||||||
|
public boolean isDateEnabled = true;
|
||||||
|
public boolean isTimeEnabled = true;
|
||||||
|
public boolean isStatusEnabled = true;
|
||||||
|
public String[] availableStatuses = new String[]{};
|
||||||
|
|
||||||
|
// Selected IDs
|
||||||
|
public Long selectedCustomerId = null;
|
||||||
|
public Long selectedStoreId = null;
|
||||||
|
public Long selectedPetId = null;
|
||||||
|
public Long selectedServiceId = null;
|
||||||
|
public Long selectedStaffId = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.AppointmentDTO;
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
import com.example.petstoremobile.repositories.AppointmentRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class AppointmentListViewModel extends ViewModel {
|
||||||
|
private final AppointmentRepository appointmentRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<AppointmentDTO>> appointments = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public AppointmentListViewModel(AppointmentRepository appointmentRepository, StoreRepository storeRepository) {
|
||||||
|
this.appointmentRepository = appointmentRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<AppointmentDTO>> getAppointments() { return appointments; }
|
||||||
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadAppointments(String query, String status, Long storeId, String date, Long employeeId) {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
appointmentRepository.getAllAppointments(0, 500, query, status, storeId, date, employeeId).observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
appointments.setValue(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadStores() {
|
||||||
|
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
stores.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
|
||||||
|
return appointmentRepository.bulkDeleteAppointments(new BulkDeleteRequest(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.AppointmentDTO;
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.repositories.AppointmentRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class AppointmentViewModel extends ViewModel {
|
|
||||||
private final AppointmentRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public AppointmentViewModel(AppointmentRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all appointments with optional filters.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<AppointmentDTO>>> getAllAppointments(int page, int size, String query, String status, Long storeId, String date, Long employeeId) {
|
|
||||||
return repository.getAllAppointments(page, size, query, status, storeId, date, employeeId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single appointment by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<AppointmentDTO>> getAppointmentById(Long id) {
|
|
||||||
return repository.getAppointmentById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new appointment.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<AppointmentDTO>> createAppointment(AppointmentDTO appointment) {
|
|
||||||
return repository.createAppointment(appointment);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing appointment record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<AppointmentDTO>> updateAppointment(Long id, AppointmentDTO appointment) {
|
|
||||||
return repository.updateAppointment(id, appointment);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an appointment record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteAppointment(Long id) {
|
|
||||||
return repository.deleteAppointment(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes multiple appointment records.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> bulkDeleteAppointments(List<String> ids) {
|
|
||||||
return repository.bulkDeleteAppointments(new BulkDeleteRequest(ids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.ConversationDTO;
|
||||||
|
import com.example.petstoremobile.dtos.CustomerDTO;
|
||||||
|
import com.example.petstoremobile.dtos.MessageDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.SendMessageRequest;
|
||||||
|
import com.example.petstoremobile.models.Chat;
|
||||||
|
import com.example.petstoremobile.models.Message;
|
||||||
|
import com.example.petstoremobile.repositories.ChatRepository;
|
||||||
|
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
import okhttp3.RequestBody;
|
||||||
|
import okhttp3.ResponseBody;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class ChatListViewModel extends ViewModel {
|
||||||
|
private final ChatRepository chatRepository;
|
||||||
|
private final CustomerRepository customerRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<Chat>> activeChats = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<Chat>> closedChats = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<Message>> messageList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final Map<Long, String> customerNames = new HashMap<>();
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
private Long lastActiveConversationId = null;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ChatListViewModel(ChatRepository chatRepository, CustomerRepository customerRepository) {
|
||||||
|
this.chatRepository = chatRepository;
|
||||||
|
this.customerRepository = customerRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<Chat>> getActiveChats() { return activeChats; }
|
||||||
|
public LiveData<List<Chat>> getClosedChats() { return closedChats; }
|
||||||
|
public LiveData<List<Message>> getMessageList() { return messageList; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public Long getLastActiveConversationId() {
|
||||||
|
return lastActiveConversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLastActiveConversationId(Long conversationId) {
|
||||||
|
this.lastActiveConversationId = conversationId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadCustomers() {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
customerRepository.getAllCustomers(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
for (CustomerDTO c : resource.data.getContent()) {
|
||||||
|
customerNames.put(c.getCustomerId(), c.getFullName());
|
||||||
|
}
|
||||||
|
loadConversations();
|
||||||
|
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadConversations() {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
chatRepository.getAllConversations().observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
List<Chat> active = new ArrayList<>();
|
||||||
|
List<Chat> closed = new ArrayList<>();
|
||||||
|
for (ConversationDTO dto : resource.data) {
|
||||||
|
String name = customerNames.getOrDefault(dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
||||||
|
Chat chat = new Chat(String.valueOf(dto.getId()), name, dto.getLastMessage(), dto.getCustomerId(), dto.getStaffId(), dto.getStatus());
|
||||||
|
if ("CLOSED".equalsIgnoreCase(dto.getStatus())) {
|
||||||
|
closed.add(chat);
|
||||||
|
} else {
|
||||||
|
active.add(chat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activeChats.setValue(active);
|
||||||
|
closedChats.setValue(closed);
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadMessageHistory(Long conversationId) {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
chatRepository.getMessages(conversationId).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
List<Message> messages = new ArrayList<>();
|
||||||
|
for (MessageDTO dto : resource.data) {
|
||||||
|
messages.add(dtoToModel(dto));
|
||||||
|
}
|
||||||
|
messageList.setValue(messages);
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource != null && resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<MessageDTO>> sendMessage(Long conversationId, String text) {
|
||||||
|
return chatRepository.sendMessage(conversationId, new SendMessageRequest(text));
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<MessageDTO>> sendMessageWithAttachment(Long conversationId, MultipartBody.Part content, MultipartBody.Part attachment) {
|
||||||
|
return chatRepository.sendMessageWithAttachment(conversationId, content, attachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<ResponseBody>> downloadAttachment(Long messageId) {
|
||||||
|
return chatRepository.downloadAttachment(messageId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addMessageLocally(MessageDTO dto) {
|
||||||
|
List<Message> current = new ArrayList<>(messageList.getValue());
|
||||||
|
current.add(dtoToModel(dto));
|
||||||
|
messageList.setValue(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateConversationLocally(ConversationDTO dto) {
|
||||||
|
updateList(activeChats, dto);
|
||||||
|
updateList(closedChats, dto);
|
||||||
|
|
||||||
|
loadConversations();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateList(MutableLiveData<List<Chat>> liveData, ConversationDTO dto) {
|
||||||
|
List<Chat> current = new ArrayList<>(liveData.getValue());
|
||||||
|
String name = customerNames.getOrDefault(dto.getCustomerId(), "Customer #" + dto.getCustomerId());
|
||||||
|
boolean updated = false;
|
||||||
|
for (int i = 0; i < current.size(); i++) {
|
||||||
|
if (current.get(i).getChatId().equals(String.valueOf(dto.getId()))) {
|
||||||
|
current.set(i, new Chat(String.valueOf(dto.getId()), name, dto.getLastMessage(), dto.getCustomerId(), dto.getStaffId(), dto.getStatus()));
|
||||||
|
updated = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (updated) liveData.setValue(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Message dtoToModel(MessageDTO dto) {
|
||||||
|
Message m = new Message();
|
||||||
|
m.setId(dto.getId());
|
||||||
|
m.setConversationId(dto.getConversationId());
|
||||||
|
m.setSenderId(dto.getSenderId());
|
||||||
|
m.setContent(dto.getContent());
|
||||||
|
m.setTimestamp(dto.getTimestamp());
|
||||||
|
m.setIsRead(dto.getIsRead());
|
||||||
|
m.setAttachmentUrl(dto.getAttachmentUrl());
|
||||||
|
m.setAttachmentName(dto.getAttachmentName());
|
||||||
|
m.setAttachmentMimeType(dto.getAttachmentMimeType());
|
||||||
|
m.setAttachmentSizeBytes(dto.getAttachmentSizeBytes());
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCustomerName(Long customerId) {
|
||||||
|
return customerNames.getOrDefault(customerId, "Customer #" + customerId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.ConversationDTO;
|
|
||||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
|
||||||
import com.example.petstoremobile.dtos.MessageDTO;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.SendMessageRequest;
|
|
||||||
import com.example.petstoremobile.repositories.ChatRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
/**
|
|
||||||
* ViewModel for managing chat-related UI state and data operations.
|
|
||||||
*/
|
|
||||||
@HiltViewModel
|
|
||||||
public class ChatViewModel extends ViewModel {
|
|
||||||
private final ChatRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ChatViewModel(ChatRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves all chat conversations for the current user.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<List<ConversationDTO>>> getAllConversations() {
|
|
||||||
return repository.getAllConversations();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves the message history for a specific conversation.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<List<MessageDTO>>> getMessages(Long conversationId) {
|
|
||||||
return repository.getMessages(conversationId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sends a plain text message to a conversation.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<MessageDTO>> sendMessage(Long conversationId, SendMessageRequest request) {
|
|
||||||
return repository.sendMessage(conversationId, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of customers.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<CustomerDTO>>> getAllCustomers(int page, int size) {
|
|
||||||
return repository.getAllCustomers(page, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CustomerDTO;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.repositories.CustomerRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class CustomerViewModel extends ViewModel {
|
|
||||||
private final CustomerRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public CustomerViewModel(CustomerRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all customers.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<CustomerDTO>>> getAllCustomers(int page, int size) {
|
|
||||||
return repository.getAllCustomers(page, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single customer by their ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<CustomerDTO>> getCustomerById(Long id) {
|
|
||||||
return repository.getCustomerById(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.EmployeeDTO;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.repositories.EmployeeRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class EmployeeViewModel extends ViewModel {
|
|
||||||
private final EmployeeRepository employeeRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public EmployeeViewModel(EmployeeRepository employeeRepository) {
|
|
||||||
this.employeeRepository = employeeRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<EmployeeDTO>>> getAllEmployees(int page, int size) {
|
|
||||||
return employeeRepository.getAllEmployees(page, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<EmployeeDTO>> getEmployeeById(Long id) {
|
|
||||||
return employeeRepository.getEmployeeById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<EmployeeDTO>> createEmployee(EmployeeDTO dto) {
|
|
||||||
return employeeRepository.createEmployee(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<EmployeeDTO>> updateEmployee(Long id, EmployeeDTO dto) {
|
|
||||||
return employeeRepository.updateEmployee(id, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<Void>> deleteEmployee(Long id) {
|
|
||||||
return employeeRepository.deleteEmployee(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
|
import com.example.petstoremobile.repositories.InventoryRepository;
|
||||||
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class InventoryDetailViewModel extends ViewModel {
|
||||||
|
private final InventoryRepository inventoryRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
|
||||||
|
private long inventoryId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public InventoryDetailViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository, ProductRepository productRepository) {
|
||||||
|
this.inventoryRepository = inventoryRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setInventoryId(long id) {
|
||||||
|
this.inventoryId = id;
|
||||||
|
this.isEditing = id != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getInventoryId() { return inventoryId; }
|
||||||
|
public boolean isEditing() { return isEditing; }
|
||||||
|
|
||||||
|
public LiveData<Resource<InventoryDTO>> loadInventory() {
|
||||||
|
return inventoryRepository.getInventoryById(inventoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||||
|
return storeRepository.getStoreDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
|
||||||
|
return productRepository.getAllProducts(null, null, 0, 500, "prodName");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<InventoryDTO>> saveInventory(InventoryDTO dto) {
|
||||||
|
if (isEditing) {
|
||||||
|
return inventoryRepository.updateInventory(inventoryId, dto);
|
||||||
|
} else {
|
||||||
|
return inventoryRepository.createInventory(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteInventory() {
|
||||||
|
return inventoryRepository.deleteInventory(inventoryId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||||
|
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||||
|
|
||||||
|
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
||||||
|
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.InventoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
import com.example.petstoremobile.repositories.InventoryRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class InventoryListViewModel extends ViewModel {
|
||||||
|
private final InventoryRepository inventoryRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<InventoryDTO>> inventory = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
private int currentPage = 0;
|
||||||
|
private boolean isLastPage = false;
|
||||||
|
private static final int PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public InventoryListViewModel(InventoryRepository inventoryRepository, StoreRepository storeRepository) {
|
||||||
|
this.inventoryRepository = inventoryRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<InventoryDTO>> getInventory() { return inventory; }
|
||||||
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
public boolean isLastPage() { return isLastPage; }
|
||||||
|
|
||||||
|
public void loadInventory(boolean reset, String query, Long storeId) {
|
||||||
|
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
currentPage = 0;
|
||||||
|
isLastPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.setValue(true);
|
||||||
|
inventoryRepository.getAllInventory(query, storeId, currentPage, PAGE_SIZE, "product.prodName").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
List<InventoryDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(inventory.getValue());
|
||||||
|
currentList.addAll(resource.data.getContent());
|
||||||
|
inventory.setValue(currentList);
|
||||||
|
isLastPage = resource.data.isLast();
|
||||||
|
if (!isLastPage) currentPage++;
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadStores() {
|
||||||
|
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
stores.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteInventory(List<String> ids) {
|
||||||
|
return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
|
||||||
import com.example.petstoremobile.dtos.InventoryDTO;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
|
||||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
|
||||||
import com.example.petstoremobile.repositories.InventoryRepository;
|
|
||||||
import com.example.petstoremobile.repositories.StoreRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class InventoryViewModel extends ViewModel {
|
|
||||||
private final InventoryRepository inventoryRepository;
|
|
||||||
private final CategoryRepository categoryRepository;
|
|
||||||
private final StoreRepository storeRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public InventoryViewModel(InventoryRepository inventoryRepository, CategoryRepository categoryRepository, StoreRepository storeRepository) {
|
|
||||||
this.inventoryRepository = inventoryRepository;
|
|
||||||
this.categoryRepository = categoryRepository;
|
|
||||||
this.storeRepository = storeRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a paginated list of inventory items, with optional filtering and sorting.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<InventoryDTO>>> getAllInventory(String query, Long storeId, int page, int size, String sort) {
|
|
||||||
return inventoryRepository.getAllInventory(query, storeId, page, size, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single inventory item by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<InventoryDTO>> getInventoryById(Long id) {
|
|
||||||
return inventoryRepository.getInventoryById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new inventory record.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<InventoryDTO>> createInventory(InventoryDTO request) {
|
|
||||||
return inventoryRepository.createInventory(request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing inventory record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<InventoryDTO>> updateInventory(Long id, InventoryDTO request) {
|
|
||||||
return inventoryRepository.updateInventory(id, request);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes an inventory record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteInventory(Long id) {
|
|
||||||
return inventoryRepository.deleteInventory(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes multiple inventory records in a single request.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> bulkDeleteInventory(List<String> ids) {
|
|
||||||
return inventoryRepository.bulkDeleteInventory(new BulkDeleteRequest(ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a paginated list of categories.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<CategoryDTO>>> getAllCategories(int page, int size) {
|
|
||||||
return categoryRepository.getAllCategories(page, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a paginated list of stores.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
|
||||||
return storeRepository.getAllStores(page, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,103 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
|
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||||
|
import com.example.petstoremobile.repositories.PetRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class PetDetailViewModel extends ViewModel {
|
||||||
|
private final PetRepository petRepository;
|
||||||
|
private final CustomerRepository customerRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<PetDTO> petState = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
private long petId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PetDetailViewModel(PetRepository petRepository, CustomerRepository customerRepository, StoreRepository storeRepository) {
|
||||||
|
this.petRepository = petRepository;
|
||||||
|
this.customerRepository = customerRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPetId(long id) {
|
||||||
|
this.petId = id;
|
||||||
|
this.isEditing = id != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getPetId() {
|
||||||
|
return petId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PetDTO>> loadPet() {
|
||||||
|
return petRepository.getPetById(petId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
||||||
|
return customerRepository.getCustomerDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||||
|
return storeRepository.getStoreDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PetDTO>> savePet(PetDTO petDTO) {
|
||||||
|
if (isEditing) {
|
||||||
|
petDTO.setPetId(petId);
|
||||||
|
return petRepository.updatePet(petId, petDTO);
|
||||||
|
} else {
|
||||||
|
return petRepository.createPet(petDTO);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deletePet() {
|
||||||
|
return petRepository.deletePet(petId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCustomerList(List<DropdownDTO> list) {
|
||||||
|
customerList.setValue(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<DropdownDTO>> getCustomerList() {
|
||||||
|
return customerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreList(List<DropdownDTO> list) {
|
||||||
|
storeList.setValue(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<DropdownDTO>> getStoreList() {
|
||||||
|
return storeList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> getIsLoading() {
|
||||||
|
return isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoading(boolean loading) {
|
||||||
|
isLoading.setValue(loading);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
import com.example.petstoremobile.repositories.PetRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class PetListViewModel extends ViewModel {
|
||||||
|
private final PetRepository petRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<PetDTO>> pets = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PetListViewModel(PetRepository petRepository, StoreRepository storeRepository) {
|
||||||
|
this.petRepository = petRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<PetDTO>> getPets() { return pets; }
|
||||||
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadPets(String query, String status, String species, Long storeId) {
|
||||||
|
if ("All Statuses".equals(status)) status = null;
|
||||||
|
if ("All Species".equals(species)) species = null;
|
||||||
|
|
||||||
|
isLoading.setValue(true);
|
||||||
|
petRepository.getAllPets(0, 100, query, status, species, storeId, null, "petName").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
pets.setValue(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadStores() {
|
||||||
|
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
stores.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeletePets(List<String> ids) {
|
||||||
|
return petRepository.bulkDeletePets(new BulkDeleteRequest(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.PetDTO;
|
||||||
|
import com.example.petstoremobile.repositories.PetRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class PetProfileViewModel extends ViewModel {
|
||||||
|
private final PetRepository repository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PetProfileViewModel(PetRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PetDTO>> getPetById(Long id) {
|
||||||
|
return repository.getPetById(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> uploadPetImage(Long id, MultipartBody.Part image) {
|
||||||
|
return repository.uploadPetImage(id, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deletePetImage(Long id) {
|
||||||
|
return repository.deletePetImage(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.PetDTO;
|
|
||||||
import com.example.petstoremobile.repositories.PetRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
import okhttp3.MultipartBody;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class PetViewModel extends ViewModel {
|
|
||||||
private final PetRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public PetViewModel(PetRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of pets with filters.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<PetDTO>>> getAllPets(int page, int size, String query, String status, String species, Long storeId, String sort) {
|
|
||||||
return repository.getAllPets(page, size, query, status, species, storeId, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single pet by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PetDTO>> getPetById(Long id) {
|
|
||||||
return repository.getPetById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new pet record.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PetDTO>> createPet(PetDTO pet) {
|
|
||||||
return repository.createPet(pet);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing pet record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PetDTO>> updatePet(Long id, PetDTO pet) {
|
|
||||||
return repository.updatePet(id, pet);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a pet record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deletePet(Long id) {
|
|
||||||
return repository.deletePet(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes multiple pet records.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> bulkDeletePets(List<String> ids) {
|
|
||||||
return repository.bulkDeletePets(new BulkDeleteRequest(ids));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads an image for a specific pet.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> uploadPetImage(Long id, MultipartBody.Part image) {
|
|
||||||
return repository.uploadPetImage(id, image);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the image associated with a specific pet.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deletePetImage(Long id) {
|
|
||||||
return repository.deletePetImage(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
|
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||||
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
import okhttp3.MultipartBody;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class ProductDetailViewModel extends ViewModel {
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
private final CategoryRepository categoryRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<CategoryDTO>> categoryList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private long prodId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ProductDetailViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) {
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
this.categoryRepository = categoryRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProdId(long id) {
|
||||||
|
this.prodId = id;
|
||||||
|
this.isEditing = id != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getProdId() {
|
||||||
|
return prodId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PageResponse<CategoryDTO>>> loadCategories() {
|
||||||
|
return categoryRepository.getAllCategories(0, 100);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<ProductDTO>> loadProduct() {
|
||||||
|
return productRepository.getProductById(prodId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<ProductDTO>> saveProduct(ProductDTO dto) {
|
||||||
|
if (isEditing) {
|
||||||
|
return productRepository.updateProduct(prodId, dto);
|
||||||
|
} else {
|
||||||
|
return productRepository.createProduct(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteProduct() {
|
||||||
|
return productRepository.deleteProduct(prodId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> uploadProductImage(MultipartBody.Part image) {
|
||||||
|
return productRepository.uploadProductImage(prodId, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteProductImage() {
|
||||||
|
return productRepository.deleteProductImage(prodId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategoryList(List<CategoryDTO> list) {
|
||||||
|
categoryList.setValue(list);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<CategoryDTO>> getCategoryList() {
|
||||||
|
return categoryList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.CategoryDTO;
|
||||||
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
|
import com.example.petstoremobile.repositories.CategoryRepository;
|
||||||
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class ProductListViewModel extends ViewModel {
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
private final CategoryRepository categoryRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<ProductDTO>> products = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<CategoryDTO>> categories = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ProductListViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) {
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
this.categoryRepository = categoryRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<ProductDTO>> getProducts() { return products; }
|
||||||
|
public LiveData<List<CategoryDTO>> getCategories() { return categories; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadProducts(String query, Long categoryId) {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
productRepository.getAllProducts(query, categoryId, 0, 100, "prodName").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
products.setValue(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadCategories() {
|
||||||
|
categoryRepository.getAllCategories(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
categories.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
|
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||||
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
|
import com.example.petstoremobile.repositories.ProductSupplierRepository;
|
||||||
|
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class ProductSupplierDetailViewModel extends ViewModel {
|
||||||
|
private final ProductSupplierRepository psRepository;
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
private final SupplierRepository supplierRepository;
|
||||||
|
|
||||||
|
private boolean isEditing = false;
|
||||||
|
private long editProductId = -1;
|
||||||
|
private long editSupplierId = -1;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<SupplierDTO>> supplierList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ProductSupplierDetailViewModel(ProductSupplierRepository psRepository, ProductRepository productRepository, SupplierRepository supplierRepository) {
|
||||||
|
this.psRepository = psRepository;
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
this.supplierRepository = supplierRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEditMode(long productId, long supplierId) {
|
||||||
|
this.isEditing = true;
|
||||||
|
this.editProductId = productId;
|
||||||
|
this.editSupplierId = supplierId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() { return isEditing; }
|
||||||
|
public long getEditProductId() { return editProductId; }
|
||||||
|
public long getEditSupplierId() { return editSupplierId; }
|
||||||
|
|
||||||
|
public LiveData<Resource<PageResponse<ProductDTO>>> loadProducts() {
|
||||||
|
return productRepository.getAllProducts(null, null, 0, 200, "prodName");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PageResponse<SupplierDTO>>> loadSuppliers() {
|
||||||
|
return supplierRepository.getAllSuppliers(0, 200, null, "supCompany");
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<ProductSupplierDTO>> saveProductSupplier(ProductSupplierDTO dto) {
|
||||||
|
if (isEditing) {
|
||||||
|
return psRepository.updateProductSupplier(editProductId, editSupplierId, dto);
|
||||||
|
} else {
|
||||||
|
return psRepository.createProductSupplier(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteProductSupplier() {
|
||||||
|
return psRepository.deleteProductSupplier(editProductId, editSupplierId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
||||||
|
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
||||||
|
|
||||||
|
public void setSupplierList(List<SupplierDTO> list) { supplierList.setValue(list); }
|
||||||
|
public LiveData<List<SupplierDTO>> getSupplierList() { return supplierList; }
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
|
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
||||||
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
|
import com.example.petstoremobile.repositories.ProductSupplierRepository;
|
||||||
|
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class ProductSupplierListViewModel extends ViewModel {
|
||||||
|
private final ProductSupplierRepository psRepository;
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
private final SupplierRepository supplierRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<ProductSupplierDTO>> productSuppliers = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<ProductDTO>> products = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<SupplierDTO>> suppliers = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ProductSupplierListViewModel(ProductSupplierRepository psRepository, ProductRepository productRepository, SupplierRepository supplierRepository) {
|
||||||
|
this.psRepository = psRepository;
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
this.supplierRepository = supplierRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<ProductSupplierDTO>> getProductSuppliers() { return productSuppliers; }
|
||||||
|
public LiveData<List<ProductDTO>> getProducts() { return products; }
|
||||||
|
public LiveData<List<SupplierDTO>> getSuppliers() { return suppliers; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadProductSuppliers(String query, Long productId, Long supplierId) {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
psRepository.getAllProductSuppliers(0, 100, query, productId, supplierId, "productName").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
productSuppliers.setValue(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadFilterData() {
|
||||||
|
productRepository.getAllProducts(null, null, 0, 100, "prodName").observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
products.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
supplierRepository.getAllSuppliers(0, 100, null, "supCompany").observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
suppliers.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(List<String> ids) {
|
||||||
|
return psRepository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.ProductSupplierDTO;
|
|
||||||
import com.example.petstoremobile.repositories.ProductSupplierRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class ProductSupplierViewModel extends ViewModel {
|
|
||||||
private final ProductSupplierRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ProductSupplierViewModel(ProductSupplierRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all product-supplier relationships.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<ProductSupplierDTO>>> getAllProductSuppliers(int page, int size, String query, Long productId, Long supplierId, String sort) {
|
|
||||||
return repository.getAllProductSuppliers(page, size, query, productId, supplierId, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new product-supplier relationship.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ProductSupplierDTO>> createProductSupplier(ProductSupplierDTO dto) {
|
|
||||||
return repository.createProductSupplier(dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing product-supplier relationship.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ProductSupplierDTO>> updateProductSupplier(Long productId, Long supplierId, ProductSupplierDTO dto) {
|
|
||||||
return repository.updateProductSupplier(productId, supplierId, dto);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a product-supplier relationship by product and supplier IDs.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteProductSupplier(Long productId, Long supplierId) {
|
|
||||||
return repository.deleteProductSupplier(productId, supplierId);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<Void>> bulkDeleteProductSuppliers(List<String> ids) {
|
|
||||||
return repository.bulkDeleteProductSuppliers(new BulkDeleteRequest(ids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,84 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.CategoryDTO;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.ProductDTO;
|
|
||||||
import com.example.petstoremobile.repositories.CategoryRepository;
|
|
||||||
import com.example.petstoremobile.repositories.ProductRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
import okhttp3.MultipartBody;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class ProductViewModel extends ViewModel {
|
|
||||||
private final ProductRepository productRepository;
|
|
||||||
private final CategoryRepository categoryRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ProductViewModel(ProductRepository productRepository, CategoryRepository categoryRepository) {
|
|
||||||
this.productRepository = productRepository;
|
|
||||||
this.categoryRepository = categoryRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a paginated list of products, optionally filtered by a query string, category and sorted.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<ProductDTO>>> getAllProducts(String query, Long categoryId, int page, int size, String sort) {
|
|
||||||
return productRepository.getAllProducts(query, categoryId, page, size, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single product by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ProductDTO>> getProductById(Long id) {
|
|
||||||
return productRepository.getProductById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new product.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ProductDTO>> createProduct(ProductDTO product) {
|
|
||||||
return productRepository.createProduct(product);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing product by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ProductDTO>> updateProduct(Long id, ProductDTO product) {
|
|
||||||
return productRepository.updateProduct(id, product);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a product by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteProduct(Long id) {
|
|
||||||
return productRepository.deleteProduct(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uploads an image for a specific product.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> uploadProductImage(Long id, MultipartBody.Part image) {
|
|
||||||
return productRepository.uploadProductImage(id, image);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the image associated with a specific product.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteProductImage(Long id) {
|
|
||||||
return productRepository.deleteProductImage(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a paginated list of all product categories.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<CategoryDTO>>> getAllCategories(int page, int size) {
|
|
||||||
return categoryRepository.getAllCategories(page, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||||
|
import com.example.petstoremobile.repositories.PurchaseOrderRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class PurchaseOrderDetailViewModel extends ViewModel {
|
||||||
|
private final PurchaseOrderRepository repository;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PurchaseOrderDetailViewModel(PurchaseOrderRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PurchaseOrderDTO>> loadPurchaseOrder(long id) {
|
||||||
|
return repository.getPurchaseOrderById(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
import com.example.petstoremobile.repositories.PurchaseOrderRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class PurchaseOrderListViewModel extends ViewModel {
|
||||||
|
private final PurchaseOrderRepository purchaseOrderRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<PurchaseOrderDTO>> purchaseOrders = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PurchaseOrderListViewModel(PurchaseOrderRepository purchaseOrderRepository, StoreRepository storeRepository) {
|
||||||
|
this.purchaseOrderRepository = purchaseOrderRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<PurchaseOrderDTO>> getPurchaseOrders() { return purchaseOrders; }
|
||||||
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadPurchaseOrders(String query, Long storeId) {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
purchaseOrderRepository.getAllPurchaseOrders(0, 100, query, storeId, "purchaseOrderId,desc").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
purchaseOrders.setValue(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadStores() {
|
||||||
|
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
stores.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.PurchaseOrderDTO;
|
|
||||||
import com.example.petstoremobile.repositories.PurchaseOrderRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class PurchaseOrderViewModel extends ViewModel {
|
|
||||||
private final PurchaseOrderRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public PurchaseOrderViewModel(PurchaseOrderRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all purchase orders.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<PurchaseOrderDTO>>> getAllPurchaseOrders(int page, int size, String query, Long storeId, String sort) {
|
|
||||||
return repository.getAllPurchaseOrders(page, size, query, storeId, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single purchase order by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PurchaseOrderDTO>> getPurchaseOrderById(Long id) {
|
|
||||||
return repository.getPurchaseOrderById(id);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
|
import com.example.petstoremobile.repositories.SaleRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class RefundViewModel extends ViewModel {
|
||||||
|
private final SaleRepository saleRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<SaleDTO>> allSales = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<SaleDTO> currentSale = new MutableLiveData<>();
|
||||||
|
private final MutableLiveData<List<RefundItem>> availableItems = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<RefundItem>> refundCart = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RefundViewModel(SaleRepository saleRepository) {
|
||||||
|
this.saleRepository = saleRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<PageResponse<SaleDTO>>> loadAllSales() {
|
||||||
|
return saleRepository.getAllSales(0, 1000, null, null, null, "saleDate,desc");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAllSales(List<SaleDTO> sales) {
|
||||||
|
allSales.setValue(sales);
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<SaleDTO> getAllSalesList() {
|
||||||
|
return allSales.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCurrentSale(SaleDTO sale) {
|
||||||
|
currentSale.setValue(sale);
|
||||||
|
buildRefundableItems();
|
||||||
|
}
|
||||||
|
|
||||||
|
public SaleDTO getCurrentSale() {
|
||||||
|
return currentSale.getValue();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<RefundItem>> getAvailableItems() {
|
||||||
|
return availableItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<RefundItem>> getRefundCart() {
|
||||||
|
return refundCart;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildRefundableItems() {
|
||||||
|
SaleDTO sale = currentSale.getValue();
|
||||||
|
List<SaleDTO> sales = allSales.getValue();
|
||||||
|
if (sale == null || sales == null || sale.getItems() == null) {
|
||||||
|
availableItems.setValue(new ArrayList<>());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<Long, Integer> alreadyRefunded = new HashMap<>();
|
||||||
|
for (SaleDTO s : sales) {
|
||||||
|
if (Boolean.TRUE.equals(s.getIsRefund())
|
||||||
|
&& sale.getSaleId().equals(s.getOriginalSaleId())
|
||||||
|
&& s.getItems() != null) {
|
||||||
|
for (SaleDTO.SaleItemDTO item : s.getItems()) {
|
||||||
|
if (item.getProdId() != null && item.getQuantity() != null) {
|
||||||
|
alreadyRefunded.merge(item.getProdId(),
|
||||||
|
Math.abs(item.getQuantity()), Integer::sum);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RefundItem> items = new ArrayList<>();
|
||||||
|
for (SaleDTO.SaleItemDTO item : sale.getItems()) {
|
||||||
|
if (item.getProdId() == null || item.getQuantity() == null) continue;
|
||||||
|
int refunded = alreadyRefunded.getOrDefault(item.getProdId(), 0);
|
||||||
|
int remaining = item.getQuantity() - refunded;
|
||||||
|
if (remaining > 0) {
|
||||||
|
items.add(new RefundItem(
|
||||||
|
item.getProdId(),
|
||||||
|
item.getProductName() != null ? item.getProductName() : "Unknown",
|
||||||
|
remaining,
|
||||||
|
item.getUnitPrice()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
availableItems.setValue(items);
|
||||||
|
refundCart.setValue(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addToCart(RefundItem item, int qty) {
|
||||||
|
List<RefundItem> cart = new ArrayList<>(refundCart.getValue());
|
||||||
|
boolean merged = false;
|
||||||
|
for (int i = 0; i < cart.size(); i++) {
|
||||||
|
if (cart.get(i).prodId == item.prodId) {
|
||||||
|
RefundItem existing = cart.get(i);
|
||||||
|
cart.set(i, new RefundItem(existing.prodId, existing.productName, existing.quantity + qty, existing.unitPrice));
|
||||||
|
merged = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!merged) {
|
||||||
|
cart.add(new RefundItem(item.prodId, item.productName, qty, item.unitPrice));
|
||||||
|
}
|
||||||
|
refundCart.setValue(cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void removeFromCart(RefundItem item) {
|
||||||
|
List<RefundItem> cart = new ArrayList<>(refundCart.getValue());
|
||||||
|
cart.remove(item);
|
||||||
|
refundCart.setValue(cart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<SaleDTO>> submitRefund(String paymentMethod) {
|
||||||
|
SaleDTO sale = currentSale.getValue();
|
||||||
|
List<RefundItem> cart = refundCart.getValue();
|
||||||
|
if (sale == null || cart == null || cart.isEmpty()) return null;
|
||||||
|
|
||||||
|
List<SaleDTO.SaleItemDTO> items = new ArrayList<>();
|
||||||
|
for (RefundItem item : cart) {
|
||||||
|
items.add(new SaleDTO.SaleItemDTO(item.prodId, -item.quantity));
|
||||||
|
}
|
||||||
|
|
||||||
|
SaleDTO dto = new SaleDTO(
|
||||||
|
sale.getStoreId(),
|
||||||
|
paymentMethod,
|
||||||
|
items,
|
||||||
|
true,
|
||||||
|
sale.getSaleId(),
|
||||||
|
null
|
||||||
|
);
|
||||||
|
|
||||||
|
return saleRepository.createSale(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RefundItem {
|
||||||
|
public long prodId;
|
||||||
|
public String productName;
|
||||||
|
public int quantity;
|
||||||
|
public BigDecimal unitPrice;
|
||||||
|
|
||||||
|
public RefundItem(long prodId, String productName, int quantity, BigDecimal unitPrice) {
|
||||||
|
this.prodId = prodId;
|
||||||
|
this.productName = productName;
|
||||||
|
this.quantity = quantity;
|
||||||
|
this.unitPrice = unitPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BigDecimal getTotal() {
|
||||||
|
return unitPrice != null ? unitPrice.multiply(BigDecimal.valueOf(quantity)) : BigDecimal.ZERO;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.DropdownDTO;
|
||||||
|
import com.example.petstoremobile.dtos.ProductDTO;
|
||||||
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
|
import com.example.petstoremobile.repositories.CustomerRepository;
|
||||||
|
import com.example.petstoremobile.repositories.ProductRepository;
|
||||||
|
import com.example.petstoremobile.repositories.SaleRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.math.BigDecimal;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class SaleDetailViewModel extends ViewModel {
|
||||||
|
private final SaleRepository saleRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
private final CustomerRepository customerRepository;
|
||||||
|
private final ProductRepository productRepository;
|
||||||
|
|
||||||
|
private long saleId = -1;
|
||||||
|
private boolean viewOnly = false;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> storeList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<DropdownDTO>> customerList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<ProductDTO>> productList = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<SaleDTO.SaleItemDTO>> cartItems = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SaleDetailViewModel(SaleRepository saleRepository, StoreRepository storeRepository,
|
||||||
|
CustomerRepository customerRepository, ProductRepository productRepository) {
|
||||||
|
this.saleRepository = saleRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
this.customerRepository = customerRepository;
|
||||||
|
this.productRepository = productRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSaleId(long id, boolean viewOnly) {
|
||||||
|
this.saleId = id;
|
||||||
|
this.viewOnly = viewOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSaleId() { return saleId; }
|
||||||
|
public boolean isViewOnly() { return viewOnly; }
|
||||||
|
|
||||||
|
public LiveData<Resource<SaleDTO>> loadSaleDetails() {
|
||||||
|
return saleRepository.getSaleById(saleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadStores() {
|
||||||
|
return storeRepository.getStoreDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<List<DropdownDTO>>> loadCustomers() {
|
||||||
|
return customerRepository.getCustomerDropdowns();
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<com.example.petstoremobile.dtos.PageResponse<ProductDTO>>> loadProducts() {
|
||||||
|
return productRepository.getAllProducts(null, null, 0, 200, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<SaleDTO>> createSale(SaleDTO sale) {
|
||||||
|
return saleRepository.createSale(sale);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setStoreList(List<DropdownDTO> list) { storeList.setValue(list); }
|
||||||
|
public LiveData<List<DropdownDTO>> getStoreList() { return storeList; }
|
||||||
|
|
||||||
|
public void setCustomerList(List<DropdownDTO> list) { customerList.setValue(list); }
|
||||||
|
public LiveData<List<DropdownDTO>> getCustomerList() { return customerList; }
|
||||||
|
|
||||||
|
public void setProductList(List<ProductDTO> list) { productList.setValue(list); }
|
||||||
|
public LiveData<List<ProductDTO>> getProductList() { return productList; }
|
||||||
|
|
||||||
|
public void addToCart(SaleDTO.SaleItemDTO item) {
|
||||||
|
List<SaleDTO.SaleItemDTO> currentCart = new ArrayList<>(cartItems.getValue());
|
||||||
|
currentCart.add(item);
|
||||||
|
cartItems.setValue(currentCart);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<SaleDTO.SaleItemDTO>> getCartItems() { return cartItems; }
|
||||||
|
|
||||||
|
public BigDecimal calculateSubtotal() {
|
||||||
|
BigDecimal total = BigDecimal.ZERO;
|
||||||
|
List<SaleDTO.SaleItemDTO> items = cartItems.getValue();
|
||||||
|
List<ProductDTO> products = productList.getValue();
|
||||||
|
if (items != null && products != null) {
|
||||||
|
for (SaleDTO.SaleItemDTO item : items) {
|
||||||
|
for (ProductDTO p : products) {
|
||||||
|
if (p.getProdId().equals(item.getProdId()) && p.getProdPrice() != null) {
|
||||||
|
total = total.add(p.getProdPrice().multiply(BigDecimal.valueOf(item.getQuantity())));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Boolean> getIsLoading() {
|
||||||
|
return isLoading;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLoading(boolean loading) {
|
||||||
|
isLoading.setValue(loading);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.SaleDTO;
|
||||||
|
import com.example.petstoremobile.dtos.StoreDTO;
|
||||||
|
import com.example.petstoremobile.repositories.SaleRepository;
|
||||||
|
import com.example.petstoremobile.repositories.StoreRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class SaleListViewModel extends ViewModel {
|
||||||
|
private final SaleRepository saleRepository;
|
||||||
|
private final StoreRepository storeRepository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<SaleDTO>> sales = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<StoreDTO>> stores = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
private int currentPage = 0;
|
||||||
|
private boolean isLastPage = false;
|
||||||
|
private static final int PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SaleListViewModel(SaleRepository saleRepository, StoreRepository storeRepository) {
|
||||||
|
this.saleRepository = saleRepository;
|
||||||
|
this.storeRepository = storeRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<SaleDTO>> getSales() { return sales; }
|
||||||
|
public LiveData<List<StoreDTO>> getStores() { return stores; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
public boolean isLastPage() { return isLastPage; }
|
||||||
|
|
||||||
|
public void loadSales(boolean reset, String query, String paymentMethod, Long storeId) {
|
||||||
|
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
currentPage = 0;
|
||||||
|
isLastPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.setValue(true);
|
||||||
|
saleRepository.getAllSales(currentPage, PAGE_SIZE, query, paymentMethod, storeId, "saleDate,desc").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
List<SaleDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(sales.getValue());
|
||||||
|
currentList.addAll(resource.data.getContent());
|
||||||
|
sales.setValue(currentList);
|
||||||
|
isLastPage = resource.data.isLast();
|
||||||
|
if (!isLastPage) currentPage++;
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadStores() {
|
||||||
|
storeRepository.getAllStores(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
stores.setValue(resource.data.getContent());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.SaleDTO;
|
|
||||||
import com.example.petstoremobile.repositories.SaleRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class SaleViewModel extends ViewModel {
|
|
||||||
private final SaleRepository saleRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SaleViewModel(SaleRepository saleRepository) {
|
|
||||||
this.saleRepository = saleRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<SaleDTO>>> getAllSales(int page, int size, String query, String paymentMethod, Long storeId, String sortBy) {
|
|
||||||
return saleRepository.getAllSales(page, size, query, paymentMethod, storeId, sortBy);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<SaleDTO>> getSaleById(Long id) {
|
|
||||||
return saleRepository.getSaleById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<SaleDTO>> createSale(SaleDTO sale) {
|
|
||||||
return saleRepository.createSale(sale);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
|
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class ServiceDetailViewModel extends ViewModel {
|
||||||
|
private final ServiceRepository repository;
|
||||||
|
private long serviceId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ServiceDetailViewModel(ServiceRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setServiceId(long id) {
|
||||||
|
this.serviceId = id;
|
||||||
|
this.isEditing = id != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getServiceId() {
|
||||||
|
return serviceId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<ServiceDTO>> loadService() {
|
||||||
|
return repository.getServiceById(serviceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<ServiceDTO>> saveService(ServiceDTO dto) {
|
||||||
|
if (isEditing) {
|
||||||
|
dto.setServiceId(serviceId);
|
||||||
|
return repository.updateService(serviceId, dto);
|
||||||
|
} else {
|
||||||
|
return repository.createService(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteService() {
|
||||||
|
return repository.deleteService(serviceId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.PageResponse;
|
||||||
|
import com.example.petstoremobile.dtos.ServiceDTO;
|
||||||
|
import com.example.petstoremobile.repositories.ServiceRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class ServiceListViewModel extends ViewModel {
|
||||||
|
private final ServiceRepository repository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<ServiceDTO>> services = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
private int currentPage = 0;
|
||||||
|
private boolean isLastPage = false;
|
||||||
|
private static final int PAGE_SIZE = 20;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public ServiceListViewModel(ServiceRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<ServiceDTO>> getServices() { return services; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
public boolean isLastPage() { return isLastPage; }
|
||||||
|
|
||||||
|
public void loadServices(boolean reset, String query) {
|
||||||
|
if (isLoading.getValue() != null && isLoading.getValue() && !reset) return;
|
||||||
|
|
||||||
|
if (reset) {
|
||||||
|
currentPage = 0;
|
||||||
|
isLastPage = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoading.setValue(true);
|
||||||
|
repository.getAllServices(currentPage, PAGE_SIZE, query, "serviceName").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
List<ServiceDTO> currentList = reset ? new ArrayList<>() : new ArrayList<>(services.getValue());
|
||||||
|
currentList.addAll(resource.data.getContent());
|
||||||
|
services.setValue(currentList);
|
||||||
|
isLastPage = resource.data.isLast();
|
||||||
|
if (!isLastPage) currentPage++;
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteServices(List<String> ids) {
|
||||||
|
return repository.bulkDeleteServices(new BulkDeleteRequest(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.ServiceDTO;
|
|
||||||
import com.example.petstoremobile.repositories.ServiceRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class ServiceViewModel extends ViewModel {
|
|
||||||
private final ServiceRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public ServiceViewModel(ServiceRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all services.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<ServiceDTO>>> getAllServices(int page, int size, String query, String sort) {
|
|
||||||
return repository.getAllServices(page, size, query, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single service by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ServiceDTO>> getServiceById(Long id) {
|
|
||||||
return repository.getServiceById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new service.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ServiceDTO>> createService(ServiceDTO service) {
|
|
||||||
return repository.createService(service);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing service by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<ServiceDTO>> updateService(Long id, ServiceDTO service) {
|
|
||||||
return repository.updateService(id, service);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a service by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteService(Long id) {
|
|
||||||
return repository.deleteService(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes multiple services.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> bulkDeleteServices(List<String> ids) {
|
|
||||||
return repository.bulkDeleteServices(new BulkDeleteRequest(ids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
|
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class StaffDetailViewModel extends ViewModel {
|
||||||
|
private final EmployeeRepository repository;
|
||||||
|
private long employeeId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public StaffDetailViewModel(EmployeeRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmployeeId(long id, boolean isEditing) {
|
||||||
|
this.employeeId = id;
|
||||||
|
this.isEditing = isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getEmployeeId() {
|
||||||
|
return employeeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<EmployeeDTO>> saveEmployee(EmployeeDTO dto) {
|
||||||
|
if (isEditing && employeeId > 0) {
|
||||||
|
return repository.updateEmployee(employeeId, dto);
|
||||||
|
} else {
|
||||||
|
return repository.createEmployee(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteEmployee() {
|
||||||
|
return repository.deleteEmployee(employeeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.EmployeeDTO;
|
||||||
|
import com.example.petstoremobile.repositories.EmployeeRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class StaffListViewModel extends ViewModel {
|
||||||
|
private final EmployeeRepository repository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<EmployeeDTO>> employees = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<List<EmployeeDTO>> filteredEmployees = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
private String lastQuery = "";
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public StaffListViewModel(EmployeeRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<EmployeeDTO>> getFilteredEmployees() { return filteredEmployees; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadStaff() {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
repository.getAllEmployees(0, 100).observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
employees.setValue(resource.data.getContent());
|
||||||
|
filter(lastQuery);
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void filter(String query) {
|
||||||
|
this.lastQuery = query;
|
||||||
|
List<EmployeeDTO> all = employees.getValue();
|
||||||
|
if (all == null) return;
|
||||||
|
|
||||||
|
if (query.isEmpty()) {
|
||||||
|
filteredEmployees.setValue(new ArrayList<>(all));
|
||||||
|
} else {
|
||||||
|
List<EmployeeDTO> filtered = new ArrayList<>();
|
||||||
|
String lower = query.toLowerCase();
|
||||||
|
for (EmployeeDTO e : all) {
|
||||||
|
if ((e.getFullName() != null && e.getFullName().toLowerCase().contains(lower))
|
||||||
|
|| (e.getUsername() != null && e.getUsername().toLowerCase().contains(lower))
|
||||||
|
|| (e.getEmail() != null && e.getEmail().toLowerCase().contains(lower))
|
||||||
|
|| (e.getPhone() != null && e.getPhone().toLowerCase().contains(lower))) {
|
||||||
|
filtered.add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
filteredEmployees.setValue(filtered);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.StoreDTO;
|
|
||||||
import com.example.petstoremobile.repositories.StoreRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class StoreViewModel extends ViewModel {
|
|
||||||
private final StoreRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public StoreViewModel(StoreRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all stores.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<StoreDTO>>> getAllStores(int page, int size) {
|
|
||||||
return repository.getAllStores(page, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
|
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class SupplierDetailViewModel extends ViewModel {
|
||||||
|
private final SupplierRepository repository;
|
||||||
|
private long supId = -1;
|
||||||
|
private boolean isEditing = false;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SupplierDetailViewModel(SupplierRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSupId(long id) {
|
||||||
|
this.supId = id;
|
||||||
|
this.isEditing = id != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getSupId() {
|
||||||
|
return supId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEditing() {
|
||||||
|
return isEditing;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<SupplierDTO>> loadSupplier() {
|
||||||
|
return repository.getSupplierById(supId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<SupplierDTO>> saveSupplier(SupplierDTO dto) {
|
||||||
|
if (isEditing) {
|
||||||
|
dto.setSupId(supId);
|
||||||
|
return repository.updateSupplier(supId, dto);
|
||||||
|
} else {
|
||||||
|
return repository.createSupplier(dto);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> deleteSupplier() {
|
||||||
|
return repository.deleteSupplier(supId);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package com.example.petstoremobile.viewmodels;
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData;
|
||||||
|
import androidx.lifecycle.MutableLiveData;
|
||||||
|
import androidx.lifecycle.ViewModel;
|
||||||
|
|
||||||
|
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
||||||
|
import com.example.petstoremobile.dtos.SupplierDTO;
|
||||||
|
import com.example.petstoremobile.repositories.SupplierRepository;
|
||||||
|
import com.example.petstoremobile.utils.Resource;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel;
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
public class SupplierListViewModel extends ViewModel {
|
||||||
|
private final SupplierRepository repository;
|
||||||
|
|
||||||
|
private final MutableLiveData<List<SupplierDTO>> suppliers = new MutableLiveData<>(new ArrayList<>());
|
||||||
|
private final MutableLiveData<Boolean> isLoading = new MutableLiveData<>(false);
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SupplierListViewModel(SupplierRepository repository) {
|
||||||
|
this.repository = repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<List<SupplierDTO>> getSuppliers() { return suppliers; }
|
||||||
|
public LiveData<Boolean> getIsLoading() { return isLoading; }
|
||||||
|
|
||||||
|
public void loadSuppliers(String query) {
|
||||||
|
isLoading.setValue(true);
|
||||||
|
repository.getAllSuppliers(0, 100, query, "supCompany").observeForever(resource -> {
|
||||||
|
if (resource != null) {
|
||||||
|
if (resource.status == Resource.Status.SUCCESS && resource.data != null) {
|
||||||
|
suppliers.setValue(resource.data.getContent());
|
||||||
|
isLoading.setValue(false);
|
||||||
|
} else if (resource.status == Resource.Status.ERROR) {
|
||||||
|
isLoading.setValue(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public LiveData<Resource<Void>> bulkDeleteSuppliers(List<String> ids) {
|
||||||
|
return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.BulkDeleteRequest;
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.SupplierDTO;
|
|
||||||
import com.example.petstoremobile.repositories.SupplierRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class SupplierViewModel extends ViewModel {
|
|
||||||
private final SupplierRepository repository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public SupplierViewModel(SupplierRepository repository) {
|
|
||||||
this.repository = repository;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches a paginated list of all suppliers.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<PageResponse<SupplierDTO>>> getAllSuppliers(int page, int size, String query, String sort) {
|
|
||||||
return repository.getAllSuppliers(page, size, query, sort);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Retrieves a single supplier by its ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<SupplierDTO>> getSupplierById(Long id) {
|
|
||||||
return repository.getSupplierById(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new supplier record.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<SupplierDTO>> createSupplier(SupplierDTO supplier) {
|
|
||||||
return repository.createSupplier(supplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates an existing supplier record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<SupplierDTO>> updateSupplier(Long id, SupplierDTO supplier) {
|
|
||||||
return repository.updateSupplier(id, supplier);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a supplier record by ID.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> deleteSupplier(Long id) {
|
|
||||||
return repository.deleteSupplier(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes multiple supplier records.
|
|
||||||
*/
|
|
||||||
public LiveData<Resource<Void>> bulkDeleteSuppliers(List<String> ids) {
|
|
||||||
return repository.bulkDeleteSuppliers(new BulkDeleteRequest(ids));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.example.petstoremobile.viewmodels;
|
|
||||||
|
|
||||||
import androidx.lifecycle.LiveData;
|
|
||||||
import androidx.lifecycle.ViewModel;
|
|
||||||
|
|
||||||
import com.example.petstoremobile.dtos.PageResponse;
|
|
||||||
import com.example.petstoremobile.dtos.UserDTO;
|
|
||||||
import com.example.petstoremobile.repositories.UserRepository;
|
|
||||||
import com.example.petstoremobile.utils.Resource;
|
|
||||||
|
|
||||||
import javax.inject.Inject;
|
|
||||||
|
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel;
|
|
||||||
|
|
||||||
@HiltViewModel
|
|
||||||
public class UserViewModel extends ViewModel {
|
|
||||||
private final UserRepository userRepository;
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
public UserViewModel(UserRepository userRepository) {
|
|
||||||
this.userRepository = userRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public LiveData<Resource<PageResponse<UserDTO>>> getUsers(String role, int page, int size) {
|
|
||||||
return userRepository.getUsers(role, page, size);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
40
android/app/src/main/res/layout/dialog_full_screen_image.xml
Normal file
40
android/app/src/main/res/layout/dialog_full_screen_image.xml
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:background="@android:color/black">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivFullScreen"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="fitCenter" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="top|end"
|
||||||
|
android:layout_margin="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnDownload"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:src="@android:drawable/stat_sys_download"
|
||||||
|
android:contentDescription="Download"
|
||||||
|
android:tint="@android:color/white" />
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnClose"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginLeft="8dp"
|
||||||
|
android:background="@android:color/transparent"
|
||||||
|
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||||
|
android:contentDescription="Close"
|
||||||
|
android:tint="@android:color/white" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,214 +1,228 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvAdoptionMode"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Add Adoption"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnDeleteAdoption"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:text="Delete"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="24dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAdoptionMode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Add Adoption"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDeleteAdoption"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Delete"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:padding="24dp">
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvAdoptionId"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="ID: #0"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<!-- Customer -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Customer"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerAdoptionCustomer"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<!-- Pet -->
|
<TextView
|
||||||
<TextView
|
android:id="@+id/tvAdoptionId"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Pet"
|
android:text="ID: #0"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="12sp"
|
android:textSize="11sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:textStyle="italic"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
<Spinner
|
<!-- Customer -->
|
||||||
android:id="@+id/spinnerAdoptionPet"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:text="Customer"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Employee -->
|
<Spinner
|
||||||
<TextView
|
android:id="@+id/spinnerAdoptionCustomer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Handled By (Staff)"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
<!-- Pet -->
|
||||||
android:id="@+id/spinnerAdoptionEmployee"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:text="Pet"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<TextView
|
<Spinner
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/spinnerAdoptionPet"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Source Store"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
<!-- Employee -->
|
||||||
android:id="@+id/spinnerAdoptionStore"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:text="Handled By (Staff)"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Adoption Date -->
|
<Spinner
|
||||||
<TextView
|
android:id="@+id/spinnerAdoptionEmployee"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Adoption Date"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
<TextView
|
||||||
android:id="@+id/etAdoptionDate"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Source Store"
|
||||||
android:hint="Tap to select date"
|
android:textColor="@color/text_dark"
|
||||||
android:inputType="none"
|
android:textSize="12sp"
|
||||||
android:focusable="false"
|
android:layout_marginBottom="4dp"/>
|
||||||
android:clickable="true"
|
|
||||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<!-- Adoption Fee -->
|
<Spinner
|
||||||
<TextView
|
android:id="@+id/spinnerAdoptionStore"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Adoption Fee"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
<!-- Adoption Date -->
|
||||||
android:id="@+id/etAdoptionFee"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="0.00"
|
android:text="Adoption Date"
|
||||||
android:inputType="numberDecimal"
|
android:textColor="@color/text_dark"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Status -->
|
<EditText
|
||||||
<TextView
|
android:id="@+id/etAdoptionDate"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Status"
|
android:hint="Tap to select date"
|
||||||
android:textColor="@color/text_dark"
|
android:inputType="none"
|
||||||
android:textSize="12sp"
|
android:focusable="false"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:clickable="true"
|
||||||
|
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<Spinner
|
<!-- Adoption Fee -->
|
||||||
android:id="@+id/spinnerAdoptionStatus"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:text="Adoption Fee"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etAdoptionFee"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="0.00"
|
||||||
|
android:inputType="numberDecimal"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Status"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerAdoptionStatus"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnAdoptionBack"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="Back"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSaveAdoption"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="Save"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnAdoptionBack"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:text="Back"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSaveAdoption"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:text="Save"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,318 +1,332 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/btnHamburgerAnalytics"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:src="@drawable/baseline_menu_36"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="Open menu"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Analytics"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnRefreshAnalytics"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Refresh"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="16dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<!-- Summary Cards Row 1 -->
|
<ImageButton
|
||||||
<LinearLayout
|
android:id="@+id/btnHamburgerAnalytics"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@drawable/baseline_menu_36"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Open menu"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_weight="1"
|
||||||
android:layout_marginBottom="8dp">
|
android:text="Analytics"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
<LinearLayout
|
<Button
|
||||||
android:layout_width="0dp"
|
android:id="@+id/btnRefreshAnalytics"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Total Revenue"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTotalRevenue"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="$0.00"
|
|
||||||
android:textColor="@color/accent_coral"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginTop="4dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Transactions"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTotalTransactions"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="0"
|
|
||||||
android:textColor="@color/primary_dark"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginTop="4dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Summary Cards Row 2 -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:text="Refresh"
|
||||||
android:layout_marginBottom="16dp">
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="4dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Avg Transaction"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvAvgTransaction"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="$0.00"
|
|
||||||
android:textColor="@color/primary_dark"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginTop="4dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="4dp"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Items Sold"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvTotalItems"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="0"
|
|
||||||
android:textColor="@color/primary_dark"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginTop="4dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Top Products by Revenue -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Top Products by Revenue"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="12dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llTopRevenue"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Top Products by Quantity -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Top Products by Quantity"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="12dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llTopQuantity"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Payment Methods -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Payment Methods"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="12dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llPaymentMethods"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Employee Performance -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Employee Performance"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="12dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llEmployeePerformance"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Daily Revenue -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Daily Revenue (Last 7 Days)"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="12dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llDailyRevenue"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
</LinearLayout>
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<!-- Summary Cards Row 1 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Total Revenue"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTotalRevenue"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="$0.00"
|
||||||
|
android:textColor="@color/accent_coral"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Transactions"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTotalTransactions"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textColor="@color/primary_dark"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Summary Cards Row 2 -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="4dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Avg Transaction"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvAvgTransaction"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="$0.00"
|
||||||
|
android:textColor="@color/primary_dark"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="4dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Items Sold"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvTotalItems"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="0"
|
||||||
|
android:textColor="@color/primary_dark"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Top Products by Revenue -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Top Products by Revenue"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llTopRevenue"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Top Products by Quantity -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Top Products by Quantity"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llTopQuantity"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Payment Methods -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Payment Methods"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llPaymentMethods"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Employee Performance -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Employee Performance"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llEmployeePerformance"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Daily Revenue -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Daily Revenue (Last 7 Days)"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="12dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llDailyRevenue"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,266 +1,282 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvApptMode"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Add Appointment"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnDeleteAppointment"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:text="Delete"
|
|
||||||
android:textColor="@color/white" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="24dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvApptMode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Add Appointment"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDeleteAppointment"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Delete"
|
||||||
|
android:textColor="@color/white" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:padding="24dp">
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvAppointmentId"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="ID: #0"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<!-- Customer -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Customer"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerCustomer"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<!-- Store -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Store"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerStore"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<!-- Pet -->
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Pet"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerPet"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<!-- Service -->
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Service"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerService"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<!-- Staff -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Staff"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerStaff"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<!-- Appointment Date -->
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Appointment Date"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/etAppointmentDate"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:hint="Tap to select date"
|
|
||||||
android:inputType="none"
|
|
||||||
android:focusable="false"
|
|
||||||
android:clickable="true"
|
|
||||||
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<!-- Appointment Time-->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Appointment Time"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:orientation="vertical"
|
||||||
android:layout_marginBottom="16dp"
|
android:background="@drawable/rounded_card"
|
||||||
android:gravity="center_vertical">
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tvAppointmentId"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Hour:"
|
android:text="ID: #0"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="13sp"
|
android:textSize="11sp"
|
||||||
android:layout_marginEnd="6dp"/>
|
android:textStyle="italic"
|
||||||
|
android:layout_gravity="end"
|
||||||
<Spinner
|
android:layout_marginBottom="8dp"/>
|
||||||
android:id="@+id/spinnerHour"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="16dp"/>
|
|
||||||
|
|
||||||
|
<!-- Customer -->
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/tvLabelCustomer"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Min:"
|
android:text="Customer"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="13sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginEnd="6dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerMinute"
|
android:id="@+id/spinnerCustomer"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Pet -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelPet"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Pet"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerPet"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Store -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelStore"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Store"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Staff -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelStaff"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Staff"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStaff"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Service -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelService"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Service"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerService"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Appointment Date -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelDate"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Appointment Date"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etAppointmentDate"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Tap to select date"
|
||||||
|
android:inputType="none"
|
||||||
|
android:focusable="false"
|
||||||
|
android:clickable="true"
|
||||||
|
android:drawableEnd="@android:drawable/ic_menu_my_calendar"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Appointment Time-->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelTime"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Appointment Time"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Hour:"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:layout_marginEnd="6dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerHour"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="16dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Min:"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="13sp"
|
||||||
|
android:layout_marginEnd="6dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerMinute"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvLabelStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Status"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerAppointmentStatus"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<!-- Status -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Status"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerAppointmentStatus"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnApptBack"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="Back"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSaveAppointment"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="Save"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnApptBack"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:text="Back"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSaveAppointment"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:text="Save"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,153 +1,240 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<androidx.drawerlayout.widget.DrawerLayout
|
<androidx.drawerlayout.widget.DrawerLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:id="@+id/chatDrawerLayout"
|
android:id="@+id/chatDrawerLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<RelativeLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical">
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp">
|
|
||||||
|
|
||||||
<ImageButton
|
<LinearLayout
|
||||||
android:id="@+id/btnHamburger"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="48dp"
|
android:layout_height="56dp"
|
||||||
android:layout_height="48dp"
|
android:background="@color/primary_dark"
|
||||||
android:src="@drawable/baseline_menu_36"
|
android:gravity="center_vertical"
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
android:paddingStart="16dp"
|
||||||
android:contentDescription="Open menu"/>
|
android:paddingEnd="16dp">
|
||||||
|
|
||||||
<TextView
|
<ImageButton
|
||||||
android:id="@+id/tvChatTitle"
|
android:id="@+id/btnHamburger"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@drawable/baseline_menu_36"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Open menu"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvChatTitle"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Customer Chat"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvMessages"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:clipToPadding="false" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutAttachmentPreview"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Customer Chat"
|
android:orientation="horizontal"
|
||||||
android:textColor="@color/white"
|
android:padding="8dp"
|
||||||
android:textSize="20sp"
|
android:background="#E0E0E0"
|
||||||
android:textStyle="bold"
|
android:gravity="center_vertical"
|
||||||
android:paddingStart="8dp"
|
android:visibility="gone">
|
||||||
android:paddingEnd="8dp"/>
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivPreview"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPreviewName"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ellipsize="middle"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnRemoveAttachment"
|
||||||
|
android:layout_width="40dp"
|
||||||
|
android:layout_height="40dp"
|
||||||
|
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Remove attachment"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:padding="8dp"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:background="@color/white">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/btnAttach"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:src="@android:drawable/ic_menu_add"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="Attach file"
|
||||||
|
android:layout_marginEnd="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etMessage"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="Type a message..."
|
||||||
|
android:inputType="text"
|
||||||
|
android:imeOptions="actionSend"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSend"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Send"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<ProgressBar
|
||||||
android:id="@+id/rvMessages"
|
android:id="@+id/progressBar"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:clipToPadding="false" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layoutAttachmentPreview"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_centerInParent="true"
|
||||||
android:padding="8dp"
|
android:visibility="gone"
|
||||||
android:background="#E0E0E0"
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<ImageView
|
</RelativeLayout>
|
||||||
android:id="@+id/ivPreview"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<TextView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:id="@+id/tvPreviewName"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:ellipsize="middle"
|
|
||||||
android:singleLine="true"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/btnRemoveAttachment"
|
|
||||||
android:layout_width="40dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:src="@android:drawable/ic_menu_close_clear_cancel"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="Remove attachment"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="8dp"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:background="@color/white">
|
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/btnAttach"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:src="@android:drawable/ic_menu_add"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="Attach file"
|
|
||||||
android:layout_marginEnd="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/etMessage"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:hint="Type a message..."
|
|
||||||
android:inputType="text"
|
|
||||||
android:imeOptions="actionSend"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSend"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Send"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/chatListDrawer"
|
android:id="@+id/chatListDrawer"
|
||||||
android:layout_width="260dp"
|
android:layout_width="260dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:layout_gravity="start"
|
android:layout_gravity="start"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark">
|
||||||
android:background="@color/primary_dark"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Active Chats"
|
android:orientation="vertical"
|
||||||
android:textColor="@color/white"
|
android:paddingTop="24dp">
|
||||||
android:textSize="18sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
<LinearLayout
|
||||||
android:id="@+id/rvChatList"
|
android:id="@+id/headerActiveChats"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"/>
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
</LinearLayout>
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="ACTIVE CHATS"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:letterSpacing="0.15"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivActiveChevron"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@android:drawable/arrow_up_float"
|
||||||
|
app:tint="@color/text_light"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvActiveChats"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/headerClosedChats"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:paddingTop="8dp"
|
||||||
|
android:paddingBottom="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="CLOSED CHATS"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:letterSpacing="0.15"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/ivClosedChevron"
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:src="@android:drawable/arrow_down_float"
|
||||||
|
app:tint="@color/text_light"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/rvClosedChats"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:nestedScrollingEnabled="false"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:paddingStart="8dp"
|
||||||
|
android:paddingEnd="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
</androidx.drawerlayout.widget.DrawerLayout>
|
</androidx.drawerlayout.widget.DrawerLayout>
|
||||||
@@ -1,155 +1,169 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<!-- Header -->
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvInventoryMode"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Add Inventory"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnDeleteInventory"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:text="Delete"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="24dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvInventoryMode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Add Inventory"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDeleteInventory"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Delete"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:padding="24dp">
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<!-- Inventory ID — edit mode only -->
|
<LinearLayout
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvInventoryId"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Inventory ID: —"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginBottom="12dp"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
<!-- Store selection label -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Store"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<!-- Store Spinner -->
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerInventoryStore"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
<!-- Product selection label -->
|
<!-- Inventory ID — edit mode only -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/tvInventoryId"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Product"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:text="Inventory ID: —"
|
||||||
android:textSize="12sp"
|
android:textColor="@color/text_light"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:textSize="11sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
<!-- Product Spinner -->
|
<!-- Store selection label -->
|
||||||
<Spinner
|
<TextView
|
||||||
android:id="@+id/spinnerInventoryProduct"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Store"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Quantity label -->
|
<!-- Store Spinner -->
|
||||||
<TextView
|
<Spinner
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/spinnerInventoryStore"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Quantity"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<!-- Quantity input -->
|
<!-- Product selection label -->
|
||||||
<EditText
|
<TextView
|
||||||
android:id="@+id/etQuantity"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Product"
|
||||||
android:hint="Enter quantity"
|
android:textColor="@color/text_dark"
|
||||||
android:inputType="number"/>
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<!-- Product Spinner -->
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerInventoryProduct"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<!-- Quantity label -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Quantity"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<!-- Quantity input -->
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etQuantity"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="Enter quantity"
|
||||||
|
android:inputType="number"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Bottom buttons -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnInventoryBack"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="Back"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSaveInventory"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="Save"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<!-- Bottom buttons -->
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnInventoryBack"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:text="Back"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSaveInventory"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:text="Save"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,229 +1,244 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvMode"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Add Pet"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnDeletePet"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:text="Delete"
|
|
||||||
android:textColor="@color/white" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="24dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvMode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Add Pet"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDeletePet"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Delete"
|
||||||
|
android:textColor="@color/white" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:padding="24dp">
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvPetId"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Pet ID: #0"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Pet Name"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/etPetName"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter pet name"
|
android:orientation="vertical"
|
||||||
android:inputType="text"
|
android:background="@drawable/rounded_card"
|
||||||
android:layout_marginBottom="16dp"
|
android:padding="16dp"
|
||||||
android:textColor="@color/text_dark"/>
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/tvPetId"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Species"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:text="Pet ID: #0"
|
||||||
android:textSize="12sp"
|
android:textColor="@color/text_light"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:textSize="11sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
<EditText
|
<TextView
|
||||||
android:id="@+id/etPetSpecies"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Pet Name"
|
||||||
android:hint="e.g. Dog, Cat, Bird"
|
android:textColor="@color/text_dark"
|
||||||
android:inputType="text"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="4dp"/>
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<TextView
|
<EditText
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/etPetName"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Breed"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:hint="Enter pet name"
|
||||||
android:textSize="12sp"
|
android:inputType="text"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="16dp"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
<EditText
|
<TextView
|
||||||
android:id="@+id/etPetBreed"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Species"
|
||||||
android:hint="Enter breed"
|
android:textColor="@color/text_dark"
|
||||||
android:inputType="text"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="4dp"/>
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<TextView
|
<EditText
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/etPetSpecies"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Age"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:hint="e.g. Dog, Cat, Bird"
|
||||||
android:textSize="12sp"
|
android:inputType="text"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="16dp"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
<EditText
|
<TextView
|
||||||
android:id="@+id/etPetAge"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Breed"
|
||||||
android:hint="Enter age"
|
android:textColor="@color/text_dark"
|
||||||
android:inputType="number"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="4dp"/>
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<TextView
|
<EditText
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/etPetBreed"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Price"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:hint="Enter breed"
|
||||||
android:textSize="12sp"
|
android:inputType="text"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="16dp"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
<EditText
|
<TextView
|
||||||
android:id="@+id/etPetPrice"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Age"
|
||||||
android:hint="Enter price"
|
android:textColor="@color/text_dark"
|
||||||
android:inputType="numberDecimal"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="16dp"
|
android:layout_marginBottom="4dp"/>
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
<TextView
|
<EditText
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/etPetAge"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Status"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:hint="Enter age"
|
||||||
android:textSize="12sp"
|
android:inputType="number"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="16dp"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
<Spinner
|
<TextView
|
||||||
android:id="@+id/spinnerPetStatus"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Price"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<TextView
|
<EditText
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/etPetPrice"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Owner"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:hint="Enter price"
|
||||||
android:textSize="12sp"
|
android:inputType="numberDecimal"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="16dp"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
<Spinner
|
<TextView
|
||||||
android:id="@+id/spinnerCustomer"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Status"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<TextView
|
<Spinner
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/spinnerPetStatus"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:text="Store"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_dark"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
<TextView
|
||||||
android:id="@+id/spinnerStore"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
android:text="Owner"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerCustomer"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Store"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerStore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnBack"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="Back"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSavePet"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Save"
|
||||||
|
android:textColor="@color/white" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnBack"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:text="Back"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSavePet"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:text="Save"
|
|
||||||
android:textColor="@color/white" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,306 +1,320 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="match_parent"
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@color/primary_dark">
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="56dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Pet Profile"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnEditPet"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Edit"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:gravity="center">
|
android:background="@color/primary_dark">
|
||||||
|
|
||||||
<ImageView
|
<LinearLayout
|
||||||
android:id="@+id/imgPet"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="146dp"
|
android:layout_height="56dp"
|
||||||
android:layout_height="140dp"
|
android:orientation="horizontal"
|
||||||
android:layout_marginBottom="12dp"
|
android:gravity="center_vertical"
|
||||||
android:background="@drawable/circle_image"
|
android:paddingStart="16dp"
|
||||||
android:clipToOutline="true"
|
android:paddingEnd="16dp">
|
||||||
android:scaleType="centerCrop"
|
|
||||||
android:src="@drawable/placeholder" />
|
|
||||||
|
|
||||||
<Button
|
<TextView
|
||||||
android:id="@+id/btnChangePhoto"
|
android:layout_width="0dp"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_weight="1"
|
||||||
android:text="Change Photo"
|
android:text="Pet Profile"
|
||||||
style="@style/Widget.Material3.Button.TextButton"
|
android:textColor="@color/white"
|
||||||
android:textColor="@color/text_light"
|
android:textSize="20sp"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
<TextView
|
<Button
|
||||||
android:id="@+id/tvPetName"
|
android:id="@+id/btnEditPet"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="NAME"
|
android:text="Edit"
|
||||||
android:textColor="@color/white"
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:textSize="26sp"
|
android:textColor="@color/white"/>
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imgPet"
|
||||||
|
android:layout_width="146dp"
|
||||||
|
android:layout_height="140dp"
|
||||||
|
android:layout_marginBottom="12dp"
|
||||||
|
android:background="@drawable/circle_image"
|
||||||
|
android:clipToOutline="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/placeholder" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnChangePhoto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Change Photo"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="NAME"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="26sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<ScrollView
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1">
|
android:orientation="vertical"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginBottom="8dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginEnd="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="SPECIES"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetSpecies"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Dog"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginStart="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="BREED"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetBreed"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Labrador"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="120dp"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:layout_marginTop="8dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginEnd="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="AGE"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetAge"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="2 yr(s)"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginStart="8dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="PRICE"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetPrice"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="$500.00"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutPetOwner"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="OWNER"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetOwner"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="No Owner"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/layoutPetStore"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="STORE"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textColor="#888888"
|
||||||
|
android:textAllCaps="true"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPetStore"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="No Store"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textColor="@color/text_dark"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="horizontal"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<LinearLayout
|
<Button
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/btnBack"
|
||||||
android:layout_height="120dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_marginBottom="8dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:layout_marginEnd="8dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="SPECIES"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="#888888"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvPetSpecies"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Dog"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:layout_marginStart="8dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="BREED"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="#888888"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvPetBreed"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Labrador"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="120dp"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_marginTop="8dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:layout_marginEnd="8dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="AGE"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="#888888"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvPetAge"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="2 yr(s)"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:layout_marginStart="8dp">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="PRICE"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="#888888"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvPetPrice"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="$500.00"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/accent_coral"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layoutPetOwner"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:text="Back"
|
||||||
android:gravity="center"
|
android:backgroundTint="@color/primary_medium"
|
||||||
android:background="@color/white"
|
android:textColor="@color/white"/>
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="OWNER"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="#888888"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvPetOwner"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="No Owner"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/layoutPetStore"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:gravity="center"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="STORE"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textColor="#888888"
|
|
||||||
android:textAllCaps="true"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvPetStore"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="No Store"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:textColor="@color/text_dark"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnBack"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Back"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,195 +1,209 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvProductMode"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Add Product"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnDeleteProduct"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:text="Delete"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="24dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProductMode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Add Product"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDeleteProduct"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Delete"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:padding="24dp">
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvProductId"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="ID: #0"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="11sp"
|
|
||||||
android:textStyle="italic"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<!-- Product Image -->
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Product Image"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<FrameLayout
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="200dp"
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
android:layout_marginBottom="16dp">
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<ImageView
|
<TextView
|
||||||
android:id="@+id/ivProductImage"
|
android:id="@+id/tvProductId"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="ID: #0"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="11sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<!-- Product Image -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Product Image"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="200dp"
|
||||||
android:scaleType="centerCrop"
|
android:layout_marginBottom="16dp">
|
||||||
android:src="@drawable/placeholder2"
|
|
||||||
android:background="@color/text_light"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
<ImageView
|
||||||
|
android:id="@+id/ivProductImage"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/placeholder2"
|
||||||
|
android:background="@color/text_light"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"/>
|
||||||
|
|
||||||
<!-- Product Name -->
|
</FrameLayout>
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Product Name"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
<!-- Product Name -->
|
||||||
android:id="@+id/etProductName"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter product name"
|
android:text="Product Name"
|
||||||
android:inputType="text"
|
android:textColor="@color/text_dark"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Category -->
|
<EditText
|
||||||
<TextView
|
android:id="@+id/etProductName"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Category"
|
android:hint="Enter product name"
|
||||||
android:textColor="@color/text_dark"
|
android:inputType="text"
|
||||||
android:textSize="12sp"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
<!-- Category -->
|
||||||
android:id="@+id/spinnerProductCategory"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:text="Category"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Description -->
|
<Spinner
|
||||||
<TextView
|
android:id="@+id/spinnerProductCategory"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Description"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<EditText
|
<!-- Description -->
|
||||||
android:id="@+id/etProductDesc"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="Enter description"
|
android:text="Description"
|
||||||
android:inputType="textMultiLine"
|
android:textColor="@color/text_dark"
|
||||||
android:minLines="2"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<!-- Price -->
|
<EditText
|
||||||
<TextView
|
android:id="@+id/etProductDesc"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Price"
|
android:hint="Enter description"
|
||||||
android:textColor="@color/text_dark"
|
android:inputType="textMultiLine"
|
||||||
android:textSize="12sp"
|
android:minLines="2"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<EditText
|
<!-- Price -->
|
||||||
android:id="@+id/etProductPrice"
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="0.00"
|
android:text="Price"
|
||||||
android:inputType="numberDecimal"
|
android:textColor="@color/text_dark"
|
||||||
android:layout_marginBottom="8dp"/>
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etProductPrice"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="0.00"
|
||||||
|
android:inputType="numberDecimal"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnProductBack"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="Back"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSaveProduct"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="Save"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnProductBack"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:text="Back"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSaveProduct"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:text="Save"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,138 +1,152 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvPSMode"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Add Product Supplier"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnDeletePS"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:text="Delete"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="24dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPSMode"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Add Product Supplier"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnDeletePS"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:text="Delete"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:padding="24dp">
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<!-- Product -->
|
<LinearLayout
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Product"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginBottom="4dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerPSProduct"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
<!-- Supplier -->
|
<!-- Product -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Supplier"
|
android:text="Product"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<Spinner
|
<Spinner
|
||||||
android:id="@+id/spinnerPSSupplier"
|
android:id="@+id/spinnerPSProduct"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<!-- Cost -->
|
<!-- Supplier -->
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Cost"
|
android:text="Supplier"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
android:layout_marginBottom="4dp"/>
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
<EditText
|
<Spinner
|
||||||
android:id="@+id/etPSCost"
|
android:id="@+id/spinnerPSSupplier"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="0.00"
|
android:layout_marginBottom="16dp"/>
|
||||||
android:inputType="numberDecimal"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
<!-- Cost -->
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Cost"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etPSCost"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="0.00"
|
||||||
|
android:inputType="numberDecimal"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPSBack"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginEnd="8dp"
|
||||||
|
android:text="Back"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnSavePS"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="Save"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnPSBack"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="8dp"
|
|
||||||
android:text="Back"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnSavePS"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginStart="8dp"
|
|
||||||
android:text="Save"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,200 +1,214 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<ScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:orientation="vertical">
|
android:background="@color/background_grey">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical">
|
||||||
android:background="@color/primary_dark"
|
|
||||||
android:gravity="center_horizontal"
|
|
||||||
android:paddingTop="32dp"
|
|
||||||
android:paddingBottom="32dp">
|
|
||||||
|
|
||||||
<ImageView
|
<LinearLayout
|
||||||
android:id="@+id/imgProfile"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="171dp"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="166dp"
|
android:orientation="vertical"
|
||||||
android:layout_marginBottom="8dp"
|
android:background="@color/primary_dark"
|
||||||
android:background="@drawable/circle_image"
|
android:gravity="center_horizontal"
|
||||||
android:clipToOutline="true"
|
android:paddingTop="32dp"
|
||||||
android:scaleType="centerCrop"
|
android:paddingBottom="32dp">
|
||||||
android:src="@drawable/placeholder" />
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/imgProfile"
|
||||||
|
android:layout_width="171dp"
|
||||||
|
android:layout_height="166dp"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:background="@drawable/circle_image"
|
||||||
|
android:clipToOutline="true"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/placeholder" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnChangePhoto"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Change Photo"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:textColor="@color/text_light"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProfileName"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="First Last"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="22sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@color/white"
|
||||||
|
android:layout_marginStart="16dp"
|
||||||
|
android:layout_marginEnd="16dp"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Email"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProfileEmail"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="No email loaded"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnEditEmail"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Edit"
|
||||||
|
android:textColor="@color/accent_coral"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#F0F0F0"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingTop="12dp"
|
||||||
|
android:paddingBottom="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Phone"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProfilePhone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="No phone loaded"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="16sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnEditPhone"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Edit"
|
||||||
|
android:textColor="@color/accent_coral"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="1dp"
|
||||||
|
android:background="#F0F0F0"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:paddingTop="12dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Role"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:textColor="#888888"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvProfileRole"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="No role loaded"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textColor="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnChangePhoto"
|
android:id="@+id/btnLogout"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Change Photo"
|
android:layout_marginStart="16dp"
|
||||||
style="@style/Widget.Material3.Button.TextButton"
|
android:layout_marginEnd="16dp"
|
||||||
android:textColor="@color/text_light"/>
|
android:layout_marginBottom="24dp"
|
||||||
|
android:text="Log Out"
|
||||||
<TextView
|
android:backgroundTint="@color/accent_coral"
|
||||||
android:id="@+id/tvProfileName"
|
android:textColor="@color/white"/>
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:text="First Last"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="22sp"
|
|
||||||
android:textStyle="bold" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<LinearLayout
|
</ScrollView>
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginTop="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
<ProgressBar
|
||||||
android:layout_width="match_parent"
|
android:id="@+id/progressBar"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:orientation="horizontal"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:layout_gravity="center"
|
||||||
android:paddingBottom="12dp">
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
<LinearLayout
|
</FrameLayout>
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Email"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="#888888"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvProfileEmail"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="No email loaded"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnEditEmail"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Edit"
|
|
||||||
android:textColor="@color/accent_coral"
|
|
||||||
style="@style/Widget.Material3.Button.TextButton"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="#F0F0F0"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingBottom="12dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Phone"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="#888888"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvProfilePhone"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="No phone loaded"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="16sp" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnEditPhone"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Edit"
|
|
||||||
android:textColor="@color/accent_coral"
|
|
||||||
style="@style/Widget.Material3.Button.TextButton"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<View
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="1dp"
|
|
||||||
android:background="#F0F0F0"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:paddingTop="12dp">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Role"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:textColor="#888888"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvProfileRole"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="No role loaded"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textColor="@color/accent_coral"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnLogout"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginStart="16dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:layout_marginBottom="24dp"
|
|
||||||
android:text="Log Out"
|
|
||||||
android:backgroundTint="@color/accent_coral"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</ScrollView>
|
|
||||||
@@ -1,140 +1,154 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Purchase Order Details"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="24dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="Purchase Order Details"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:background="@drawable/rounded_card"
|
android:padding="24dp">
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp">
|
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:id="@+id/tvPODetailId"
|
android:layout_width="match_parent"
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_light"
|
android:orientation="vertical"
|
||||||
android:textSize="11sp"
|
android:background="@drawable/rounded_card"
|
||||||
android:textStyle="italic"
|
android:padding="16dp"
|
||||||
android:layout_gravity="end"
|
android:layout_marginBottom="16dp">
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/tvPODetailId"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Supplier"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="12sp"/>
|
android:textSize="11sp"
|
||||||
|
android:textStyle="italic"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvPODetailSupplier"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Supplier"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="16sp"
|
android:textSize="12sp"/>
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/tvPODetailSupplier"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Store"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"/>
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvPODetailStore"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Store"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="16sp"
|
android:textSize="12sp"/>
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/tvPODetailStore"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Order Date"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"/>
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvPODetailDate"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Order Date"
|
||||||
android:textColor="@color/text_dark"
|
android:textColor="@color/text_light"
|
||||||
android:textSize="15sp"
|
android:textSize="12sp"/>
|
||||||
android:layout_marginBottom="16dp"/>
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/tvPODetailDate"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:text="Status"
|
android:layout_height="wrap_content"
|
||||||
android:textColor="@color/text_light"
|
android:textColor="@color/text_dark"
|
||||||
android:textSize="12sp"/>
|
android:textSize="15sp"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/tvPODetailStatus"
|
android:layout_width="wrap_content"
|
||||||
android:layout_width="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:text="Status"
|
||||||
android:textSize="15sp"
|
android:textColor="@color/text_light"
|
||||||
android:layout_marginBottom="16dp"/>
|
android:textSize="12sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvPODetailStatus"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:layout_marginBottom="16dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</ScrollView>
|
||||||
|
|
||||||
</ScrollView>
|
<LinearLayout
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnPOBack"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:text="Back"
|
android:background="@color/white"
|
||||||
android:backgroundTint="@color/primary_medium"
|
android:padding="16dp">
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnPOBack"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Back"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
@@ -1,222 +1,236 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent">
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@color/background_grey">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/primary_dark"
|
android:orientation="vertical"
|
||||||
android:gravity="center_vertical"
|
android:background="@color/background_grey">
|
||||||
android:paddingStart="16dp"
|
|
||||||
android:paddingEnd="16dp"
|
|
||||||
android:orientation="horizontal">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:text="Process Refund"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp"
|
|
||||||
android:textStyle="bold"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ScrollView
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="0dp"
|
|
||||||
android:layout_weight="1">
|
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="56dp"
|
||||||
android:orientation="vertical"
|
android:background="@color/primary_dark"
|
||||||
android:padding="16dp">
|
android:gravity="center_vertical"
|
||||||
|
android:paddingStart="16dp"
|
||||||
|
android:paddingEnd="16dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
<!-- Load Sale Card -->
|
<TextView
|
||||||
<LinearLayout
|
android:layout_width="0dp"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:layout_weight="1"
|
||||||
android:background="@drawable/rounded_card"
|
android:text="Process Refund"
|
||||||
android:padding="16dp"
|
android:textColor="@color/white"
|
||||||
android:layout_marginBottom="16dp">
|
android:textSize="20sp"
|
||||||
|
android:textStyle="bold"/>
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Load Original Sale"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:gravity="center_vertical">
|
|
||||||
|
|
||||||
<EditText
|
|
||||||
android:id="@+id/etRefundSaleId"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:hint="Enter Sale ID"
|
|
||||||
android:inputType="number"
|
|
||||||
android:layout_marginEnd="8dp"/>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
android:id="@+id/btnLoadSale"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Load"
|
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvSaleInfo"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="@color/text_light"
|
|
||||||
android:textSize="12sp"
|
|
||||||
android:layout_marginTop="8dp"
|
|
||||||
android:visibility="gone"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Original Sale Items Card -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/cardOriginalItems"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Original Sale Items"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llOriginalItems"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Refund Items Card -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/cardRefundItems"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Items to Refund"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/llRefundItems"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/tvRefundTotal"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Refund Total: $0.00"
|
|
||||||
android:textColor="@color/accent_coral"
|
|
||||||
android:textSize="16sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_gravity="end"
|
|
||||||
android:layout_marginTop="12dp"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<!-- Payment Method Card -->
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/cardPayment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:background="@drawable/rounded_card"
|
|
||||||
android:padding="16dp"
|
|
||||||
android:layout_marginBottom="16dp"
|
|
||||||
android:visibility="gone">
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:text="Refund Payment Method"
|
|
||||||
android:textColor="@color/text_dark"
|
|
||||||
android:textSize="14sp"
|
|
||||||
android:textStyle="bold"
|
|
||||||
android:layout_marginBottom="8dp"/>
|
|
||||||
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/spinnerRefundPayment"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</ScrollView>
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:layout_weight="1">
|
||||||
|
|
||||||
<!-- Bottom Buttons -->
|
<LinearLayout
|
||||||
<LinearLayout
|
android:layout_width="match_parent"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:orientation="vertical"
|
||||||
android:orientation="horizontal"
|
android:padding="16dp">
|
||||||
android:background="@color/white"
|
|
||||||
android:padding="16dp">
|
|
||||||
|
|
||||||
<Button
|
<!-- Load Sale Card -->
|
||||||
android:id="@+id/btnRefundBack"
|
<LinearLayout
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Load Original Sale"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:gravity="center_vertical">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/etRefundSaleId"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:hint="Enter Sale ID"
|
||||||
|
android:inputType="number"
|
||||||
|
android:layout_marginEnd="8dp"/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnLoadSale"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Load"
|
||||||
|
android:backgroundTint="@color/primary_medium"
|
||||||
|
android:textColor="@color/white"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvSaleInfo"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/text_light"
|
||||||
|
android:textSize="12sp"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Original Sale Items Card -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/cardOriginalItems"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Original Sale Items"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llOriginalItems"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Refund Items Card -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/cardRefundItems"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Items to Refund"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/llRefundItems"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/tvRefundTotal"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Refund Total: $0.00"
|
||||||
|
android:textColor="@color/accent_coral"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_gravity="end"
|
||||||
|
android:layout_marginTop="12dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<!-- Payment Method Card -->
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/cardPayment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:background="@drawable/rounded_card"
|
||||||
|
android:padding="16dp"
|
||||||
|
android:layout_marginBottom="16dp"
|
||||||
|
android:visibility="gone">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Refund Payment Method"
|
||||||
|
android:textColor="@color/text_dark"
|
||||||
|
android:textSize="14sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="8dp"/>
|
||||||
|
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/spinnerRefundPayment"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
</ScrollView>
|
||||||
|
|
||||||
|
<!-- Bottom Buttons -->
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:orientation="horizontal"
|
||||||
android:layout_marginEnd="8dp"
|
android:background="@color/white"
|
||||||
android:text="Back"
|
android:padding="16dp">
|
||||||
android:backgroundTint="@color/primary_medium"
|
|
||||||
android:textColor="@color/white"/>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/btnProcessRefund"
|
android:id="@+id/btnRefundBack"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:layout_marginStart="8dp"
|
android:layout_marginEnd="8dp"
|
||||||
android:text="Process Refund"
|
android:text="Back"
|
||||||
android:backgroundTint="@color/accent_coral"
|
android:backgroundTint="@color/primary_medium"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"/>
|
||||||
android:visibility="gone"/>
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/btnProcessRefund"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="Process Refund"
|
||||||
|
android:backgroundTint="@color/accent_coral"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:visibility="gone"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
<ProgressBar
|
||||||
|
android:id="@+id/progressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:indeterminateTint="@color/accent_coral"/>
|
||||||
|
|
||||||
|
</FrameLayout>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user