From b619f04e7c80e8b1ed1fab62e15213008a731eb3 Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Wed, 2 Sep 2026 18:16:53 -0400 Subject: [PATCH 1/3] fix: update gitignore and add a temp reroute for user not found error when logging in --- .gitignore | 5 +++-- .../android/viewmodel/onboarding/LandingViewModel.kt | 7 +++++++ gradlew | 0 3 files changed, 10 insertions(+), 2 deletions(-) mode change 100644 => 100755 gradlew diff --git a/.gitignore b/.gitignore index 62c68b19..56b7ebd7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ local.properties # Log/OS Files *.log +.DS_Store # Android Studio generated files and folders captures/ @@ -28,11 +29,11 @@ render.experimental.xml *.keystore # Google Services (e.g. APIs or Firebase) -app/google-services.json +google-services.json # Android Profiling *.hprof # Secrets secrets.properties -app/src/main/assets/resell-service.json +resell-service.json diff --git a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/onboarding/LandingViewModel.kt b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/onboarding/LandingViewModel.kt index 4fb09394..811b0080 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/viewmodel/onboarding/LandingViewModel.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/viewmodel/onboarding/LandingViewModel.kt @@ -28,6 +28,7 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import retrofit2.HttpException import javax.inject.Inject @HiltViewModel @@ -171,6 +172,12 @@ class LandingViewModel @Inject constructor( rootNavigationRepository.navigate(ResellRootRoute.MAIN) } } catch (e: Exception) { + if (e is HttpException && e.code() == 403) { + Log.d("LandingViewModel", "User not found on backend; routing to onboarding.") + rootNavigationRepository.navigate(ResellRootRoute.ONBOARDING) + return@launch + } + Log.e("LandingViewModel", "Error getting user: ", e) onSignInFailed(showSheet = false) rootConfirmationRepository.showError( diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 From 17c9fc57e3011862244093dc7feac585e5aa131a Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Thu, 3 Sep 2026 23:00:43 -0400 Subject: [PATCH 2/3] fix: move bookmark FAB to bottom right and reduce padding from the sheet --- .../android/ui/screens/pdp/PostDetailPage.kt | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt index 4e6a3c72..737a1040 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt @@ -19,7 +19,9 @@ import androidx.compose.foundation.layout.systemBarsPadding import androidx.compose.foundation.layout.width import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon @@ -144,7 +146,8 @@ private fun Content( val screenHeight = LocalConfiguration.current.screenHeightDp.dp // TODO the plus at the end seems wrong. Test on other devices. - val peekHeight = screenHeight - imageHeight + 96.dp +// val peekHeight = screenHeight - imageHeight + 96.dp + val peekHeight = screenHeight - imageHeight Box( modifier = Modifier.fillMaxWidth() @@ -225,7 +228,7 @@ private fun Content( selected = bookmarked, onClick = onBookmarkClick, modifier = Modifier - .align(Alignment.BottomStart) + .align(Alignment.BottomEnd) .defaultHorizontalPadding() .padding(bottom = sheetHeightFromBottom) ) @@ -315,11 +318,14 @@ private fun BottomSheetContent( // Calculate maximum height for the sheet content based on padding from top val maxSheetHeight = screenHeight - paddingTop +// val scrollState = rememberScrollState() + Column( modifier = Modifier .fillMaxWidth() .background(Color.White) - .height(maxSheetHeight) + .heightIn(max = maxSheetHeight) +// .verticalScroll(scrollState) ) { Row( modifier = Modifier @@ -335,8 +341,12 @@ private fun BottomSheetContent( val distanceFromBottomPx = screenHeightPx - (textPosition + textHeight) val textDistanceFromBottom = with(density) { distanceFromBottomPx.toDp() } + //Bookmark FAB size = 72.dp, plus 24 dp for bottom padding + val bookmarkSize = 72.dp + val bookmarkPadding = bookmarkSize + 24.dp + // Tell the parent that the height has changed. - onHeightChanged(textDistanceFromBottom + 170.dp) + onHeightChanged(textDistanceFromBottom + bookmarkPadding) }, verticalAlignment = Alignment.CenterVertically ) { From cc852aa83fc600e7ce1902920c7aca54cee202ad Mon Sep 17 00:00:00 2001 From: Ryan Cheung Date: Fri, 4 Sep 2026 00:05:03 -0400 Subject: [PATCH 3/3] fix: use top of bottomsheet as anchor for bookmark FAB and picture selection dot. Allow bottomsheet content to be scrollable for longer descriptions. Bottomsheet content now uses a fixed height that relates to the content size instead of a size based on how much possible space it could use. --- .../android/ui/screens/pdp/PostDetailPage.kt | 123 +++++++++--------- 1 file changed, 64 insertions(+), 59 deletions(-) diff --git a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt index 737a1040..f5671aeb 100644 --- a/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt +++ b/app/src/main/java/com/cornellappdev/resell/android/ui/screens/pdp/PostDetailPage.kt @@ -7,11 +7,15 @@ 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.WindowInsets +import androidx.compose.foundation.layout.asPaddingValues import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn +import androidx.compose.foundation.layout.navigationBars import androidx.compose.foundation.layout.navigationBarsPadding import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -25,13 +29,15 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.BottomSheetScaffold import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon +import androidx.compose.material3.SheetValue import androidx.compose.material3.Text +import androidx.compose.material3.rememberBottomSheetScaffoldState +import androidx.compose.material3.rememberStandardBottomSheetState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.BlurredEdgeTreatment.Companion.Rectangle @@ -41,8 +47,6 @@ import androidx.compose.ui.graphics.Brush import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.layout.onGloballyPositioned -import androidx.compose.ui.layout.positionInRoot import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource @@ -50,6 +54,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.max import androidx.hilt.navigation.compose.hiltViewModel import coil.compose.AsyncImage import com.cornellappdev.resell.android.R @@ -73,21 +78,9 @@ fun PostDetailPage( ) { val uiState = postDetailViewModel.collectUiStateValue() - // Image will take up at most this proportion of the screen + // When the sheet is peeked (collapsed), the image may grow up to this fraction of the screen. val imageProp = .75f val maxImageHeight = LocalConfiguration.current.screenHeightDp.dp * imageProp - val minAspectRatio = uiState.minAspectRatio - val screenWidth = LocalConfiguration.current.screenWidthDp.dp - - // Preferred height of the tallest image, given the aspect ratio - val aspectRatioPreferredHeight = screenWidth / minAspectRatio - - // Cap at the max image height. - val imageHeight = if (aspectRatioPreferredHeight > maxImageHeight) { - maxImageHeight - } else { - aspectRatioPreferredHeight - } LaunchedEffect(uiState.hideSheetEvent) { uiState.hideSheetEvent?.consumeSuspend { @@ -100,7 +93,7 @@ fun PostDetailPage( onContactClick = postDetailViewModel::onContactClick, onEllipseClick = postDetailViewModel::onEllipseClick, images = uiState.images, - imageHeight = imageHeight, + maxImageHeight = maxImageHeight, userPfp = uiState.profileImageUrl, username = uiState.username, title = uiState.title, @@ -122,7 +115,7 @@ fun PostDetailPage( @Preview @Composable private fun Content( - imageHeight: Dp = 500.dp, + maxImageHeight: Dp = 500.dp, images: List = emptyList(), similarImageUrls: ResellApiResponse> = ResellApiResponse.Pending, onContactClick: () -> Unit = {}, @@ -139,20 +132,45 @@ private fun Content( onUserClick: () -> Unit = {}, showContact: Boolean = false, ) { - var sheetHeightFromBottom by remember { mutableStateOf(0.dp) } val pagerState = rememberPagerState(pageCount = { images.size }) - - // Derive peekHeight as screen height minus image height: + val density = LocalDensity.current val screenHeight = LocalConfiguration.current.screenHeightDp.dp - // TODO the plus at the end seems wrong. Test on other devices. -// val peekHeight = screenHeight - imageHeight + 96.dp - val peekHeight = screenHeight - imageHeight + // Sheet starts collapsed so only a strip of details is visible; image fills the rest. + val peekHeight = max(screenHeight - maxImageHeight, 200.dp) + val peekedImageHeight = screenHeight - peekHeight + + val scaffoldState = rememberBottomSheetScaffoldState( + bottomSheetState = rememberStandardBottomSheetState( + initialValue = SheetValue.PartiallyExpanded, + skipHiddenState = true, + ) + ) + + // requireOffset() is the Y of the sheet top. Drive image height and overlay + // positions from that so they stay glued to the sheet while dragging. + val sheetTopOffsetPx by remember { + derivedStateOf { + runCatching { scaffoldState.bottomSheetState.requireOffset() }.getOrDefault(0f) + } + } + val liveImageHeight = if (sheetTopOffsetPx == 0f) { + peekedImageHeight + } else { + with(density) { sheetTopOffsetPx.toDp() } + } + // Bottom padding so overlays sit just above the sheet top. + val overlayBottomPadding = if (sheetTopOffsetPx == 0f) { + peekHeight + 24.dp + } else { + with(density) { (screenHeight.toPx() - sheetTopOffsetPx).toDp() } + 24.dp + } Box( modifier = Modifier.fillMaxWidth() ) { BottomSheetScaffold( + scaffoldState = scaffoldState, sheetContent = { BottomSheetContent( profilePictureUrl = userPfp, @@ -160,12 +178,10 @@ private fun Content( title = title, price = price, description = description, - onHeightChanged = { - sheetHeightFromBottom = it - }, onSimilarClick = onSimilarClick, similarImageUrls = similarImageUrls, - onUserClick = onUserClick + onUserClick = onUserClick, + showContact = showContact, ) }, sheetPeekHeight = peekHeight, @@ -184,7 +200,7 @@ private fun Content( ) { Column(modifier = Modifier.fillMaxHeight()) { PdpImageBlurredBackground( - imageHeight = imageHeight, + imageHeight = liveImageHeight, bitmap = images[it] ) @@ -220,7 +236,7 @@ private fun Content( WhichPage( pagerState = pagerState, modifier = Modifier - .padding(bottom = sheetHeightFromBottom) + .padding(bottom = overlayBottomPadding) .align(Alignment.BottomCenter) ) @@ -230,7 +246,7 @@ private fun Content( modifier = Modifier .align(Alignment.BottomEnd) .defaultHorizontalPadding() - .padding(bottom = sheetHeightFromBottom) + .padding(bottom = overlayBottomPadding) ) } } @@ -260,7 +276,7 @@ private fun PdpImageBlurredBackground( modifier = Modifier .fillMaxWidth() .height(imageHeight), - contentScale = ContentScale.FillWidth + contentScale = ContentScale.Crop ) } } @@ -306,48 +322,35 @@ private fun BottomSheetContent( username: String, paddingTop: Dp = 116.dp, similarImageUrls: ResellApiResponse>, - onHeightChanged: (Dp) -> Unit, onSimilarClick: (Int) -> Unit, onUserClick: () -> Unit, + showContact: Boolean = false, ) { - - // Get screen height val screenHeight = LocalConfiguration.current.screenHeightDp.dp - val density = LocalDensity.current - - // Calculate maximum height for the sheet content based on padding from top val maxSheetHeight = screenHeight - paddingTop -// val scrollState = rememberScrollState() + // Clear the floating Contact Seller button: nav bars + 46.dp offset for below button + // + ~52.dp button itself + gap between button and similar items. + val navBottom = WindowInsets.navigationBars + .asPaddingValues() + .calculateBottomPadding() + val bottomClearance = if (showContact) { + navBottom + 46.dp + 52.dp + 24.dp + } else { + navBottom + 16.dp + } Column( modifier = Modifier .fillMaxWidth() .background(Color.White) .heightIn(max = maxSheetHeight) -// .verticalScroll(scrollState) + .verticalScroll(rememberScrollState()) ) { Row( modifier = Modifier .fillMaxWidth() - .defaultHorizontalPadding() - .onGloballyPositioned { layoutCoordinates -> - val textPosition = layoutCoordinates.positionInRoot().y - val textHeight = layoutCoordinates.size.height - - val screenHeightPx = with(density) { screenHeight.toPx() } - - // Calculate distance from bottom in px and convert to dp - val distanceFromBottomPx = screenHeightPx - (textPosition + textHeight) - val textDistanceFromBottom = with(density) { distanceFromBottomPx.toDp() } - - //Bookmark FAB size = 72.dp, plus 24 dp for bottom padding - val bookmarkSize = 72.dp - val bookmarkPadding = bookmarkSize + 24.dp - - // Tell the parent that the height has changed. - onHeightChanged(textDistanceFromBottom + bookmarkPadding) - }, + .defaultHorizontalPadding(), verticalAlignment = Alignment.CenterVertically ) { Text( @@ -417,6 +420,8 @@ private fun BottomSheetContent( ) } } + + Spacer(Modifier.height(bottomClearance)) } }