Skip to content
Merged
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
4 changes: 2 additions & 2 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@ android {
applicationId = "com.cornellappdev.transit"
minSdk = 26
targetSdk = 36
versionCode = 10
versionName = "2.0"
versionCode = 11
versionName = "2.1"

testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

<queries>
<package android:name="com.cornellappdev.android.eatery" />
<package android:name="com.cornellappdev.uplift" />
</queries>

<application
android:name=".TransitApplication"
android:allowBackup="true"
Expand All @@ -24,7 +29,6 @@
android:screenOrientation="portrait">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
Expand All @@ -40,6 +41,7 @@ import com.cornellappdev.transit.ui.theme.SecondaryText
import com.cornellappdev.transit.ui.theme.Style
import com.cornellappdev.transit.ui.theme.TransitBlue
import com.cornellappdev.transit.util.BOTTOM_SHEET_MAX_HEIGHT_PERCENT
import com.cornellappdev.transit.util.IntentUtils.openDeepLink

@Composable
fun DetailedPlaceSheetContent(
Expand All @@ -51,6 +53,8 @@ fun DetailedPlaceSheetContent(
modifier: Modifier = Modifier,
distanceStringToPlace: (Double?, Double?) -> String
) {
val context = LocalContext.current

Column(
modifier = modifier
.fillMaxWidth()
Expand Down Expand Up @@ -98,6 +102,9 @@ fun DetailedPlaceSheetContent(
onFavoriteClick = {
onFavoriteStarClick(ecosystemPlace.toPlace())
},
onDeepLinkClick = {
context.openDeepLink("com.cornellappdev.android.eatery")
},
distanceString = distanceStringToPlace(
ecosystemPlace.latitude,
ecosystemPlace.longitude
Expand All @@ -122,6 +129,9 @@ fun DetailedPlaceSheetContent(
onFavoriteClick = {
onFavoriteStarClick(ecosystemPlace.toPlace())
},
onDeepLinkClick = {
context.openDeepLink("com.cornellappdev.uplift")
},
distanceString = distanceStringToPlace(
ecosystemPlace.latitude,
ecosystemPlace.longitude
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.cornellappdev.transit.ui.components.home

import android.content.ActivityNotFoundException
import android.content.Intent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -10,9 +13,11 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
Expand All @@ -28,12 +33,14 @@ import com.cornellappdev.transit.util.StringUtils.createDeepLink
import com.cornellappdev.transit.util.TimeUtils.isOpenAnnotatedStringFromOperatingHours
import com.cornellappdev.transit.util.TimeUtils.rotateOperatingHours
import com.cornellappdev.transit.util.getAboutContent
import androidx.core.net.toUri

@Composable
fun EateryDetailsContent(
eatery: Eatery,
isFavorite: Boolean,
onFavoriteClick: () -> Unit,
onDeepLinkClick: () -> Unit,
distanceString: String,
) {
Column(
Expand Down Expand Up @@ -88,12 +95,16 @@ fun EateryDetailsContent(
val (annotatedString, inlineContent) =
stringResource(R.string.view_menu).createDeepLink(R.drawable.eaterylink)

Text(
text = annotatedString,
inlineContent = inlineContent,
style = Style.heading2,
color = TransitBlue
)
TextButton(
onClick = onDeepLinkClick
) {
Text(
text = annotatedString,
inlineContent = inlineContent,
style = Style.heading2,
color = TransitBlue,
)
}

Spacer(modifier = Modifier.height(24.dp))

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

import android.content.ActivityNotFoundException
import android.content.Intent
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
Expand All @@ -10,12 +13,15 @@ import androidx.compose.foundation.layout.size
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import com.cornellappdev.transit.R
import com.cornellappdev.transit.models.ecosystem.UpliftGym
import com.cornellappdev.transit.ui.theme.DividerGray
Expand All @@ -39,6 +45,7 @@ fun GymDetailsContent(
gym: UpliftGym,
isFavorite: Boolean,
onFavoriteClick: () -> Unit,
onDeepLinkClick: () -> Unit,
distanceString: String
) {
val isOpen = getOpenStatus(gym.operatingHours()).isOpen
Expand Down Expand Up @@ -97,12 +104,16 @@ fun GymDetailsContent(
val (annotatedString, inlineContent) =
stringResource(R.string.view_gym).createDeepLink(R.drawable.upliftlink)

Text(
text = annotatedString,
inlineContent = inlineContent,
style = Style.heading2,
color = TransitBlue
)
TextButton(
onClick = onDeepLinkClick
) {
Text(
text = annotatedString,
inlineContent = inlineContent,
style = Style.heading2,
color = TransitBlue,
)
}

Spacer(modifier = Modifier.height(24.dp))

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,18 @@ class HomeViewModel @Inject constructor(
) : ViewModel() {

val libraryCardsFlow: StateFlow<ApiResponse<List<LibraryCardUiState>>> =
routeRepository.libraryFlow.map { response ->
combine(
routeRepository.libraryFlow,
locationRepository.currentLocation
) { response, location ->
val usersLocation = location?.let { LatLng(it.latitude, it.longitude) }
when (val filteredResponse = response.withExcludedLibrariesRemoved()) {
is ApiResponse.Success -> {
val sortedLibraries = filteredResponse.data.sortedBy {
numericalDistanceToPlace(it.latitude, it.longitude, usersLocation)
}
ApiResponse.Success(
filteredResponse.data
sortedLibraries
.map { it.toLibraryCardUiState() }
)
}
Expand Down Expand Up @@ -119,6 +126,9 @@ class HomeViewModel @Inject constructor(
gymRepository.gymFlow,
locationRepository.currentLocation
) { printers, libraries, eateries, gyms, location ->
val userLocation = location?.let { LatLng(it.latitude, it.longitude) }


StaticPlaces(
printers = sortApiResponse(
response = if (printers is ApiResponse.Success) {
Expand All @@ -127,23 +137,27 @@ class HomeViewModel @Inject constructor(
printers
},
getLatitude = { it.latitude },
getLongitude = { it.longitude }
getLongitude = { it.longitude },
userLocation = userLocation
),
libraries = sortApiResponse(
response = libraries,
response = libraries.withExcludedLibrariesRemoved(),
getLatitude = { it.latitude },
getLongitude = { it.longitude }
).withExcludedLibrariesRemoved(),
getLongitude = { it.longitude },
userLocation = userLocation
),
eateries = sortApiResponse(
response = eateries,
getLatitude = { it.latitude },
getLongitude = { it.longitude },
userLocation = userLocation,
getIsOpen = { TimeUtils.getOpenStatus(it.operatingHours()).isOpen }
),
gyms = sortApiResponse(
response = gyms,
getLatitude = { it.latitude },
getLongitude = { it.longitude },
userLocation = userLocation,
getIsOpen = { TimeUtils.getOpenStatus(it.operatingHours()).isOpen }
)
)
Expand Down Expand Up @@ -513,14 +527,14 @@ class HomeViewModel @Inject constructor(
/**
* Returns a numerical distance from a location to the current location if both exist, otherwise returns Double.MAX_VALUE
*/
fun numericalDistanceToPlace(latitude: Double?, longitude: Double?): Double {
val currentLocationSnapshot = currentLocation.value
return if (currentLocationSnapshot != null && latitude != null && longitude != null) {
fun numericalDistanceToPlace(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably be private if only used in this vm. Can also potentially be moved into one of the util files to keep this vm leaner

latitude: Double?,
longitude: Double?,
userLocation: LatLng?
): Double {
return if (userLocation != null && latitude != null && longitude != null) {
calculateDistance(
LatLng(
currentLocationSnapshot.latitude,
currentLocationSnapshot.longitude
), LatLng(latitude, longitude)
userLocation, LatLng(latitude, longitude)
)
} else {
Double.MAX_VALUE
Expand All @@ -534,15 +548,16 @@ class HomeViewModel @Inject constructor(
response: ApiResponse<List<T>>,
getLatitude: (T) -> Double?,
getLongitude: (T) -> Double?,
userLocation: LatLng?,
getIsOpen: ((T) -> Boolean)? = null
): ApiResponse<List<T>> {
if (response is ApiResponse.Success) {
val sortedData = response.data.sortedWith(
if (getIsOpen != null) {
compareByDescending<T> { getIsOpen(it) }
.thenBy { numericalDistanceToPlace(getLatitude(it),getLongitude(it)) }
.thenBy { numericalDistanceToPlace(getLatitude(it),getLongitude(it), userLocation) }
} else {
compareBy { numericalDistanceToPlace(getLatitude(it),getLongitude(it)) }
compareBy { numericalDistanceToPlace(getLatitude(it),getLongitude(it), userLocation) }
}
)
return ApiResponse.Success(sortedData)
Expand Down
32 changes: 32 additions & 0 deletions app/src/main/java/com/cornellappdev/transit/util/IntentUtils.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.cornellappdev.transit.util

import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.util.Log
import androidx.core.net.toUri

object IntentUtils {
fun Context.openDeepLink(packageName: String) {
val launchIntent = packageManager.getLaunchIntentForPackage(packageName)

if (launchIntent != null) {
startActivity(launchIntent)
} else {
val playStoreIntent = Intent(Intent.ACTION_VIEW, "market://details?id=$packageName".toUri())
.setPackage("com.android.vending")
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
startActivity(playStoreIntent)
} catch (e: ActivityNotFoundException) {
val webStoreIntent = Intent(Intent.ACTION_VIEW, "https://play.google.com/store/apps/details?id=$packageName".toUri())
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
try {
startActivity(webStoreIntent)
} catch (e2: ActivityNotFoundException) {
Log.e("IntentUtils","no handler for play store web URL" ,e2)
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -258,13 +258,13 @@ private fun Route.effectiveArrivalInstantOrNull(
return endInstant.plusSeconds(delaySeconds.toLong())
}

/** True when route arrives on or before provided cutoff (including grace already applied by caller). */
/** True when route arrives on or before provided cutoff (strict, no grace period). */
private fun Route.arrivesBy(
cutoffWithGrace: Instant,
cutoff: Instant,
diagnostics: RouteProcessingDiagnostics? = null,
): Boolean {
val arrivalInstant = effectiveArrivalInstantOrNull(diagnostics) ?: return false
return !arrivalInstant.isAfter(cutoffWithGrace)
return !arrivalInstant.isAfter(cutoff)
}

/** Comparator for Arrive By ordering: latest departure first, then shorter distance. */
Expand Down Expand Up @@ -333,18 +333,16 @@ private fun compareByEffectiveLeaveTime(

/**
* Section-level Arrive By filtering and ordering.
* Keeps routes that arrive by cutoff (with grace), then sorts by latest departure first.
* Keeps routes that arrive by cutoff, then sorts by latest departure first.
*/
private fun List<Route>?.filterAndSortRoutesForArriveBy(
cutoff: Instant,
diagnostics: RouteProcessingDiagnostics? = null,
): List<Route>? {
if (this == null) return null

val cutoffWithGrace = cutoff.plus(Duration.ofMinutes(ARRIVE_BY_CUTOFF_GRACE_MINUTES))

return this
.filter { route -> route.arrivesBy(cutoffWithGrace, diagnostics) }
.filter { route -> route.arrivesBy(cutoff, diagnostics) }
.sortedWith(::compareArriveByRoutes)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ const val LEAVE_CUTOFF_HORIZON_MINUTES = 45L

const val LEAVE_CUTOFF_GRACE_MINUTES = 2L

const val ARRIVE_BY_CUTOFF_GRACE_MINUTES = 2L

// Hide transit options when walking arrives at the same time or sooner (+ tie buffer).
const val WALKING_TRANSIT_TIE_MINUTES = 1L
Expand Down