Skip to content

Commit fdfd149

Browse files
committed
added: online catalogue and reading
1 parent 98dfe35 commit fdfd149

25 files changed

Lines changed: 432 additions & 117 deletions

app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ apply plugin: 'kotlin-kapt'
44

55
android {
66
// compileSdkVersion 34
7-
compileSdk 34
7+
compileSdk 35
88
// buildToolsVersion '33.0.1'
99

1010
defaultConfig {
@@ -85,7 +85,7 @@ dependencies {
8585
implementation 'com.google.apis:google-api-services-drive:v3-rev20220815-2.0.0'
8686

8787
//Image loader
88-
implementation "io.coil-kt:coil-compose:2.5.0"
88+
implementation "io.coil-kt:coil-compose:2.7.0"
8989

9090

9191
//For REST api

app/src/main/java/com/home/reader/api/ApiHandler.kt

Lines changed: 61 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
package com.home.reader.api
22

3-
import android.content.Context
4-
import android.util.Log
53
import androidx.compose.runtime.mutableStateOf
4+
import com.home.reader.api.dto.Issue
5+
import com.home.reader.api.dto.Series
6+
import com.home.reader.api.dto.Result
7+
import com.home.reader.api.exception.TokenUpdateFailedException
8+
import com.home.reader.api.exception.Unauthorized
9+
import com.home.reader.api.exception.UnknownException
610
import com.home.reader.persistence.entity.User
711
import com.home.reader.persistence.repository.UserRepository
12+
import okhttp3.Response
813

9-
class ApiHandler(
10-
context: Context,
11-
private val userRepository: UserRepository
12-
) {
14+
class ApiHandler(private val userRepository: UserRepository) {
1315

14-
private val processor = ApiProcessor("https://paper.webhop.me/api", context)
16+
private val processor = ApiProcessor("https://paper.webhop.me/api")
1517

1618
private val tokenState = mutableStateOf<String?>(null)
1719

@@ -25,14 +27,6 @@ class ApiHandler(
2527
suspend fun login(username: String, password: String): Boolean {
2628
val result = processor.login(username, password)
2729
if (!result.isSuccess()) {
28-
Log.i(
29-
"Login request",
30-
"""
31-
Code: ${result.failReason?.code}
32-
Reason: ${result.failReason?.text}
33-
""".trimIndent()
34-
)
35-
3630
return false
3731
}
3832

@@ -57,4 +51,56 @@ class ApiHandler(
5751
return true
5852
}
5953

54+
private suspend fun updateToken(user: User): String {
55+
val result = processor.login(user.username, user.password)
56+
if (result.isSuccess()) {
57+
userRepository.update(user.copy(token = result.value!!.token))
58+
return result.value.token
59+
}
60+
61+
throw TokenUpdateFailedException()
62+
}
63+
64+
suspend fun getSeries(): List<Series> {
65+
return withToken { processor.getSeries(it) }
66+
}
67+
68+
suspend fun getIssues(seriesId: Long): List<Issue> {
69+
return withToken { processor.getIssues(seriesId, it) }
70+
}
71+
72+
suspend fun updateProgress(issueId: Long, currentPage: Int) {
73+
withToken { processor.updateProgress(issueId, currentPage, it) }
74+
}
75+
76+
fun buildImageUrl(url: String): String = processor.buildImageUrl(url)
77+
78+
private suspend fun <T> withToken(action: (String) -> Result<T>): T {
79+
val user = userRepository.get() ?: throw Unauthorized()
80+
val token = user.token ?: throw Unauthorized()
81+
val result = action(token)
82+
83+
if (result.isSuccess()) {
84+
return result.value!!
85+
}
86+
87+
if (result.failReason?.code == 403) {
88+
val updatedToken = updateToken(user)
89+
val attemptResult = action(updatedToken)
90+
if (attemptResult.isSuccess()) {
91+
return attemptResult.value!!
92+
}
93+
94+
if (!attemptResult.isSuccess()) {
95+
throw UnknownException(
96+
attemptResult.failReason?.code,
97+
attemptResult.failReason?.text
98+
)
99+
}
100+
}
101+
102+
throw UnknownException(result.failReason?.code, result.failReason?.text)
103+
104+
}
105+
60106
}

app/src/main/java/com/home/reader/api/ApiProcessor.kt

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
package com.home.reader.api
22

3-
import android.content.Context
4-
import coil.request.ImageRequest
53
import com.google.gson.Gson
64
import com.google.gson.reflect.TypeToken
75
import com.home.reader.api.dto.Credentials
86
import com.home.reader.api.dto.Issue
7+
import com.home.reader.api.dto.ReadingProgressUpdate
98
import com.home.reader.api.dto.Result
109
import com.home.reader.api.dto.Series
1110
import com.home.reader.api.dto.Token
@@ -16,8 +15,10 @@ import okhttp3.RequestBody.Companion.toRequestBody
1615
import okhttp3.Response
1716
import java.io.InputStreamReader
1817
import java.lang.reflect.Type
18+
import java.time.ZoneId
19+
import java.time.ZonedDateTime
1920

20-
class ApiProcessor(private val host: String, private val context: Context) {
21+
class ApiProcessor(private val host: String) {
2122
private val client = OkHttpClient()
2223

2324
private companion object {
@@ -34,8 +35,9 @@ class ApiProcessor(private val host: String, private val context: Context) {
3435
).type
3536

3637
const val AUTH = "/customer/login"
37-
const val ALL_SERIES = "/comics/series"
38-
const val ISSUES_OF_SERIES = "/comics/series/%s/issues"
38+
const val SERIES = "/series"
39+
const val ISSUE = "/issue"
40+
const val ISSUES = "/issues"
3941
}
4042

4143
fun login(username: String, password: String): Result<Token> {
@@ -53,27 +55,19 @@ class ApiProcessor(private val host: String, private val context: Context) {
5355
fun getSeries(token: String): Result<List<Series>> {
5456
val request = Request.Builder()
5557
.get()
56-
.url("$host$ALL_SERIES")
58+
.url("$host$SERIES")
5759
.addAuthorizationHeader(token)
5860
.build()
5961

6062
return client.newCall(request).execute().toResult(SERIES_RESULT_TYPE)
6163
}
6264

63-
fun buildImageRequest(
64-
issueId: Long,
65-
token: String,
66-
page: Int = 0,
67-
size: String = "origin"
68-
): ImageRequest {
69-
return ImageRequest.Builder(context = context)
70-
.data("$host/file/$issueId/$page?size=$size")
71-
.addHeader("Authorization", "Bearer $token")
72-
.build()
65+
fun buildImageUrl(url: String): String {
66+
return host + url
7367
}
7468

7569
fun getIssues(seriesId: Long, token: String): Result<List<Issue>> {
76-
val path = ISSUES_OF_SERIES.format(seriesId)
70+
val path = "$SERIES/$seriesId$ISSUES"
7771
val request = Request.Builder()
7872
.get()
7973
.url("$host$path")
@@ -83,6 +77,29 @@ class ApiProcessor(private val host: String, private val context: Context) {
8377
return client.newCall(request).execute().toResult(ISSUES_RESULT_TYPE)
8478
}
8579

80+
fun updateProgress(issueId: Long, currentPage: Int, token: String): Result<Boolean> {
81+
val progressUpdate = ReadingProgressUpdate(
82+
currentPage = currentPage,
83+
updateTime = ZonedDateTime.now(ZoneId.of("UTC")).toEpochSecond()
84+
)
85+
86+
val body = Gson().toJson(progressUpdate).toRequestBody(CONTENT_TYPE)
87+
88+
val path = "$ISSUE/$issueId"
89+
val request = Request.Builder()
90+
.put(body)
91+
.url(host + path)
92+
.addAuthorizationHeader(token)
93+
.build()
94+
95+
val response = client.newCall(request).execute()
96+
if (response.code != 200) {
97+
return Result.failure(response.code)
98+
}
99+
100+
return Result(true)
101+
}
102+
86103
private fun Request.Builder.addAuthorizationHeader(token: String): Request.Builder {
87104
return this.addHeader("Authorization", "Bearer $token")
88105
}
Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,21 @@
11
package com.home.reader.api.dto
22

3+
import java.util.Date
4+
35
data class Issue(
6+
47
val id: Long,
8+
59
val number: String,
10+
11+
val summary: String = "",
12+
13+
val seriesId: Long,
14+
615
val pagesCount: Int,
7-
val currentPage: Int = 0
8-
)
16+
17+
val currentPage: Int = 0,
18+
19+
val publicationDate: Date
20+
)
21+
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package com.home.reader.api.dto
2+
3+
data class ReadingProgressUpdate(
4+
val currentPage: Int,
5+
val updateTime: Long
6+
)

app/src/main/java/com/home/reader/api/dto/Series.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ package com.home.reader.api.dto
22

33
data class Series(
44
val id: Long,
5-
val name: String,
6-
val cover: Long,
7-
val issuesCount: Long = 0,
8-
val completedIssues: Long = 0
5+
val title: String,
6+
val publisher: String,
7+
val issuesCount: Int,
8+
val cover: String,
9+
val subscribed: Boolean = false
910
)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.home.reader.api.exception
2+
3+
class TokenUpdateFailedException : RuntimeException("Token update failed")
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
package com.home.reader.api.exception
2+
3+
class Unauthorized : RuntimeException("Unauthorized")
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
package com.home.reader.api.exception
2+
3+
class UnknownException(code: Int?, text: String?) :
4+
RuntimeException("Unknown error code: $code, reason: $text")

app/src/main/java/com/home/reader/persistence/AppContainer.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,6 @@ class AppDataContainer(context: Context) : AppContainer {
2424

2525
override val userRepository = DefaultUserRepository(AppDatabase.invoke(context).userDao())
2626

27-
override val api: ApiHandler = ApiHandler(context, userRepository)
27+
override val api: ApiHandler = ApiHandler(userRepository)
2828

2929
}

0 commit comments

Comments
 (0)