diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index ccfac8f0..d0667882 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -4,6 +4,6 @@
-
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 67e75140..00c1ff6a 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -5,6 +5,7 @@ plugins {
id("org.jetbrains.kotlin.plugin.serialization")
id("com.google.devtools.ksp")
alias(libs.plugins.compose.compiler)
+ alias(libs.plugins.ktorfit)
}
@@ -73,9 +74,14 @@ dependencies {
implementation(libs.volley)
- implementation(libs.retrofit)
- implementation(libs.retrofit.json)
- implementation(libs.logging.interceptor)
+ implementation(libs.ktor.client.core)
+ implementation(libs.ktor.client.content.negotiation)
+ implementation(libs.ktor.client.serialization)
+ implementation(libs.ktor.serialization.json)
+ implementation(libs.ktor.client.logging)
+ implementation(libs.ktor.client.okhttp)
+ implementation(libs.ktorfit)
+ implementation(libs.ktorfit.call)
implementation(libs.androidx.runtime.livedata)
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/remote/PixelfedApi.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/remote/PixelfedApi.kt
index c1eeb0e6..6779ffcc 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/remote/PixelfedApi.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/remote/PixelfedApi.kt
@@ -27,19 +27,18 @@ import com.daniebeler.pfpixelix.data.remote.dto.WellKnownDomainsDto
import com.daniebeler.pfpixelix.data.remote.dto.nodeinfo.FediSoftwareDto
import com.daniebeler.pfpixelix.data.remote.dto.nodeinfo.NodeInfoDto
import com.daniebeler.pfpixelix.data.remote.dto.nodeinfo.WrapperDto
-import okhttp3.RequestBody
-import retrofit2.Call
-import retrofit2.Response
-import retrofit2.http.Body
-import retrofit2.http.DELETE
-import retrofit2.http.Field
-import retrofit2.http.FormUrlEncoded
-import retrofit2.http.GET
-import retrofit2.http.POST
-import retrofit2.http.PUT
-import retrofit2.http.Path
-import retrofit2.http.Query
-import retrofit2.http.Url
+import de.jensklingenberg.ktorfit.Call
+import de.jensklingenberg.ktorfit.http.Body
+import de.jensklingenberg.ktorfit.http.DELETE
+import de.jensklingenberg.ktorfit.http.Field
+import de.jensklingenberg.ktorfit.http.FormUrlEncoded
+import de.jensklingenberg.ktorfit.http.GET
+import de.jensklingenberg.ktorfit.http.POST
+import de.jensklingenberg.ktorfit.http.PUT
+import de.jensklingenberg.ktorfit.http.Path
+import de.jensklingenberg.ktorfit.http.Query
+import de.jensklingenberg.ktorfit.http.Url
+import io.ktor.client.request.forms.MultiPartFormDataContent
interface PixelfedApi {
@@ -143,7 +142,7 @@ interface PixelfedApi {
@POST("api/v1/accounts/update_credentials?_pe=1")
fun updateAccount(
- @Body body: RequestBody
+ @Body body: MultiPartFormDataContent
): Call
@GET("api/v1/accounts/{accountid}/statuses?pe=1")
@@ -363,7 +362,7 @@ interface PixelfedApi {
@POST("/api/v2/media")
fun uploadMedia(
- @Body body: RequestBody
+ @Body body: MultiPartFormDataContent
): Call
@FormUrlEncoded
@@ -376,7 +375,7 @@ interface PixelfedApi {
@POST("/api/v1/statuses")
suspend fun createPost(
@Body createPostDto: CreatePostDto
- ): Response
+ ): Call
@POST("/api/v1/statuses")
fun createReply(
@@ -386,7 +385,7 @@ interface PixelfedApi {
@PUT("/api/v1/statuses/{id}")
suspend fun updatePost(
@Path("id") postId: String, @Body updatePostDto: UpdatePostDto
- ): Response
+ ): Call
@DELETE("/api/v1/statuses/{id}")
fun deletePost(
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/AccountRepositoryImpl.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/AccountRepositoryImpl.kt
index fa611d51..cd4c06dc 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/AccountRepositoryImpl.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/AccountRepositoryImpl.kt
@@ -10,10 +10,10 @@ import com.daniebeler.pfpixelix.domain.model.Relationship
import com.daniebeler.pfpixelix.domain.model.Settings
import com.daniebeler.pfpixelix.domain.repository.AccountRepository
import com.daniebeler.pfpixelix.utils.NetworkCall
+import com.daniebeler.pfpixelix.utils.execute
+import io.ktor.client.request.forms.MultiPartFormDataContent
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
-import okhttp3.RequestBody
-import retrofit2.awaitResponse
import javax.inject.Inject
class AccountRepositoryImpl @Inject constructor(
@@ -44,7 +44,7 @@ class AccountRepositoryImpl @Inject constructor(
)
}
- override fun updateAccount(body: RequestBody): Flow> {
+ override fun updateAccount(body: MultiPartFormDataContent): Flow> {
return NetworkCall().makeCall(
pixelfedApi.updateAccount(
body
@@ -142,16 +142,12 @@ class AccountRepositoryImpl @Inject constructor(
try {
emit(Resource.Loading())
val response = if (maxId.isNotEmpty()) {
- pixelfedApi.getAccountsFollowing(accountId, maxId).awaitResponse()
+ pixelfedApi.getAccountsFollowing(accountId, maxId).execute()
} else {
- pixelfedApi.getAccountsFollowing(accountId).awaitResponse()
+ pixelfedApi.getAccountsFollowing(accountId).execute()
}
- if (response.isSuccessful) {
- val res = response.body()?.map { it.toModel() } ?: emptyList()
+ val res = response.map { it.toModel() }
emit(Resource.Success(res))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
} catch (exception: Exception) {
emit(Resource.Error("Unknown Error"))
}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CollectionRepositoryImpl.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CollectionRepositoryImpl.kt
index 1e46bbdd..92caf46f 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CollectionRepositoryImpl.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CollectionRepositoryImpl.kt
@@ -8,9 +8,9 @@ import com.daniebeler.pfpixelix.domain.model.Collection
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.repository.CollectionRepository
import com.daniebeler.pfpixelix.utils.NetworkCall
+import com.daniebeler.pfpixelix.utils.execute
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
-import retrofit2.awaitResponse
import javax.inject.Inject
class CollectionRepositoryImpl @Inject constructor(
@@ -48,13 +48,8 @@ class CollectionRepositoryImpl @Inject constructor(
emit(Resource.Loading())
val response = pixelfedApi.removePostOfCollection(
collectionId, postId
- ).awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()
- emit(Resource.Success(res!!))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
+ ).execute()
+ emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message ?: "Unknown Error"))
}
@@ -67,13 +62,8 @@ class CollectionRepositoryImpl @Inject constructor(
emit(Resource.Loading())
val response = pixelfedApi.addPostOfCollection(
collectionId, postId
- ).awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()
- emit(Resource.Success(res!!))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
+ ).execute()
+ emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message ?: "Unknown Error"))
}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CountryRepositoryImpl.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CountryRepositoryImpl.kt
index 0a3d13f3..5dafeb87 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CountryRepositoryImpl.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/CountryRepositoryImpl.kt
@@ -35,10 +35,10 @@ import com.daniebeler.pfpixelix.domain.model.nodeinfo.FediSoftware
import com.daniebeler.pfpixelix.domain.model.nodeinfo.NodeInfo
import com.daniebeler.pfpixelix.domain.repository.CountryRepository
import com.daniebeler.pfpixelix.utils.NetworkCall
+import com.daniebeler.pfpixelix.utils.execute
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map
-import retrofit2.awaitResponse
import javax.inject.Inject
@@ -81,17 +81,13 @@ class CountryRepositoryImpl @Inject constructor(
try {
emit(Resource.Loading())
val response = if (maxNotificationId.isNotEmpty()) {
- pixelfedApi.getNotifications(maxNotificationId).awaitResponse()
+ pixelfedApi.getNotifications(maxNotificationId).execute()
} else {
- pixelfedApi.getNotifications().awaitResponse()
+ pixelfedApi.getNotifications().execute()
}
- if (response.isSuccessful) {
- val res = response.body()?.map { it.toModel() } ?: emptyList()
- emit(Resource.Success(res))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
+ val res = response.map { it.toModel() }
+ emit(Resource.Success(res))
} catch (exception: Exception) {
emit(Resource.Error(exception.message ?: "Unknown Error"))
}
@@ -110,13 +106,8 @@ class CountryRepositoryImpl @Inject constructor(
override fun search(searchText: String, type: String?, limit: Int): Flow> = flow {
try {
emit(Resource.Loading())
- val response = pixelfedApi.getSearch(searchText, type, limit).awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()!!.toModel()
- emit(Resource.Success(res))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
+ val response = pixelfedApi.getSearch(searchText, type, limit).execute().toModel()
+ emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error("Unknown Error"))
}
@@ -144,12 +135,7 @@ class CountryRepositoryImpl @Inject constructor(
override suspend fun createApplication(): Application? {
return try {
- val response = pixelfedApi.createApplication().awaitResponse()
- if (response.isSuccessful) {
- response.body()?.toModel()
- } else {
- null
- }
+ pixelfedApi.createApplication().execute().toModel()
} catch (exception: Exception) {
null
}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/DirectMessagesRepositoryImpl.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/DirectMessagesRepositoryImpl.kt
index bb576919..05bc34b5 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/DirectMessagesRepositoryImpl.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/DirectMessagesRepositoryImpl.kt
@@ -6,17 +6,14 @@ import com.daniebeler.pfpixelix.data.remote.dto.ChatDto
import com.daniebeler.pfpixelix.data.remote.dto.ConversationDto
import com.daniebeler.pfpixelix.data.remote.dto.CreateMessageDto
import com.daniebeler.pfpixelix.data.remote.dto.MessageDto
-import com.daniebeler.pfpixelix.data.remote.dto.TagDto
import com.daniebeler.pfpixelix.domain.model.Chat
import com.daniebeler.pfpixelix.domain.model.Conversation
import com.daniebeler.pfpixelix.domain.model.Message
-import com.daniebeler.pfpixelix.domain.model.Tag
import com.daniebeler.pfpixelix.domain.repository.DirectMessagesRepository
-import com.daniebeler.pfpixelix.domain.repository.HashtagRepository
import com.daniebeler.pfpixelix.utils.NetworkCall
+import com.daniebeler.pfpixelix.utils.execute
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
-import retrofit2.awaitResponse
import javax.inject.Inject
class DirectMessagesRepositoryImpl @Inject constructor(
@@ -42,13 +39,8 @@ class DirectMessagesRepositoryImpl @Inject constructor(
override fun deleteMessage(id: String): Flow>> = flow {
try {
emit(Resource.Loading())
- val response = pixelfedApi.deleteMessage(id).awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()
- emit(Resource.Success(res!!))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
+ val response = pixelfedApi.deleteMessage(id).execute()
+ emit(Resource.Success(response))
} catch (exception: Exception) {
emit(Resource.Error(exception.message ?: "Unknown Error"))
}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostEditorRepositoryImpl.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostEditorRepositoryImpl.kt
index 2369ec0b..8fdc2922 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostEditorRepositoryImpl.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostEditorRepositoryImpl.kt
@@ -13,21 +13,20 @@ import com.daniebeler.pfpixelix.data.remote.dto.MediaAttachmentDto
import com.daniebeler.pfpixelix.data.remote.dto.PostDto
import com.daniebeler.pfpixelix.data.remote.dto.UpdatePostDto
import com.daniebeler.pfpixelix.domain.model.MediaAttachment
-import com.daniebeler.pfpixelix.domain.model.MediaAttachmentConfiguration
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.repository.PostEditorRepository
import com.daniebeler.pfpixelix.utils.GetFile
import com.daniebeler.pfpixelix.utils.MimeType
import com.daniebeler.pfpixelix.utils.NetworkCall
+import com.daniebeler.pfpixelix.utils.execute
+import io.ktor.client.request.forms.MultiPartFormDataContent
+import io.ktor.client.request.forms.formData
+import io.ktor.http.Headers
+import io.ktor.http.HttpHeaders
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
-import okhttp3.MediaType.Companion.toMediaTypeOrNull
-import okhttp3.MultipartBody
-import okhttp3.RequestBody
-import okhttp3.RequestBody.Companion.toRequestBody
-import retrofit2.awaitResponse
import java.io.ByteArrayOutputStream
import javax.inject.Inject
@@ -48,35 +47,31 @@ class PostEditorRepositoryImpl @Inject constructor(
//val pixelfedApi = buildPixelFedApi(true)
val fileType = MimeType.getMimeType(uri, context.contentResolver) ?: "image/*"
-
- val inputStream = context.contentResolver.openInputStream(uri)
- val fileRequestBody =
- inputStream?.readBytes()?.toRequestBody(fileType.toMediaTypeOrNull())
-
+ val bytes = context.contentResolver.openInputStream(uri)?.readBytes()
val file = GetFile.getFile(uri, context) ?: return@flow
- val builder: MultipartBody.Builder = MultipartBody.Builder().setType(MultipartBody.FORM)
- builder.addFormDataPart("description", description)
- .addFormDataPart("file", file.name, fileRequestBody!!)
+ val thumbnailBitmap = if (fileType.take(5) != "image" || fileType == "image/gif") {
+ getThumbnail(uri, context)
+ } else null
- if (fileType.take(5) != "image" || fileType == "image/gif") {
- val thumbnailBitmap = getThumbnail(uri, context)
+ val data = MultiPartFormDataContent(formData {
+ append("file", bytes!!, Headers.build {
+ append(HttpHeaders.ContentType, fileType)
+ append(HttpHeaders.ContentDisposition, file.name)
+ })
if (thumbnailBitmap != null) {
bitmapToBytes(thumbnailBitmap)?.let {
- builder.addFormDataPart(
- "thumbnail", "thumbnail", it.toRequestBody()
- )
+ append("thumbnail", it, Headers.build {
+ append(HttpHeaders.ContentDisposition, "thumbnail")
+ })
}
}
- }
+ })
- val requestBody: RequestBody = builder.build()
- val response = pixelfedApi.uploadMedia(requestBody
- ).awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()!!.toModel()
+ try {
+ val res = pixelfedApi.uploadMedia(data).execute().toModel()
emit(Resource.Success(res))
- } else {
+ } catch (e: Exception) {
emit(Resource.Error("Unknown Error"))
}
} catch (exception: Exception) {
@@ -113,13 +108,8 @@ class PostEditorRepositoryImpl @Inject constructor(
override fun createPost(createPostDto: CreatePostDto): Flow> = flow {
try {
emit(Resource.Loading())
- val response = pixelfedApi.createPost(createPostDto)
- if (response != null) {
- val res = response.body()!!.toModel()
- emit(Resource.Success(res))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
+ val res = pixelfedApi.createPost(createPostDto).execute().toModel()
+ emit(Resource.Success(res))
} catch (exception: Exception) {
if (exception.message != null) {
emit(Resource.Error(exception.message!!))
@@ -132,12 +122,8 @@ class PostEditorRepositoryImpl @Inject constructor(
override fun updatePost(postId: String, updatePostDto: UpdatePostDto): Flow> = flow {
try {
emit(Resource.Loading())
- val response = pixelfedApi.updatePost(postId, updatePostDto)
- if (response.code() == 200) {
- emit(Resource.Success(null))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
+ pixelfedApi.updatePost(postId, updatePostDto).execute()
+ emit(Resource.Success(null))
} catch (exception: Exception) {
if (exception.message != null) {
emit(Resource.Error(exception.message!!))
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostRepositoryImpl.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostRepositoryImpl.kt
index 25742a5f..6210e6ca 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostRepositoryImpl.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/PostRepositoryImpl.kt
@@ -7,9 +7,9 @@ import com.daniebeler.pfpixelix.domain.model.LikedPostsWithNext
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.repository.PostRepository
import com.daniebeler.pfpixelix.utils.NetworkCall
+import com.daniebeler.pfpixelix.utils.executeWithResponse
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
-import retrofit2.awaitResponse
import javax.inject.Inject
class PostRepositoryImpl @Inject constructor(
@@ -68,28 +68,24 @@ class PostRepositoryImpl @Inject constructor(
override fun getLikedPosts(maxId: String): Flow> = flow {
try {
emit(Resource.Loading())
- val response = if (maxId.isNotBlank()) {
- pixelfedApi.getLikedPosts(maxId).awaitResponse()
+ val (response, data) = if (maxId.isNotBlank()) {
+ pixelfedApi.getLikedPosts(maxId).executeWithResponse()
} else {
- pixelfedApi.getLikedPosts().awaitResponse()
+ pixelfedApi.getLikedPosts().executeWithResponse()
}
- if (response.isSuccessful) {
- val linkHeader = response.headers()["link"] ?: ""
+ val linkHeader = response.headers["link"] ?: ""
val onlyLink =
linkHeader.substringAfter("rel=\"next\",<", "").substringBefore(">", "")
val nextMinId = onlyLink.substringAfter("min_id=", "")
- val res = response.body()?.map { it.toModel() } ?: emptyList()
+ val res = data.map { it.toModel() }
val result = LikedPostsWithNext(res, nextMinId)
emit(Resource.Success(result))
- } else {
- emit(Resource.Error("Unknown Error"))
- }
} catch (exception: Exception) {
emit(Resource.Error(exception.message ?: "Unknown Error"))
}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/WidgetRepositoryImpl.kt b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/WidgetRepositoryImpl.kt
index 6d02d14f..a4b11ee6 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/data/repository/WidgetRepositoryImpl.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/data/repository/WidgetRepositoryImpl.kt
@@ -4,28 +4,28 @@ import com.daniebeler.pfpixelix.common.Resource
import com.daniebeler.pfpixelix.data.remote.PixelfedApi
import com.daniebeler.pfpixelix.domain.model.Post
import com.daniebeler.pfpixelix.domain.repository.WidgetRepository
-import retrofit2.awaitResponse
+import com.daniebeler.pfpixelix.utils.execute
class WidgetRepositoryImpl constructor(private val pixelfedApi: PixelfedApi): WidgetRepository{
override suspend fun getNotifications(): Resource> {
- val response = pixelfedApi.getNotifications().awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()?.map { it.toModel() } ?: emptyList()
+ try {
+ val response = pixelfedApi.getNotifications().execute()
+ val res = response.map { it.toModel() }
return Resource.Success(res)
- } else {
+ } catch (e: Exception) {
return Resource.Error("an unexpected error occured")
}
}
override suspend fun getLatestImage(): Resource {
- val response = pixelfedApi.getHomeTimelineWithLimit(1).awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()?.map { it.toModel() }
- if (res.isNullOrEmpty()) {
+ try {
+ val response = pixelfedApi.getHomeTimelineWithLimit(1).execute()
+ val res = response.map { it.toModel() }
+ if (res.isEmpty()) {
return Resource.Error("an unexpected error occured")
}
return Resource.Success(res.first())
- } else {
+ } catch (e: Exception) {
return Resource.Error("an unexpected error occured")
}
}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptor.kt b/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptor.kt
index 4c4e2b3b..7104a858 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptor.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptor.kt
@@ -1,9 +1,8 @@
import com.daniebeler.pfpixelix.di.HostSelectionInterceptorInterface
-import okhttp3.Interceptor
-import okhttp3.OkHttpClient
-import okhttp3.Request
-import okhttp3.Response
-import java.io.IOException
+import io.ktor.client.call.HttpClientCall
+import io.ktor.client.plugins.Sender
+import io.ktor.client.request.HttpRequestBuilder
+import io.ktor.http.set
/** An interceptor that allows runtime changes to the URL hostname. */
class HostSelectionInterceptor : HostSelectionInterceptorInterface {
@@ -20,22 +19,13 @@ class HostSelectionInterceptor : HostSelectionInterceptorInterface {
this.token = token
}
- @Throws(IOException::class)
- override fun intercept(chain: Interceptor.Chain): Response {
- var request: Request = chain.request()
- val host = host
+ override suspend fun Sender.intercept(request: HttpRequestBuilder): HttpClientCall {
if (request.url.toString().startsWith("https://err.or")) {
- if (host != null) {
- val newUrl = request.url.newBuilder().host(host).build()
- request = request.newBuilder().url(newUrl).build()
- }
-
- val token = token
- if (token != null) {
- request = request.newBuilder().addHeader("Authorization", "Bearer $token").build()
+ request.apply {
+ url.set(host = host)
+ headers["Authorization"] = "Bearer $token"
}
}
-
- return chain.proceed(request)
+ return execute(request)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptorInterface.kt b/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptorInterface.kt
index 2ebeb7f2..0d10b50d 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptorInterface.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/di/HostSelectionInterceptorInterface.kt
@@ -1,8 +1,13 @@
package com.daniebeler.pfpixelix.di
-import okhttp3.Interceptor
+import io.ktor.client.call.HttpClientCall
+import io.ktor.client.plugins.Sender
+import io.ktor.client.request.HttpRequestBuilder
-interface HostSelectionInterceptorInterface: Interceptor {
+interface HostSelectionInterceptorInterface {
- fun setHost(host: String?)fun setToken(token: String?)
+ fun setHost(host: String?)
+ fun setToken(token: String?)
+
+ suspend fun Sender.intercept(request: HttpRequestBuilder): HttpClientCall
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/di/HtmlEntityDecodingInterceptor.kt b/app/src/main/java/com/daniebeler/pfpixelix/di/HtmlEntityDecodingInterceptor.kt
deleted file mode 100644
index 652ab7ef..00000000
--- a/app/src/main/java/com/daniebeler/pfpixelix/di/HtmlEntityDecodingInterceptor.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package com.daniebeler.pfpixelix.di
-
-import com.fleeksoft.ksoup.parser.Parser
-import okhttp3.Interceptor
-import okhttp3.Response
-import okhttp3.ResponseBody.Companion.toResponseBody
-
-class HtmlEntityDecodingInterceptor: Interceptor {
- override fun intercept(chain: Interceptor.Chain): Response {
- val response = chain.proceed(chain.request())
- val originalBody = response.body
- val decodedBody = originalBody?.string()?.let { decodeHtmlEntities(it) }
- return response.newBuilder()
- .body(decodedBody?.toResponseBody(response.body?.contentType()))
- .build()
- }
-}
-
-fun decodeHtmlEntities(input: String): String {
- return Parser.unescapeEntities(input, false)
-}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/di/Module.kt b/app/src/main/java/com/daniebeler/pfpixelix/di/Module.kt
index 591eceb0..7827711f 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/di/Module.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/di/Module.kt
@@ -5,19 +5,25 @@ import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.data.remote.PixelfedApi
+import com.daniebeler.pfpixelix.data.remote.createPixelfedApi
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
+import de.jensklingenberg.ktorfit.Ktorfit
+import de.jensklingenberg.ktorfit.converter.CallConverterFactory
+import io.ktor.client.HttpClient
+import io.ktor.client.plugins.HttpSend
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.logging.LogLevel
+import io.ktor.client.plugins.logging.Logging
+import io.ktor.client.plugins.plugin
+import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.ExperimentalSerializationApi
import kotlinx.serialization.json.Json
-import okhttp3.MediaType.Companion.toMediaType
-import okhttp3.OkHttpClient
-import okhttp3.logging.HttpLoggingInterceptor
-import retrofit2.Retrofit
-import retrofit2.converter.kotlinx.serialization.asConverterFactory
import javax.inject.Singleton
@@ -54,25 +60,41 @@ class Module {
@Provides
@Singleton
- fun provideOKHttpClient(hostSelectionInterceptor: HostSelectionInterceptorInterface): OkHttpClient {
-
- val loggi = HttpLoggingInterceptor()
- loggi.setLevel(HttpLoggingInterceptor.Level.BODY)
-
- return OkHttpClient.Builder().addInterceptor(hostSelectionInterceptor).addInterceptor(loggi).addInterceptor(HtmlEntityDecodingInterceptor())
- .build()
+ fun provideHttpClient(
+ json: Json,
+ hostSelectionInterceptor: HostSelectionInterceptorInterface
+ ): HttpClient = HttpClient {
+ install(ContentNegotiation) { json(json) }
+ install(Logging) {
+ logger = object : io.ktor.client.plugins.logging.Logger {
+ override fun log(message: String) {
+ Logger.v("HttpClient") {
+ message.lines().joinToString { "\n\t\t$it"}
+ }
+ }
+ }
+ level = LogLevel.BODY
+ }
+ }.apply {
+ plugin(HttpSend).intercept { request ->
+ with(hostSelectionInterceptor) {
+ intercept(request)
+ }
+ }
}
@Provides
@Singleton
- fun provideRetrofit(client: OkHttpClient, json: Json): Retrofit = Retrofit.Builder().addConverterFactory(
- json.asConverterFactory("application/json; charset=UTF8".toMediaType())
- ).client(client).baseUrl("https://err.or/").build()
+ fun provideKtorfit(client: HttpClient): Ktorfit = Ktorfit.Builder()
+ .converterFactories(CallConverterFactory())
+ .httpClient(client)
+ .baseUrl("https://err.or/")
+ .build()
@Provides
@Singleton
- fun providePixelfedApi(retrofit: Retrofit): PixelfedApi =
- retrofit.create(PixelfedApi::class.java)
+ fun providePixelfedApi(ktorfit: Ktorfit): PixelfedApi =
+ ktorfit.createPixelfedApi()
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/domain/repository/AccountRepository.kt b/app/src/main/java/com/daniebeler/pfpixelix/domain/repository/AccountRepository.kt
index b9e32da8..50442c4d 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/domain/repository/AccountRepository.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/domain/repository/AccountRepository.kt
@@ -4,8 +4,8 @@ import com.daniebeler.pfpixelix.common.Resource
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.domain.model.Relationship
import com.daniebeler.pfpixelix.domain.model.Settings
+import io.ktor.client.request.forms.MultiPartFormDataContent
import kotlinx.coroutines.flow.Flow
-import okhttp3.RequestBody
interface AccountRepository {
fun getAccount(accountId: String): Flow>
@@ -22,6 +22,6 @@ interface AccountRepository {
fun getAccountsFollowing(accountId: String, maxId: String = ""): Flow>>
fun getLikedBy(postId: String): Flow>>
fun getMutualFollowers(userId: String): Flow>>
- fun updateAccount(body: RequestBody): Flow>
+ fun updateAccount(body: MultiPartFormDataContent): Flow>
fun getAccountSettings(): Flow>
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/domain/usecase/UpdateAccountUseCase.kt b/app/src/main/java/com/daniebeler/pfpixelix/domain/usecase/UpdateAccountUseCase.kt
index 0be5b25b..0b22c7da 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/domain/usecase/UpdateAccountUseCase.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/domain/usecase/UpdateAccountUseCase.kt
@@ -2,16 +2,16 @@ package com.daniebeler.pfpixelix.domain.usecase
import android.content.Context
import android.net.Uri
-import co.touchlab.kermit.Logger
+import android.util.Log
import com.daniebeler.pfpixelix.common.Resource
import com.daniebeler.pfpixelix.domain.model.Account
import com.daniebeler.pfpixelix.domain.repository.AccountRepository
import com.daniebeler.pfpixelix.utils.MimeType
+import io.ktor.client.request.forms.MultiPartFormDataContent
+import io.ktor.client.request.forms.formData
+import io.ktor.http.Headers
+import io.ktor.http.HttpHeaders
import kotlinx.coroutines.flow.Flow
-import okhttp3.MediaType.Companion.toMediaTypeOrNull
-import okhttp3.MultipartBody
-import okhttp3.RequestBody
-import okhttp3.RequestBody.Companion.toRequestBody
class UpdateAccountUseCase(
@@ -20,30 +20,28 @@ class UpdateAccountUseCase(
operator fun invoke(
displayName: String, note: String, website: String, privateProfile: Boolean, avatarUri: Uri?, context: Context
): Flow> {
- val builder: MultipartBody.Builder = MultipartBody.Builder().setType(MultipartBody.FORM)
-
- if (avatarUri != null) {
- try {
-
- val fileType = MimeType.getMimeType(avatarUri, context.contentResolver) ?: "image/*"
- val inputStream = context.contentResolver.openInputStream(avatarUri)
- val fileRequestBody = inputStream?.readBytes()?.toRequestBody(fileType.toMediaTypeOrNull())
- builder.addFormDataPart("avatar", "avatar", fileRequestBody!!)
- } catch (e: Exception) {
- Logger.e("UpdateAccountUseCase") { e.message!! }
+ val data = MultiPartFormDataContent(formData {
+ if (avatarUri != null) {
+ try {
+ val fileType = MimeType.getMimeType(avatarUri, context.contentResolver) ?: "image/*"
+ val fileName = "filename=avatar"
+ val bytes = context.contentResolver.openInputStream(avatarUri)?.readBytes()
+ append("avatar", bytes!!, Headers.build {
+ append(HttpHeaders.ContentType, fileType)
+ append(HttpHeaders.ContentDisposition, fileName)
+ })
+ } catch (e: Exception) {
+ Log.e("UpdateAccountUseCase", e.message!!)
+ }
}
+ append("display_name", displayName)
+ append("note", note)
+ append("website", website)
+ append("locked", privateProfile.toString())
+ })
- }
-
- builder.addFormDataPart("display_name", displayName)
- builder.addFormDataPart("note", note)
- builder.addFormDataPart("website", website)
- builder.addFormDataPart("locked", privateProfile.toString())
-
- val requestBody: RequestBody = builder.build()
-
- return accountRepository.updateAccount(requestBody)
+ return accountRepository.updateAccount(data)
}
}
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/utils/NetworkCall.kt b/app/src/main/java/com/daniebeler/pfpixelix/utils/NetworkCall.kt
index 9a01ce85..da446804 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/utils/NetworkCall.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/utils/NetworkCall.kt
@@ -2,20 +2,23 @@ package com.daniebeler.pfpixelix.utils
import com.daniebeler.pfpixelix.common.Resource
import com.daniebeler.pfpixelix.data.remote.dto.DtoInterface
+import de.jensklingenberg.ktorfit.Call
+import de.jensklingenberg.ktorfit.Callback
+import io.ktor.client.statement.HttpResponse
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
-import retrofit2.Call
-import retrofit2.awaitResponse
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlin.coroutines.suspendCoroutine
class NetworkCall> {
fun makeCall(call: Call): Flow> = flow {
try {
emit(Resource.Loading())
- val response = call.awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()!!.toModel()
+ try {
+ val res = call.execute().toModel()
emit(Resource.Success(res!!))
- } else {
+ } catch (e: Exception) {
emit(Resource.Error("Unknown Error"))
}
} catch (exception: Exception) {
@@ -26,15 +29,38 @@ class NetworkCall> {
fun makeCallList(call: Call>): Flow>> = flow {
try {
emit(Resource.Loading())
- val response = call.awaitResponse()
- if (response.isSuccessful) {
- val res = response.body()?.map { it.toModel() } ?: emptyList()
+ try {
+ val res = call.execute().map { it.toModel() }
emit(Resource.Success(res))
- } else {
+ } catch (e: Exception) {
emit(Resource.Error("Unknown Error"))
}
} catch (exception: Exception) {
emit(Resource.Error(exception.message ?: "Unknown Error"))
}
}
+}
+
+internal suspend fun Call.execute() = suspendCoroutine { cont ->
+ onExecute(object : Callback {
+ override fun onResponse(call: T, response: HttpResponse) {
+ cont.resume(call)
+ }
+
+ override fun onError(exception: Throwable) {
+ cont.resumeWithException(exception)
+ }
+ })
+}
+
+internal suspend fun Call.executeWithResponse() = suspendCoroutine { cont ->
+ onExecute(object : Callback {
+ override fun onResponse(call: T, response: HttpResponse) {
+ cont.resume(response to call)
+ }
+
+ override fun onError(exception: Throwable) {
+ cont.resumeWithException(exception)
+ }
+ })
}
\ No newline at end of file
diff --git a/app/src/main/java/com/daniebeler/pfpixelix/widget/WidgetRepositoryProvider.kt b/app/src/main/java/com/daniebeler/pfpixelix/widget/WidgetRepositoryProvider.kt
index 5edff960..788fd794 100644
--- a/app/src/main/java/com/daniebeler/pfpixelix/widget/WidgetRepositoryProvider.kt
+++ b/app/src/main/java/com/daniebeler/pfpixelix/widget/WidgetRepositoryProvider.kt
@@ -2,23 +2,27 @@ package com.daniebeler.pfpixelix.widget
import HostSelectionInterceptor
import androidx.datastore.core.DataStore
+import co.touchlab.kermit.Logger
import com.daniebeler.pfpixelix.data.remote.PixelfedApi
+import com.daniebeler.pfpixelix.data.remote.createPixelfedApi
import com.daniebeler.pfpixelix.data.repository.WidgetRepositoryImpl
import com.daniebeler.pfpixelix.domain.model.AuthData
import com.daniebeler.pfpixelix.domain.model.LoginData
import com.daniebeler.pfpixelix.domain.repository.WidgetRepository
+import de.jensklingenberg.ktorfit.Ktorfit
+import de.jensklingenberg.ktorfit.converter.CallConverterFactory
+import io.ktor.client.HttpClient
+import io.ktor.client.plugins.HttpSend
+import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
+import io.ktor.client.plugins.logging.LogLevel
+import io.ktor.client.plugins.logging.Logging
+import io.ktor.client.plugins.plugin
+import io.ktor.serialization.kotlinx.json.json
import kotlinx.coroutines.flow.first
import kotlinx.serialization.json.Json
-import okhttp3.MediaType.Companion.toMediaType
-import okhttp3.OkHttpClient
-import okhttp3.logging.HttpLoggingInterceptor
-import retrofit2.Retrofit
-import retrofit2.converter.kotlinx.serialization.asConverterFactory
class WidgetRepositoryProvider(private val dataStore: DataStore) {
suspend operator fun invoke(): WidgetRepository? {
- val logging = HttpLoggingInterceptor()
- logging.setLevel(HttpLoggingInterceptor.Level.BODY)
val hostSelectionInterceptor = HostSelectionInterceptor()
val loginData: LoginData? = getAuthData()
if (loginData == null) {
@@ -33,19 +37,40 @@ class WidgetRepositoryProvider(private val dataStore: DataStore) {
hostSelectionInterceptor.setToken(
jwtToken
)
- val client = OkHttpClient.Builder().addInterceptor(hostSelectionInterceptor)
- .addInterceptor(logging).build()
+
val json = Json {
ignoreUnknownKeys = true
isLenient = true
}
- val retrofit: Retrofit = Retrofit.Builder().addConverterFactory(
- json.asConverterFactory("application/json; charset=UTF8".toMediaType())
- ).client(client).baseUrl("https://err.or/").build()
+ val client = HttpClient {
+ install(ContentNegotiation) { json(json) }
+ install(Logging) {
+ logger = object : io.ktor.client.plugins.logging.Logger {
+ override fun log(message: String) {
+ Logger.v("HttpClient") {
+ message.lines().joinToString { "\n\t\t$it" }
+ }
+ }
+ }
+ level = LogLevel.BODY
+ }
+ }.apply {
+ plugin(HttpSend).intercept { request ->
+ with(hostSelectionInterceptor) {
+ intercept(request)
+ }
+ }
+ }
+
+ val ktorfit = Ktorfit.Builder()
+ .converterFactories(CallConverterFactory())
+ .httpClient(client)
+ .baseUrl("https://err.or/")
+ .build()
- val service = retrofit.create(PixelfedApi::class.java)
+ val service: PixelfedApi = ktorfit.createPixelfedApi()
return WidgetRepositoryImpl(service)
}
diff --git a/build.gradle.kts b/build.gradle.kts
index b3c873b6..a0aadeb2 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -9,6 +9,7 @@ plugins {
alias(libs.plugins.androidApplication) apply false
alias(libs.plugins.jetbrainsKotlinAndroid) apply false
alias(libs.plugins.hilt) apply false
- id("com.google.devtools.ksp") version "2.0.0-1.0.22" apply false
+ id("com.google.devtools.ksp") version "2.0.21-1.0.27" apply false
alias(libs.plugins.compose.compiler) apply false
+ alias(libs.plugins.ktorfit) apply false
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index f20f4478..4fc7cb32 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -17,21 +17,21 @@ kotlinSerialization = "2.0.0"
kotlinxCollectionsImmutable = "0.3.5"
kotlinxSerializationJson = "1.5.1"
lifecycle = "2.8.6"
-loggingInterceptor = "4.11.0"
material = "1.12.0"
mat3 = "1.4.0-alpha07"
media3Exoplayer = "1.5.1"
media3ExoplayerDash = "1.5.1"
media3Ui = "1.5.1"
navigationCompose = "2.8.3"
-retrofit = "2.11.0"
volley = "1.2.1"
workRuntimeKtx = "2.9.1"
-kotlin = "2.0.0"
+kotlin = "2.0.21"
agp = "8.7.1"
ksoup = "0.2.0"
kermit = "2.0.4"
kotlinx-datetime = "0.6.1"
+ktorfit = "2.2.0"
+ktor = "3.0.3"
[libraries]
@@ -75,18 +75,26 @@ kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", v
kotlinx-collections-immutable = { module = "org.jetbrains.kotlinx:kotlinx-collections-immutable", version.ref = "kotlinxCollectionsImmutable" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
lifecycle-runtime-ktx = { module = "androidx.lifecycle:lifecycle-runtime-ktx", version.ref = "lifecycle" }
-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "loggingInterceptor" }
material = { module = "com.google.android.material:material", version.ref = "material" }
material3 = { module = "androidx.compose.material3:material3", version.ref = "mat3" }
-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
-retrofit-json = { module = "com.squareup.retrofit2:converter-kotlinx-serialization", version.ref = "retrofit" }
volley = { module = "com.android.volley:volley", version.ref = "volley" }
ksoup = {module = "com.fleeksoft.ksoup:ksoup", version.ref = "ksoup"}
kermit = {module = "co.touchlab:kermit", version.ref = "kermit"}
kotlinx-datetime = { module = "org.jetbrains.kotlinx:kotlinx-datetime", version.ref = "kotlinx-datetime" }
+ktorfit = { module = "de.jensklingenberg.ktorfit:ktorfit-lib", version.ref = "ktorfit" }
+ktorfit-call = { module = "de.jensklingenberg.ktorfit:ktorfit-converters-call", version.ref = "ktorfit" }
+ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
+ktor-client-content-negotiation = { module = "io.ktor:ktor-client-content-negotiation", version.ref = "ktor" }
+ktor-client-serialization = { module = "io.ktor:ktor-client-serialization", version.ref = "ktor" }
+ktor-serialization-json = { module = "io.ktor:ktor-serialization-kotlinx-json", version.ref = "ktor" }
+ktor-client-logging = { module = "io.ktor:ktor-client-logging", version.ref = "ktor" }
+ktor-client-darwin = { module = "io.ktor:ktor-client-darwin", version.ref = "ktor" }
+ktor-client-okhttp = { module = "io.ktor:ktor-client-okhttp", version.ref = "ktor" }
+
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }
jetbrainsKotlinAndroid = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+ktorfit = { id = "de.jensklingenberg.ktorfit", version.ref = "ktorfit" }