A cheap attempt at a native Bluesky client for Android
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

ShowSkeets: pull to refresh

geesawra da2dcb18 c646a5a5

+67 -53
+57 -47
app/src/main/java/industries/geesawra/jerryno/ShowSkeets.kt
··· 10 10 import androidx.compose.foundation.lazy.LazyListState 11 11 import androidx.compose.foundation.lazy.rememberLazyListState 12 12 import androidx.compose.material3.CircularProgressIndicator 13 + import androidx.compose.material3.pulltorefresh.PullToRefreshBox 13 14 import androidx.compose.runtime.Composable 14 15 import androidx.compose.runtime.LaunchedEffect 15 16 import androidx.compose.runtime.derivedStateOf 16 17 import androidx.compose.runtime.getValue 18 + import androidx.compose.runtime.mutableStateOf 17 19 import androidx.compose.runtime.remember 18 20 import androidx.compose.ui.Alignment 19 21 import androidx.compose.ui.Modifier ··· 32 34 } 33 35 } 34 36 35 - LazyColumn( 36 - state = state, 37 - modifier = modifier.fillMaxSize(), 38 - verticalArrangement = Arrangement.spacedBy(8.dp), 37 + val isRefreshing = remember { mutableStateOf(false) } 38 + 39 + PullToRefreshBox( 40 + isRefreshing = isRefreshing.value, 41 + onRefresh = { 42 + viewModel.reset() 43 + viewModel.fetchTimeline() 44 + }, 39 45 ) { 40 46 41 - if (viewModel.uiState.skeets.isEmpty()) { 42 - item { 43 - Box( 44 - contentAlignment = Alignment.Center, 45 - modifier = Modifier.fillMaxSize() 46 - ) { 47 - CircularProgressIndicator( 48 - modifier = Modifier 49 - .width(64.dp), 50 - ) 51 - } 52 - } 53 - } else { 54 - viewModel.uiState.skeets.forEach { skeet -> 55 - item(key = skeet.post.uri.toString()) { 56 - SkeetRowView(skeet) 57 - } 58 - } 47 + LazyColumn( 48 + state = state, 49 + modifier = modifier.fillMaxSize(), 50 + verticalArrangement = Arrangement.spacedBy(8.dp), 51 + ) { 59 52 60 - if (viewModel.uiState.isFetchingMoreTimeline) { 53 + if (viewModel.uiState.skeets.isEmpty()) { 61 54 item { 62 55 Box( 63 - modifier = Modifier 64 - .fillMaxWidth() 65 - .padding(16.dp), 66 - contentAlignment = Alignment.Center 56 + contentAlignment = Alignment.Center, 57 + modifier = Modifier.fillMaxSize() 67 58 ) { 59 + CircularProgressIndicator() 60 + } 61 + } 62 + } else { 63 + viewModel.uiState.skeets.forEach { skeet -> 64 + item(key = skeet.post.uri.toString()) { 65 + SkeetRowView(skeet) 66 + } 67 + } 68 + 69 + if (viewModel.uiState.isFetchingMoreTimeline) { 70 + item { 68 71 Box( 69 - contentAlignment = Alignment.Center, 70 - modifier = Modifier.fillMaxSize() 72 + modifier = Modifier 73 + .fillMaxWidth() 74 + .padding(16.dp), 75 + contentAlignment = Alignment.Center 71 76 ) { 72 - CircularProgressIndicator( 73 - modifier = Modifier 74 - .width(64.dp), 75 - ) 77 + Box( 78 + contentAlignment = Alignment.Center, 79 + modifier = Modifier.fillMaxSize() 80 + ) { 81 + CircularProgressIndicator( 82 + modifier = Modifier 83 + .width(64.dp), 84 + ) 85 + } 76 86 } 77 87 } 78 88 } 79 89 } 80 90 } 81 - } 82 91 83 - val endOfListReached by remember { 84 - derivedStateOf { 85 - val layoutInfo = state.layoutInfo 86 - val visibleItemsInfo = layoutInfo.visibleItemsInfo 87 - if (layoutInfo.totalItemsCount == 0) { 88 - false 89 - } else { 90 - val lastVisibleItem = visibleItemsInfo.lastOrNull() 91 - lastVisibleItem != null && lastVisibleItem.index == layoutInfo.totalItemsCount - 1 92 + val endOfListReached by remember { 93 + derivedStateOf { 94 + val layoutInfo = state.layoutInfo 95 + val visibleItemsInfo = layoutInfo.visibleItemsInfo 96 + if (layoutInfo.totalItemsCount == 0) { 97 + false 98 + } else { 99 + val lastVisibleItem = visibleItemsInfo.lastOrNull() 100 + lastVisibleItem != null && lastVisibleItem.index == layoutInfo.totalItemsCount - 1 101 + } 92 102 } 93 103 } 94 - } 95 104 96 - LaunchedEffect(endOfListReached) { 97 - if (endOfListReached && viewModel.uiState.skeets.isNotEmpty()) { 98 - viewModel.fetchTimeline() 105 + LaunchedEffect(endOfListReached) { 106 + if (endOfListReached && viewModel.uiState.skeets.isNotEmpty()) { 107 + viewModel.fetchTimeline() 108 + } 99 109 } 100 110 } 101 111 }
+10 -6
app/src/main/java/industries/geesawra/jerryno/datalayer/TimelineViewModel.kt
··· 72 72 isFetchingMoreTimeline = false 73 73 ) 74 74 }.onFailure { 75 - uiState = uiState.copy(isFetchingMoreTimeline = true) 75 + uiState = uiState.copy(isFetchingMoreTimeline = false) 76 76 Log.e("TimelineViewModel", "Failed to fetch timeline: ${it.message}") 77 77 } 78 78 } 79 79 } 80 80 81 - fun post(content: String, images: List<Uri>? = null, video: Uri? = null) { 82 - viewModelScope.launch { 83 - bskyConn.post(content, images, video).onFailure { 84 - uiState = uiState.copy(postError = it.message) 85 - } 81 + fun reset() { 82 + uiState = uiState.copy( 83 + skeets = listOf(), isFetchingMoreTimeline = false, cursor = null, 84 + ) 85 + } 86 + 87 + suspend fun post(content: String, images: List<Uri>? = null, video: Uri? = null): Result<Unit> { 88 + return bskyConn.post(content, images, video).onFailure { 89 + uiState = uiState.copy(postError = it.message) 86 90 } 87 91 } 88 92 }