Skip to content

Commit e6f4b3b

Browse files
authored
Merge pull request #21 from cuappdev/maxwell/SearchFlow
Search Flow View Model Implementation
2 parents 749232c + 342458f commit e6f4b3b

File tree

15 files changed

+257
-137
lines changed

15 files changed

+257
-137
lines changed

app/src/main/java/com/cornellappdev/scoop/data/NetworkApi.kt

+4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.cornellappdev.scoop.data
22

33
import com.cornellappdev.scoop.data.models.Ride
44
import com.cornellappdev.scoop.data.models.RideRequestBody
5+
import com.cornellappdev.scoop.data.models.Search
56
import retrofit2.http.Body
67
import retrofit2.http.GET
78
import retrofit2.http.POST
@@ -16,4 +17,7 @@ interface NetworkApi {
1617

1718
@GET("/api/ride/{ride_id}")
1819
suspend fun getRide(@Path("ride_id") rideId: Int): Ride
20+
21+
@GET("/api/search/")
22+
suspend fun searchForRides(@Body searchBody: Search): List<Ride>
1923
}
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
package com.cornellappdev.scoop.data.models
22

3+
import com.squareup.moshi.Json
4+
import com.squareup.moshi.JsonClass
5+
6+
@JsonClass(generateAdapter = true)
37
data class Search(
4-
var departureLocation: String? = null,
5-
var arrivalLocation: String? = null,
6-
var departureDate: String? = null
8+
@Json(name = "start_location_place_id") var departureLocationPlaceId: String? = null,
9+
@Json(name = "start_location_name") var departureLocationName: String? = null,
10+
@Json(name = "end_location_place_id") var arrivalLocationPlaceId: String? = null,
11+
@Json(name = "end_location_name") var arrivalLocationName: String? = null,
12+
@Json(name = "departure_datetime") var departureDate: String? = null
713
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.cornellappdev.scoop.data.repositories
2+
3+
import com.cornellappdev.scoop.data.NetworkApi
4+
import com.cornellappdev.scoop.data.models.Ride
5+
import com.cornellappdev.scoop.data.models.Search
6+
import javax.inject.Inject
7+
import javax.inject.Singleton
8+
9+
@Singleton
10+
class SearchRepository @Inject constructor(private val networkApi: NetworkApi) {
11+
12+
suspend fun searchForRides(
13+
startLocationPlaceId: String,
14+
startLocationName: String,
15+
endLocationPlaceId: String,
16+
endLocationName: String,
17+
departureDateTime: String,
18+
): List<Ride> =
19+
networkApi.searchForRides(
20+
Search(
21+
startLocationPlaceId,
22+
startLocationName,
23+
endLocationPlaceId,
24+
endLocationName,
25+
departureDateTime
26+
)
27+
)
28+
29+
suspend fun getAllRides(): List<Ride> = networkApi.getAllRides()
30+
}

app/src/main/java/com/cornellappdev/scoop/ui/components/general/DatePicker.kt

+14-13
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package com.cornellappdev.scoop.ui.components.general
22

3-
import android.util.Log
43
import androidx.compose.foundation.border
54
import androidx.compose.foundation.clickable
65
import androidx.compose.foundation.layout.*
@@ -24,7 +23,7 @@ import java.text.SimpleDateFormat
2423
import java.util.*
2524

2625
/**
27-
* Allows users to select a city using Place API autocomplete.
26+
* Allows users to select a date using datePickerDialog.
2827
*
2928
* @param dateState State that represents the current time selected by user
3029
* @param placeholder The placeholder to be displayed when the text field is in focus and the input text is empty
@@ -54,7 +53,7 @@ fun DatePicker(
5453

5554
val datePickerDialog = createDatePickerDialog(
5655
LocalContext.current,
57-
{ Log.d("Date changed", it); onDateChanged(it) },
56+
onDateChanged,
5857
dateFormatter
5958
)
6059

@@ -73,17 +72,10 @@ fun DatePicker(
7372
) {
7473
Row(
7574
modifier = Modifier
76-
.padding(start = 10.dp)
75+
.padding(horizontal = 10.dp)
76+
.fillMaxWidth(),
77+
horizontalArrangement = Arrangement.SpaceBetween
7778
) {
78-
Icon(
79-
icon,
80-
null,
81-
modifier = Modifier
82-
.padding(vertical = 15.dp)
83-
)
84-
Spacer(
85-
modifier = Modifier.width(5.dp)
86-
)
8779
Column(
8880
modifier = Modifier.fillMaxHeight(),
8981
verticalArrangement = Arrangement.Center
@@ -105,6 +97,15 @@ fun DatePicker(
10597
Divider(color = Color.Black, thickness = 2.dp)
10698
}
10799
}
100+
Spacer(
101+
modifier = Modifier.width(5.dp)
102+
)
103+
Icon(
104+
icon,
105+
null,
106+
modifier = Modifier
107+
.padding(vertical = 15.dp)
108+
)
108109
}
109110
}
110111
}

app/src/main/java/com/cornellappdev/scoop/ui/components/search/DisplaySearchesPage.kt

+6-4
Original file line numberDiff line numberDiff line change
@@ -28,20 +28,22 @@ import androidx.compose.ui.unit.dp
2828
import androidx.compose.ui.unit.sp
2929
import com.cornellappdev.scoop.R
3030
import com.cornellappdev.scoop.data.models.Ride
31-
import com.cornellappdev.scoop.data.models.Search
3231
import com.cornellappdev.scoop.ui.components.general.FilterRow
3332
import com.cornellappdev.scoop.ui.components.general.RideCard
33+
import com.cornellappdev.scoop.ui.viewmodel.SearchScreenViewModel
3434

3535
/**
3636
* This page allows users to look at their search, filter and view their results.
3737
*
3838
* Users also have the ability to edit their search.
3939
*
40-
* @param searchState State that represents the current search the user inputted
40+
* @param searchScreenViewModel ViewModel that represents the current search the user inputted
4141
*/
4242
@SuppressLint("UnusedMaterialScaffoldPaddingParameter")
4343
@Composable
44-
fun DisplaySearchesPage(searchState: MutableState<Search>) {
44+
fun DisplaySearchesPage(
45+
searchScreenViewModel: SearchScreenViewModel
46+
) {
4547
val searchResults: MutableState<List<Ride>> = remember {
4648
mutableStateOf(listOf())
4749
}
@@ -81,7 +83,7 @@ fun DisplaySearchesPage(searchState: MutableState<Search>) {
8183
}) {
8284
Column(modifier = Modifier.padding(10.dp)) {
8385
SearchCard(
84-
searchState,
86+
searchScreenViewModel,
8587
filter,
8688
isEditing
8789
) {

app/src/main/java/com/cornellappdev/scoop/ui/components/search/SearchCard.kt

+21-16
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,9 @@ import androidx.compose.ui.res.stringResource
2727
import androidx.compose.ui.unit.dp
2828
import com.cornellappdev.scoop.R
2929
import com.cornellappdev.scoop.data.models.Ride
30-
import com.cornellappdev.scoop.data.models.Search
3130
import com.cornellappdev.scoop.ui.components.general.CityPicker
3231
import com.cornellappdev.scoop.ui.components.post.createDatePickerDialog
32+
import com.cornellappdev.scoop.ui.viewmodel.SearchScreenViewModel
3333
import java.text.SimpleDateFormat
3434
import java.util.*
3535

@@ -50,25 +50,28 @@ import java.util.*
5050
*/
5151
@Composable
5252
fun SearchCard(
53-
search: MutableState<Search>,
53+
searchScreenViewModel: SearchScreenViewModel,
5454
filter: MutableState<String?>,
5555
isEditing: MutableState<Boolean>,
5656
onSearchCompleted: (List<Ride>) -> Unit,
5757
) {
58-
// CityPicker requires MutableStates for it's values but the Search model does
59-
// not have MutableStates for it's fields, so we must convert them and update the
58+
// CityPicker requires MutableStates for its values but the Search model does
59+
// not have MutableStates for its fields, so we must convert them and update the
6060
// search state in the callback of CityPicker.
61-
val departureLocation = remember { mutableStateOf(search.value.departureLocation!!) }
62-
val arrivalLocation = remember { mutableStateOf(search.value.arrivalLocation!!) }
63-
val departureDate = remember { mutableStateOf(search.value.departureDate!!) }
61+
val departureLocationName =
62+
remember { mutableStateOf(searchScreenViewModel.search.departureLocationName.orEmpty()) }
63+
val arrivalLocationName =
64+
remember { mutableStateOf(searchScreenViewModel.search.arrivalLocationName.orEmpty()) }
65+
val departureDate =
66+
remember { mutableStateOf(searchScreenViewModel.search.departureDate.orEmpty()) }
6467

6568
val dateFormatter =
6669
SimpleDateFormat(stringResource(R.string.month_name_day_year_format), Locale.US)
6770
val datePickerDialog = createDatePickerDialog(
6871
LocalContext.current,
6972
{ newDate ->
7073
if (departureDate.value != newDate) {
71-
search.value.departureDate = newDate
74+
searchScreenViewModel.search.departureDate = newDate
7275
departureDate.value = newDate
7376

7477
// Query backend to get results with given date, filter if there's a filter
@@ -99,7 +102,7 @@ fun SearchCard(
99102
contentDescription = stringResource(R.string.details_icon_description)
100103
)
101104
CityPicker(
102-
cityState = departureLocation,
105+
cityState = departureLocationName,
103106
modifier = Modifier.apply {
104107
if (isEditing.value) {
105108
align(Alignment.Bottom)
@@ -112,9 +115,10 @@ fun SearchCard(
112115
enabled = isEditing.value,
113116
disabledTextStyle = MaterialTheme.typography.subtitle1,
114117
disableDivider = !isEditing.value
115-
) { it, _ ->
116-
if (search.value.departureLocation != it) {
117-
search.value.departureLocation = it
118+
) { name, id ->
119+
if (searchScreenViewModel.search.departureLocationPlaceId != id) {
120+
searchScreenViewModel.setDepartureName(name)
121+
searchScreenViewModel.setDeparturePlaceId(id)
118122

119123
// TODO: Networking for searching for rides should be inserted here and passed into callback.
120124
onSearchCompleted(listOf())
@@ -158,7 +162,7 @@ fun SearchCard(
158162
contentDescription = stringResource(R.string.details_icon_description)
159163
)
160164
CityPicker(
161-
cityState = arrivalLocation,
165+
cityState = arrivalLocationName,
162166
modifier = Modifier.apply {
163167
if (isEditing.value) {
164168
align(Alignment.Bottom)
@@ -171,9 +175,10 @@ fun SearchCard(
171175
enabled = isEditing.value,
172176
disabledTextStyle = MaterialTheme.typography.subtitle1,
173177
disableDivider = !isEditing.value
174-
) { it, _ ->
175-
if (search.value.arrivalLocation != it) {
176-
search.value.arrivalLocation = it
178+
) { name, id ->
179+
if (searchScreenViewModel.search.arrivalLocationPlaceId != id) {
180+
searchScreenViewModel.setArrivalName(name)
181+
searchScreenViewModel.setArrivalPlaceId(id)
177182

178183
/** TODO: Networking for searching for rides should be inserted here and passed into callback. */
179184
onSearchCompleted(listOf())

app/src/main/java/com/cornellappdev/scoop/ui/components/search/SearchFirstPage.kt

+67-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
package com.cornellappdev.scoop.ui.components.search
22

3+
import androidx.compose.foundation.background
34
import androidx.compose.foundation.layout.*
5+
import androidx.compose.foundation.shape.RoundedCornerShape
6+
import androidx.compose.material.ButtonDefaults
47
import androidx.compose.material.Text
8+
import androidx.compose.material.TextButton
59
import androidx.compose.material.icons.Icons
610
import androidx.compose.material.icons.filled.CalendarToday
711
import androidx.compose.material.icons.filled.NearMe
@@ -10,24 +14,48 @@ import androidx.compose.runtime.Composable
1014
import androidx.compose.runtime.mutableStateOf
1115
import androidx.compose.runtime.saveable.rememberSaveable
1216
import androidx.compose.ui.Alignment
17+
import androidx.compose.ui.Alignment.Companion.Center
1318
import androidx.compose.ui.Modifier
1419
import androidx.compose.ui.graphics.Color
1520
import androidx.compose.ui.res.stringResource
1621
import androidx.compose.ui.text.TextStyle
22+
import androidx.compose.ui.text.font.FontWeight.Companion.Bold
1723
import androidx.compose.ui.text.style.TextAlign
1824
import androidx.compose.ui.unit.dp
1925
import androidx.compose.ui.unit.sp
2026
import com.cornellappdev.scoop.R
2127
import com.cornellappdev.scoop.ui.components.general.CityPicker
2228
import com.cornellappdev.scoop.ui.components.general.DatePicker
29+
import com.cornellappdev.scoop.ui.theme.Green
2330
import com.cornellappdev.scoop.ui.theme.PlaceholderGray
31+
import com.cornellappdev.scoop.ui.theme.typography
32+
import com.cornellappdev.scoop.ui.viewmodel.SearchScreenViewModel
2433

34+
/**
35+
* Displays the first page of search including departure location, arrival location, and date
36+
*/
2537
@Composable
26-
fun SearchFirstPage() {
38+
fun SearchFirstPage(
39+
onProceedClicked: () -> Unit,
40+
searchScreenViewModel: SearchScreenViewModel
41+
) {
2742
val departureLocation = rememberSaveable { mutableStateOf("") }
2843
val arrivalLocation = rememberSaveable { mutableStateOf("") }
2944
val dateText = rememberSaveable { mutableStateOf("") }
3045

46+
val proceedEnabled = rememberSaveable {
47+
mutableStateOf(
48+
searchScreenViewModel.search.departureLocationName != null
49+
&& searchScreenViewModel.search.arrivalLocationName != null
50+
&& searchScreenViewModel.search.departureDate != null
51+
)
52+
}
53+
val updateProceedEnabled = {
54+
proceedEnabled.value = searchScreenViewModel.search.departureLocationName != null
55+
&& searchScreenViewModel.search.arrivalLocationName != null
56+
&& searchScreenViewModel.search.departureDate != null
57+
}
58+
3159
Column(
3260
modifier = Modifier
3361
.fillMaxWidth()
@@ -42,15 +70,19 @@ fun SearchFirstPage() {
4270
textAlign = TextAlign.Center
4371
)
4472
Spacer(
45-
modifier = Modifier.height(120.dp)
73+
modifier = Modifier.height(150.dp)
4674
)
4775
CityPicker(
4876
cityState = departureLocation,
4977
placeholder = "Departure location",
5078
placeholderColor = PlaceholderGray,
5179
icon = Icons.Filled.NearMe,
5280
disableDivider = true
53-
)
81+
) { name, id ->
82+
searchScreenViewModel.setDepartureName(name)
83+
searchScreenViewModel.setDeparturePlaceId(id)
84+
updateProceedEnabled()
85+
}
5486
Spacer(
5587
modifier = Modifier.height(24.dp)
5688
)
@@ -60,7 +92,11 @@ fun SearchFirstPage() {
6092
placeholderColor = PlaceholderGray,
6193
icon = Icons.Filled.Place,
6294
disableDivider = true
63-
)
95+
) { name, id ->
96+
searchScreenViewModel.setArrivalName(name)
97+
searchScreenViewModel.setArrivalPlaceId(id)
98+
updateProceedEnabled()
99+
}
64100
Spacer(
65101
modifier = Modifier.height(24.dp)
66102
)
@@ -71,9 +107,36 @@ fun SearchFirstPage() {
71107
disableDivider = true,
72108
) {
73109
dateText.value = it
110+
searchScreenViewModel.setDepartureDate(it)
111+
updateProceedEnabled()
74112
}
75113
Spacer(
76114
modifier = Modifier.height(71.dp)
77115
)
116+
TextButton(
117+
onClick = onProceedClicked,
118+
modifier = Modifier
119+
.fillMaxWidth()
120+
.height(50.dp)
121+
.padding(horizontal = 8.dp)
122+
.background(Green, RoundedCornerShape(25.dp)),
123+
shape = RoundedCornerShape(25.dp),
124+
colors = ButtonDefaults.buttonColors(
125+
backgroundColor = Green,
126+
disabledBackgroundColor = Color(0xFFDBE5DF)
127+
),
128+
enabled = proceedEnabled.value
129+
) {
130+
Box(modifier = Modifier.fillMaxSize()) {
131+
Text(
132+
text = "Find Trips",
133+
style = typography.body1,
134+
color = Color.White,
135+
fontWeight = Bold,
136+
modifier = Modifier.align(Center),
137+
textAlign = TextAlign.Center
138+
)
139+
}
140+
}
78141
}
79142
}

0 commit comments

Comments
 (0)