From eb61935ce6975f90a31392584442ea26660c5155 Mon Sep 17 00:00:00 2001 From: scimmiamorta <ale.beni95@gmail.com> Date: Mon, 23 Dec 2024 13:44:54 +0100 Subject: [PATCH] First Commit --- app/build.gradle.kts | 1 + .../taskmanagement/auth/LoginFragment.kt | 10 + .../taskmanagement/auth/ProfileFragment.kt | 180 ++++------ .../com/example/taskmanagement/chat/Chat.kt | 2 +- .../taskmanagement/chat/ChatFragment.kt | 2 +- .../taskmanagement/chat/ChatViewAdapter.kt | 6 +- .../taskmanagement/chat/DialogFragment.kt | 6 +- .../chat/UserSelectionFragment.kt | 52 ++- .../taskmanagement/task/HomeFragment.kt | 252 +++++++------ .../taskmanagement/task/SearchFragment.kt | 155 +++++--- .../taskmanagement/task/SearchSubFragment.kt | 31 +- .../taskmanagement/task/SubTaskFragment.kt | 11 +- .../taskmanagement/task/TaskAdapter.kt | 2 +- app/src/main/res/layout/fragment_home.xml | 50 ++- app/src/main/res/layout/fragment_profile.xml | 333 +++++++++--------- app/src/main/res/layout/fragment_search.xml | 28 +- app/src/main/res/layout/fragment_sub_task.xml | 28 +- .../res/layout/fragment_user_selection.xml | 14 + app/src/main/res/values-it/strings.xml | 8 + app/src/main/res/values/strings.xml | 9 + gradle/libs.versions.toml | 2 + 21 files changed, 685 insertions(+), 497 deletions(-) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad013af..b50f326 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -52,6 +52,7 @@ dependencies { implementation(libs.firebase.auth.ktx) implementation(libs.androidx.navigation.fragment.ktx) implementation(libs.androidx.navigation.ui.ktx) + implementation(libs.androidx.swiperefreshlayout) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/example/taskmanagement/auth/LoginFragment.kt b/app/src/main/java/com/example/taskmanagement/auth/LoginFragment.kt index d57d43a..e91919d 100644 --- a/app/src/main/java/com/example/taskmanagement/auth/LoginFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/auth/LoginFragment.kt @@ -126,6 +126,8 @@ class LoginFragment : Fragment() { requireContext(), getString(R.string.login_succeffully), Toast.LENGTH_SHORT ).show() + val role = document.getString("role") + saveUserRoleLocally(role) findNavController().navigate(R.id.action_loginFragment_to_homeFragment) } } else { @@ -159,6 +161,14 @@ class LoginFragment : Fragment() { editor.putString("user", userJson) editor.apply() } + private fun saveUserRoleLocally(role: String?) { + val sharedPreferences = + requireContext().getSharedPreferences("TaskManagerPrefs", Context.MODE_PRIVATE) + val editor = sharedPreferences.edit() + editor.putString("role", role) + editor.apply() + } + private fun init() { mAuth = FirebaseAuth.getInstance() diff --git a/app/src/main/java/com/example/taskmanagement/auth/ProfileFragment.kt b/app/src/main/java/com/example/taskmanagement/auth/ProfileFragment.kt index 616429b..99b81f0 100644 --- a/app/src/main/java/com/example/taskmanagement/auth/ProfileFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/auth/ProfileFragment.kt @@ -4,159 +4,107 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ArrayAdapter import android.widget.Toast import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController import com.example.taskmanagement.R import com.example.taskmanagement.databinding.FragmentProfileBinding +import com.example.taskmanagement.task.HomeFragment import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.firestore.DocumentSnapshot import com.google.firebase.firestore.FirebaseFirestore +import com.google.firebase.firestore.QuerySnapshot -class ProfileFragment : Fragment(R.layout.fragment_profile) { +class ProfileFragment : Fragment() { private lateinit var binding: FragmentProfileBinding + private var firebaseAuth: FirebaseAuth? = null + private var firestore: FirebaseFirestore? = null - private val auth: FirebaseAuth = FirebaseAuth.getInstance() - private val firestore: FirebaseFirestore = FirebaseFirestore.getInstance() + private var taskList: MutableList<String> = mutableListOf() + private var taskAdapter: ArrayAdapter<String>? = null override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View { + ): View? { binding = FragmentProfileBinding.inflate(inflater, container, false) - return binding.root - } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - val currentUser = auth.currentUser - binding.userName.text = getString(R.string.welcome_message, currentUser?.email ?: "Utente") - binding.userMail.text = currentUser?.email + firebaseAuth = FirebaseAuth.getInstance() + firestore = FirebaseFirestore.getInstance() - // Conta i task per stato - loadTaskCounts() + taskAdapter = ArrayAdapter(requireContext(), android.R.layout.simple_list_item_1, taskList) + binding.assignedTasksList.adapter = taskAdapter + loadUserData() - // Logout binding.buttonLogout.setOnClickListener { - auth.signOut() - findNavController().navigate(R.id.action_profileFragment_to_welcomeFragment) + firebaseAuth?.signOut() + Toast.makeText(context, "Logout effettuato", Toast.LENGTH_SHORT).show() + HomeFragment.deleteSavedTasksFiles(requireContext()) + findNavController().popBackStack(R.id.welcomeFragment, false) } + + return binding.root } - private fun loadTaskCounts() { - val currentUser = auth.currentUser - if (currentUser == null) { - Toast.makeText(requireContext(), "Utente non autenticato.", Toast.LENGTH_SHORT).show() - return - } + private fun loadUserData() { + val userId = firebaseAuth!!.currentUser!!.uid - val userEmail = currentUser.email ?: return - countTasksByState( - userEmail = userEmail, - onSuccess = { counts -> - binding.initialTask.text = counts["Initial"]?.toString() ?: "0" - binding.inprogressTask.text = counts["inProgress"]?.toString() ?: "0" - binding.completeTask.text = counts["Complete"]?.toString() ?: "0" - }, - onFailure = { exception -> - Toast.makeText( - requireContext(), - "Errore nel caricamento dei task: ${exception.message}", - Toast.LENGTH_SHORT - ).show() + binding.userName.text = firebaseAuth!!.currentUser!!.email + binding.welcomeMessage.text = "Benvenuto, ${firebaseAuth!!.currentUser!!.email}" + + firestore!!.collection("tasks") + .whereEqualTo("assignedTo", userId) + .get() + .addOnSuccessListener { queryDocumentSnapshots: QuerySnapshot -> + taskList.clear() + for (snapshot in queryDocumentSnapshots) { + val taskName = snapshot.getString("taskName") + val deadline = snapshot.getString("deadline") + taskList.add("$taskName - Scadenza: $deadline") + } + taskAdapter!!.notifyDataSetChanged() } - ) - - countAllTasks( - userEmail = userEmail, - onSuccess = { totalCount -> - binding.assignedTaskCount.text = totalCount.toString() - }, - onFailure = { exception -> + .addOnFailureListener { Toast.makeText( - requireContext(), - "Errore nel caricamento dei task totali: ${exception.message}", + context, + "Errore nel caricamento dei task", Toast.LENGTH_SHORT ).show() } - ) - val userUID = currentUser.uid - firestore.collection("users") - .document(userUID) + firestore!!.collection("tasks") + .whereEqualTo("assignedTo", userId) .get() - .addOnSuccessListener { documentSnapshot -> - if (documentSnapshot.exists()) { - val role = documentSnapshot.getString("role") ?: getString(R.string.role_not_assigned) - binding.group.text = role - } else { - binding.group.text = getString(R.string.role_not_found) - } - - } - .addOnFailureListener { exception -> - val errorMessage = getString(R.string.error_loading_role, exception.message) - Toast.makeText(requireContext(), errorMessage, Toast.LENGTH_SHORT).show() - } - - } - - private fun countTasksByState( - userEmail: String, - onSuccess: (Map<String, Int>) -> Unit, - onFailure: (Exception) -> Unit - ) { - val states = listOf( - getString(R.string.state_initial), - getString(R.string.state_in_progress), - getString(R.string.state_complete) - ) - - val taskCounts = mutableMapOf<String, Int>() - val firestore = FirebaseFirestore.getInstance() - - var queriesCompleted = 0 - var queryFailed = false - - for (state in states) { - firestore.collection("tasks") - .whereEqualTo("assignedTo", userEmail) - .whereEqualTo("state", state) - .get() - .addOnSuccessListener { querySnapshot -> - if (!queryFailed) { - taskCounts[state] = querySnapshot.size() - queriesCompleted++ - - if (queriesCompleted == states.size) { - onSuccess(taskCounts) - } - } - } - .addOnFailureListener { exception -> - if (!queryFailed) { - queryFailed = true - onFailure(exception) + .addOnSuccessListener { queryDocumentSnapshots: QuerySnapshot -> + var completed = 0 + var todo = 0 + var assigned = 0 + + for (snapshot in queryDocumentSnapshots) { + val status = snapshot.getString("status") + if (status == "Completed") { + completed++ + } else if (status == "Assigned") { + assigned++ + } else if (status == "Todo") { + todo++ } } - } - } - private fun countAllTasks( - userEmail: String, - onSuccess: (Int) -> Unit, - onFailure: (Exception) -> Unit - ) { - firestore.collection("tasks") - .whereEqualTo("assignedTo", userEmail) - .get() - .addOnSuccessListener { querySnapshot -> - onSuccess(querySnapshot.size()) + binding.completedProjects.text = completed.toString() + binding.projectsToStart.text = todo.toString() + binding.averageCompletionTime.text = assigned.toString() // Projects Assigned } - .addOnFailureListener { exception -> - onFailure(exception) + .addOnFailureListener { + Toast.makeText( + context, + "Errore nel caricamento delle statistiche", + Toast.LENGTH_SHORT + ).show() } } diff --git a/app/src/main/java/com/example/taskmanagement/chat/Chat.kt b/app/src/main/java/com/example/taskmanagement/chat/Chat.kt index 75d0d1c..d02741d 100644 --- a/app/src/main/java/com/example/taskmanagement/chat/Chat.kt +++ b/app/src/main/java/com/example/taskmanagement/chat/Chat.kt @@ -3,5 +3,5 @@ package com.example.taskmanagement.chat data class Chat( var id: String = "", val participants: List<String> = listOf(), - val lastMessage: Message? = null + var lastMessage: Message? = null ) diff --git a/app/src/main/java/com/example/taskmanagement/chat/ChatFragment.kt b/app/src/main/java/com/example/taskmanagement/chat/ChatFragment.kt index 8098a52..ca0cfad 100644 --- a/app/src/main/java/com/example/taskmanagement/chat/ChatFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/chat/ChatFragment.kt @@ -188,7 +188,7 @@ class ChatFragment : Fragment() { Log.d("ChatFragment", "Chat with ID: $chatId already exists.") } else { val participants = - listOf(currentUserId, receiverId).sorted() // Ordina i partecipanti + listOf(currentUserId, receiverId).sorted() val chatData = hashMapOf( "participants" to participants ) diff --git a/app/src/main/java/com/example/taskmanagement/chat/ChatViewAdapter.kt b/app/src/main/java/com/example/taskmanagement/chat/ChatViewAdapter.kt index 81beda3..fd8a20c 100644 --- a/app/src/main/java/com/example/taskmanagement/chat/ChatViewAdapter.kt +++ b/app/src/main/java/com/example/taskmanagement/chat/ChatViewAdapter.kt @@ -31,8 +31,8 @@ class ChatViewAdapter( holder.chatName.text = otherParticipant ?: holder.itemView.context.getString(R.string.user_not_authenticated) - holder.lastMessage.text = - chat.lastMessage?.text ?: holder.itemView.context.getString(R.string.no_message) + holder.lastMessage.text = chat.lastMessage?.text ?: + holder.itemView.context.getString(R.string.no_message) holder.itemView.setOnClickListener { onChatClicked(chat) @@ -40,5 +40,7 @@ class ChatViewAdapter( } + + override fun getItemCount() = chatList.size } diff --git a/app/src/main/java/com/example/taskmanagement/chat/DialogFragment.kt b/app/src/main/java/com/example/taskmanagement/chat/DialogFragment.kt index b80a96e..f70cee5 100644 --- a/app/src/main/java/com/example/taskmanagement/chat/DialogFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/chat/DialogFragment.kt @@ -1,5 +1,6 @@ package com.example.taskmanagement.chat +import android.content.Context import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -51,8 +52,9 @@ class DialogFragment : DialogFragment() { firestore.collection("users") .document(currentUser.uid) .get() - .addOnSuccessListener { document -> - val currentUserRole = document.getString("role") ?: return@addOnSuccessListener + .addOnSuccessListener { + val sharedPrefs = requireContext().getSharedPreferences("TaskManagerPrefs", Context.MODE_PRIVATE) + val currentUserRole = sharedPrefs.getString("role", "defaultRole")?: return@addOnSuccessListener val rolesToLoad = when (currentUserRole) { "PM" -> listOf("PL") diff --git a/app/src/main/java/com/example/taskmanagement/chat/UserSelectionFragment.kt b/app/src/main/java/com/example/taskmanagement/chat/UserSelectionFragment.kt index c2ecaf3..011ffa8 100644 --- a/app/src/main/java/com/example/taskmanagement/chat/UserSelectionFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/chat/UserSelectionFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.TextView import android.widget.Toast import androidx.fragment.app.Fragment import androidx.navigation.fragment.findNavController @@ -22,6 +23,7 @@ class UserSelectionFragment : Fragment() { private val currentUser = FirebaseAuth.getInstance().currentUser private val chatList = mutableListOf<Chat>() private lateinit var chatAdapter: ChatViewAdapter + private lateinit var noChatsMessage: TextView override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -31,6 +33,7 @@ class UserSelectionFragment : Fragment() { recyclerViewChats = view.findViewById(R.id.recyclerViewChats) btnNewChat = view.findViewById(R.id.newChatButton) + noChatsMessage= view.findViewById(R.id.noChatsMessage) setupRecyclerView() loadChats() @@ -74,13 +77,21 @@ class UserSelectionFragment : Fragment() { .addOnSuccessListener { documents -> if (isAdded) { chatList.clear() - for (document in documents) { - val chat = document.toObject(Chat::class.java).apply { - id = document.id + if (documents.isEmpty) { + noChatsMessage.visibility = View.VISIBLE + recyclerViewChats.visibility = View.GONE + } else { + noChatsMessage.visibility = View.GONE + recyclerViewChats.visibility = View.VISIBLE + + for (document in documents) { + val chat = document.toObject(Chat::class.java).apply { + id = document.id + } + loadLastMessage(chat) } - chatList.add(chat) } - chatAdapter.notifyDataSetChanged() + } } .addOnFailureListener { e -> @@ -94,6 +105,37 @@ class UserSelectionFragment : Fragment() { } } + private fun loadLastMessage(chat: Chat) { + firestore.collection("chats") + .document(chat.id) + .collection("messages") + .orderBy("timestamp", com.google.firebase.firestore.Query.Direction.DESCENDING) + .limit(1) + .get() + .addOnSuccessListener { messages -> + if (isAdded) { + if (!messages.isEmpty) { + val lastMessage = messages.documents[0].toObject(Message::class.java) + chat.lastMessage = lastMessage + } + chatList.add(chat) + chatAdapter.notifyDataSetChanged() + } + } + .addOnFailureListener { e -> + if (isAdded) { + Toast.makeText( + requireContext(), + getString(R.string.error_loading_last_message, e.message ?: "Unknown error"), + Toast.LENGTH_SHORT + ).show() + } + } + } + + + + private fun openChat(chat: Chat) { val currentUserId = currentUser?.email if (currentUserId.isNullOrEmpty()) { diff --git a/app/src/main/java/com/example/taskmanagement/task/HomeFragment.kt b/app/src/main/java/com/example/taskmanagement/task/HomeFragment.kt index f3af89d..47c8e69 100644 --- a/app/src/main/java/com/example/taskmanagement/task/HomeFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/task/HomeFragment.kt @@ -1,12 +1,11 @@ package com.example.taskmanagement.task - +import android.content.Context import android.os.Bundle import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast import androidx.fragment.app.Fragment @@ -14,17 +13,19 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.example.taskmanagement.R import com.google.firebase.auth.FirebaseAuth import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.QueryDocumentSnapshot +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await +import java.io.File class HomeFragment : Fragment() { private lateinit var taskRecyclerView: RecyclerView - private lateinit var progressBar: ProgressBar private lateinit var taskAdapter: TaskAdapter private lateinit var fabMain: com.google.android.material.floatingactionbutton.FloatingActionButton @@ -33,6 +34,8 @@ class HomeFragment : Fragment() { private val auth = FirebaseAuth.getInstance() private lateinit var noTasksMessage: TextView + private lateinit var swipeRefreshLayout: SwipeRefreshLayout + private val taskIdMap = mutableMapOf<String, String>() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -40,9 +43,9 @@ class HomeFragment : Fragment() { ): View? { val view = inflater.inflate(R.layout.fragment_home, container, false) taskRecyclerView = view.findViewById(R.id.task_list) - progressBar = view.findViewById(R.id.progressBar) fabMain = view.findViewById(R.id.fab_main) noTasksMessage = view.findViewById(R.id.noTasksMessage) + swipeRefreshLayout = view.findViewById(R.id.swipeRefreshLayout) taskRecyclerView.layoutManager = LinearLayoutManager(requireContext()) taskAdapter = @@ -51,23 +54,37 @@ class HomeFragment : Fragment() { val currentUser = auth.currentUser if (currentUser != null) { - loadTasks(currentUser.uid) + loadTasksFromFile() + if (taskList.isEmpty()) { + loadTasks() + } + } else { + Toast.makeText( + requireContext(), + getString(R.string.failed_to_load_tasks), + Toast.LENGTH_SHORT + ).show() } fabMain.setOnClickListener { findNavController().navigate(R.id.action_homeFragment_to_addTaskFragment) } + swipeRefreshLayout.setOnRefreshListener { + currentUser?.let { + loadTasks() + } + } + return view } - private fun loadTasks(userId: String) { - progressBar.visibility = View.VISIBLE + private fun loadTasks() { + swipeRefreshLayout.isRefreshing = true lifecycleScope.launch { try { - val document = firestore.collection("users").document(userId).get().await() - val role = document.getString("role") - + val sharedPrefs = requireContext().getSharedPreferences("TaskManagerPrefs", Context.MODE_PRIVATE) + val role = sharedPrefs.getString("role", "defaultRole") taskList.clear() taskIdMap.clear() @@ -78,12 +95,9 @@ class HomeFragment : Fragment() { } if (isAdded) { - if (taskList.isEmpty()) { - noTasksMessage.visibility = View.VISIBLE - } else { - noTasksMessage.visibility = View.GONE - } + noTasksMessage.visibility = if (taskList.isEmpty()) View.VISIBLE else View.GONE } + saveTasksToFile() } catch (e: Exception) { if (isAdded) { @@ -95,48 +109,33 @@ class HomeFragment : Fragment() { } } finally { if (isAdded) { - progressBar.visibility = View.GONE + swipeRefreshLayout.isRefreshing = false taskAdapter.notifyDataSetChanged() } } } } - private val taskIdMap = mutableMapOf<Task, String>() - private suspend fun loadTasksForPM() { + fabMain.visibility = View.VISIBLE try { val currentUserEmail = auth.currentUser?.email ?: return - val createdByPMResult = firestore.collection("tasks") .whereEqualTo("createdBy", currentUserEmail) .get() .await() - // Inizializza una lista per i task - val allTasks = mutableListOf<Task>() - - // Processa i task creati dal Project Manager - createdByPMResult.documents.forEach { document -> + for (document in createdByPMResult.documents) { val task = document.toObject(Task::class.java) - task?.let { - allTasks.add(it) - taskIdMap[it] = document.id + if (task != null) { + calculateSubTasks(task, document.id) + taskList.add(task) + taskIdMap[task.name] = document.id } } + taskList.sortBy { it.name } - // Aggiorna i task aggiuntivi con il calcolo dei subtasks - for (task in allTasks) { - calculateSubTasks(task, taskIdMap[task] ?: return) - } - - // Aggiungi i task caricati alla lista principale e aggiorna l'interfaccia utente - taskList.clear() - taskList.addAll(allTasks) - - if (isAdded) { - fabMain.visibility = View.VISIBLE - } + if (isAdded) fabMain.visibility = View.VISIBLE } catch (e: Exception) { if (isAdded) { @@ -149,11 +148,11 @@ class HomeFragment : Fragment() { } } - - private suspend fun loadTasksForPL() { + fabMain.visibility = View.VISIBLE try { val currentUserEmail = auth.currentUser?.email ?: return + val createdByPLResult = firestore.collection("tasks") .whereEqualTo("createdBy", currentUserEmail) .get() @@ -164,37 +163,18 @@ class HomeFragment : Fragment() { .get() .await() - val allTasks = mutableListOf<Task>() - - createdByPLResult.documents.forEach { document -> - val task = document.toObject(Task::class.java) - task?.let { - allTasks.add(it) - taskIdMap[it] = document.id - } - } - - assignedToPLResult.documents.forEach { document -> + for (document in createdByPLResult.documents + assignedToPLResult.documents) { val task = document.toObject(Task::class.java) - task?.let { - if (!taskIdMap.containsKey(it)) { - allTasks.add(it) - taskIdMap[it] = document.id + if (task != null) { + calculateSubTasks(task, document.id) + if (!taskIdMap.containsKey(task.name)) { + taskList.add(task) + taskIdMap[task.name] = document.id } } } - for (task in allTasks) { - calculateSubTasks(task, taskIdMap[task] ?: return) - } - - taskList.clear() - taskList.addAll(allTasks) - - if (isAdded) { - fabMain.visibility = View.VISIBLE - } - + taskList.sortBy { it.name } } catch (e: Exception) { if (isAdded) { Toast.makeText( @@ -206,43 +186,27 @@ class HomeFragment : Fragment() { } } - - - - private suspend fun loadTasksForDev() { + fabMain.visibility = View.GONE try { val currentUserEmail = auth.currentUser?.email ?: return + val assignedToDevResult = firestore.collection("tasks") .whereEqualTo("assignedTo", currentUserEmail) .get() .await() - // Inizializza una lista per i task - val allTasks = mutableListOf<Task>() - - // Processa i task assegnati al Developer - assignedToDevResult.documents.forEach { document -> + for (document in assignedToDevResult.documents) { val task = document.toObject(Task::class.java) - task?.let { - allTasks.add(it) - taskIdMap[it] = document.id + if (task != null) { + calculateSubTasks(task, document.id) + taskList.add(task) + taskIdMap[task.name] = document.id } } - // Aggiorna i task aggiuntivi con il calcolo dei subtasks - for (task in allTasks) { - calculateSubTasks(task, taskIdMap[task] ?: return) - } - - // Aggiungi i task caricati alla lista principale e aggiorna l'interfaccia utente - taskList.clear() - taskList.addAll(allTasks) - - if (isAdded) { - fabMain.visibility = View.VISIBLE - } - + taskList.sortBy { it.name } + if (isAdded) fabMain.visibility = View.VISIBLE } catch (e: Exception) { if (isAdded) { Toast.makeText( @@ -254,8 +218,6 @@ class HomeFragment : Fragment() { } } - - private suspend fun calculateSubTasks(task: Task, taskId: String) { try { val subTasksResult = firestore.collection("tasks") @@ -264,12 +226,8 @@ class HomeFragment : Fragment() { .get() .await() - val progressList = mutableListOf<Int>() - for (subTaskDocument in subTasksResult) { - val subTask = subTaskDocument.toObject(SubTask::class.java) - progressList.add(subTask.progress) - Log.d("TaskAdapter", "Subtask progress: ${subTask.progress}") - } + val progressList = + subTasksResult.mapNotNull { it.toObject(SubTask::class.java).progress } val averageProgress = if (progressList.isNotEmpty()) { progressList.average().toInt() @@ -287,23 +245,111 @@ class HomeFragment : Fragment() { Toast.LENGTH_SHORT ).show() } + } + private fun saveTasksToFile() { + val tasksJson = Gson().toJson(taskList) + val taskIdMapJson = Gson().toJson(taskIdMap) + + val tasksFile = File(requireContext().filesDir, "taskList.json") + tasksFile.writeText(tasksJson) + + val taskIdMapFile = File(requireContext().filesDir, "taskIdMap.json") + taskIdMapFile.writeText(taskIdMapJson) + + Log.d("SearchFragment", "Saved taskList and taskIdMap separately") } + private fun loadTasksFromFile() { + val tasksFile = File(requireContext().filesDir, "taskList.json") + val taskIdMapFile = File(requireContext().filesDir, "taskIdMap.json") + + if (tasksFile.exists() && taskIdMapFile.exists()) { + try { + val tasksJson = tasksFile.readText() + val taskIdMapJson = taskIdMapFile.readText() + + val savedTasks = Gson().fromJson(tasksJson, Array<Task>::class.java).toList() + val savedTaskIdMap = Gson().fromJson(taskIdMapJson, Map::class.java) + + taskList.clear() + taskList.addAll(savedTasks) + taskIdMap.clear() + + savedTaskIdMap.entries.forEach { + taskIdMap[it.key as String] = it.value as String + } + + taskAdapter.notifyDataSetChanged() + + } catch (e: JsonSyntaxException) { + Log.e("SearchFragment", "Error loading tasks: ${e.message}") + e.printStackTrace() + } + } + } + private fun onTaskClick(task: Task) { - val taskId = taskIdMap[task] + val sharedPrefs = + requireContext().getSharedPreferences("TaskManagerPrefs", Context.MODE_PRIVATE) + val role = sharedPrefs.getString("role", "defaultRole") + Log.d("HomeFragment", "Role: $role") + + if (role == "PM") { + return + } + val taskId = taskIdMap[task.name] val bundle = Bundle().apply { putString("taskId", taskId) } findNavController().navigate(R.id.action_homeFragment_to_subTaskFragment, bundle) } + private fun onEditTask(task: Task) { - val taskId = taskIdMap[task] + val taskId = taskIdMap[task.name] val bundle = Bundle().apply { putString("taskId", taskId) } findNavController().navigate(R.id.action_homeFragment_to_modifyTaskFragment, bundle) } + + companion object { + fun deleteSavedTasksFiles(context: Context) { + try { + val tasksFile = File(context.filesDir, "taskList.json") + val taskIdMapFile = File(context.filesDir, "taskIdMap.json") + + var deletedFilesCount = 0 + if (tasksFile.exists()) { + tasksFile.delete() + deletedFilesCount++ + } + if (taskIdMapFile.exists()) { + taskIdMapFile.delete() + deletedFilesCount++ + } + + val message = if (deletedFilesCount > 0) { + context.getString(R.string.files_deleted_successfully) + } else { + context.getString(R.string.no_files_to_delete) + } + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + + Log.d("HomeFragment", "Saved files deleted successfully") + } catch (e: Exception) { + Toast.makeText( + context, + context.getString(R.string.error_deleting_files), + Toast.LENGTH_SHORT + ).show() + Log.e("HomeFragment", "Error deleting saved files: ${e.message}") + e.printStackTrace() + } + } + } + } + diff --git a/app/src/main/java/com/example/taskmanagement/task/SearchFragment.kt b/app/src/main/java/com/example/taskmanagement/task/SearchFragment.kt index 6ab1624..5ab2d15 100644 --- a/app/src/main/java/com/example/taskmanagement/task/SearchFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/task/SearchFragment.kt @@ -1,5 +1,6 @@ package com.example.taskmanagement.task +import android.content.Context import android.os.Bundle import android.util.Log import android.view.LayoutInflater @@ -13,13 +14,15 @@ import androidx.lifecycle.lifecycleScope import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import com.example.taskmanagement.R - import com.google.firebase.auth.FirebaseAuth import com.google.firebase.firestore.FirebaseFirestore -import com.google.firebase.firestore.QueryDocumentSnapshot +import com.google.gson.Gson +import com.google.gson.JsonSyntaxException import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await +import java.io.File class SearchFragment : Fragment() { @@ -30,6 +33,8 @@ class SearchFragment : Fragment() { private val firestore = FirebaseFirestore.getInstance() private val auth = FirebaseAuth.getInstance() private lateinit var noTasksMessage: TextView + private lateinit var swipeRefreshSearch: SwipeRefreshLayout + private val taskIdMap = mutableMapOf<String, String>() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -37,8 +42,9 @@ class SearchFragment : Fragment() { ): View? { val view = inflater.inflate(R.layout.fragment_search, container, false) searchView = view.findViewById(R.id.search_view) - recyclerView = view.findViewById(R.id.recycler_view) + recyclerView = view.findViewById(R.id.searchList) noTasksMessage = view.findViewById(R.id.noTasksMessage) + swipeRefreshSearch = view.findViewById(R.id.swipeRefreshSearch) recyclerView.layoutManager = LinearLayoutManager(requireContext()) taskAdapter = @@ -47,7 +53,31 @@ class SearchFragment : Fragment() { val currentUser = auth.currentUser if (currentUser != null) { - loadTasks(currentUser.uid) + + loadTasksFromFile() + + if (taskList.isEmpty()) { + loadTasks() + } + } else { + Toast.makeText( + requireContext(), + getString(R.string.failed_to_load_tasks), + Toast.LENGTH_SHORT + ).show() + } + + swipeRefreshSearch.setOnRefreshListener { + val currentUsers = auth.currentUser + if (currentUsers != null) { + loadTasks() + } else { + Toast.makeText( + requireContext(), + getString(R.string.failed_to_load_tasks), + Toast.LENGTH_SHORT + ).show() + } } searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { @@ -64,11 +94,14 @@ class SearchFragment : Fragment() { return view } - private fun loadTasks(userId: String) { + private fun loadTasks() { + if (!swipeRefreshSearch.isRefreshing) { + swipeRefreshSearch.isRefreshing = true + } lifecycleScope.launch { try { - val document = firestore.collection("users").document(userId).get().await() - val role = document.getString("role") + val sharedPrefs = requireContext().getSharedPreferences("TaskManagerPrefs", Context.MODE_PRIVATE) + val role = sharedPrefs.getString("role", "defaultRole") taskList.clear() taskIdMap.clear() @@ -81,13 +114,14 @@ class SearchFragment : Fragment() { if (isAdded) { if (taskList.isEmpty()) { - noTasksMessage.visibility = View.VISIBLE } else { noTasksMessage.visibility = View.GONE } } + saveTasksToFile() + } catch (e: Exception) { if (isAdded) { Toast.makeText( @@ -98,13 +132,13 @@ class SearchFragment : Fragment() { } } finally { if (isAdded) { + swipeRefreshSearch.isRefreshing = false taskAdapter.notifyDataSetChanged() } } } } - private val taskIdMap = mutableMapOf<Task, String>() private suspend fun loadTasksForPM() { try { @@ -115,17 +149,15 @@ class SearchFragment : Fragment() { .get() .await() - val allTasks = mutableListOf<QueryDocumentSnapshot>() - allTasks.addAll(createdByPMResult.documents as List<QueryDocumentSnapshot>) - - for (document in allTasks) { + for (document in createdByPMResult.documents) { val task = document.toObject(Task::class.java) - taskList.add(task) - taskIdMap[task] = document.id - - calculateSubTasks(task, document.id) + if (task != null) { + taskList.add(task) + taskIdMap[task.name] = document.id + calculateSubTasks(task, document.id) + } + taskList.sortBy { it.name } } - } catch (e: Exception) { if (isAdded) { Toast.makeText( @@ -141,6 +173,7 @@ class SearchFragment : Fragment() { private suspend fun loadTasksForPL() { try { val currentUserEmail = auth.currentUser?.email ?: return + val createdByPLResult = firestore.collection("tasks") .whereEqualTo("createdBy", currentUserEmail) .get() @@ -151,21 +184,17 @@ class SearchFragment : Fragment() { .get() .await() - val allTasks = mutableListOf<QueryDocumentSnapshot>() - allTasks.addAll(createdByPLResult.documents as List<QueryDocumentSnapshot>) - allTasks.addAll(assignedToPLResult.documents as List<QueryDocumentSnapshot>) - - for (document in allTasks) { + for (document in createdByPLResult.documents + assignedToPLResult.documents) { val task = document.toObject(Task::class.java) - - - calculateSubTasks(task, document.id) - - if (!taskIdMap.containsKey(task)) { - taskList.add(task) - taskIdMap[task] = document.id + if (task != null) { + calculateSubTasks(task, document.id) + if (!taskIdMap.containsKey(task.name)) { + taskList.add(task) + taskIdMap[task.name] = document.id + } } } + taskList.sortBy { it.name } } catch (e: Exception) { if (isAdded) { Toast.makeText( @@ -177,27 +206,24 @@ class SearchFragment : Fragment() { } } - private suspend fun loadTasksForDev() { try { val currentUserEmail = auth.currentUser?.email ?: return + val assignedToDevResult = firestore.collection("tasks") .whereEqualTo("assignedTo", currentUserEmail) .get() .await() - val allTasks = mutableListOf<QueryDocumentSnapshot>() - allTasks.addAll(assignedToDevResult.documents as List<QueryDocumentSnapshot>) - - for (document in allTasks) { + for (document in assignedToDevResult.documents) { val task = document.toObject(Task::class.java) - - calculateSubTasks(task, document.id) - - taskList.add(task) - taskIdMap[task] = document.id + if (task != null) { + calculateSubTasks(task, document.id) + taskList.add(task) + taskIdMap[task.name] = document.id + } } - + taskList.sortBy { it.name } } catch (e: Exception) { if (isAdded) { Toast.makeText( @@ -209,7 +235,6 @@ class SearchFragment : Fragment() { } } - private suspend fun calculateSubTasks(task: Task, taskId: String) { try { val subTasksResult = firestore.collection("tasks") @@ -241,12 +266,54 @@ class SearchFragment : Fragment() { Toast.LENGTH_SHORT ).show() } + } + + private fun saveTasksToFile() { + val tasksJson = Gson().toJson(taskList) + val taskIdMapJson = Gson().toJson(taskIdMap) + + val tasksFile = File(requireContext().filesDir, "taskList.json") + tasksFile.writeText(tasksJson) + + val taskIdMapFile = File(requireContext().filesDir, "taskIdMap.json") + taskIdMapFile.writeText(taskIdMapJson) + Log.d("SearchFragment", "Saved taskList and taskIdMap separately") } + private fun loadTasksFromFile() { + val tasksFile = File(requireContext().filesDir, "taskList.json") + val taskIdMapFile = File(requireContext().filesDir, "taskIdMap.json") + + if (tasksFile.exists() && taskIdMapFile.exists()) { + try { + val tasksJson = tasksFile.readText() + val taskIdMapJson = taskIdMapFile.readText() + + val savedTasks = Gson().fromJson(tasksJson, Array<Task>::class.java).toList() + val savedTaskIdMap = Gson().fromJson(taskIdMapJson, Map::class.java) + + taskList.clear() + taskList.addAll(savedTasks) + taskIdMap.clear() + + savedTaskIdMap.entries.forEach { + taskIdMap[it.key as String] = it.value as String + } + + taskAdapter.notifyDataSetChanged() + + } catch (e: JsonSyntaxException) { + Log.e("SearchFragment", "Error loading tasks: ${e.message}") + e.printStackTrace() + } + } + } + private fun onTaskClick(task: Task) { - val taskId = taskIdMap[task] + val taskId = taskIdMap[task.name] + Log.e("SearchFragment", "Task ID: $taskId") val bundle = Bundle().apply { putString("taskId", taskId) } @@ -254,7 +321,7 @@ class SearchFragment : Fragment() { } private fun onEditTask(task: Task) { - val taskId = taskIdMap[task] + val taskId = taskIdMap[task.name] val bundle = Bundle().apply { putString("taskId", taskId) } diff --git a/app/src/main/java/com/example/taskmanagement/task/SearchSubFragment.kt b/app/src/main/java/com/example/taskmanagement/task/SearchSubFragment.kt index 0793cfb..83c89f3 100644 --- a/app/src/main/java/com/example/taskmanagement/task/SearchSubFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/task/SearchSubFragment.kt @@ -6,6 +6,7 @@ import android.view.View import android.view.ViewGroup import android.widget.ImageButton import android.widget.SearchView +import android.widget.TextView import android.widget.Toast import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope @@ -27,7 +28,6 @@ class SearchSubFragment : Fragment() { private val firestore = FirebaseFirestore.getInstance() private var taskID: String? = null - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -65,6 +65,8 @@ class SearchSubFragment : Fragment() { return view } + private val subtaskIdMap = mutableMapOf<SubTask, String>() + private fun loadSubtasks(taskId: String) { subtaskRecyclerView.visibility = View.VISIBLE @@ -79,25 +81,34 @@ class SearchSubFragment : Fragment() { subtaskList.clear() for (document in result) { val subTask = document.toObject(SubTask::class.java) + subtaskIdMap[subTask] = document.id subtaskList.add(subTask) } + + subtaskList.sortBy { it.name } subtaskAdapter.updateSubTasks(subtaskList) - } catch (e: Exception) { - Toast.makeText( - requireContext(), - getString(R.string.failed_to_load_subtasks), - Toast.LENGTH_SHORT - ).show() + val noSubtasksMessage: TextView = + view?.findViewById(R.id.no_subtasks_message) ?: return@launch + if (subtaskList.isEmpty()) { + noSubtasksMessage.visibility = View.VISIBLE + } else { + noSubtasksMessage.visibility = View.GONE + } + + } catch (e: Exception) { + Toast.makeText(requireContext(), "Failed to load subtasks", Toast.LENGTH_SHORT) + .show() } } - } private fun onEditSubTask(subtask: SubTask) { + val subtaskId = subtaskIdMap[subtask] val bundle = Bundle().apply { - putString("subtaskId", subtask.name) + putString("taskID", taskID) + putString("subtaskId", subtaskId) } - findNavController().navigate(R.id.action_subTaskFragment_to_modifySubTaskFragment, bundle) + findNavController().navigate(R.id.action_searchSubFragment_to_modifySubTaskFragment, bundle) } } diff --git a/app/src/main/java/com/example/taskmanagement/task/SubTaskFragment.kt b/app/src/main/java/com/example/taskmanagement/task/SubTaskFragment.kt index d1f103b..08dbcf9 100644 --- a/app/src/main/java/com/example/taskmanagement/task/SubTaskFragment.kt +++ b/app/src/main/java/com/example/taskmanagement/task/SubTaskFragment.kt @@ -5,6 +5,7 @@ import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import android.widget.ProgressBar import android.widget.TextView import android.widget.Toast @@ -52,6 +53,12 @@ class SubTaskFragment : Fragment() { loadSubtasks(taskID!!) } + val backArrow: ImageView = view.findViewById(R.id.backArrow) + backArrow.setOnClickListener { + findNavController().popBackStack() + } + + return view } @@ -90,7 +97,6 @@ class SubTaskFragment : Fragment() { private fun loadSubtasks(taskId: String) { progressBar.visibility = View.VISIBLE subtaskRecyclerView.visibility = View.VISIBLE - lifecycleScope.launch { try { val result = firestore.collection("tasks") @@ -107,9 +113,10 @@ class SubTaskFragment : Fragment() { subtaskIdMap[subTask] = document.id subtaskList.add(subTask) } - + subtaskList.sortBy { it.name } subtaskAdapter.updateSubTasks(subtaskList) + val noSubtasksMessage: TextView = view?.findViewById(R.id.no_subtasks_message) ?: return@launch if (subtaskList.isEmpty()) { diff --git a/app/src/main/java/com/example/taskmanagement/task/TaskAdapter.kt b/app/src/main/java/com/example/taskmanagement/task/TaskAdapter.kt index 62c12cc..ecf12f8 100644 --- a/app/src/main/java/com/example/taskmanagement/task/TaskAdapter.kt +++ b/app/src/main/java/com/example/taskmanagement/task/TaskAdapter.kt @@ -35,7 +35,7 @@ class TaskAdapter( override fun getItemCount(): Int = taskList.size - fun updateTasks(newTaskList: List<Task>) { + private fun updateTasks(newTaskList: List<Task>) { val diffCallback = TaskDiffCallback(taskList, newTaskList) val diffResult = DiffUtil.calculateDiff(diffCallback) diff --git a/app/src/main/res/layout/fragment_home.xml b/app/src/main/res/layout/fragment_home.xml index 7969968..f5cb7c3 100644 --- a/app/src/main/res/layout/fragment_home.xml +++ b/app/src/main/res/layout/fragment_home.xml @@ -1,4 +1,3 @@ -<?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" @@ -9,14 +8,32 @@ android:fitsSystemWindows="true" tools:context=".task.HomeFragment"> + <androidx.swiperefreshlayout.widget.SwipeRefreshLayout + android:id="@+id/swipeRefreshLayout" + android:layout_width="0dp" + android:layout_height="0dp" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" + android:layout_marginBottom="80dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/fragmentTitle"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/task_list" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="vertical" /> + + </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> + <TextView android:id="@+id/fragmentTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="12dp" android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" - android:gravity="start" android:text="@string/task" android:textAppearance="@style/TextAppearance.Material3.BodyLarge" android:textColor="@android:color/black" @@ -24,39 +41,16 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/task_list" - android:layout_width="match_parent" - android:layout_height="0dp" - android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" - android:layout_marginBottom="80dp" - app:layout_constraintBottom_toBottomOf="parent" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@+id/fragmentTitle" /> - - <ProgressBar - android:id="@+id/progressBar" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:layout_marginStart="8dp" - android:layout_marginTop="340dp" - android:layout_marginEnd="8dp" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toTopOf="parent" /> <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/fab_main" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" - android:layout_marginBottom="95dp" - android:background="@color/purple_200" + android:layout_marginBottom="90dp" android:contentDescription="@string/add_new_task" android:src="@drawable/baseline_add_24" - android:visibility="gone" + android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index 0134003..2b89920 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -3,196 +3,195 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" + android:orientation="vertical" android:fitsSystemWindows="true" - tools:context="auth.ProfileFragment" android:background="@drawable/gradient_background" - android:orientation="vertical"> + tools:context="auth.ProfileFragment"> <RelativeLayout android:layout_width="match_parent" - android:layout_height="405dp"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="350dp" - android:orientation="vertical"> - <ImageView - android:layout_marginTop="45dp" - android:layout_gravity="center_horizontal" - android:layout_width="150dp" - android:layout_height="150dp" - android:id="@+id/profileImage" - android:src="@drawable/ic_baseline_account_circle_24"/> - <TextView - android:id="@+id/userName" - android:layout_marginTop="10dp" - android:layout_gravity="center_horizontal" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Nome utente" - android:textColor="@color/black" - android:textStyle="bold" - android:textSize="21sp"/> - </LinearLayout> - <LinearLayout - android:layout_width="400dp" + android:layout_height="250dp"> + + <ImageView + android:id="@+id/profileImage" + android:layout_width="120dp" android:layout_height="120dp" android:layout_centerHorizontal="true" - android:layout_marginTop="275dp"> - <LinearLayout - android:layout_width="match_parent" - android:layout_height="match_parent" - android:orientation="horizontal" - - android:weightSum="3"> - <LinearLayout - android:gravity="center" - android:orientation="vertical" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="match_parent"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="Initial" - android:textColor="@color/black" - android:textSize="20sp" - /> - <TextView - android:id="@+id/initialTask" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="1" - android:textStyle="bold" - android:textSize="20sp" - android:paddingTop="10dp" - android:textColor="@color/black"/> - - </LinearLayout> - <LinearLayout - android:gravity="center" - android:orientation="vertical" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="match_parent"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="In Progress" - android:textColor="@color/black" - android:textSize="20sp" - /> - <TextView - android:id="@+id/inprogressTask" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="5" - android:textStyle="bold" - android:textSize="20sp" - android:paddingTop="10dp" - android:textColor="@color/black"/> - - </LinearLayout> - <LinearLayout - android:gravity="center" - android:orientation="vertical" - android:layout_width="0dp" - android:layout_weight="1" - android:layout_height="match_parent"> - <TextView - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:textColor="@color/black" - android:text="Complete" - android:textSize="20sp" - /> - <TextView - android:id="@+id/completeTask" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:text="360" - android:textStyle="bold" - android:textSize="20sp" - android:paddingTop="10dp" - android:textColor="@color/black"/> - - </LinearLayout> - </LinearLayout> + android:layout_marginTop="30dp" + android:src="@drawable/ic_baseline_account_circle_24" + android:scaleType="centerCrop" + android:background="@drawable/ic_baseline_account_circle_24" /> - </LinearLayout> + <TextView + android:id="@+id/userName" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/profileImage" + android:layout_centerHorizontal="true" + android:layout_marginTop="15dp" + android:text="Nome Utente" + android:textColor="@android:color/black" + android:textSize="18sp" + android:textStyle="bold" /> + + <TextView + android:id="@+id/welcomeMessage" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/userName" + android:layout_centerHorizontal="true" + android:layout_marginTop="8dp" + android:text="@string/welcome_message" + android:textColor="@android:color/black" + android:textSize="16sp" /> </RelativeLayout> + <!-- Task List --> <LinearLayout - android:layout_width="360dp" + android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:layout_gravity="center" - android:layout_marginTop="45dp"> - <LinearLayout - android:orientation="horizontal" - android:paddingLeft="25dp" + android:padding="16dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Task Assegnati" + android:textColor="@color/black" + android:textSize="18sp" + android:textStyle="bold" /> + + <ListView + android:id="@+id/assignedTasksList" android:layout_width="match_parent" - android:layout_height="wrap_content"> - <ImageView - android:layout_width="36dp" - android:layout_height="36dp" - android:src="@drawable/baseline_how_to_reg_24"/> - <TextView - android:textStyle="bold" - android:layout_gravity="center_vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingLeft="20dp" - android:text="email" - android:id="@+id/userMail"/> - </LinearLayout> - <LinearLayout - android:layout_marginTop="25dp" - android:orientation="horizontal" - android:paddingLeft="25dp" + android:layout_height="200dp" + android:divider="@android:color/black" + android:dividerHeight="1dp" /> + </LinearLayout> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Skills" + android:textColor="@color/black" + android:textSize="18sp" + android:textStyle="bold" /> + + <TextView + android:id="@+id/skillsList" android:layout_width="match_parent" - android:layout_height="wrap_content"> - <ImageView - android:layout_width="36dp" - android:layout_height="36dp" - android:src="@drawable/baseline_add_box_24"/> - <TextView - android:textStyle="bold" - android:id="@+id/assignedTaskCount" - android:layout_gravity="center_vertical" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:paddingLeft="20dp" - android:text="Task assegnati"/> - </LinearLayout> + android:layout_height="wrap_content" + android:text="Skill 1, Skill 2, Skill 3" + android:textColor="@color/black" + android:layout_marginTop="8dp" /> + </LinearLayout> + + <!-- Statistics Section --> + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:padding="16dp"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Statistiche" + android:textColor="@color/black" + android:textSize="18sp" + android:textStyle="bold" /> + <LinearLayout - android:layout_marginTop="25dp" - android:orientation="horizontal" - android:paddingLeft="25dp" android:layout_width="match_parent" - android:layout_height="wrap_content"> - <ImageView - android:layout_width="36dp" - android:layout_height="36dp" - android:src="@drawable/baseline_groups_24"/> - <TextView - android:textStyle="bold" - android:layout_gravity="center_vertical" - android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:layout_marginTop="16dp"> + + <LinearLayout + android:layout_width="0dp" android:layout_height="wrap_content" - android:paddingLeft="20dp" - android:text="Add to group" - android:id="@+id/group"/> - </LinearLayout> + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Progetti Conclusi" + android:textColor="@color/black" /> + + <TextView + android:id="@+id/completedProjects" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="5" + android:textColor="@color/black" + android:textSize="18sp" + android:textStyle="bold" /> + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Progetti da Iniziare" + android:textColor="@color/black" /> + + <TextView + android:id="@+id/projectsToStart" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="3" + android:textColor="@color/black" + android:textSize="18sp" + android:textStyle="bold" /> + </LinearLayout> + + <LinearLayout + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" + android:orientation="vertical" + android:gravity="center"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Tempo Medio" + android:textColor="@color/black" /> + + <TextView + android:id="@+id/averageCompletionTime" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="10 giorni" + android:textColor="@color/black" + android:textSize="18sp" + android:textStyle="bold" /> + </LinearLayout> + </LinearLayout> </LinearLayout> + <!-- Logout Button --> <Button - android:textColor="#fff" android:id="@+id/buttonLogout" - android:layout_marginTop="35dp" - android:layout_gravity="center_horizontal" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:text="@string/logout"/> + android:layout_gravity="center_horizontal" + android:layout_marginTop="20dp" + android:text="Logout" + android:textColor="@android:color/white" /> -</LinearLayout> \ No newline at end of file +</LinearLayout> diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index fe8dab9..cd11bf3 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -7,13 +7,13 @@ android:fitsSystemWindows="true" tools:context=".task.SearchFragment"> + <!-- Titolo del Fragment --> <TextView android:id="@+id/fragmentTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="12dp" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" + android:layout_marginStart="16dp" + android:layout_marginTop="16dp" android:gravity="start" android:text="@string/search" android:textAppearance="@style/TextAppearance.Material3.BodyLarge" @@ -26,26 +26,38 @@ android:id="@+id/search_view" android:layout_width="0dp" android:layout_height="wrap_content" - android:queryHint="Cerca Task" + android:layout_marginTop="8dp" + android:layout_marginEnd="16dp" + android:contentDescription="@string/find_tasks" + android:queryHint="@string/find_tasks" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/fragmentTitle" /> - <androidx.recyclerview.widget.RecyclerView - android:id="@+id/recycler_view" + <androidx.swiperefreshlayout.widget.SwipeRefreshLayout + android:id="@+id/swipeRefreshSearch" android:layout_width="0dp" android:layout_height="0dp" - android:layout_marginTop="16dp" + android:layout_marginEnd="16dp" android:layout_marginBottom="80dp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/search_view" /> + app:layout_constraintTop_toBottomOf="@id/search_view"> + + <androidx.recyclerview.widget.RecyclerView + android:id="@+id/searchList" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="vertical" /> + + </androidx.swiperefreshlayout.widget.SwipeRefreshLayout> <TextView android:id="@+id/noTasksMessage" android:layout_width="wrap_content" android:layout_height="wrap_content" + android:layout_margin="16dp" android:gravity="center" android:text="@string/non_ci_sono_tasks_presenti" android:textColor="@android:color/black" diff --git a/app/src/main/res/layout/fragment_sub_task.xml b/app/src/main/res/layout/fragment_sub_task.xml index e54f3aa..200cb58 100644 --- a/app/src/main/res/layout/fragment_sub_task.xml +++ b/app/src/main/res/layout/fragment_sub_task.xml @@ -9,19 +9,34 @@ android:fitsSystemWindows="true" tools:context=".task.SubTaskFragment"> + <!-- Freccia per tornare indietro a sinistra del titolo --> + + <ImageView + android:id="@+id/backArrow" + android:layout_width="32dp" + android:layout_height="31dp" + android:layout_marginStart="16dp" + android:layout_marginTop="22dp" + android:clickable="true" + android:contentDescription="back" + android:focusable="true" + android:gravity="center_horizontal" + android:src="@drawable/baseline_arrow_back_24" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + <TextView android:id="@+id/fragmentTitle" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginStart="12dp" - android:layout_marginTop="12dp" - android:layout_marginBottom="12dp" + android:layout_marginStart="5dp" + android:layout_marginTop="16dp" android:gravity="start" android:text="@string/subtask" android:textAppearance="@style/TextAppearance.Material3.BodyLarge" android:textColor="@android:color/black" android:textSize="32sp" - app:layout_constraintStart_toStartOf="parent" + app:layout_constraintStart_toEndOf="@id/backArrow" app:layout_constraintTop_toTopOf="parent" /> <androidx.recyclerview.widget.RecyclerView @@ -29,7 +44,7 @@ android:layout_width="match_parent" android:layout_height="0dp" android:layout_marginStart="8dp" - android:layout_marginEnd="8dp" + android:layout_marginEnd="5dp" android:layout_marginBottom="16dp" app:layout_constraintBottom_toTopOf="@+id/bottomNavMenu" app:layout_constraintEnd_toEndOf="parent" @@ -93,7 +108,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:gravity="center" - android:text="Non ci sono subtasks presenti! ðŸ˜" + android:text="@string/non_ci_sono_subtasks_presenti" android:textColor="@android:color/black" android:textSize="18sp" android:visibility="gone" @@ -102,5 +117,4 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> - </androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/layout/fragment_user_selection.xml b/app/src/main/res/layout/fragment_user_selection.xml index 40da1cb..f3481e9 100644 --- a/app/src/main/res/layout/fragment_user_selection.xml +++ b/app/src/main/res/layout/fragment_user_selection.xml @@ -33,6 +33,20 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/fragmentTitle" /> + <TextView + android:id="@+id/noChatsMessage" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:text="@string/no_chats_present" + android:textAppearance="@style/TextAppearance.Material3.BodyLarge" + android:visibility="gone" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" /> + + <com.google.android.material.floatingactionbutton.FloatingActionButton android:id="@+id/newChatButton" android:layout_width="wrap_content" diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 0117b6c..3103fdf 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -147,5 +147,13 @@ <string name="unknown">Sconosciuto</string> <string name="please_fill_all_fields">Per favore, inserisci tutti i campi!</string> <string name="error_calculating_subtask_progress">Errore durante il calcolo dell\'avanzamento dell\'attività secondaria. Per favore riprova più tardi.</string> + <string name="find_tasks">Cerca Task per: Nome, Descrizione, Scadenza, Stato, Assegnato a</string> + <string name="error_loading_last_message">Errore durante il caricamento dell\'ultimo messaggio: %1$s</string> + <string name="error_clearing_cache">Errore durante la pulizia della cache: %1$s</string> + <string name="files_deleted_successfully">File eliminati con successo</string> + <string name="no_files_to_delete">Nessun file da eliminare</string> + <string name="error_deleting_files">Errore durante l\'eliminazione dei file</string> + <string name="no_chats_present">Nessuna chat presente, inizia a chattare!</string> + </resources> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 61124b1..e878fa5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -166,5 +166,14 @@ <string name="unknown">Unknown</string> <string name="please_fill_all_fields">Please, fill all fields!</string> <string name="error_calculating_subtask_progress">Error while calculating subtask progress. Please try again later.</string> + <string name="find_tasks">Search Tasks by: Name, Description, Deadline, Status, Assigned to</string> + <string name="error_loading_last_message">Error loading the last message: %1$s</string> + <string name="error_clearing_cache">Error clearing cache: %1$s</string> + <string name="files_deleted_successfully">Files successfully deleted</string> + <string name="no_files_to_delete">No files to delete</string> + <string name="error_deleting_files">Error deleting files</string> + <string name="no_chats_present">No chat present, start chatting!</string> + + </resources> \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec64368..ae74a88 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -15,6 +15,7 @@ firebaseFirestore = "25.1.1" firebaseAuthKtx = "23.1.0" navigationFragmentKtx = "2.8.5" navigationUiKtx = "2.8.5" +swiperefreshlayout = "1.1.0" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -30,6 +31,7 @@ firebase-firestore = { group = "com.google.firebase", name = "firebase-firestore firebase-auth-ktx = { group = "com.google.firebase", name = "firebase-auth-ktx", version.ref = "firebaseAuthKtx" } androidx-navigation-fragment-ktx = { group = "androidx.navigation", name = "navigation-fragment-ktx", version.ref = "navigationFragmentKtx" } androidx-navigation-ui-ktx = { group = "androidx.navigation", name = "navigation-ui-ktx", version.ref = "navigationUiKtx" } +androidx-swiperefreshlayout = { group = "androidx.swiperefreshlayout", name = "swiperefreshlayout", version.ref = "swiperefreshlayout" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" } -- GitLab