diff --git a/.DS_Store b/.DS_Store index 13d6aa2..fbadfa5 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/app/src/main/java/com/example/estiaseek/EstiaSeekScreen.kt b/app/src/main/java/com/example/estiaseek/EstiaSeekScreen.kt new file mode 100644 index 0000000..fba1db9 --- /dev/null +++ b/app/src/main/java/com/example/estiaseek/EstiaSeekScreen.kt @@ -0,0 +1,77 @@ +package com.example.estiaseek + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import com.example.estiaseek.ui.profile.Profile +import com.example.estiaseek.ui.screens.CandidateSearchScreen +import com.example.estiaseek.ui.screens.CandidateSearchViewModel +import com.example.estiaseek.ui.screens.CreateApplicant +import com.example.estiaseek.ui.screens.CreateApplicantViewModel +import com.example.estiaseek.ui.screens.HomeScreen +import com.example.estiaseek.ui.screens.ResultsScreen +import com.example.estiaseek.ui.viewmodels.ProfileViewModel +import com.example.estiaseek.ui.viewmodels.SearchViewModel + + +enum class EstiaSeekScreen() { + Start, + CreateApplicant, + ShowResults, + Profile, + Search +} + +@Composable +fun EstiaSeekScreen( + navController: NavHostController = rememberNavController(), + searchViewModel: SearchViewModel = viewModel(), + profileViewModel: ProfileViewModel = viewModel(), + viewModel : CandidateSearchViewModel, + createApplicantViewModel : CreateApplicantViewModel +) { + NavHost( + navController = navController, + startDestination = EstiaSeekScreen.Start.name, + modifier = Modifier, + ) { + composable(route = EstiaSeekScreen.Start.name) { + HomeScreen( + onSearchButtonClicked = { navController.navigate(EstiaSeekScreen.Search.name) }, + onCreateApplicantButtonClicked = { navController.navigate(EstiaSeekScreen.CreateApplicant.name) } + ) + } + composable(route = EstiaSeekScreen.CreateApplicant.name) { + CreateApplicant( + onCreateApplicantButtonClicked = { navController.navigate(EstiaSeekScreen.Start.name)}, + viewModel = createApplicantViewModel //fixme + ) + } + composable(route = EstiaSeekScreen.ShowResults.name) { + ResultsScreen( + onProfileClicked = { navController.navigate(EstiaSeekScreen.Profile.name)}, + searchViewModel = searchViewModel, + profileViewModel = profileViewModel + ) + } + composable(route = EstiaSeekScreen.Profile.name) { + Profile( + profileViewModel = profileViewModel + ) + } + composable(route = EstiaSeekScreen.Search.name) { + CandidateSearchScreen( + onSearchButtonClicked = { navController.navigate(EstiaSeekScreen.ShowResults.name)}, + searchViewModel = searchViewModel, + viewModel = viewModel + ) + } + } + +} + + diff --git a/app/src/main/java/com/example/estiaseek/MainActivity.kt b/app/src/main/java/com/example/estiaseek/MainActivity.kt index 443b657..516336a 100644 --- a/app/src/main/java/com/example/estiaseek/MainActivity.kt +++ b/app/src/main/java/com/example/estiaseek/MainActivity.kt @@ -13,6 +13,7 @@ import com.example.estiaseek.data.OfflineUsersRepository import com.example.estiaseek.ui.screens.CandidateSearchScreen import com.example.estiaseek.ui.screens.CandidateSearchViewModel import com.example.estiaseek.ui.screens.CandidateSearchViewModelFactory +import com.example.estiaseek.ui.screens.CreateApplicantViewModel import com.example.estiaseek.ui.theme.EstiaSeekTheme class MainActivity : ComponentActivity() { @@ -25,7 +26,10 @@ class MainActivity : ComponentActivity() { // Create the ViewModel using the factory val factory = CandidateSearchViewModelFactory(usersRepository) - val viewModel: CandidateSearchViewModel = ViewModelProvider(this, factory).get(CandidateSearchViewModel::class.java) + val candidateSearchViewModel: CandidateSearchViewModel = ViewModelProvider(this, factory).get(CandidateSearchViewModel::class.java) + + //FIXME + val createApplicantViewModel : CreateApplicantViewModel = CreateApplicantViewModel(usersRepository=usersRepository) setContent { EstiaSeekTheme { @@ -33,7 +37,10 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - CandidateSearchScreen(viewModel = viewModel) + EstiaSeekScreen( + viewModel = candidateSearchViewModel, + createApplicantViewModel = createApplicantViewModel + ) } } } diff --git a/app/src/main/java/com/example/estiaseek/ui/components/BottomNavigationBar.kt b/app/src/main/java/com/example/estiaseek/ui/components/BottomNavigationBar.kt index 8c3e153..3c35d6e 100644 --- a/app/src/main/java/com/example/estiaseek/ui/components/BottomNavigationBar.kt +++ b/app/src/main/java/com/example/estiaseek/ui/components/BottomNavigationBar.kt @@ -1,23 +1,16 @@ package com.example.estiaseek.ui.components import androidx.compose.foundation.Image -import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.foundation.shape.CircleShape import androidx.compose.material3.BottomAppBar import androidx.compose.material3.IconButton -import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp import androidx.navigation.NavController diff --git a/app/src/main/java/com/example/estiaseek/ui/components/DropdownMenuField.kt b/app/src/main/java/com/example/estiaseek/ui/components/DropdownMenuField.kt index 296933f..d57a6b1 100644 --- a/app/src/main/java/com/example/estiaseek/ui/components/DropdownMenuField.kt +++ b/app/src/main/java/com/example/estiaseek/ui/components/DropdownMenuField.kt @@ -3,7 +3,6 @@ package com.example.estiaseek.ui.components import androidx.annotation.StringRes import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background -import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape diff --git a/app/src/main/java/com/example/estiaseek/ui/profile/profile.kt b/app/src/main/java/com/example/estiaseek/ui/profile/Profile.kt similarity index 95% rename from app/src/main/java/com/example/estiaseek/ui/profile/profile.kt rename to app/src/main/java/com/example/estiaseek/ui/profile/Profile.kt index 4e14e50..eade688 100644 --- a/app/src/main/java/com/example/estiaseek/ui/profile/profile.kt +++ b/app/src/main/java/com/example/estiaseek/ui/profile/Profile.kt @@ -3,14 +3,14 @@ package com.example.estiaseek.ui.profile import android.content.Intent import android.net.Uri import androidx.annotation.OptIn +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.Image -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.* import androidx.compose.runtime.* @@ -24,18 +24,22 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView -import coil.compose.rememberAsyncImagePainter import androidx.media3.common.MediaItem import androidx.media3.common.util.UnstableApi import androidx.media3.exoplayer.ExoPlayer import androidx.media3.ui.AspectRatioFrameLayout import androidx.media3.ui.PlayerView +import coil.compose.rememberAsyncImagePainter +import com.example.estiaseek.ui.viewmodels.ProfileViewModel @OptIn(UnstableApi::class) @Composable -fun Profile() { +fun Profile( + profileViewModel: ProfileViewModel +) { val context = LocalContext.current + val profileViewState by profileViewModel.profileViewState.collectAsState() // Remember ExoPlayer instance val exoPlayer = remember { @@ -105,7 +109,7 @@ fun Profile() { ) { // User Name Text( - text = "Lil Pop", + text = "Lil Pop - ${profileViewState.username}", style = MaterialTheme.typography.headlineMedium, fontWeight = FontWeight.Bold ) @@ -198,5 +202,7 @@ fun Profile() { @Preview @Composable fun PreviewProfile() { - Profile() + Profile( + profileViewModel = ProfileViewModel() + ) } diff --git a/app/src/main/java/com/example/estiaseek/ui/screens/CandidateSearchScreen.kt b/app/src/main/java/com/example/estiaseek/ui/screens/CandidateSearchScreen.kt index 3fad858..8d0aa84 100644 --- a/app/src/main/java/com/example/estiaseek/ui/screens/CandidateSearchScreen.kt +++ b/app/src/main/java/com/example/estiaseek/ui/screens/CandidateSearchScreen.kt @@ -1,15 +1,7 @@ package com.example.estiaseek.ui.screens import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.safeDrawingPadding +import androidx.compose.foundation.layout.* import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll @@ -18,17 +10,8 @@ import androidx.compose.material.icons.rounded.LocationOn import androidx.compose.material.icons.rounded.Person import androidx.compose.material.icons.rounded.Search import androidx.compose.material.icons.rounded.Star -import androidx.compose.material3.Button -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue +import androidx.compose.material3.* +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Brush @@ -38,12 +21,26 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.example.estiaseek.R import com.example.estiaseek.ui.components.DropdownMenuField +import com.example.estiaseek.ui.viewmodels.SearchUiState +import com.example.estiaseek.ui.viewmodels.SearchViewModel +import androidx.compose.material3.Button +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue + @Composable -fun CandidateSearchScreen(viewModel: CandidateSearchViewModel) { - var selectedJobTitle by remember { mutableStateOf("Any") } - var selectedLocation by remember { mutableStateOf("Any") } - var selectedExperience by remember { mutableStateOf("Any") } +fun CandidateSearchScreen( + onSearchButtonClicked: (SearchUiState) -> Unit, + searchViewModel : SearchViewModel, + viewModel: CandidateSearchViewModel + ) { + + val searchUiState by searchViewModel.searchUIState.collectAsState() val context = LocalContext.current @@ -113,8 +110,14 @@ fun CandidateSearchScreen(viewModel: CandidateSearchViewModel) { DropdownMenuField( label = R.string.job_title, options = jobTitles, - selectedOption = selectedJobTitle, - onOptionSelected = { selectedJobTitle = it }, + selectedOption = searchUiState.selectedJobTitle, + onOptionSelected = { + searchViewModel.updateSearchState( + newState = searchUiState.copy( + selectedJobTitle = it + ) + ) + }, icon = Icons.Rounded.Person, modifier = Modifier.fillMaxWidth() ) @@ -122,8 +125,14 @@ fun CandidateSearchScreen(viewModel: CandidateSearchViewModel) { DropdownMenuField( label = R.string.location, options = locations, - selectedOption = selectedLocation, - onOptionSelected = { selectedLocation = it }, + selectedOption = searchUiState.selectedLocation, + onOptionSelected = { + searchViewModel.updateSearchState( + newState = searchUiState.copy( + selectedLocation = it + ) + ) + }, icon = Icons.Rounded.LocationOn, modifier = Modifier.fillMaxWidth() ) @@ -131,8 +140,14 @@ fun CandidateSearchScreen(viewModel: CandidateSearchViewModel) { DropdownMenuField( label = R.string.experience_level, options = experienceLevels, - selectedOption = selectedExperience, - onOptionSelected = { selectedExperience = it }, + selectedOption = searchUiState.selectedExperience, + onOptionSelected = { + searchViewModel.updateSearchState( + newState = searchUiState.copy( + selectedExperience = it + ) + ) + }, icon = Icons.Rounded.Star, modifier = Modifier.fillMaxWidth() ) @@ -142,7 +157,11 @@ fun CandidateSearchScreen(viewModel: CandidateSearchViewModel) { // Search Button Button( onClick = { - viewModel.searchCandidates(selectedJobTitle, selectedLocation, selectedExperience) + onSearchButtonClicked(searchUiState) + viewModel.searchCandidates( + searchUiState.selectedJobTitle, + searchUiState.selectedLocation, + searchUiState.selectedExperience) }, modifier = Modifier .fillMaxWidth() diff --git a/app/src/main/java/com/example/estiaseek/ui/screens/CreateApplicant.kt b/app/src/main/java/com/example/estiaseek/ui/screens/CreateApplicant.kt index 3390957..e766f43 100644 --- a/app/src/main/java/com/example/estiaseek/ui/screens/CreateApplicant.kt +++ b/app/src/main/java/com/example/estiaseek/ui/screens/CreateApplicant.kt @@ -38,10 +38,14 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import com.example.estiaseek.R import com.example.estiaseek.ui.components.DropdownMenuField +import kotlin.Unit @Composable -fun CreateApplicant(viewModel: CreateApplicantViewModel) { +fun CreateApplicant( + onCreateApplicantButtonClicked: () -> Unit, + viewModel: CreateApplicantViewModel +) { var name by remember { mutableStateOf("") } var email by remember { mutableStateOf("") } var bio by remember { mutableStateOf("") } @@ -285,6 +289,7 @@ fun CreateApplicant(viewModel: CreateApplicantViewModel) { location = selectedLocation, experience = selectedExperience ) + onCreateApplicantButtonClicked() } }, modifier = Modifier diff --git a/app/src/main/java/com/example/estiaseek/ui/screens/HomeScreen.kt b/app/src/main/java/com/example/estiaseek/ui/screens/HomeScreen.kt new file mode 100644 index 0000000..f084d9c --- /dev/null +++ b/app/src/main/java/com/example/estiaseek/ui/screens/HomeScreen.kt @@ -0,0 +1,101 @@ +package com.example.estiaseek.ui.screens + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.example.estiaseek.R +import com.example.estiaseek.ui.theme.EstiaSeekTheme + +@Composable +fun HomeScreen( + onSearchButtonClicked: () -> Unit, + onCreateApplicantButtonClicked: () -> Unit, +) { + EstiaSeekTheme { // Ensure the app's theme is applied + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background // Consistent background + ) { + Box( + modifier = Modifier + .fillMaxSize() + .verticalScroll(rememberScrollState()) + .safeDrawingPadding() + .padding(horizontal = 40.dp) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(16.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + // Heading + Text( + text = "Welcome to EstiaSeek", + style = MaterialTheme.typography.headlineMedium.copy( + fontWeight = FontWeight.Bold // Make the text bold + ), + modifier = Modifier.padding(bottom = 24.dp) + ) + + // Image + Image( + painter = painterResource(id = R.drawable.emp), + contentDescription = "Example Image", + modifier = Modifier + .size(150.dp) + .padding(bottom = 24.dp) + ) + + // First Button + Button( + onClick = { + onSearchButtonClicked() + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + ) { + Text(text = "Search Candidate") + } + + Spacer(modifier = Modifier.height(16.dp)) + + // Second Button + Button( + onClick = { + onCreateApplicantButtonClicked() + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + ) { + Text(text = "Create Applicant") + } + } + } + } + } +} + +@Preview(showBackground = true) +@Composable +fun HomeScreenPreview() { + HomeScreen( + onSearchButtonClicked = {}, + onCreateApplicantButtonClicked = {} + ) +} diff --git a/app/src/main/java/com/example/estiaseek/ui/screens/ResultsScreen.kt b/app/src/main/java/com/example/estiaseek/ui/screens/ResultsScreen.kt index 0653173..56fc261 100644 --- a/app/src/main/java/com/example/estiaseek/ui/screens/ResultsScreen.kt +++ b/app/src/main/java/com/example/estiaseek/ui/screens/ResultsScreen.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close +import androidx.compose.material3.Button import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.InputChip @@ -23,6 +24,7 @@ import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember @@ -40,11 +42,21 @@ import androidx.navigation.compose.rememberNavController import com.example.estiaseek.R import com.example.estiaseek.ui.components.BottomNavigationBar import com.example.estiaseek.ui.components.ImageCard +import com.example.estiaseek.ui.viewmodels.ProfileViewModel +import com.example.estiaseek.ui.viewmodels.SearchViewModel @OptIn(ExperimentalMaterial3Api::class) -@Preview(showBackground = false) @Composable -fun ResultsScreen() { +fun ResultsScreen( + searchViewModel : SearchViewModel, + profileViewModel: ProfileViewModel, + onProfileClicked: () -> Unit, +) { + val profileViewState by profileViewModel.profileViewState.collectAsState() + + val searchUiState by searchViewModel.searchUIState.collectAsState() + + //TODO we cant have a full fat nav controller here val navController = rememberNavController() Scaffold( @@ -53,7 +65,23 @@ fun ResultsScreen() { } ) { paddingValues -> - + // TODO Clicking on profile should envoke onProfileClicked and updaye state with username + // FIXME TEST BUTTON REMOVE WHEN FIXED + Button( + onClick = { + profileViewModel.updateProfileView( + newState = profileViewState.copy( + username = "panos1b" + ) + ) + onProfileClicked() + }, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 32.dp) + ) { + Text(text = "GO TO PROFILE DEMO BUTTON") + } Column(modifier = Modifier.padding(35.dp).padding(paddingValues)) { Row( modifier = Modifier @@ -175,7 +203,11 @@ fun ResultsScreen() { @Composable @Preview(showBackground = false) fun PreviewResults() { - ResultsScreen() + ResultsScreen( + searchViewModel = SearchViewModel(), + profileViewModel = ProfileViewModel(), + onProfileClicked = {} + ) } @Composable diff --git a/app/src/main/java/com/example/estiaseek/ui/viewmodels/ProfileViewModel.kt b/app/src/main/java/com/example/estiaseek/ui/viewmodels/ProfileViewModel.kt new file mode 100644 index 0000000..e3ad6b6 --- /dev/null +++ b/app/src/main/java/com/example/estiaseek/ui/viewmodels/ProfileViewModel.kt @@ -0,0 +1,20 @@ +package com.example.estiaseek.ui.viewmodels + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +data class ProfileViewState( + val username: String = "", +) + + + +class ProfileViewModel : ViewModel() { + private val _profileViewState = MutableStateFlow(ProfileViewState()) + val profileViewState: StateFlow get() = _profileViewState + + fun updateProfileView(newState: ProfileViewState) { + _profileViewState.value = newState + } +} diff --git a/app/src/main/java/com/example/estiaseek/ui/viewmodels/SearchViewModel.kt b/app/src/main/java/com/example/estiaseek/ui/viewmodels/SearchViewModel.kt new file mode 100644 index 0000000..c99598a --- /dev/null +++ b/app/src/main/java/com/example/estiaseek/ui/viewmodels/SearchViewModel.kt @@ -0,0 +1,20 @@ +package com.example.estiaseek.ui.viewmodels + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +data class SearchUiState( + val selectedJobTitle: String = "Any", + val selectedLocation: String = "Any", + val selectedExperience: String = "Any", +) + +class SearchViewModel : ViewModel() { + private val _searchUIState = MutableStateFlow(SearchUiState()) + val searchUIState: StateFlow get() = _searchUIState + + fun updateSearchState(newState: SearchUiState) { + _searchUIState.value = newState + } +} diff --git a/app/src/main/res/drawable/emp.png b/app/src/main/res/drawable/emp.png new file mode 100644 index 0000000..bf32155 Binary files /dev/null and b/app/src/main/res/drawable/emp.png differ