11package ui
22
33import androidx.compose.foundation.layout.Arrangement
4+ import androidx.compose.foundation.layout.Box
45import androidx.compose.foundation.layout.Column
56import androidx.compose.foundation.layout.Row
67import androidx.compose.foundation.layout.Spacer
@@ -12,13 +13,17 @@ import androidx.compose.foundation.layout.width
1213import androidx.compose.foundation.rememberScrollState
1314import androidx.compose.foundation.verticalScroll
1415import androidx.compose.material.icons.Icons
16+ import androidx.compose.material.icons.automirrored.sharp.KeyboardArrowLeft
17+ import androidx.compose.material.icons.automirrored.sharp.KeyboardArrowRight
1518import androidx.compose.material.icons.sharp.Close
1619import androidx.compose.material3.Card
17- import androidx.compose.material3.ElevatedButton
20+ import androidx.compose.material3.DropdownMenu
21+ import androidx.compose.material3.DropdownMenuItem
1822import androidx.compose.material3.Icon
1923import androidx.compose.material3.IconButton
2024import androidx.compose.material3.LinearProgressIndicator
2125import androidx.compose.material3.MaterialTheme
26+ import androidx.compose.material3.OutlinedButton
2227import androidx.compose.material3.OutlinedCard
2328import androidx.compose.material3.Text
2429import androidx.compose.runtime.Composable
@@ -39,6 +44,7 @@ import io.github.jsixface.common.JobStatus.Failed
3944import io.github.jsixface.common.JobStatus.InProgress
4045import io.github.jsixface.common.JobStatus.Queued
4146import io.github.jsixface.common.JobStatus.Starting
47+ import io.github.jsixface.common.JobsResponse
4248import kotlinx.coroutines.launch
4349import org.koin.compose.koinInject
4450import ui.model.ModelState
@@ -58,26 +64,32 @@ fun JobsScreen() {
5864 ) {
5965 val jobsScreenModel = koinInject<JobsScreenModel >()
6066 val scope = rememberCoroutineScope()
61- var jobs by remember { mutableStateOf(listOf< ConversionJob >() ) }
67+ var jobsResponse by remember { mutableStateOf< JobsResponse ?>( null ) }
6268 LaunchedEffect (Unit ) {
6369 scope.launch {
64- jobsScreenModel.jobs .collect { jobResult ->
70+ jobsScreenModel.jobsResponse .collect { jobResult ->
6571 when (jobResult) {
6672 is ModelState .Error , is ModelState .Init -> {}
67- is ModelState .Success -> jobs = jobResult.result
73+ is ModelState .Success -> jobsResponse = jobResult.result
6874 }
6975 }
7076 }
7177 }
7278 JobContent (
73- jobs,
74- onClear = { scope.launch { jobsScreenModel.clearJobs() } },
79+ jobsResponse,
80+ onPageChange = { jobsScreenModel.setPage(it) },
81+ onItemsPerPageChange = { jobsScreenModel.setItemsPerPage(it) },
7582 onDelete = { scope.launch { jobsScreenModel.delete(it) } })
7683 }
7784}
7885
7986@Composable
80- fun JobContent (jobs : List <ConversionJob >, onDelete : (String ) -> Unit , onClear : () -> Unit ) {
87+ fun JobContent (
88+ jobsResponse : JobsResponse ? ,
89+ onPageChange : (Int ) -> Unit ,
90+ onItemsPerPageChange : (Int ) -> Unit ,
91+ onDelete : (String ) -> Unit
92+ ) {
8193 Card (
8294 modifier = Modifier .width(width = 900 .dp).fillMaxHeight().padding(20 .dp)
8395 ) {
@@ -91,16 +103,64 @@ fun JobContent(jobs: List<ConversionJob>, onDelete: (String) -> Unit, onClear: (
91103 )
92104 }
93105 Column {
94- jobs.forEach { job -> JobItem (job) { onDelete(job.jobId) } }
106+ jobsResponse?. jobs? .forEach { job -> JobItem (job) { onDelete(job.jobId) } }
95107 }
96- Row (modifier = Modifier .fillMaxSize()) {
97- Spacer (modifier = Modifier .weight(1f ))
98- ElevatedButton (onClick = onClear, modifier = padding) {
99- Text (" Clear Completed" )
108+ if (jobsResponse != null ) {
109+ PaginationControls (
110+ totalItems = jobsResponse.totalCompleted,
111+ currentPage = jobsResponse.page,
112+ itemsPerPage = jobsResponse.itemsPerPage,
113+ onPageChange = onPageChange,
114+ onItemsPerPageChange = onItemsPerPageChange
115+ )
116+ }
117+ }
118+ }
119+ }
120+
121+ @Composable
122+ fun PaginationControls (
123+ totalItems : Long ,
124+ currentPage : Int ,
125+ itemsPerPage : Int ,
126+ onPageChange : (Int ) -> Unit ,
127+ onItemsPerPageChange : (Int ) -> Unit
128+ ) {
129+ val totalPages = kotlin.math.ceil(totalItems.toDouble() / itemsPerPage).toInt().coerceAtLeast(1 )
130+ Row (
131+ modifier = Modifier .fillMaxWidth().padding(16 .dp),
132+ verticalAlignment = Alignment .CenterVertically ,
133+ horizontalArrangement = Arrangement .Center
134+ ) {
135+ Text (" Items per page:" )
136+ Spacer (modifier = Modifier .width(8 .dp))
137+ var expanded by remember { mutableStateOf(false ) }
138+ Box {
139+ OutlinedButton (onClick = { expanded = true }) {
140+ Text (itemsPerPage.toString())
141+ }
142+ DropdownMenu (expanded = expanded, onDismissRequest = { expanded = false }) {
143+ listOf (10 , 20 , 50 , 100 ).forEach { limit ->
144+ DropdownMenuItem (
145+ text = { Text (limit.toString()) },
146+ onClick = {
147+ onItemsPerPageChange(limit)
148+ expanded = false
149+ }
150+ )
100151 }
101- Spacer (modifier = Modifier .weight(1f ))
102152 }
103153 }
154+
155+ Spacer (modifier = Modifier .weight(1f ))
156+
157+ IconButton (onClick = { onPageChange(currentPage - 1 ) }, enabled = currentPage > 1 ) {
158+ Icon (Icons .AutoMirrored .Sharp .KeyboardArrowLeft , contentDescription = " Previous Page" )
159+ }
160+ Text (" Page $currentPage of $totalPages " )
161+ IconButton (onClick = { onPageChange(currentPage + 1 ) }, enabled = currentPage < totalPages) {
162+ Icon (Icons .AutoMirrored .Sharp .KeyboardArrowRight , contentDescription = " Next Page" )
163+ }
104164 }
105165}
106166
@@ -115,30 +175,80 @@ fun JobItem(job: ConversionJob, onDelete: () -> Unit) {
115175 Column (modifier = Modifier .weight(1f ).padding(8 .dp)) {
116176 Text (job.file.fileName, modifier = paddingSmall)
117177 when (job.status) {
118- Starting -> LinearProgressIndicator (modifier = progressPadding)
119- InProgress -> LinearProgressIndicator (
120- progress = { job.progress / 100.0f },
121- modifier = progressPadding,
122- )
178+ Starting -> Column {
179+ LinearProgressIndicator (modifier = progressPadding)
180+ Text (
181+ " Started at: ${job.startedAt} " ,
182+ style = MaterialTheme .typography.labelSmall,
183+ modifier = paddingSmall
184+ )
185+ }
186+
187+ InProgress -> Column {
188+ LinearProgressIndicator (
189+ progress = { job.progress / 100.0f },
190+ modifier = progressPadding,
191+ )
192+ Text (
193+ " Started at: ${job.startedAt} " ,
194+ style = MaterialTheme .typography.labelSmall,
195+ modifier = paddingSmall
196+ )
197+ }
123198
124- Completed -> Text (
125- " Completed" ,
126- style = MaterialTheme .typography.labelSmall,
127- modifier = paddingSmall
128- )
199+ Completed -> Row {
200+ Text (
201+ " Completed" ,
202+ style = MaterialTheme .typography.labelSmall,
203+ modifier = paddingSmall
204+ )
205+ Text (
206+ " Started at: ${job.startedAt} " ,
207+ style = MaterialTheme .typography.labelSmall,
208+ modifier = paddingSmall
209+ )
210+ job.duration?.let {
211+ Text (
212+ " Duration: $it " ,
213+ style = MaterialTheme .typography.labelSmall,
214+ modifier = paddingSmall
215+ )
216+ }
217+ }
129218
130- Failed -> Text (
131- " Failed" ,
132- style = MaterialTheme .typography.labelSmall,
133- color = MaterialTheme .colorScheme.error,
134- modifier = paddingSmall
135- )
219+ Failed -> Row {
220+ Text (
221+ " Failed" ,
222+ style = MaterialTheme .typography.labelSmall,
223+ color = MaterialTheme .colorScheme.error,
224+ modifier = paddingSmall
225+ )
226+ Text (
227+ " Started at: ${job.startedAt} " ,
228+ style = MaterialTheme .typography.labelSmall,
229+ modifier = paddingSmall
230+ )
231+ job.duration?.let {
232+ Text (
233+ " Duration: $it " ,
234+ style = MaterialTheme .typography.labelSmall,
235+ modifier = paddingSmall
236+ )
237+ }
238+ }
136239
137- Queued -> Text (
138- " Queued" ,
139- style = MaterialTheme .typography.labelSmall,
140- modifier = paddingSmall
141- )
240+ Queued -> Column {
241+ Text (
242+ " Queued" ,
243+ style = MaterialTheme .typography.labelSmall,
244+ modifier = paddingSmall
245+ )
246+ Text (
247+ " Started at: ${job.startedAt} " ,
248+ style = MaterialTheme .typography.labelSmall,
249+ modifier = paddingSmall
250+ )
251+ }
142252 }
143253 }
144254 if (job.status !in listOf (Failed , Completed )) {
0 commit comments