TASK-069: the first screen fades in under the cup instead of gliding
Until the session is known the route shown is the repository list; when it becomes Home the change played the full-width tab glide just as the launch cover was fading. That hand-over is now a plain fade, and the cover waits for it (at most 1.5 s longer) before it leaves. Agent: main (opus) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0f96a8ed76
commit
a28feb1a47
@ -442,7 +442,11 @@ class MainActivity : ComponentActivity() {
|
||||
// screen, so moving between code tabs or pages of one screen does not replay it.
|
||||
val stackDepth = routeStack.size
|
||||
val shownDepth = remember { intArrayOf(stackDepth) }
|
||||
SideEffect { shownDepth[0] = stackDepth }
|
||||
val shownReady = remember { booleanArrayOf(sessionReady) }
|
||||
SideEffect {
|
||||
shownDepth[0] = stackDepth
|
||||
shownReady[0] = sessionReady
|
||||
}
|
||||
AnimatedContent(
|
||||
targetState = currentRoute.takeIf { sessionReady } ?: Route.Repositories,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
@ -450,7 +454,12 @@ class MainActivity : ComponentActivity() {
|
||||
val fromTab = initialState.tab
|
||||
val toTab = targetState.tab
|
||||
val tabSwitch = stackDepth == shownDepth[0] && fromTab != null && toTab != null
|
||||
if (tabSwitch) {
|
||||
if (!shownReady[0]) {
|
||||
// The session has just been found: the first screen takes the
|
||||
// waiting one's place where it stands. Nothing was navigated, so
|
||||
// nothing slides.
|
||||
fadeIn(tween(200)).togetherWith(fadeOut(tween(90)))
|
||||
} else if (tabSwitch) {
|
||||
// Tabs sit side by side: the old one glides out as the new one
|
||||
// glides in, the whole width, like turning to the next page.
|
||||
val direction = (toTab.ordinal - fromTab.ordinal).coerceIn(-1, 1)
|
||||
@ -685,7 +694,7 @@ class MainActivity : ComponentActivity() {
|
||||
},
|
||||
)
|
||||
}
|
||||
SplashOverlay()
|
||||
SplashOverlay(settled = sessionReady || state.sessionReadyOrNull() != null)
|
||||
}
|
||||
}
|
||||
consumeCallback(intent)
|
||||
|
||||
@ -41,8 +41,10 @@ import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberUpdatedState
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.runtime.snapshotFlow
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
@ -67,6 +69,8 @@ import com.ubisam.ubgitea.ui.theme.UBGiteaTheme
|
||||
import kotlin.math.PI
|
||||
import kotlin.math.sin
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.withTimeoutOrNull
|
||||
|
||||
// The app's moving parts. All of them draw the launcher icon's tea cup, so the app looks like one
|
||||
// thing from the home screen inwards.
|
||||
@ -310,16 +314,23 @@ fun <S> TabGlide(
|
||||
|
||||
const val TabGlideMillis = 320
|
||||
|
||||
const val SplashLongestWaitMillis = 1500L
|
||||
|
||||
private val SplashGradient = listOf(Color(0xFF4DB0FF), Color(0xFF2F98F9), Color(0xFF0A6FD8))
|
||||
|
||||
/**
|
||||
* The launcher icon, enlarged and steaming, over the whole window for the first second after a
|
||||
* cold start. It covers the app rather than delaying it: everything underneath loads meanwhile.
|
||||
*
|
||||
* It leaves once [settled] says whether there is a session, so its fade does not share frames
|
||||
* with the first screen arriving underneath, and never stays longer than [SplashLongestWaitMillis]
|
||||
* past its own second.
|
||||
*/
|
||||
@Composable
|
||||
fun SplashOverlay() {
|
||||
fun SplashOverlay(settled: Boolean) {
|
||||
var finished by rememberSaveable { mutableStateOf(false) }
|
||||
if (finished) return
|
||||
val settledNow by rememberUpdatedState(settled)
|
||||
val alpha = remember { Animatable(1f) }
|
||||
val scale = remember { Animatable(0.85f) }
|
||||
LaunchedEffect(Unit) {
|
||||
@ -327,6 +338,11 @@ fun SplashOverlay() {
|
||||
}
|
||||
LaunchedEffect(Unit) {
|
||||
delay(1100)
|
||||
if (!settledNow) {
|
||||
withTimeoutOrNull(SplashLongestWaitMillis) { snapshotFlow { settledNow }.first { it } }
|
||||
// The first screen fades in over the waiting one; let that finish under the cover.
|
||||
delay(250)
|
||||
}
|
||||
alpha.animateTo(0f, tween(280))
|
||||
finished = true
|
||||
}
|
||||
|
||||
@ -1615,3 +1615,17 @@ This is the append-only implementation and handoff record shared by Codex and Cl
|
||||
- Caution: published without having been seen on a device, at the product owner's decision.
|
||||
0.2.3 and older still update through the browser download; the in-app update is first used
|
||||
going from 0.2.4 to whatever comes next, so it stays unproven until then.
|
||||
|
||||
## 2026-10-12 (continued) — TASK-069: the start no longer stutters as the cup leaves
|
||||
|
||||
- Request: the product owner reported that at launch the cup seemed to move off towards the top
|
||||
left as it disappeared, and that this part stuttered.
|
||||
- Cause found in the code: until the session is known the route shown is the repository list;
|
||||
once it is known the route becomes Home. Both are tabs, so the change played the full-width tab
|
||||
glide, at about the moment the cover was fading, on top of Home's first composition.
|
||||
- What changed: `MainActivity` gives that hand-over a plain fade and no slide. `SplashOverlay`
|
||||
takes `settled` and, after its own 1.1 seconds, waits until the session is known (at most 1.5
|
||||
seconds more) and for the fade underneath before it leaves.
|
||||
- Checks and results: the five required checks pass.
|
||||
- Caution: diagnosed from the code, not seen on a device. If the stutter is still there, the
|
||||
remaining suspect is the cost of Home's first frames on that phone.
|
||||
|
||||
@ -22,6 +22,10 @@ The app moves where it used to jump, and what moves is the launcher icon's tea c
|
||||
9. Added after that (product owner, 2026-10-12): the tabs inside a screen glide the same way.
|
||||
전체 저장소/즐겨찾기 and 이슈/풀 리퀘스트 keep their header and tab row still while the list under
|
||||
them changes sides. The branch screen's tabs already slid (a pager) and are unchanged.
|
||||
10. Reported by the product owner (2026-10-12): the start stuttered as the cup left. The first
|
||||
screen was gliding in sideways underneath, as if a tab had been switched, while the cover was
|
||||
fading. The first screen now fades in where it stands, and the cover waits for it (at most
|
||||
1.5 seconds longer) before it leaves.
|
||||
|
||||
## Scope
|
||||
|
||||
@ -46,7 +50,7 @@ The app moves where it used to jump, and what moves is the launcher icon's tea c
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [x] Each of the nine items above is in the code and the app builds.
|
||||
- [x] Each of the ten items above is in the code and the app builds.
|
||||
- [x] The splash covers the app without delaying it: sign-in, loading and the OAuth callback run
|
||||
underneath, and taps do not pass through the cover.
|
||||
- [x] Moving between code tabs, or between pages of one screen, does not replay the screen
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user