1+ /*
2+ * Variant - A digital comic book reading application for the iPad and Android tablets.
3+ * Copyright (C) 2025, The ComiXed Project
4+ *
5+ * This program is free software: you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation, either version 3 of the License, or
8+ * (at your option) any later version.
9+ *
10+ * This program is distributed in the hope that it will be useful,
11+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+ * GNU General Public License for more details.
14+ *
15+ * You should have received a copy of the GNU General Public License
16+ * along with this program. If not, see <http://www.gnu.org/licenses>
17+ */
18+
19+ package org.comixedproject.variant.android.view.reading
20+
21+ import android.graphics.BitmapFactory
22+ import androidx.compose.foundation.Image
23+ import androidx.compose.foundation.layout.Row
24+ import androidx.compose.foundation.layout.fillMaxHeight
25+ import androidx.compose.foundation.layout.fillMaxSize
26+ import androidx.compose.foundation.layout.fillMaxWidth
27+ import androidx.compose.foundation.layout.padding
28+ import androidx.compose.material.icons.Icons
29+ import androidx.compose.material.icons.automirrored.filled.ArrowBack
30+ import androidx.compose.material.icons.automirrored.filled.ArrowForward
31+ import androidx.compose.material3.BottomAppBar
32+ import androidx.compose.material3.Icon
33+ import androidx.compose.material3.IconButton
34+ import androidx.compose.material3.MaterialTheme
35+ import androidx.compose.material3.Scaffold
36+ import androidx.compose.material3.Slider
37+ import androidx.compose.material3.Text
38+ import androidx.compose.runtime.Composable
39+ import androidx.compose.runtime.LaunchedEffect
40+ import androidx.compose.runtime.getValue
41+ import androidx.compose.runtime.mutableStateOf
42+ import androidx.compose.runtime.remember
43+ import androidx.compose.runtime.setValue
44+ import androidx.compose.ui.Alignment
45+ import androidx.compose.ui.Modifier
46+ import androidx.compose.ui.graphics.asImageBitmap
47+ import androidx.compose.ui.res.stringResource
48+ import androidx.compose.ui.text.style.TextOverflow
49+ import androidx.compose.ui.tooling.preview.Preview
50+ import org.comixedproject.variant.adaptor.ArchiveAPI
51+ import org.comixedproject.variant.android.COMIC_BOOK_LIST
52+ import org.comixedproject.variant.android.R
53+ import org.comixedproject.variant.android.VariantTheme
54+ import org.comixedproject.variant.platform.Log
55+
56+ private const val TAG = " ReadingPageView"
57+
58+ @Composable
59+ fun ReadingPageView (
60+ comicFilename : String ,
61+ pageFilename : String ,
62+ title : String ,
63+ currentPage : Int ,
64+ totalPages : Int ,
65+ onChangePage : (Int ) -> Unit ,
66+ onStopReading : () -> Unit ,
67+ modifier : Modifier = Modifier
68+ ) {
69+ var currentPageContent by remember { mutableStateOf<ByteArray ?>(null ) }
70+
71+ Scaffold (
72+ content = { padding ->
73+ if (currentPageContent == null ) {
74+ LaunchedEffect (currentPageContent) {
75+ currentPageContent =
76+ ArchiveAPI .loadPage(comicFilename, pageFilename)
77+ }
78+ } else {
79+ currentPageContent?.let { content ->
80+ Image (
81+ bitmap = BitmapFactory .decodeByteArray(content, 0 , content.size)
82+ .asImageBitmap(),
83+ contentDescription = title,
84+ modifier = Modifier
85+ .padding(padding)
86+ .fillMaxHeight()
87+ )
88+ }
89+ }
90+ },
91+ topBar = {
92+ Row (
93+ verticalAlignment = Alignment .CenterVertically ,
94+ modifier = Modifier .fillMaxWidth()
95+ ) {
96+ IconButton (
97+ onClick = {
98+ Log .debug(
99+ TAG ,
100+ " Closing comic book"
101+ )
102+ onStopReading()
103+ }
104+ ) {
105+ Icon (
106+ Icons .AutoMirrored .Filled .ArrowBack ,
107+ contentDescription = stringResource(R .string.stopReadingLabel)
108+ )
109+ }
110+
111+ Text (
112+ title,
113+ style = MaterialTheme .typography.titleMedium,
114+ maxLines = 1 ,
115+ overflow = TextOverflow .Ellipsis ,
116+ modifier = Modifier .fillMaxWidth()
117+ )
118+ }
119+ },
120+ bottomBar = {
121+ BottomAppBar {
122+ Row (modifier = Modifier .fillMaxWidth()) {
123+ IconButton (onClick = {
124+ currentPageContent = null
125+ onChangePage(currentPage - 1 )
126+ }, enabled = (currentPage > 0 )) {
127+ Icon (
128+ imageVector = Icons .AutoMirrored .Filled .ArrowBack ,
129+ contentDescription = stringResource(R .string.previousPageLabel)
130+ )
131+ }
132+
133+ Slider (
134+ value = currentPage.toFloat(),
135+ valueRange = 0f .. (totalPages - 1 ).toFloat(),
136+ steps = totalPages,
137+ onValueChange = {
138+ currentPageContent = null
139+ onChangePage(it.toInt())
140+ },
141+ modifier = Modifier .weight(0.9f )
142+ )
143+
144+ IconButton (onClick = {
145+ currentPageContent = null
146+ onChangePage(currentPage + 1 )
147+ }, enabled = (currentPage < (totalPages - 1 ))) {
148+ Icon (
149+ imageVector = Icons .AutoMirrored .Filled .ArrowForward ,
150+ contentDescription = stringResource(R .string.nextPageLabel)
151+ )
152+ }
153+ }
154+ }
155+ },
156+ modifier = modifier
157+ .fillMaxSize()
158+ )
159+ }
160+
161+
162+ @Composable
163+ @Preview
164+ fun ReadingPageViewPreview () {
165+ val comic = COMIC_BOOK_LIST .get(0 )
166+ VariantTheme {
167+ ReadingPageView (
168+ comic.filename,
169+ comic.pages.get(0 ).filename,
170+ " Page Title" ,
171+ 5 ,
172+ 10 ,
173+ onChangePage = {},
174+ onStopReading = {}
175+ )
176+ }
177+ }
0 commit comments