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
5 changes: 5 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.0")

// Images
implementation("io.coil-kt.coil3:coil-compose:3.0.4")
implementation("io.coil-kt.coil3:coil-network-okhttp:3.0.4")
implementation("com.valentinilk.shimmer:compose-shimmer:1.3.1")

//Maps
implementation("com.google.maps.android:maps-compose:4.0.0")
implementation("com.google.android.gms:play-services-maps:19.0.0")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,26 @@
package com.cornellappdev.transit

import android.app.Application
import coil3.ImageLoader
import coil3.PlatformContext
import coil3.SingletonImageLoader
import coil3.disk.DiskCache
import coil3.disk.directory
import coil3.memory.MemoryCache
import coil3.request.crossfade
import dagger.hilt.android.HiltAndroidApp

@HiltAndroidApp
class TransitApplication : Application()
class TransitApplication : Application(), SingletonImageLoader.Factory {
override fun newImageLoader(context: PlatformContext): ImageLoader {
// Default image loading procedure for network request images
return ImageLoader.Builder(context)
.memoryCache {
MemoryCache.Builder()
.maxSizePercent(context, 0.25)
.build()
}
.crossfade(true)
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.cornellappdev.transit.models.ecosystem

/**
* Mapping of a day of the week to the hours for that day
*/
data class DayOperatingHours(
val dayOfWeek: String,
val hours: List<String>
)
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.cornellappdev.transit.models.ecosystem

import com.cornellappdev.transit.util.TimeUtils.toPascalCaseString
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import java.time.DayOfWeek
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter

/**
* Data class representing an eatery
*/
@JsonClass(generateAdapter = true)
data class Eatery(
@Json(name = "id") var id: Int,
@Json(name = "name") var name: String,
Expand All @@ -18,4 +24,79 @@ data class Eatery(
@Json(name = "payment_accepts_meal_swipes") var paymentAcceptsMealSwipes: Boolean?,
@Json(name = "payment_accepts_brbs") var paymentAcceptsBrbs: Boolean?,
@Json(name = "payment_accepts_cash") var paymentAcceptsCash: Boolean?,
) : DetailedEcosystemPlace
@Json(name = "events") val events: List<Event>?
) : DetailedEcosystemPlace {

/**
* Value to represent the custom order of days in a week (with Sunday as
* the first day due to a particular design choice). Used for sorting purposes
*/
private val dayOrder = mapOf(
"Sunday" to 1,
"Monday" to 2,
"Tuesday" to 3,
"Wednesday" to 4,
"Thursday" to 5,
"Friday" to 6,
"Saturday" to 7
)

/**
* @Return a list of pairs representing each day of the week
* and the corresponding times that an eatery is open. The list is sorted
* by day with the custom dayOrder (Sunday first).
*/
fun formatOperatingHours(): List<DayOperatingHours> {
val dailyHours = operatingHours()

// Convert map to list and sort by custom day order
return dailyHours.entries
.sortedBy { entry ->
val dayName =
entry.key.toPascalCaseString()
dayOrder[dayName] ?: Int.MAX_VALUE
}
.map { entry ->
val dayName =
entry.key.toPascalCaseString()
DayOperatingHours(dayName, entry.value)
}
}

/**
* @Return a map of each day of the week to its list of operating hours
*/
private fun operatingHours(): Map<DayOfWeek, MutableList<String>> {
val dailyHours = mutableMapOf<DayOfWeek, MutableList<String>>()

events?.forEach { event ->
val dayOfWeek = event.startTime?.dayOfWeek
val openTime = event.startTime?.format(DateTimeFormatter.ofPattern("h:mm a"))
val closeTime = event.endTime?.format(DateTimeFormatter.ofPattern("h:mm a"))

val timeString = "$openTime - $closeTime"

if (dayOfWeek != null && dailyHours[dayOfWeek]?.none { it.contains(timeString) } != false) {
dailyHours.computeIfAbsent(dayOfWeek) { mutableListOf() }.add(timeString)
}
}

DayOfWeek.entries.forEach { dayOfWeek ->
dailyHours.computeIfAbsent(dayOfWeek) { mutableListOf("Closed") }
}

return dailyHours
}
}


@JsonClass(generateAdapter = true)
data class Event(
@Json(name = "id") val id: Int? = null,
/**
* Descriptions tend to be "Lunch", "Dinner", etc..
*/
@Json(name = "event_description") val description: String? = null,
@Json(name = "start") val startTime: LocalDateTime? = null,
@Json(name = "end") val endTime: LocalDateTime? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import com.cornellappdev.transit.models.ecosystem.Eatery
import retrofit2.http.GET

interface EateryNetworkApi {
@GET("/eatery/simple")
@GET("/eatery/")
suspend fun getEateries(): List<Eatery>

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ package com.cornellappdev.transit.networking
import com.google.android.gms.maps.model.LatLng
import com.squareup.moshi.FromJson
import com.squareup.moshi.Json
import com.squareup.moshi.ToJson
import java.text.ParseException
import java.time.Instant
import java.time.LocalDateTime
import java.time.ZoneId
import java.time.format.DateTimeFormatter

class LatLngAdapter {

Expand All @@ -21,4 +27,22 @@ class LatLngAdapter {
fun fromJson(coord: Coordinate): LatLng {
return LatLng(coord.latitude, coord.longitude)
}
}

class DateTimeAdapter {
@ToJson
fun toJson(dateTime: LocalDateTime): String {
return dateTime.format(DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss'Z'"))
}

@FromJson
fun fromJson(dateTime: Long): LocalDateTime {
try {
val instant = Instant.ofEpochSecond(dateTime)
return LocalDateTime.ofInstant(instant, ZoneId.systemDefault())
} catch (e: ParseException) {
e.printStackTrace()
}
return LocalDateTime.MIN
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ object NetworkModule {
fun provideMoshi(): Moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.add(LatLngAdapter())
.add(DateTimeAdapter())
.build()

@Provides
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.cornellappdev.transit.ui.components.home

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.cornellappdev.transit.ui.theme.MetadataGray

@Composable
fun CenteredSpinningIndicator() {
Row(
modifier = Modifier
.fillMaxWidth()
.height(138.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
CircularProgressIndicator(color = MetadataGray, modifier = Modifier.size(40.dp))
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package com.cornellappdev.transit.ui.components.home

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.cornellappdev.transit.ui.theme.PrimaryText
import com.cornellappdev.transit.ui.theme.SecondaryText
import com.cornellappdev.transit.ui.theme.Style

/**
* Text area of detailed place header with favorites star
*/
@Composable
fun DetailedPlaceHeaderSection(
title: String,
subtitle: String?,
leftAnnotatedString: AnnotatedString? = null,
rightAnnotatedString: AnnotatedString? = null,
onFavoriteClick: () -> Unit,
isFavorite: Boolean
) {
Box(
modifier = Modifier
.fillMaxWidth()
.padding(top = 24.dp)
) {
Column(
modifier = Modifier
.align(Alignment.CenterStart),
) {
Text(
text = title,
style = Style.detailHeading,
color = PrimaryText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(end = 32.dp, bottom = 12.dp)
)
subtitle?.let {
Text(
text = subtitle,
style = Style.cardSubtitle,
color = SecondaryText,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.padding(end = 32.dp, bottom = 8.dp)

)
}
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
leftAnnotatedString?.let {
Text(
text = leftAnnotatedString,
style = Style.cardSubtitle
)
}
Spacer(modifier = Modifier.weight(1f))
rightAnnotatedString?.let {
Text(
text = rightAnnotatedString,
style = Style.cardSubtitle
)
}
}
}

FavoritesStar(onFavoriteClick, isFavorite)
}
}

@Preview(showBackground = true)
@Composable
private fun DetailedPlaceHeaderSectionPreview() {
DetailedPlaceHeaderSection(
title = "Atrium Cafe",
subtitle = "Sage Hall",
onFavoriteClick = {},
isFavorite = false
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -90,8 +90,13 @@ fun DetailedPlaceSheetContent(
// Main Content
when (ecosystemPlace) {
is Eatery -> {
//TODO
Text(ecosystemPlace.name)
EateryDetailsContent(
eatery = ecosystemPlace,
isFavorite = ecosystemPlace.toPlace() in favorites,
onFavoriteClick = {
onFavoriteStarClick(ecosystemPlace.toPlace())
}
)
}

is Library -> {
Expand Down Expand Up @@ -131,7 +136,9 @@ fun DetailedPlaceSheetContent(
.clickable {
when (ecosystemPlace) {
is Eatery -> {
//TODO
navigateToPlace(
ecosystemPlace.toPlace()
)
}

is Library -> {
Expand Down
Loading