1+ package com.bringyour.network.ui.balance_codes
2+
3+ import android.widget.Toast
4+ import androidx.compose.foundation.layout.Arrangement
5+ import androidx.compose.foundation.layout.Box
6+ import androidx.compose.foundation.layout.Row
7+ import androidx.compose.foundation.layout.fillMaxSize
8+ import androidx.compose.foundation.layout.fillMaxWidth
9+ import androidx.compose.foundation.layout.padding
10+ import androidx.compose.foundation.lazy.LazyColumn
11+ import androidx.compose.foundation.lazy.items
12+ import androidx.compose.foundation.lazy.rememberLazyListState
13+ import androidx.compose.material.icons.Icons
14+ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowLeft
15+ import androidx.compose.material3.CenterAlignedTopAppBar
16+ import androidx.compose.material3.ExperimentalMaterial3Api
17+ import androidx.compose.material3.HorizontalDivider
18+ import androidx.compose.material3.Icon
19+ import androidx.compose.material3.IconButton
20+ import androidx.compose.material3.MaterialTheme
21+ import androidx.compose.material3.Scaffold
22+ import androidx.compose.material3.Text
23+ import androidx.compose.material3.TopAppBarDefaults
24+ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
25+ import androidx.compose.material3.rememberModalBottomSheetState
26+ import androidx.compose.runtime.Composable
27+ import androidx.compose.runtime.collectAsState
28+ import androidx.compose.runtime.getValue
29+ import androidx.compose.runtime.rememberCoroutineScope
30+ import androidx.compose.ui.Alignment
31+ import androidx.compose.ui.Modifier
32+ import androidx.compose.ui.platform.LocalContext
33+ import androidx.compose.ui.res.painterResource
34+ import androidx.compose.ui.res.stringResource
35+ import androidx.compose.ui.unit.dp
36+ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
37+ import androidx.navigation.NavController
38+ import com.bringyour.network.R
39+ import com.bringyour.network.ui.components.redeemTransferBalanceCode.RedeemTransferBalanceCodeSheet
40+ import com.bringyour.network.ui.theme.Black
41+ import com.bringyour.network.ui.theme.TextMuted
42+ import com.bringyour.network.ui.theme.TopBarTitleTextStyle
43+ import com.bringyour.sdk.RedeemedBalanceCode
44+
45+ @OptIn(ExperimentalMaterial3Api ::class )
46+ @Composable
47+ fun BalanceCodesScreen (
48+ navController : NavController ,
49+ viewModel : BalanceCodesViewModel = hiltViewModel<BalanceCodesViewModel >()
50+ ) {
51+
52+ val sheetState = rememberModalBottomSheetState(
53+ skipPartiallyExpanded = true
54+ )
55+ val scope = rememberCoroutineScope()
56+
57+ val balanceCodes by viewModel.balanceCodes.collectAsState()
58+
59+ val context = LocalContext .current
60+
61+ val errorEvents = viewModel.errorEvents.collectAsState(initial = null )
62+ errorEvents.value?.let { message ->
63+ Toast .makeText(context, message, Toast .LENGTH_SHORT ).show()
64+ }
65+
66+ Scaffold (
67+ topBar = {
68+ CenterAlignedTopAppBar (
69+ title = {
70+ Text (
71+ stringResource(id = R .string.balance_codes_title),
72+ style = TopBarTitleTextStyle
73+ )
74+ },
75+ navigationIcon = {
76+ IconButton (onClick = { navController.popBackStack() }) {
77+ Icon (Icons .AutoMirrored .Filled .KeyboardArrowLeft , contentDescription = " Back" )
78+ }
79+ },
80+ colors = TopAppBarDefaults .topAppBarColors(
81+ containerColor = Black
82+ ),
83+ actions = {
84+ IconButton (onClick = {
85+ viewModel.setDisplayBottomSheet(true )
86+ }) {
87+ Icon (
88+ painter = painterResource(id = R .drawable.plus_icon),
89+ contentDescription = stringResource(id = R .string.redeem_balance_code)
90+ )
91+ }
92+ },
93+ )
94+ },
95+ containerColor = Black
96+ ) { padding ->
97+
98+ Box (
99+ modifier = Modifier
100+ .padding(padding)
101+ .fillMaxSize()
102+ ) {
103+
104+ BalanceCodesTable (
105+ balanceCodes = balanceCodes,
106+ onRefresh = {
107+ viewModel.fetchNetworkBalanceCodes()
108+ },
109+ isRefreshing = viewModel.isFetchingBalanceCodes.collectAsState().value
110+ )
111+
112+ }
113+
114+ if (viewModel.displayBottonSheet.collectAsState().value) {
115+ RedeemTransferBalanceCodeSheet (
116+ sheetState = sheetState,
117+ setIsPresenting = {
118+ viewModel.setDisplayBottomSheet(it)
119+ },
120+ onSuccess = {
121+ viewModel.fetchNetworkBalanceCodes()
122+ }
123+ )
124+ }
125+
126+ }
127+
128+ }
129+
130+ @Composable
131+ private fun BalanceCodesTable (
132+ balanceCodes : List <RedeemedBalanceCode >,
133+ onRefresh : () -> Unit ,
134+ isRefreshing : Boolean
135+ ) {
136+
137+ val listState = rememberLazyListState()
138+
139+ PullToRefreshBox (
140+ onRefresh = onRefresh,
141+ isRefreshing = isRefreshing
142+ ) {
143+
144+ if (balanceCodes.isEmpty()) {
145+
146+ LazyColumn (
147+ modifier = Modifier .fillMaxSize(),
148+ horizontalAlignment = Alignment .CenterHorizontally ,
149+ verticalArrangement = Arrangement .Center
150+ ) {
151+ item {
152+ Text (
153+ stringResource(id = R .string.no_redeemed_balance_codes_found),
154+ style = MaterialTheme .typography.bodyLarge,
155+ color = TextMuted
156+ )
157+ }
158+ }
159+
160+ } else {
161+
162+ LazyColumn (
163+ modifier = Modifier
164+ .fillMaxSize(),
165+ state = listState
166+ ) {
167+ item {
168+ Row (
169+ modifier = Modifier
170+ .fillMaxWidth()
171+ .padding(16 .dp),
172+ ) {
173+ Text (
174+ stringResource(id = R .string.balance_code),
175+ modifier = Modifier .weight(1f ),
176+ color = TextMuted ,
177+ style = MaterialTheme .typography.labelMedium
178+ )
179+
180+ Text (
181+ stringResource(id = R .string.data),
182+ modifier = Modifier .weight(1f ),
183+ color = TextMuted ,
184+ style = MaterialTheme .typography.labelMedium
185+ )
186+
187+ Text (
188+ stringResource(id = R .string.redeem_time),
189+ modifier = Modifier .weight(1f ),
190+ color = TextMuted ,
191+ style = MaterialTheme .typography.labelMedium
192+ )
193+
194+ Text (
195+ stringResource(id = R .string.expiry_time),
196+ modifier = Modifier .weight(1f ),
197+ color = TextMuted ,
198+ style = MaterialTheme .typography.labelMedium
199+ )
200+ }
201+ }
202+
203+ items(balanceCodes, key = {it.balanceCodeId.idStr}) { balanceCode ->
204+ BalanceCodeListItem (
205+ balanceCode = balanceCode,
206+ )
207+ HorizontalDivider ()
208+ }
209+ }
210+ }
211+ }
212+ }
213+
214+ @Composable
215+ fun BalanceCodeListItem (
216+ balanceCode : RedeemedBalanceCode ,
217+ ) {
218+
219+ val maskSecret: (String ) -> String = { secret ->
220+ if (secret.length <= 6 ) secret
221+ else " ${secret.take(3 )} ...${secret.takeLast(3 )} "
222+ }
223+
224+ val formatBytes: (Long ) -> String = { bytes ->
225+ val oneTiB = 1024L * 1024 * 1024 * 1024
226+ val oneGiB = 1024L * 1024 * 1024
227+ when {
228+ bytes >= oneTiB -> String .format(" %.2f TiB" , bytes.toDouble() / oneTiB)
229+ else -> String .format(" %.2f GiB" , bytes.toDouble() / oneGiB)
230+ }
231+ }
232+
233+ Row (
234+ modifier = Modifier
235+ .fillMaxWidth()
236+ .padding(16 .dp),
237+ ) {
238+
239+ Text (
240+ maskSecret(balanceCode.secret),
241+ modifier = Modifier .weight(1f )
242+ )
243+
244+ Text (
245+ " +${formatBytes(balanceCode.balanceByteCount)} " ,
246+ modifier = Modifier .weight(1f )
247+ )
248+
249+ Text (
250+ balanceCode.redeemTime.format(" Jan 2, 2006" ),
251+ modifier = Modifier .weight(1f )
252+ )
253+
254+ Text (
255+ balanceCode.endTime.format(" Jan 2, 2006" ),
256+ modifier = Modifier .weight(1f )
257+ )
258+
259+ }
260+
261+ }
0 commit comments