Skip to content

Commit 26026dc

Browse files
committed
fix: add a loading state to boost details
1 parent f556b2f commit 26026dc

File tree

2 files changed

+147
-39
lines changed

2 files changed

+147
-39
lines changed

app/src/main/java/to/bitkit/ui/screens/wallets/activity/ActivityExploreScreen.kt

Lines changed: 102 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package to.bitkit.ui.screens.wallets.activity
22

33
import android.content.Intent
44
import androidx.compose.foundation.layout.Arrangement
5+
import androidx.compose.foundation.layout.Box
56
import androidx.compose.foundation.layout.Column
67
import androidx.compose.foundation.layout.ColumnScope
78
import androidx.compose.foundation.layout.Row
@@ -73,55 +74,117 @@ fun ActivityExploreScreen(
7374
route: Routes.ActivityExplore,
7475
onBackClick: () -> Unit,
7576
) {
76-
val activities by listViewModel.filteredActivities.collectAsStateWithLifecycle()
77-
val item = activities?.find { it.rawId() == route.id } ?: return
77+
val uiState by detailViewModel.uiState.collectAsStateWithLifecycle()
7878

79-
val app = appViewModel ?: return
80-
val context = LocalContext.current
81-
82-
val txDetails by detailViewModel.txDetails.collectAsStateWithLifecycle()
83-
var boostTxDoesExist by remember { mutableStateOf<Map<String, Boolean>>(emptyMap()) }
84-
85-
LaunchedEffect(item) {
86-
if (item is Activity.Onchain) {
87-
detailViewModel.fetchTransactionDetails(item.v1.txId)
88-
if (item.v1.boostTxIds.isNotEmpty()) {
89-
boostTxDoesExist = detailViewModel.getBoostTxDoesExist(item.v1.boostTxIds)
90-
}
91-
} else {
92-
detailViewModel.clearTransactionDetails()
93-
}
79+
// Load activity on composition
80+
LaunchedEffect(route.id) {
81+
detailViewModel.loadActivity(route.id)
9482
}
9583

84+
// Clear state on disposal
9685
DisposableEffect(Unit) {
9786
onDispose {
98-
detailViewModel.clearTransactionDetails()
87+
detailViewModel.clearActivityState()
9988
}
10089
}
10190

10291
ScreenColumn {
103-
AppTopBar(
104-
titleText = stringResource(item.getScreenTitleRes()),
105-
onBackClick = onBackClick,
106-
actions = { DrawerNavIcon() },
107-
)
108-
ActivityExploreContent(
109-
item = item,
110-
txDetails = txDetails,
111-
boostTxDoesExist = boostTxDoesExist,
112-
onCopy = { text ->
113-
app.toast(
114-
type = Toast.ToastType.SUCCESS,
115-
title = context.getString(R.string.common__copied),
116-
description = text.ellipsisMiddle(40),
92+
when (val loadState = uiState.activityLoadState) {
93+
is ActivityDetailViewModel.ActivityLoadState.Initial,
94+
is ActivityDetailViewModel.ActivityLoadState.Loading,
95+
-> {
96+
// Loading state
97+
AppTopBar(
98+
titleText = stringResource(R.string.wallet__activity),
99+
onBackClick = onBackClick,
100+
actions = { DrawerNavIcon() },
117101
)
118-
},
119-
onClickExplore = { txid ->
120-
val url = getBlockExplorerUrl(txid)
121-
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
122-
context.startActivity(intent)
123-
},
124-
)
102+
Box(
103+
modifier = Modifier
104+
.fillMaxHeight()
105+
.padding(16.dp),
106+
contentAlignment = Alignment.Center
107+
) {
108+
CircularProgressIndicator()
109+
}
110+
}
111+
112+
is ActivityDetailViewModel.ActivityLoadState.Error -> {
113+
// Error state
114+
AppTopBar(
115+
titleText = stringResource(R.string.wallet__activity),
116+
onBackClick = onBackClick,
117+
actions = { DrawerNavIcon() },
118+
)
119+
Column(
120+
modifier = Modifier
121+
.fillMaxHeight()
122+
.padding(16.dp),
123+
horizontalAlignment = Alignment.CenterHorizontally,
124+
verticalArrangement = Arrangement.Center
125+
) {
126+
BodySSB(
127+
text = loadState.message,
128+
modifier = Modifier.padding(bottom = 16.dp)
129+
)
130+
PrimaryButton(
131+
text = stringResource(R.string.common__back),
132+
onClick = onBackClick
133+
)
134+
}
135+
}
136+
137+
is ActivityDetailViewModel.ActivityLoadState.Success -> {
138+
val item = loadState.activity
139+
val app = appViewModel ?: return@ScreenColumn
140+
val context = LocalContext.current
141+
142+
val txDetails by detailViewModel.txDetails.collectAsStateWithLifecycle()
143+
var boostTxDoesExist by remember { mutableStateOf<Map<String, Boolean>>(emptyMap()) }
144+
145+
LaunchedEffect(item) {
146+
if (item is Activity.Onchain) {
147+
detailViewModel.fetchTransactionDetails(item.v1.txId)
148+
if (item.v1.boostTxIds.isNotEmpty()) {
149+
boostTxDoesExist = detailViewModel.getBoostTxDoesExist(item.v1.boostTxIds)
150+
}
151+
} else {
152+
detailViewModel.clearTransactionDetails()
153+
}
154+
}
155+
156+
DisposableEffect(Unit) {
157+
onDispose {
158+
detailViewModel.clearTransactionDetails()
159+
}
160+
}
161+
162+
AppTopBar(
163+
titleText = stringResource(item.getScreenTitleRes()),
164+
onBackClick = onBackClick,
165+
actions = { DrawerNavIcon() },
166+
)
167+
168+
val toastMessage = stringResource(R.string.common__copied)
169+
ActivityExploreContent(
170+
item = item,
171+
txDetails = txDetails,
172+
boostTxDoesExist = boostTxDoesExist,
173+
onCopy = { text ->
174+
app.toast(
175+
type = Toast.ToastType.SUCCESS,
176+
title = toastMessage,
177+
description = text.ellipsisMiddle(40),
178+
)
179+
},
180+
onClickExplore = { txid ->
181+
val url = getBlockExplorerUrl(txid)
182+
val intent = Intent(Intent.ACTION_VIEW, url.toUri())
183+
context.startActivity(intent)
184+
},
185+
)
186+
}
187+
}
125188
}
126189
}
127190

app/src/main/java/to/bitkit/viewmodels/ActivityDetailViewModel.kt

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.synonym.bitkitcore.IBtOrder
77
import dagger.hilt.android.lifecycle.HiltViewModel
88
import kotlinx.coroutines.CoroutineDispatcher
99
import kotlinx.coroutines.flow.MutableStateFlow
10+
import kotlinx.coroutines.flow.StateFlow
1011
import kotlinx.coroutines.flow.asStateFlow
1112
import kotlinx.coroutines.flow.update
1213
import kotlinx.coroutines.launch
@@ -41,11 +42,55 @@ class ActivityDetailViewModel @Inject constructor(
4142

4243
private var activity: Activity? = null
4344

45+
sealed interface ActivityLoadState {
46+
data object Initial : ActivityLoadState
47+
data object Loading : ActivityLoadState
48+
data class Success(val activity: Activity) : ActivityLoadState
49+
data class Error(val message: String) : ActivityLoadState
50+
}
51+
52+
data class ActivityDetailUiState(
53+
val activityLoadState: ActivityLoadState = ActivityLoadState.Initial,
54+
)
55+
56+
private val _uiState = MutableStateFlow(ActivityDetailUiState())
57+
val uiState: StateFlow<ActivityDetailUiState> = _uiState.asStateFlow()
58+
4459
fun setActivity(activity: Activity) {
4560
this.activity = activity
4661
loadTags()
4762
}
4863

64+
fun loadActivity(activityId: String) {
65+
viewModelScope.launch(bgDispatcher) {
66+
_uiState.update { it.copy(activityLoadState = ActivityLoadState.Loading) }
67+
68+
activityRepo.getActivity(activityId)
69+
.onSuccess { activity ->
70+
if (activity != null) {
71+
this@ActivityDetailViewModel.activity = activity
72+
_uiState.update { it.copy(activityLoadState = ActivityLoadState.Success(activity)) }
73+
loadTags()
74+
} else {
75+
_uiState.update {
76+
it.copy(activityLoadState = ActivityLoadState.Error("Activity not found"))
77+
}
78+
}
79+
}
80+
.onFailure { e ->
81+
Logger.error("Failed to load activity $activityId", e, TAG)
82+
_uiState.update {
83+
it.copy(activityLoadState = ActivityLoadState.Error(e.message ?: "Failed to load activity"))
84+
}
85+
}
86+
}
87+
}
88+
89+
fun clearActivityState() {
90+
_uiState.update { it.copy(activityLoadState = ActivityLoadState.Initial) }
91+
activity = null
92+
}
93+
4994
fun loadTags() {
5095
val id = activity?.rawId() ?: return
5196
viewModelScope.launch(bgDispatcher) {

0 commit comments

Comments
 (0)