Azure deployment setup #297

Closed
RecentRunner wants to merge 429 commits from azure-deploy into main
4 changed files with 218 additions and 59 deletions
Showing only changes of commit c2faeb06ce - Show all commits

View File

@@ -10,11 +10,18 @@ import android.os.Bundle;
import android.os.Environment;
import android.provider.MediaStore;
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.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 androidx.activity.result.ActivityResultLauncher;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull;
@@ -22,6 +29,7 @@ import androidx.core.view.GravityCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.LinearLayoutManager;
import com.bumptech.glide.Glide;
import com.example.petstoremobile.R;
import com.example.petstoremobile.adapters.ChatAdapter;
@@ -45,7 +53,8 @@ import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.*;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import javax.inject.Named;
@@ -65,10 +74,12 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
private FragmentChatBinding binding;
private ChatListViewModel viewModel;
private ChatAdapter chatAdapter;
private ChatAdapter activeChatAdapter;
private ChatAdapter closedChatAdapter;
private MessageAdapter messageAdapter;
private final List<Chat> chatList = new ArrayList<>();
private final List<Chat> activeChatList = new ArrayList<>();
private final List<Chat> closedChatList = new ArrayList<>();
private final List<Message> messageList = new ArrayList<>();
private Uri pendingAttachmentUri;
@@ -82,7 +93,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
viewModel = new ViewModelProvider(this).get(ChatListViewModel.class);
viewModel = new ViewModelProvider(requireActivity()).get(ChatListViewModel.class);
attachmentLauncher = registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
result -> {
@@ -117,6 +128,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
binding.btnAttach.setOnClickListener(v -> selectAttachment());
binding.btnRemoveAttachment.setOnClickListener(v -> removeAttachment());
setupDrawerToggles();
setupRecyclerViews();
observeViewModel();
loadInitialData();
@@ -124,10 +136,36 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
return binding.getRoot();
}
private void setupDrawerToggles() {
binding.headerActiveChats.setOnClickListener(v -> {
if (binding.rvActiveChats.getVisibility() == View.VISIBLE) {
binding.rvActiveChats.setVisibility(View.GONE);
binding.ivActiveChevron.setImageResource(android.R.drawable.arrow_down_float);
} else {
binding.rvActiveChats.setVisibility(View.VISIBLE);
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() {
chatAdapter = new ChatAdapter(chatList, this);
binding.rvChatList.setLayoutManager(new LinearLayoutManager(getContext()));
binding.rvChatList.setAdapter(chatAdapter);
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);
messageAdapter = new MessageAdapter(messageList, null);
messageAdapter.setBaseUrl(baseUrl);
@@ -144,7 +182,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
lm.setStackFromEnd(true);
binding.rvMessages.setLayoutManager(lm);
binding.rvMessages.setAdapter(messageAdapter);
setConversationActive(false);
setConversationActive(false, null);
}
private void showFullScreenImage(Message message) {
@@ -226,19 +264,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
}
private void observeViewModel() {
viewModel.getChatList().observe(getViewLifecycleOwner(), list -> {
chatList.clear();
chatList.addAll(list);
chatAdapter.notifyDataSetChanged();
viewModel.getActiveChats().observe(getViewLifecycleOwner(), list -> {
activeChatList.clear();
activeChatList.addAll(list);
activeChatAdapter.notifyDataSetChanged();
updateTitleAndStateIfActive(list);
});
if (activeConversationId != null) {
for (Chat chat : list) {
if (chat.getChatId().equals(String.valueOf(activeConversationId))) {
binding.tvChatTitle.setText(chat.getCustomerName());
break;
}
}
}
viewModel.getClosedChats().observe(getViewLifecycleOwner(), list -> {
closedChatList.clear();
closedChatList.addAll(list);
closedChatAdapter.notifyDataSetChanged();
updateTitleAndStateIfActive(list);
});
viewModel.getMessageList().observe(getViewLifecycleOwner(), list -> {
@@ -253,6 +290,18 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
});
}
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;
}
}
}
}
private void loadInitialData() {
String token = tokenManager.getToken();
Long currentUserId = tokenManager.getUserId();
@@ -274,21 +323,29 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
} else if (getActivity() != null && getActivity().getIntent().hasExtra("conversation_id")) {
activeConversationId = getActivity().getIntent().getLongExtra("conversation_id", -1);
getActivity().getIntent().removeExtra("conversation_id");
} else {
// Restore last active conversation if any
activeConversationId = viewModel.getLastActiveConversationId();
}
viewModel.loadCustomers();
//
if (activeConversationId != null) {
setConversationActive(true);
// Re-subscribe and load history if there is an active conversation ID
if (stompChatManager != null) stompChatManager.subscribeToConversation(activeConversationId);
viewModel.loadMessageHistory(activeConversationId);
} else {
setConversationActive(false, null);
}
}
@Override
public void onChatClick(Chat chat) {
activeConversationId = Long.parseLong(chat.getChatId());
setConversationActive(true);
viewModel.setLastActiveConversationId(activeConversationId);
setConversationActive(true, chat.getStatus());
binding.tvChatTitle.setText(chat.getCustomerName());
binding.chatDrawerLayout.closeDrawer(GravityCompat.START);
@@ -377,8 +434,6 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
}
}
viewModel.updateConversationLocally(new ConversationDTO(dto.getConversationId(), 0L, 0L, dto.getContent(), ""));
// Re-load coversations to get correct names if needed or just let local update handle it
viewModel.loadConversations();
});
}
@@ -387,7 +442,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
requireActivity().runOnUiThread(() -> {
viewModel.updateConversationLocally(dto);
if (activeConversationId != null && activeConversationId.equals(dto.getId())) {
setConversationActive(true);
setConversationActive(true, dto.getStatus());
binding.tvChatTitle.setText(viewModel.getCustomerName(dto.getCustomerId()));
}
});
@@ -424,8 +479,10 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
}
}
private void setConversationActive(boolean active) {
UIUtils.setViewsEnabled(active, binding.btnSend, binding.etMessage, binding.btnAttach);
private void setConversationActive(boolean active, String status) {
boolean isClosed = "CLOSED".equalsIgnoreCase(status);
UIUtils.setViewsEnabled(active && !isClosed, binding.btnSend, binding.etMessage, binding.btnAttach);
if (!active) {
activeConversationId = null;
ChatNotificationService.activeConversationIdInUi = null;
@@ -437,7 +494,7 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
binding.etMessage.setText("");
binding.etMessage.setHint("Select a chat to start messaging");
} else {
binding.etMessage.setHint("Type a message...");
binding.etMessage.setHint(isClosed ? "This chat is closed" : "Type a message...");
ChatNotificationService.activeConversationIdInUi = activeConversationId;
}
}
@@ -449,4 +506,4 @@ public class ChatFragment extends Fragment implements ChatAdapter.OnChatClickLis
ChatNotificationService.activeConversationIdInUi = null;
if (stompChatManager != null) stompChatManager.disconnect();
}
}
}

View File

@@ -6,13 +6,15 @@ public class Chat {
private String lastMessage;
private Long customerId;
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.customerName = customerName;
this.lastMessage = lastMessage;
this.customerId = customerId;
this.staffId = staffId;
this.status = status;
}
public String getChatId() {
@@ -34,4 +36,8 @@ public class Chat {
public Long getStaffId() {
return staffId;
}
public String getStatus() {
return status;
}
}

View File

@@ -19,6 +19,7 @@ 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;
@@ -32,10 +33,13 @@ public class ChatListViewModel extends ViewModel {
private final ChatRepository chatRepository;
private final CustomerRepository customerRepository;
private final MutableLiveData<List<Chat>> chatList = new MutableLiveData<>(new ArrayList<>());
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) {
@@ -43,10 +47,19 @@ public class ChatListViewModel extends ViewModel {
this.customerRepository = customerRepository;
}
public LiveData<List<Chat>> getChatList() { return chatList; }
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() {
customerRepository.getAllCustomers(0, 100).observeForever(resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
@@ -62,12 +75,19 @@ public class ChatListViewModel extends ViewModel {
isLoading.setValue(true);
chatRepository.getAllConversations().observeForever(resource -> {
if (resource != null && resource.status == Resource.Status.SUCCESS && resource.data != null) {
List<Chat> chats = new ArrayList<>();
List<Chat> active = new ArrayList<>();
List<Chat> closed = new ArrayList<>();
for (ConversationDTO dto : resource.data) {
String name = customerNames.getOrDefault(dto.getCustomerId(), "Customer #" + dto.getCustomerId());
chats.add(new Chat(String.valueOf(dto.getId()), name, dto.getLastMessage(), dto.getCustomerId(), dto.getStaffId()));
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);
}
}
chatList.setValue(chats);
activeChats.setValue(active);
closedChats.setValue(closed);
isLoading.setValue(false);
} else if (resource != null && resource.status == Resource.Status.ERROR) {
isLoading.setValue(false);
@@ -106,21 +126,24 @@ public class ChatListViewModel extends ViewModel {
}
public void updateConversationLocally(ConversationDTO dto) {
List<Chat> current = new ArrayList<>(chatList.getValue());
boolean updated = false;
String name = customerNames.getOrDefault(dto.getCustomerId(), "Customer #" + dto.getCustomerId());
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()));
current.set(i, new Chat(String.valueOf(dto.getId()), name, dto.getLastMessage(), dto.getCustomerId(), dto.getStaffId(), dto.getStatus()));
updated = true;
break;
}
}
if (!updated) {
current.add(0, new Chat(String.valueOf(dto.getId()), name, dto.getLastMessage(), dto.getCustomerId(), dto.getStaffId()));
}
chatList.setValue(current);
if (updated) liveData.setValue(current);
}
private Message dtoToModel(MessageDTO dto) {

View File

@@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/chatDrawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
@@ -125,29 +126,101 @@
</LinearLayout>
<LinearLayout
<androidx.core.widget.NestedScrollView
android:id="@+id/chatListDrawer"
android:layout_width="260dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:orientation="vertical"
android:background="@color/primary_dark"
android:padding="16dp">
android:background="@color/primary_dark">
<TextView
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Active Chats"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp"/>
android:orientation="vertical"
android:paddingTop="24dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvChatList"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<LinearLayout
android:id="@+id/headerActiveChats"
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: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>