Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/inspectionProfiles/Project_Default.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 1 addition & 2 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
alias(libs.plugins.kotlin.compose)
id("kotlin-kapt")
id("kotlin-parcelize")
alias(libs.plugins.kotlin.ksp)
}

android {
Expand Down Expand Up @@ -95,4 +97,13 @@ dependencies {
// Image loading with Coil
implementation("io.coil-kt:coil-compose:2.4.0")

//websocket
implementation("com.squareup.okhttp3:okhttp:5.1.0")

//room
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)
ksp(libs.androidx.room.compiler)
implementation(libs.androidx.lifecycle.viewmodel.compose)

}
2 changes: 2 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>

<application
android:networkSecurityConfig="@xml/network_security_config"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
Expand Down
50 changes: 47 additions & 3 deletions app/src/main/java/com/example/smarthr_app/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -1,28 +1,41 @@
package com.example.smarthr_app

import android.app.Application
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.compose.rememberNavController
import com.example.smarthr_app.data.local.DataStoreManager
import com.example.smarthr_app.data.repository.AuthRepository
import com.example.smarthr_app.data.repository.ChatRepository
import com.example.smarthr_app.data.repository.TaskRepository
import com.example.smarthr_app.presentation.navigation.NavGraph
import com.example.smarthr_app.presentation.navigation.Screen
import com.example.smarthr_app.presentation.theme.SmartHRTheme
import com.example.smarthr_app.presentation.viewmodel.AuthViewModel
import com.example.smarthr_app.presentation.viewmodel.ChatViewModel
import com.example.smarthr_app.utils.createNotificationChannel
import com.example.smarthr_app.utils.showNotification
import kotlinx.coroutines.delay

class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
createNotificationChannel(applicationContext)
requestNotificationPermissionIfNeeded()
enableEdgeToEdge()
setContent {
SmartHRTheme {
Expand All @@ -35,6 +48,23 @@ class MainActivity : ComponentActivity() {
}
}
}
private fun requestNotificationPermissionIfNeeded() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ContextCompat.checkSelfPermission(
this,
android.Manifest.permission.POST_NOTIFICATIONS
) != PackageManager.PERMISSION_GRANTED
) {
ActivityCompat.requestPermissions(
this,
arrayOf(android.Manifest.permission.POST_NOTIFICATIONS),
1001
)
}
}
}


}

@Composable
Expand All @@ -43,11 +73,24 @@ fun SmartHRApp() {
val context = LocalContext.current
val dataStoreManager = DataStoreManager(context)
val authRepository = AuthRepository(dataStoreManager)
val chatRepository = ChatRepository(dataStoreManager)
val authViewModel: AuthViewModel = viewModel { AuthViewModel(authRepository) }
val chatViewModel : ChatViewModel = viewModel { ChatViewModel(chatRepository) }
val user = authViewModel.user.collectAsState(initial = null).value

var startDestination by remember { mutableStateOf<String?>(null) }
var isInitialized by remember { mutableStateOf(false) }

val notificationEvent by chatViewModel.notificationEvent.collectAsState()

// Show notification
LaunchedEffect(notificationEvent) {
notificationEvent?.let { (title, message) ->
showNotification(context, title, message)
chatViewModel.clearNotificationEvent() // prevent repeat
}
}

// Determine start destination based on auth state with delay to check persistence
LaunchedEffect(Unit) {
// Add small delay to ensure DataStore is properly loaded
Expand All @@ -57,8 +100,8 @@ fun SmartHRApp() {
if (isLoggedIn) {
authRepository.user.collect { user ->
startDestination = when (user?.role) {
com.example.smarthr_app.data.model.UserRole.ROLE_HR -> Screen.HRDashboard.route
com.example.smarthr_app.data.model.UserRole.ROLE_USER -> Screen.EmployeeDashboard.route
com.example.smarthr_app.data.model.UserRole.ROLE_HR -> Screen.ChatList.route
com.example.smarthr_app.data.model.UserRole.ROLE_USER -> Screen.ChatList.route
else -> Screen.RoleSelection.route
}
isInitialized = true
Expand All @@ -78,4 +121,5 @@ fun SmartHRApp() {
startDestination = startDestination!!
)
}
}
}

13 changes: 13 additions & 0 deletions app/src/main/java/com/example/smarthr_app/data/model/ChatList.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.smarthr_app.data.model

data class Chat(
val companyCode: String,
val id: String, //chat id
val lastMessage: String,
val lastMessageStatus: String, //SEEN,DELIVERED
val lastMessageType: String,
val lastMessageSender:String,
val lastUpdated: String,
val user1: UserInfo, // me
val user2: UserInfo
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.smarthr_app.data.model

data class ChatMessage(
val id: String, // message id
val chatId:String,
val sender: UserInfo,
val receiver: UserInfo,
val content: String,
val messageType: String,
val companyCode: String,
val timestamp: String,
val messageStatus: String
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.smarthr_app.data.model

data class SeenMessage(
val chatId:String,
val userId: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -173,4 +173,29 @@ interface ApiService {
@Path("leaveId") leaveId: String
): Response<SuccessApiResponseMessage>

@GET("chats/myChats")
suspend fun getMyChatList(
@Header("Authorization") token: String,
@Query("companyCode") companyCode: String
) : Response<List<Chat>>

@GET("companies/everybody")
suspend fun getAllHrAndEmployeeOfCompany(
@Header("Authorization") token: String,
) : Response<List<UserInfo>>

@GET("chats/history")
suspend fun getChatBetweenUser(
@Header("Authorization") token: String,
@Query("companyCode") companyCode: String,
@Query("otherUserId") otherUserId: String
) : Response<List<ChatMessage>>

@PUT("chats/seen/{chatId}")
suspend fun markChatSeen(
@Header("Authorization") token: String,
@Path("chatId") chatId:String,
@Query("userId") userId : String,
): Response<SuccessApiResponseMessage>

}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import retrofit2.converter.gson.GsonConverterFactory
import java.util.concurrent.TimeUnit

object RetrofitInstance {
const val BASE_URL = "https://smarthr-backend-jx0v.onrender.com/"
const val BASE_URL = "https://smarthr-backend-jx0v.onrender.com"

private val loggingInterceptor = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.example.smarthr_app.data.repository

import android.util.Log
import com.example.smarthr_app.data.local.DataStoreManager
import com.example.smarthr_app.data.model.Chat
import com.example.smarthr_app.data.model.ChatMessage
import com.example.smarthr_app.data.model.SuccessApiResponseMessage
import com.example.smarthr_app.data.model.UserInfo
import com.example.smarthr_app.data.remote.RetrofitInstance
import kotlinx.coroutines.flow.first

class ChatRepository(private val dataStoreManager: DataStoreManager) {


suspend fun getMyChatList(companyCode: String): List<Chat>? {
return try {
val token = dataStoreManager.token.first()
if (token != null) {
val response = RetrofitInstance.api.getMyChatList("Bearer $token", companyCode)
Log.d("ChatList", "Response: ${response.body()}")
if (response.isSuccessful) {
response.body()
} else {
emptyList()
}
} else {
emptyList()
}
} catch (e: Exception) {
emptyList()
}
}

suspend fun getAllUsers(): List<UserInfo>? {
return try {
val token = dataStoreManager.token.first()
if (token != null) {
val response = RetrofitInstance.api.getAllHrAndEmployeeOfCompany("Bearer $token")
Log.d("User List", "Response: ${response.body()}")
if (response.isSuccessful) {
response.body()
} else {
emptyList()
}
} else {
emptyList()
}
} catch (e: Exception) {
emptyList()
}
}

suspend fun getChatBetweenUser(
companyCode: String,
otherUerId: String
): List<ChatMessage>? {
return try {
val token = dataStoreManager.token.first()
if (token != null) {
val response = RetrofitInstance.api.getChatBetweenUser("Bearer $token", companyCode = companyCode, otherUserId = otherUerId)
Log.i("FatUsers", "Response: ${response.body()}")
if (response.isSuccessful) {
response.body()
} else {
emptyList()
}
} else {
emptyList()
}
} catch (e: Exception) {
emptyList()
}
}

suspend fun markChatAsSeen(
chatId:String,
userId:String,
): SuccessApiResponseMessage? {
return try {
val token = dataStoreManager.token.first()
if (token != null) {
val response = RetrofitInstance.api.markChatSeen(token = "Bearer $token", chatId = chatId, userId = userId)
Log.i("ChatSeen", "Response: ${response.body()}")
if (response.isSuccessful) {
response.body()
} else {

null }
} else {
null

}
} catch (e: Exception) {
null
}
}


}
Loading