Skip to content

Commit d7ff04c

Browse files
authored
Merge pull request #10 from utsmannn/feature/grid-scrolling
feat: enhance horizontal grid with snap and caching features
2 parents c864e36 + 827d4f4 commit d7ff04c

8 files changed

Lines changed: 309 additions & 85 deletions

File tree

compose-remote-layout-router/src/commonMain/kotlin/com/utsman/composeremote/router/CachedKtorLayoutFetcher.kt

Lines changed: 44 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,12 @@ class CachedKtorLayoutFetcher(
1212
private val maxCacheSize: Int = 100,
1313
private val cacheTtlMillis: Long = 15.minutes.inWholeMilliseconds,
1414
) : LayoutFetcher {
15-
private val cache = ConcurrentHashMap<String, CacheEntry>()
15+
private val cache =
16+
ConcurrentHashMap<String, CacheEntry>()
1617

1718
private val cacheMutex = Mutex()
1819

1920
override suspend fun fetchLayout(url: String): Result<String> {
20-
println("Fetching layout cached from: $url")
2121
val cachedEntry = cache.get(url)
2222
if (cachedEntry != null && !isExpired(cachedEntry)) {
2323
return Result.success(cachedEntry.layout)
@@ -33,38 +33,61 @@ class CachedKtorLayoutFetcher(
3333
oldestKey?.let { cache.remove(it) }
3434
}
3535

36-
cache.put(url, CacheEntry(layout, Clock.System.now().toEpochMilliseconds()))
36+
cache.put(
37+
url,
38+
CacheEntry(
39+
layout,
40+
Clock.System.now()
41+
.toEpochMilliseconds(),
42+
),
43+
)
3744
}
3845
}
3946

4047
return result
4148
}
4249

4350
override fun fetchLayoutAsFlow(url: String): Flow<ResultLayout<String>> = flow {
44-
println("Fetching layout cached from: $url")
4551
emit(ResultLayout.Loading)
4652

4753
try {
4854
val cachedEntry = cache.get(url)
49-
if (cachedEntry != null && !isExpired(cachedEntry)) {
55+
if (cachedEntry != null &&
56+
!isExpired(
57+
cachedEntry,
58+
)
59+
) {
5060
emit(ResultLayout.success(cachedEntry.layout))
5161
return@flow
5262
}
5363

54-
delegate.fetchLayoutAsFlow(url).collect { result ->
55-
if (result is ResultLayout.Success) {
56-
cacheMutex.withLock {
57-
if (cache.size >= maxCacheSize) {
58-
val oldestKey = findOldestEntry()
59-
oldestKey?.let { cache.remove(it) }
64+
delegate.fetchLayoutAsFlow(url)
65+
.collect { result ->
66+
if (result is ResultLayout.Success) {
67+
cacheMutex.withLock {
68+
if (cache.size >= maxCacheSize) {
69+
val oldestKey =
70+
findOldestEntry()
71+
oldestKey?.let {
72+
cache.remove(
73+
it,
74+
)
75+
}
76+
}
77+
78+
cache.put(
79+
url,
80+
CacheEntry(
81+
result.data,
82+
Clock.System.now()
83+
.toEpochMilliseconds(),
84+
),
85+
)
6086
}
61-
62-
cache.put(url, CacheEntry(result.data, Clock.System.now().toEpochMilliseconds()))
6387
}
64-
}
6588

66-
emit(result)
67-
}
89+
emit(result)
90+
}
6891
} catch (e: Exception) {
6992
emit(ResultLayout.failure(e))
7093
}
@@ -110,4 +133,8 @@ class CachedKtorLayoutFetcher(
110133
fun KtorHttpLayoutFetcher.cached(
111134
maxCacheSize: Int = 100,
112135
cacheTtlMillis: Long = 15.minutes.inWholeMilliseconds,
113-
): CachedKtorLayoutFetcher = CachedKtorLayoutFetcher(this, maxCacheSize, cacheTtlMillis)
136+
): CachedKtorLayoutFetcher = CachedKtorLayoutFetcher(
137+
this,
138+
maxCacheSize,
139+
cacheTtlMillis,
140+
)

compose-remote-layout-router/src/commonMain/kotlin/com/utsman/composeremote/router/KtorHttpLayoutFetcher.kt

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,16 +9,17 @@ import io.ktor.client.statement.HttpResponse
99
import io.ktor.client.statement.bodyAsText
1010
import kotlinx.coroutines.flow.Flow
1111

12-
class KtorHttpLayoutFetcher : LayoutFetcher {
13-
private val client = HttpClient {
14-
install(Logging) {
15-
level = LogLevel.BODY
16-
}
17-
install(HttpCache)
12+
val KtorClientDefault = HttpClient {
13+
install(Logging) {
14+
level = LogLevel.BODY
1815
}
16+
install(HttpCache)
17+
}
1918

19+
class KtorHttpLayoutFetcher(
20+
private val client: HttpClient = KtorClientDefault,
21+
) : LayoutFetcher {
2022
override suspend fun fetchLayout(url: String): Result<String> = try {
21-
println("Fetching layout from: $url")
2223
val response: HttpResponse = client.get(url)
2324
val layoutText = response.bodyAsText()
2425
Result.success(layoutText)
@@ -27,9 +28,14 @@ class KtorHttpLayoutFetcher : LayoutFetcher {
2728
}
2829

2930
override fun fetchLayoutAsFlow(url: String): Flow<ResultLayout<String>> = ResultLayout.flow {
30-
println("Fetching layout from: $url")
3131
val response: HttpResponse = client.get(url)
32-
response.bodyAsText()
32+
val content = response.bodyAsText()
33+
if (!content.startsWith("{")) {
34+
throw IllegalStateException(
35+
"Invalid layout content",
36+
)
37+
}
38+
content
3339
}
3440

3541
fun close() {

compose-remote-layout-router/src/commonMain/kotlin/com/utsman/composeremote/router/RemoteRouter.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface RemoteRouter {
2929
fun reload()
3030
}
3131

32-
class ResultRemoteRouterImpl(
32+
internal class ResultRemoteRouterImpl(
3333
private val fetcher: LayoutFetcher,
3434
private val scope: CoroutineScope,
3535
) : RemoteRouter {

compose-remote-layout-router/src/commonMain/kotlin/com/utsman/composeremote/router/ResultLayout.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ sealed class ResultLayout<out T> {
6565
try {
6666
emit(Success(block()))
6767
} catch (e: Exception) {
68+
e.printStackTrace()
6869
emit(Failure(e))
6970
}
7071
}

compose-remote-layout/src/commonMain/kotlin/com/utsman/composeremote/CacheScrollPosition.kt

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,26 @@ class CacheScrollPosition {
1919
fun remove(key: String): Int? = map.remove(key)
2020

2121
fun clear() = map.clear()
22+
}
23+
24+
class CacheScrollOffsetPosition {
25+
private val map = mutableMapOf<String, Pair<Int, Int>>()
26+
27+
fun put(
28+
key: String,
29+
value: Pair<Int, Int>,
30+
): Pair<Int, Int>? = map.put(key, value)
31+
32+
fun get(
33+
key: String?,
34+
): Pair<Int, Int> = map[key] ?: Pair(0, 0)
35+
36+
val size get() = map.size
37+
38+
fun remove(key: String): Pair<Int, Int>? = map.remove(key)
2239

23-
operator fun plus(other: CacheScrollPosition): CacheScrollPosition {
24-
val otherMap = other.map
25-
map.putAll(otherMap)
26-
return this
27-
}
40+
fun clear() = map.clear()
2841
}
2942

3043
val LocalCacheScrollPosition = compositionLocalOf { CacheScrollPosition() }
44+
val LocalCacheScrollOffsetPosition = compositionLocalOf { CacheScrollOffsetPosition() }

0 commit comments

Comments
 (0)