Formatting, license header

This commit is contained in:
Adolfo Santiago 2022-01-15 20:36:53 +01:00
parent 237241baa1
commit 6d8aa8ff47
No known key found for this signature in database
GPG key ID: 244D6F9A317B4A65
2 changed files with 429 additions and 319 deletions

View file

@ -1,21 +1,53 @@
/* Copyright 2017 Andrew Dawson /*
* Husky -- A Pleroma client for Android
* *
* This file is a part of Tusky. * Copyright (C) 2021 The Husky Developers
* Copyright (C) 2017 Alibek "a1batross" Omarov
* *
* This program is free software; you can redistribute it and/or modify it under the terms of the * This program is free software: you can redistribute it and/or modify
* GNU General Public License as published by the Free Software Foundation; either version 3 of the * it under the terms of the GNU General Public License as published by
* License, or (at your option) any later version. * the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* *
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even * This program is distributed in the hope that it will be useful,
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * but WITHOUT ANY WARRANTY; without even the implied warranty of
* Public License for more details. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* *
* You should have received a copy of the GNU General Public License along with Tusky; if not, * You should have received a copy of the GNU General Public License
* see <http://www.gnu.org/licenses>. */ * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.network package com.keylesspalace.tusky.network
import com.keylesspalace.tusky.entity.* import com.keylesspalace.tusky.entity.AccessToken
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Announcement
import com.keylesspalace.tusky.entity.AppCredentials
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Chat
import com.keylesspalace.tusky.entity.ChatMessage
import com.keylesspalace.tusky.entity.Conversation
import com.keylesspalace.tusky.entity.DeletedStatus
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.entity.EmojiReaction
import com.keylesspalace.tusky.entity.Filter
import com.keylesspalace.tusky.entity.IdentityProof
import com.keylesspalace.tusky.entity.Instance
import com.keylesspalace.tusky.entity.Marker
import com.keylesspalace.tusky.entity.MastoList
import com.keylesspalace.tusky.entity.NewChatMessage
import com.keylesspalace.tusky.entity.NewStatus
import com.keylesspalace.tusky.entity.NodeInfo
import com.keylesspalace.tusky.entity.NodeInfoLinks
import com.keylesspalace.tusky.entity.Notification
import com.keylesspalace.tusky.entity.Poll
import com.keylesspalace.tusky.entity.Relationship
import com.keylesspalace.tusky.entity.ScheduledStatus
import com.keylesspalace.tusky.entity.SearchResult
import com.keylesspalace.tusky.entity.Status
import com.keylesspalace.tusky.entity.StatusContext
import com.keylesspalace.tusky.entity.StickerPack
import io.reactivex.Completable import io.reactivex.Completable
import io.reactivex.Single import io.reactivex.Single
import okhttp3.MultipartBody import okhttp3.MultipartBody
@ -23,8 +55,21 @@ import okhttp3.RequestBody
import okhttp3.ResponseBody import okhttp3.ResponseBody
import retrofit2.Call import retrofit2.Call
import retrofit2.Response import retrofit2.Response
import retrofit2.http.* import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.Field import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.HTTP
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.PATCH
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Url
/** /**
* for documentation of the Mastodon REST API see https://docs.joinmastodon.org/api/ * for documentation of the Mastodon REST API see https://docs.joinmastodon.org/api/
@ -53,66 +98,66 @@ interface MastodonApi {
@GET("api/v1/timelines/home?with_muted=true") @GET("api/v1/timelines/home?with_muted=true")
fun homeTimeline( fun homeTimeline(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Call<List<Status>> ): Call<List<Status>>
@GET("api/v1/timelines/home?with_muted=true") @GET("api/v1/timelines/home?with_muted=true")
fun homeTimelineSingle( fun homeTimelineSingle(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Single<List<Status>> ): Single<List<Status>>
@GET("api/v1/timelines/public?with_muted=true") @GET("api/v1/timelines/public?with_muted=true")
fun publicTimeline( fun publicTimeline(
@Query("local") local: Boolean?, @Query("local") local: Boolean?,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Call<List<Status>> ): Call<List<Status>>
@GET("api/v1/timelines/tag/{hashtag}?with_muted=true") @GET("api/v1/timelines/tag/{hashtag}?with_muted=true")
fun hashtagTimeline( fun hashtagTimeline(
@Path("hashtag") hashtag: String, @Path("hashtag") hashtag: String,
@Query("any[]") any: List<String>?, @Query("any[]") any: List<String>?,
@Query("local") local: Boolean?, @Query("local") local: Boolean?,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Call<List<Status>> ): Call<List<Status>>
@GET("api/v1/timelines/list/{listId}?with_muted=true") @GET("api/v1/timelines/list/{listId}?with_muted=true")
fun listTimeline( fun listTimeline(
@Path("listId") listId: String, @Path("listId") listId: String,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Call<List<Status>> ): Call<List<Status>>
@GET("api/v1/notifications") @GET("api/v1/notifications")
fun notifications( fun notifications(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int?, @Query("limit") limit: Int?,
@Query("exclude_types[]") excludes: Set<Notification.Type>?, @Query("exclude_types[]") excludes: Set<Notification.Type>?,
@Query("with_muted") withMuted: Boolean? @Query("with_muted") withMuted: Boolean?
): Call<List<Notification>> ): Call<List<Notification>>
@GET("api/v1/markers") @GET("api/v1/markers")
fun markersWithAuth( fun markersWithAuth(
@Header("Authorization") auth: String, @Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String, @Header(DOMAIN_HEADER) domain: String,
@Query("timeline[]") timelines: List<String> @Query("timeline[]") timelines: List<String>
): Single<Map<String, Marker>> ): Single<Map<String, Marker>>
@GET("api/v1/notifications?with_muted=true") @GET("api/v1/notifications?with_muted=true")
fun notificationsWithAuth( fun notificationsWithAuth(
@Header("Authorization") auth: String, @Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String, @Header(DOMAIN_HEADER) domain: String,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("include_types[]") includeTypes: List<String>? @Query("include_types[]") includeTypes: List<String>?
): Single<List<Notification>> ): Single<List<Notification>>
@POST("api/v1/notifications/clear") @POST("api/v1/notifications/clear")
@ -120,122 +165,122 @@ interface MastodonApi {
@GET("api/v1/notifications/{id}") @GET("api/v1/notifications/{id}")
fun notification( fun notification(
@Path("id") notificationId: String @Path("id") notificationId: String
): Call<Notification> ): Call<Notification>
@Multipart @Multipart
@POST("api/v1/media") @POST("api/v1/media")
fun uploadMedia( fun uploadMedia(
@Part file: MultipartBody.Part, @Part file: MultipartBody.Part,
@Part description: MultipartBody.Part? = null @Part description: MultipartBody.Part? = null
): Single<Attachment> ): Single<Attachment>
@FormUrlEncoded @FormUrlEncoded
@PUT("api/v1/media/{mediaId}") @PUT("api/v1/media/{mediaId}")
fun updateMedia( fun updateMedia(
@Path("mediaId") mediaId: String, @Path("mediaId") mediaId: String,
@Field("description") description: String @Field("description") description: String
): Single<Attachment> ): Single<Attachment>
@POST("api/v1/statuses") @POST("api/v1/statuses")
fun createStatus( fun createStatus(
@Header("Authorization") auth: String, @Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String, @Header(DOMAIN_HEADER) domain: String,
@Header("Idempotency-Key") idempotencyKey: String, @Header("Idempotency-Key") idempotencyKey: String,
@Body status: NewStatus @Body status: NewStatus
): Call<Status> ): Call<Status>
@GET("api/v1/statuses/{id}") @GET("api/v1/statuses/{id}")
fun status( fun status(
@Path("id") statusId: String @Path("id") statusId: String
): Call<Status> ): Call<Status>
@GET("api/v1/statuses/{id}") @GET("api/v1/statuses/{id}")
fun statusSingle( fun statusSingle(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@GET("api/v1/statuses/{id}/context") @GET("api/v1/statuses/{id}/context")
fun statusContext( fun statusContext(
@Path("id") statusId: String @Path("id") statusId: String
): Call<StatusContext> ): Call<StatusContext>
@GET("api/v1/statuses/{id}/reblogged_by") @GET("api/v1/statuses/{id}/reblogged_by")
fun statusRebloggedBy( fun statusRebloggedBy(
@Path("id") statusId: String, @Path("id") statusId: String,
@Query("max_id") maxId: String? @Query("max_id") maxId: String?
): Single<Response<List<Account>>> ): Single<Response<List<Account>>>
@GET("api/v1/statuses/{id}/favourited_by") @GET("api/v1/statuses/{id}/favourited_by")
fun statusFavouritedBy( fun statusFavouritedBy(
@Path("id") statusId: String, @Path("id") statusId: String,
@Query("max_id") maxId: String? @Query("max_id") maxId: String?
): Single<Response<List<Account>>> ): Single<Response<List<Account>>>
@DELETE("api/v1/statuses/{id}") @DELETE("api/v1/statuses/{id}")
fun deleteStatus( fun deleteStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<DeletedStatus> ): Single<DeletedStatus>
@POST("api/v1/statuses/{id}/reblog") @POST("api/v1/statuses/{id}/reblog")
fun reblogStatus( fun reblogStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/unreblog") @POST("api/v1/statuses/{id}/unreblog")
fun unreblogStatus( fun unreblogStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/favourite") @POST("api/v1/statuses/{id}/favourite")
fun favouriteStatus( fun favouriteStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/unfavourite") @POST("api/v1/statuses/{id}/unfavourite")
fun unfavouriteStatus( fun unfavouriteStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/bookmark") @POST("api/v1/statuses/{id}/bookmark")
fun bookmarkStatus( fun bookmarkStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/unbookmark") @POST("api/v1/statuses/{id}/unbookmark")
fun unbookmarkStatus( fun unbookmarkStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/pin") @POST("api/v1/statuses/{id}/pin")
fun pinStatus( fun pinStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/unpin") @POST("api/v1/statuses/{id}/unpin")
fun unpinStatus( fun unpinStatus(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/mute") @POST("api/v1/statuses/{id}/mute")
fun muteConversation( fun muteConversation(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@POST("api/v1/statuses/{id}/unmute") @POST("api/v1/statuses/{id}/unmute")
fun unmuteConversation( fun unmuteConversation(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@GET("api/v1/scheduled_statuses") @GET("api/v1/scheduled_statuses")
fun scheduledStatuses( fun scheduledStatuses(
@Query("limit") limit: Int? = null, @Query("limit") limit: Int? = null,
@Query("max_id") maxId: String? = null @Query("max_id") maxId: String? = null
): Single<List<ScheduledStatus>> ): Single<List<ScheduledStatus>>
@DELETE("api/v1/scheduled_statuses/{id}") @DELETE("api/v1/scheduled_statuses/{id}")
fun deleteScheduledStatus( fun deleteScheduledStatus(
@Path("id") scheduledStatusId: String @Path("id") scheduledStatusId: String
): Single<ResponseBody> ): Single<ResponseBody>
@GET("api/v1/accounts/verify_credentials") @GET("api/v1/accounts/verify_credentials")
@ -244,39 +289,39 @@ interface MastodonApi {
@FormUrlEncoded @FormUrlEncoded
@PATCH("api/v1/accounts/update_credentials") @PATCH("api/v1/accounts/update_credentials")
fun accountUpdateSource( fun accountUpdateSource(
@Field("source[privacy]") privacy: String?, @Field("source[privacy]") privacy: String?,
@Field("source[sensitive]") sensitive: Boolean? @Field("source[sensitive]") sensitive: Boolean?
): Call<Account> ): Call<Account>
@Multipart @Multipart
@PATCH("api/v1/accounts/update_credentials") @PATCH("api/v1/accounts/update_credentials")
fun accountUpdateCredentials( fun accountUpdateCredentials(
@Part(value = "display_name") displayName: RequestBody?, @Part(value = "display_name") displayName: RequestBody?,
@Part(value = "note") note: RequestBody?, @Part(value = "note") note: RequestBody?,
@Part(value = "locked") locked: RequestBody?, @Part(value = "locked") locked: RequestBody?,
@Part avatar: MultipartBody.Part?, @Part avatar: MultipartBody.Part?,
@Part header: MultipartBody.Part?, @Part header: MultipartBody.Part?,
@Part(value = "fields_attributes[0][name]") fieldName0: RequestBody?, @Part(value = "fields_attributes[0][name]") fieldName0: RequestBody?,
@Part(value = "fields_attributes[0][value]") fieldValue0: RequestBody?, @Part(value = "fields_attributes[0][value]") fieldValue0: RequestBody?,
@Part(value = "fields_attributes[1][name]") fieldName1: RequestBody?, @Part(value = "fields_attributes[1][name]") fieldName1: RequestBody?,
@Part(value = "fields_attributes[1][value]") fieldValue1: RequestBody?, @Part(value = "fields_attributes[1][value]") fieldValue1: RequestBody?,
@Part(value = "fields_attributes[2][name]") fieldName2: RequestBody?, @Part(value = "fields_attributes[2][name]") fieldName2: RequestBody?,
@Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?, @Part(value = "fields_attributes[2][value]") fieldValue2: RequestBody?,
@Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?, @Part(value = "fields_attributes[3][name]") fieldName3: RequestBody?,
@Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody? @Part(value = "fields_attributes[3][value]") fieldValue3: RequestBody?
): Call<Account> ): Call<Account>
@GET("api/v1/accounts/search") @GET("api/v1/accounts/search")
fun searchAccounts( fun searchAccounts(
@Query("q") query: String, @Query("q") query: String,
@Query("resolve") resolve: Boolean? = null, @Query("resolve") resolve: Boolean? = null,
@Query("limit") limit: Int? = null, @Query("limit") limit: Int? = null,
@Query("following") following: Boolean? = null @Query("following") following: Boolean? = null
): Single<List<Account>> ): Single<List<Account>>
@GET("api/v1/accounts/{id}") @GET("api/v1/accounts/{id}")
fun account( fun account(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Account> ): Single<Account>
/** /**
@ -290,71 +335,71 @@ interface MastodonApi {
*/ */
@GET("api/v1/accounts/{id}/statuses?with_muted=true") @GET("api/v1/accounts/{id}/statuses?with_muted=true")
fun accountStatuses( fun accountStatuses(
@Path("id") accountId: String, @Path("id") accountId: String,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int?, @Query("limit") limit: Int?,
@Query("exclude_replies") excludeReplies: Boolean?, @Query("exclude_replies") excludeReplies: Boolean?,
@Query("only_media") onlyMedia: Boolean?, @Query("only_media") onlyMedia: Boolean?,
@Query("pinned") pinned: Boolean? @Query("pinned") pinned: Boolean?
): Call<List<Status>> ): Call<List<Status>>
@GET("api/v1/accounts/{id}/followers") @GET("api/v1/accounts/{id}/followers")
fun accountFollowers( fun accountFollowers(
@Path("id") accountId: String, @Path("id") accountId: String,
@Query("max_id") maxId: String? @Query("max_id") maxId: String?
): Single<Response<List<Account>>> ): Single<Response<List<Account>>>
@GET("api/v1/accounts/{id}/following") @GET("api/v1/accounts/{id}/following")
fun accountFollowing( fun accountFollowing(
@Path("id") accountId: String, @Path("id") accountId: String,
@Query("max_id") maxId: String? @Query("max_id") maxId: String?
): Single<Response<List<Account>>> ): Single<Response<List<Account>>>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/accounts/{id}/follow") @POST("api/v1/accounts/{id}/follow")
fun followAccount( fun followAccount(
@Path("id") accountId: String, @Path("id") accountId: String,
@Field("reblogs") showReblogs: Boolean? = null, @Field("reblogs") showReblogs: Boolean? = null,
@Field("notify") notify: Boolean? = null @Field("notify") notify: Boolean? = null
): Single<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/unfollow") @POST("api/v1/accounts/{id}/unfollow")
fun unfollowAccount( fun unfollowAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/block") @POST("api/v1/accounts/{id}/block")
fun blockAccount( fun blockAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/unblock") @POST("api/v1/accounts/{id}/unblock")
fun unblockAccount( fun unblockAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Relationship> ): Single<Relationship>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/accounts/{id}/mute") @POST("api/v1/accounts/{id}/mute")
fun muteAccount( fun muteAccount(
@Path("id") accountId: String, @Path("id") accountId: String,
@Field("notifications") notifications: Boolean? = null, @Field("notifications") notifications: Boolean? = null,
@Field("duration") duration: Int? = null @Field("duration") duration: Int? = null
): Single<Relationship> ): Single<Relationship>
@POST("api/v1/accounts/{id}/unmute") @POST("api/v1/accounts/{id}/unmute")
fun unmuteAccount( fun unmuteAccount(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Relationship> ): Single<Relationship>
@GET("api/v1/accounts/relationships") @GET("api/v1/accounts/relationships")
fun relationships( fun relationships(
@Query("id[]") accountIds: List<String> @Query("id[]") accountIds: List<String>
): Single<List<Relationship>> ): Single<List<Relationship>>
@GET("api/v1/accounts/{id}/identity_proofs") @GET("api/v1/accounts/{id}/identity_proofs")
fun identityProofs( fun identityProofs(
@Path("id") accountId: String @Path("id") accountId: String
): Single<List<IdentityProof>> ): Single<List<IdentityProof>>
@POST("api/v1/pleroma/accounts/{id}/subscribe") @POST("api/v1/pleroma/accounts/{id}/subscribe")
@ -369,25 +414,25 @@ interface MastodonApi {
@GET("api/v1/blocks") @GET("api/v1/blocks")
fun blocks( fun blocks(
@Query("max_id") maxId: String? @Query("max_id") maxId: String?
): Single<Response<List<Account>>> ): Single<Response<List<Account>>>
@GET("api/v1/mutes") @GET("api/v1/mutes")
fun mutes( fun mutes(
@Query("max_id") maxId: String? @Query("max_id") maxId: String?
): Single<Response<List<Account>>> ): Single<Response<List<Account>>>
@GET("api/v1/domain_blocks") @GET("api/v1/domain_blocks")
fun domainBlocks( fun domainBlocks(
@Query("max_id") maxId: String? = null, @Query("max_id") maxId: String? = null,
@Query("since_id") sinceId: String? = null, @Query("since_id") sinceId: String? = null,
@Query("limit") limit: Int? = null @Query("limit") limit: Int? = null
): Single<Response<List<String>>> ): Single<Response<List<String>>>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/domain_blocks") @POST("api/v1/domain_blocks")
fun blockDomain( fun blockDomain(
@Field("domain") domain: String @Field("domain") domain: String
): Call<Any> ): Call<Any>
@FormUrlEncoded @FormUrlEncoded
@ -397,107 +442,107 @@ interface MastodonApi {
@GET("api/v1/favourites?with_muted=true") @GET("api/v1/favourites?with_muted=true")
fun favourites( fun favourites(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Call<List<Status>> ): Call<List<Status>>
@GET("api/v1/bookmarks?with_muted=true") @GET("api/v1/bookmarks?with_muted=true")
fun bookmarks( fun bookmarks(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Call<List<Status>> ): Call<List<Status>>
@GET("api/v1/follow_requests") @GET("api/v1/follow_requests")
fun followRequests( fun followRequests(
@Query("max_id") maxId: String? @Query("max_id") maxId: String?
): Single<Response<List<Account>>> ): Single<Response<List<Account>>>
@POST("api/v1/follow_requests/{id}/authorize") @POST("api/v1/follow_requests/{id}/authorize")
fun authorizeFollowRequest( fun authorizeFollowRequest(
@Path("id") accountId: String @Path("id") accountId: String
): Call<Relationship> ): Call<Relationship>
@POST("api/v1/follow_requests/{id}/reject") @POST("api/v1/follow_requests/{id}/reject")
fun rejectFollowRequest( fun rejectFollowRequest(
@Path("id") accountId: String @Path("id") accountId: String
): Call<Relationship> ): Call<Relationship>
@POST("api/v1/follow_requests/{id}/authorize") @POST("api/v1/follow_requests/{id}/authorize")
fun authorizeFollowRequestObservable( fun authorizeFollowRequestObservable(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Relationship> ): Single<Relationship>
@POST("api/v1/follow_requests/{id}/reject") @POST("api/v1/follow_requests/{id}/reject")
fun rejectFollowRequestObservable( fun rejectFollowRequestObservable(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Relationship> ): Single<Relationship>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/apps") @POST("api/v1/apps")
fun authenticateApp( fun authenticateApp(
@Header(DOMAIN_HEADER) domain: String, @Header(DOMAIN_HEADER) domain: String,
@Field("client_name") clientName: String, @Field("client_name") clientName: String,
@Field("redirect_uris") redirectUris: String, @Field("redirect_uris") redirectUris: String,
@Field("scopes") scopes: String, @Field("scopes") scopes: String,
@Field("website") website: String @Field("website") website: String
): Call<AppCredentials> ): Call<AppCredentials>
@FormUrlEncoded @FormUrlEncoded
@POST("oauth/token") @POST("oauth/token")
fun fetchOAuthToken( fun fetchOAuthToken(
@Header(DOMAIN_HEADER) domain: String, @Header(DOMAIN_HEADER) domain: String,
@Field("client_id") clientId: String, @Field("client_id") clientId: String,
@Field("client_secret") clientSecret: String, @Field("client_secret") clientSecret: String,
@Field("redirect_uri") redirectUri: String, @Field("redirect_uri") redirectUri: String,
@Field("code") code: String, @Field("code") code: String,
@Field("grant_type") grantType: String @Field("grant_type") grantType: String
): Call<AccessToken> ): Call<AccessToken>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/lists") @POST("api/v1/lists")
fun createList( fun createList(
@Field("title") title: String @Field("title") title: String
): Single<MastoList> ): Single<MastoList>
@FormUrlEncoded @FormUrlEncoded
@PUT("api/v1/lists/{listId}") @PUT("api/v1/lists/{listId}")
fun updateList( fun updateList(
@Path("listId") listId: String, @Path("listId") listId: String,
@Field("title") title: String @Field("title") title: String
): Single<MastoList> ): Single<MastoList>
@DELETE("api/v1/lists/{listId}") @DELETE("api/v1/lists/{listId}")
fun deleteList( fun deleteList(
@Path("listId") listId: String @Path("listId") listId: String
): Completable ): Completable
@GET("api/v1/lists/{listId}/accounts") @GET("api/v1/lists/{listId}/accounts")
fun getAccountsInList( fun getAccountsInList(
@Path("listId") listId: String, @Path("listId") listId: String,
@Query("limit") limit: Int @Query("limit") limit: Int
): Single<List<Account>> ): Single<List<Account>>
@FormUrlEncoded @FormUrlEncoded
// @DELETE doesn't support fields // @DELETE doesn't support fields
@HTTP(method = "DELETE", path = "api/v1/lists/{listId}/accounts", hasBody = true) @HTTP(method = "DELETE", path = "api/v1/lists/{listId}/accounts", hasBody = true)
fun deleteAccountFromList( fun deleteAccountFromList(
@Path("listId") listId: String, @Path("listId") listId: String,
@Field("account_ids[]") accountIds: List<String> @Field("account_ids[]") accountIds: List<String>
): Completable ): Completable
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/lists/{listId}/accounts") @POST("api/v1/lists/{listId}/accounts")
fun addCountToList( fun addCountToList(
@Path("listId") listId: String, @Path("listId") listId: String,
@Field("account_ids[]") accountIds: List<String> @Field("account_ids[]") accountIds: List<String>
): Completable ): Completable
@GET("/api/v1/conversations") @GET("/api/v1/conversations")
fun getConversations( fun getConversations(
@Query("max_id") maxId: String? = null, @Query("max_id") maxId: String? = null,
@Query("limit") limit: Int @Query("limit") limit: Int
): Call<List<Conversation>> ): Call<List<Conversation>>
data class PostFilter( data class PostFilter(
@ -513,82 +558,82 @@ interface MastodonApi {
@PUT("api/v1/filters/{id}") @PUT("api/v1/filters/{id}")
fun updateFilter( fun updateFilter(
@Path("id") id: String, @Path("id") id: String,
@Body body: PostFilter @Body body: PostFilter
): Call<Filter> ): Call<Filter>
@DELETE("api/v1/filters/{id}") @DELETE("api/v1/filters/{id}")
fun deleteFilter( fun deleteFilter(
@Path("id") id: String @Path("id") id: String
): Call<ResponseBody> ): Call<ResponseBody>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/polls/{id}/votes") @POST("api/v1/polls/{id}/votes")
fun voteInPoll( fun voteInPoll(
@Path("id") id: String, @Path("id") id: String,
@Field("choices[]") choices: List<Int> @Field("choices[]") choices: List<Int>
): Single<Poll> ): Single<Poll>
@GET("api/v1/announcements") @GET("api/v1/announcements")
fun listAnnouncements( fun listAnnouncements(
@Query("with_dismissed") withDismissed: Boolean = true @Query("with_dismissed") withDismissed: Boolean = true
): Single<List<Announcement>> ): Single<List<Announcement>>
@POST("api/v1/announcements/{id}/dismiss") @POST("api/v1/announcements/{id}/dismiss")
fun dismissAnnouncement( fun dismissAnnouncement(
@Path("id") announcementId: String @Path("id") announcementId: String
): Single<ResponseBody> ): Single<ResponseBody>
@PUT("api/v1/announcements/{id}/reactions/{name}") @PUT("api/v1/announcements/{id}/reactions/{name}")
fun addAnnouncementReaction( fun addAnnouncementReaction(
@Path("id") announcementId: String, @Path("id") announcementId: String,
@Path("name") name: String @Path("name") name: String
): Single<ResponseBody> ): Single<ResponseBody>
@DELETE("api/v1/announcements/{id}/reactions/{name}") @DELETE("api/v1/announcements/{id}/reactions/{name}")
fun removeAnnouncementReaction( fun removeAnnouncementReaction(
@Path("id") announcementId: String, @Path("id") announcementId: String,
@Path("name") name: String @Path("name") name: String
): Single<ResponseBody> ): Single<ResponseBody>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/reports") @POST("api/v1/reports")
fun reportObservable( fun reportObservable(
@Field("account_id") accountId: String, @Field("account_id") accountId: String,
@Field("status_ids[]") statusIds: List<String>, @Field("status_ids[]") statusIds: List<String>,
@Field("comment") comment: String, @Field("comment") comment: String,
@Field("forward") isNotifyRemote: Boolean? @Field("forward") isNotifyRemote: Boolean?
): Single<ResponseBody> ): Single<ResponseBody>
@GET("api/v1/accounts/{id}/statuses?with_muted=true") @GET("api/v1/accounts/{id}/statuses?with_muted=true")
fun accountStatusesObservable( fun accountStatusesObservable(
@Path("id") accountId: String, @Path("id") accountId: String,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("limit") limit: Int?, @Query("limit") limit: Int?,
@Query("exclude_reblogs") excludeReblogs: Boolean? @Query("exclude_reblogs") excludeReblogs: Boolean?
): Single<List<Status>> ): Single<List<Status>>
@GET("api/v1/statuses/{id}") @GET("api/v1/statuses/{id}")
fun statusObservable( fun statusObservable(
@Path("id") statusId: String @Path("id") statusId: String
): Single<Status> ): Single<Status>
@GET("api/v2/search") @GET("api/v2/search")
fun searchObservable( fun searchObservable(
@Query("q") query: String?, @Query("q") query: String?,
@Query("type") type: String? = null, @Query("type") type: String? = null,
@Query("resolve") resolve: Boolean? = null, @Query("resolve") resolve: Boolean? = null,
@Query("limit") limit: Int? = null, @Query("limit") limit: Int? = null,
@Query("offset") offset: Int? = null, @Query("offset") offset: Int? = null,
@Query("following") following: Boolean? = null @Query("following") following: Boolean? = null
): Single<SearchResult> ): Single<SearchResult>
@GET(".well-known/nodeinfo") @GET(".well-known/nodeinfo")
fun getNodeinfoLinks() : Single<NodeInfoLinks> fun getNodeinfoLinks(): Single<NodeInfoLinks>
@GET @GET
fun getNodeinfo(@Url url: String) : Single<NodeInfo> fun getNodeinfo(@Url url: String): Single<NodeInfo>
@PUT("api/v1/pleroma/statuses/{id}/reactions/{emoji}") @PUT("api/v1/pleroma/statuses/{id}/reactions/{emoji}")
fun reactWithEmoji( fun reactWithEmoji(
@ -611,7 +656,7 @@ interface MastodonApi {
// NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS // NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS NOT AN API CALLS
// just for testing and because puniko asked me // just for testing and because puniko asked me
@GET("static/stickers.json") @GET("static/stickers.json")
fun getStickers() : Single<Map<String, String>> fun getStickers(): Single<Map<String, String>>
@GET @GET
fun getStickerPack( fun getStickerPack(
@ -621,64 +666,64 @@ interface MastodonApi {
@POST("api/v1/pleroma/chats/{id}/messages/{message_id}/read") @POST("api/v1/pleroma/chats/{id}/messages/{message_id}/read")
fun markChatMessageAsRead( fun markChatMessageAsRead(
@Path("id") chatId: String, @Path("id") chatId: String,
@Path("message_id") messageId: String @Path("message_id") messageId: String
): Single<ChatMessage> ): Single<ChatMessage>
@DELETE("api/v1/pleroma/chats/{id}/messages/{message_id}") @DELETE("api/v1/pleroma/chats/{id}/messages/{message_id}")
fun deleteChatMessage( fun deleteChatMessage(
@Path("id") chatId: String, @Path("id") chatId: String,
@Path("message_id") messageId: String @Path("message_id") messageId: String
): Single<ChatMessage> ): Single<ChatMessage>
@GET("api/v2/pleroma/chats") @GET("api/v2/pleroma/chats")
fun getChats( fun getChats(
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("min_id") minId: String?, @Query("min_id") minId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("offset") offset: Int?, @Query("offset") offset: Int?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Single<List<Chat>> ): Single<List<Chat>>
@GET("api/v1/pleroma/chats/{id}/messages") @GET("api/v1/pleroma/chats/{id}/messages")
fun getChatMessages( fun getChatMessages(
@Path("id") chatId: String, @Path("id") chatId: String,
@Query("max_id") maxId: String?, @Query("max_id") maxId: String?,
@Query("min_id") minId: String?, @Query("min_id") minId: String?,
@Query("since_id") sinceId: String?, @Query("since_id") sinceId: String?,
@Query("offset") offset: Int?, @Query("offset") offset: Int?,
@Query("limit") limit: Int? @Query("limit") limit: Int?
): Single<List<ChatMessage>> ): Single<List<ChatMessage>>
@POST("api/v1/pleroma/chats/{id}/messages") @POST("api/v1/pleroma/chats/{id}/messages")
fun createChatMessage( fun createChatMessage(
@Header("Authorization") auth: String, @Header("Authorization") auth: String,
@Header(DOMAIN_HEADER) domain: String, @Header(DOMAIN_HEADER) domain: String,
@Path("id") chatId: String, @Path("id") chatId: String,
@Body chatMessage: NewChatMessage @Body chatMessage: NewChatMessage
): Call<ChatMessage> ): Call<ChatMessage>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/pleroma/chats/{id}/read") @POST("api/v1/pleroma/chats/{id}/read")
fun markChatAsRead( fun markChatAsRead(
@Path("id") chatId: String, @Path("id") chatId: String,
@Field("last_read_id") lastReadId: String? = null @Field("last_read_id") lastReadId: String? = null
): Single<Chat> ): Single<Chat>
@POST("api/v1/pleroma/chats/by-account-id/{id}") @POST("api/v1/pleroma/chats/by-account-id/{id}")
fun createChat( fun createChat(
@Path("id") accountId: String @Path("id") accountId: String
): Single<Chat> ): Single<Chat>
@GET("api/v1/pleroma/chats/{id}") @GET("api/v1/pleroma/chats/{id}")
fun getChat( fun getChat(
@Path("id") chatId: String @Path("id") chatId: String
): Single<Chat> ): Single<Chat>
@FormUrlEncoded @FormUrlEncoded
@POST("api/v1/accounts/{id}/note") @POST("api/v1/accounts/{id}/note")
fun updateAccountNote( fun updateAccountNote(
@Path("id") accountId: String, @Path("id") accountId: String,
@Field("comment") note: String @Field("comment") note: String
): Single<Relationship> ): Single<Relationship>
} }

View file

@ -1,3 +1,23 @@
/*
* Husky -- A Pleroma client for Android
*
* Copyright (C) 2021 The Husky Developers
* Copyright (C) 2021 Andrew Dawson
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.keylesspalace.tusky.repository package com.keylesspalace.tusky.repository
import android.text.SpannedString import android.text.SpannedString
@ -5,8 +25,16 @@ import androidx.core.text.parseAsHtml
import androidx.core.text.toHtml import androidx.core.text.toHtml
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
import com.keylesspalace.tusky.db.* import com.keylesspalace.tusky.db.AccountManager
import com.keylesspalace.tusky.entity.* import com.keylesspalace.tusky.db.ChatEntity
import com.keylesspalace.tusky.db.ChatEntityWithAccount
import com.keylesspalace.tusky.db.ChatMessageEntity
import com.keylesspalace.tusky.db.ChatsDao
import com.keylesspalace.tusky.entity.Account
import com.keylesspalace.tusky.entity.Attachment
import com.keylesspalace.tusky.entity.Chat
import com.keylesspalace.tusky.entity.ChatMessage
import com.keylesspalace.tusky.entity.Emoji
import com.keylesspalace.tusky.network.MastodonApi import com.keylesspalace.tusky.network.MastodonApi
import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK import com.keylesspalace.tusky.repository.TimelineRequestMode.DISK
import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK import com.keylesspalace.tusky.repository.TimelineRequestMode.NETWORK
@ -17,39 +45,56 @@ import com.keylesspalace.tusky.util.trimTrailingWhitespace
import io.reactivex.Single import io.reactivex.Single
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
import java.io.IOException import java.io.IOException
import java.util.* import java.util.Date
typealias ChatStatus = Either<Placeholder, Chat> typealias ChatStatus = Either<Placeholder, Chat>
typealias ChatMesssageOrPlaceholder = Either<Placeholder, ChatMessage> typealias ChatMesssageOrPlaceholder = Either<Placeholder, ChatMessage>
interface ChatRepository { interface ChatRepository {
fun getChats(maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, fun getChats(
requestMode: TimelineRequestMode): Single<out List<ChatStatus>> maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int,
requestMode: TimelineRequestMode
): Single<out List<ChatStatus>>
fun getChatMessages(chatId: String, maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, requestMode: TimelineRequestMode) : Single<out List<ChatMesssageOrPlaceholder>> fun getChatMessages(
chatId: String,
maxId: String?,
sinceId: String?,
sincedIdMinusOne: String?,
limit: Int,
requestMode: TimelineRequestMode
): Single<out List<ChatMesssageOrPlaceholder>>
} }
class ChatRepositoryImpl( class ChatRepositoryImpl(
private val chatsDao: ChatsDao, private val chatsDao: ChatsDao,
private val mastodonApi: MastodonApi, private val mastodonApi: MastodonApi,
private val accountManager: AccountManager, private val accountManager: AccountManager,
private val gson: Gson private val gson: Gson
) : ChatRepository { ) : ChatRepository {
override fun getChats(maxId: String?, sinceId: String?, sincedIdMinusOne: String?, override fun getChats(
limit: Int, requestMode: TimelineRequestMode maxId: String?, sinceId: String?, sincedIdMinusOne: String?,
limit: Int, requestMode: TimelineRequestMode
): Single<out List<ChatStatus>> { ): Single<out List<ChatStatus>> {
val acc = accountManager.activeAccount ?: throw IllegalStateException() val acc = accountManager.activeAccount ?: throw IllegalStateException()
val accountId = acc.id val accountId = acc.id
return if (requestMode == DISK) { return if(requestMode == DISK) {
this.getChatsFromDb(accountId, maxId, sinceId, limit) this.getChatsFromDb(accountId, maxId, sinceId, limit)
} else { } else {
getChatsFromNetwork(maxId, sinceId, sincedIdMinusOne, limit, accountId, requestMode) getChatsFromNetwork(maxId, sinceId, sincedIdMinusOne, limit, accountId, requestMode)
} }
} }
override fun getChatMessages(chatId: String, maxId: String?, sinceId: String?, sincedIdMinusOne: String?, limit: Int, requestMode: TimelineRequestMode) : Single<out List<ChatMesssageOrPlaceholder>> { override fun getChatMessages(
chatId: String,
maxId: String?,
sinceId: String?,
sincedIdMinusOne: String?,
limit: Int,
requestMode: TimelineRequestMode
): Single<out List<ChatMesssageOrPlaceholder>> {
val acc = accountManager.activeAccount ?: throw IllegalStateException() val acc = accountManager.activeAccount ?: throw IllegalStateException()
val accountId = acc.id val accountId = acc.id
@ -62,9 +107,10 @@ class ChatRepositoryImpl(
return getChatMessagesFromNetwork(chatId, maxId, null, null, limit, accountId, requestMode) return getChatMessagesFromNetwork(chatId, maxId, null, null, limit, accountId, requestMode)
} }
private fun getChatsFromNetwork(maxId: String?, sinceId: String?, private fun getChatsFromNetwork(
sinceIdMinusOne: String?, limit: Int, maxId: String?, sinceId: String?,
accountId: Long, requestMode: TimelineRequestMode sinceIdMinusOne: String?, limit: Int,
accountId: Long, requestMode: TimelineRequestMode
): Single<out List<ChatStatus>> { ): Single<out List<ChatStatus>> {
return mastodonApi.getChats(null, null, sinceIdMinusOne, 0, limit + 1) return mastodonApi.getChats(null, null, sinceIdMinusOne, 0, limit + 1)
.map { chats -> .map { chats ->
@ -74,17 +120,18 @@ class ChatRepositoryImpl(
this.addFromDbIfNeeded(accountId, chats, maxId, sinceId, limit, requestMode) this.addFromDbIfNeeded(accountId, chats, maxId, sinceId, limit, requestMode)
} }
.onErrorResumeNext { error -> .onErrorResumeNext { error ->
if (error is IOException && requestMode != NETWORK) { if(error is IOException && requestMode != NETWORK) {
this.getChatsFromDb(accountId, maxId, sinceId, limit) this.getChatsFromDb(accountId, maxId, sinceId, limit)
} else { } else {
Single.error(error) Single.error(error)
}
} }
}
} }
private fun getChatMessagesFromNetwork(chatId: String, maxId: String?, sinceId: String?, private fun getChatMessagesFromNetwork(
sinceIdMinusOne: String?, limit: Int, chatId: String, maxId: String?, sinceId: String?,
accountId: Long, requestMode: TimelineRequestMode sinceIdMinusOne: String?, limit: Int,
accountId: Long, requestMode: TimelineRequestMode
): Single<out List<ChatMesssageOrPlaceholder>> { ): Single<out List<ChatMesssageOrPlaceholder>> {
return mastodonApi.getChatMessages(chatId, maxId, null, sinceIdMinusOne, 0, limit + 1).map { return mastodonApi.getChatMessages(chatId, maxId, null, sinceIdMinusOne, 0, limit + 1).map {
it.mapTo(mutableListOf(), ChatMessage::lift) it.mapTo(mutableListOf(), ChatMessage::lift)
@ -92,62 +139,66 @@ class ChatRepositoryImpl(
} }
private fun addFromDbIfNeeded(accountId: Long, chats: List<ChatStatus>, private fun addFromDbIfNeeded(
maxId: String?, sinceId: String?, limit: Int, accountId: Long, chats: List<ChatStatus>,
requestMode: TimelineRequestMode maxId: String?, sinceId: String?, limit: Int,
requestMode: TimelineRequestMode
): Single<List<ChatStatus>> { ): Single<List<ChatStatus>> {
return if (requestMode != NETWORK && chats.size < 2) { return if(requestMode != NETWORK && chats.size < 2) {
val newMaxID = if (chats.isEmpty()) { val newMaxID = if(chats.isEmpty()) {
maxId maxId
} else { } else {
chats.last { it.isRight() }.asRight().id chats.last { it.isRight() }.asRight().id
} }
this.getChatsFromDb(accountId, newMaxID, sinceId, limit) this.getChatsFromDb(accountId, newMaxID, sinceId, limit)
.map { fromDb -> .map { fromDb ->
// If it's just placeholders and less than limit (so we exhausted both // If it's just placeholders and less than limit (so we exhausted both
// db and server at this point) // db and server at this point)
if (fromDb.size < limit && fromDb.all { !it.isRight() }) { if(fromDb.size < limit && fromDb.all { !it.isRight() }) {
chats chats
} else { } else {
chats + fromDb chats + fromDb
}
} }
}
} else { } else {
Single.just(chats) Single.just(chats)
} }
} }
private fun getChatsFromDb(accountId: Long, maxId: String?, sinceId: String?, private fun getChatsFromDb(
limit: Int): Single<out List<ChatStatus>> { accountId: Long, maxId: String?, sinceId: String?,
limit: Int
): Single<out List<ChatStatus>> {
return chatsDao.getChatsForAccount(accountId, maxId, sinceId, limit) return chatsDao.getChatsForAccount(accountId, maxId, sinceId, limit)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.map { chats -> .map { chats ->
chats.map { it.toChat(gson) } chats.map { it.toChat(gson) }
} }
} }
private fun saveChatsToDb(accountId: Long, chats: List<Chat>, private fun saveChatsToDb(
maxId: String?, sinceId: String? accountId: Long, chats: List<Chat>,
maxId: String?, sinceId: String?
): List<ChatStatus> { ): List<ChatStatus> {
var placeholderToInsert: Placeholder? = null var placeholderToInsert: Placeholder? = null
// Look for overlap // Look for overlap
val resultChats = if (chats.isNotEmpty() && sinceId != null) { val resultChats = if(chats.isNotEmpty() && sinceId != null) {
val indexOfSince = chats.indexOfLast { it.id == sinceId } val indexOfSince = chats.indexOfLast { it.id == sinceId }
if (indexOfSince == -1) { if(indexOfSince == -1) {
// We didn't find the status which must be there. Add a placeholder // We didn't find the status which must be there. Add a placeholder
placeholderToInsert = Placeholder(sinceId.inc()) placeholderToInsert = Placeholder(sinceId.inc())
chats.mapTo(mutableListOf(), Chat::lift) chats.mapTo(mutableListOf(), Chat::lift)
.apply { .apply {
add(Either.Left(placeholderToInsert)) add(Either.Left(placeholderToInsert))
} }
} else { } else {
// There was an overlap. Remove all overlapped statuses. No need for a placeholder. // There was an overlap. Remove all overlapped statuses. No need for a placeholder.
chats.mapTo(mutableListOf(), Chat::lift) chats.mapTo(mutableListOf(), Chat::lift)
.apply { .apply {
subList(indexOfSince, size).clear() subList(indexOfSince, size).clear()
} }
} }
} else { } else {
// Just a normal case. // Just a normal case.
@ -160,13 +211,13 @@ class ChatRepositoryImpl(
chatsDao.deleteRange(accountId, chats.last().id, chats.first().id) chatsDao.deleteRange(accountId, chats.last().id, chats.first().id)
} }
for (chat in chats) { for(chat in chats) {
val pair = chat.toEntity(accountId, gson) val pair = chat.toEntity(accountId, gson)
chatsDao.insertInTransaction( chatsDao.insertInTransaction(
pair.first, pair.first,
pair.second, pair.second,
chat.account.toEntity(accountId, gson) chat.account.toEntity(accountId, gson)
) )
} }
@ -176,21 +227,24 @@ class ChatRepositoryImpl(
// If we're loading in the bottom insert placeholder after every load // If we're loading in the bottom insert placeholder after every load
// (for requests on next launches) but not return it. // (for requests on next launches) but not return it.
if (sinceId == null && chats.isNotEmpty()) { if(sinceId == null && chats.isNotEmpty()) {
chatsDao.insertChatIfNotThere( chatsDao.insertChatIfNotThere(
Placeholder(chats.last().id.dec()).toChatEntity(accountId)) Placeholder(chats.last().id.dec()).toChatEntity(accountId)
)
} }
// There may be placeholders which we thought could be from our TL but they are not // There may be placeholders which we thought could be from our TL but they are not
if (chats.size > 2) { if(chats.size > 2) {
chatsDao.removeAllPlaceholdersBetween(accountId, chats.first().id, chatsDao.removeAllPlaceholdersBetween(
chats.last().id) accountId, chats.first().id,
} else if (placeholderToInsert == null && maxId != null && sinceId != null) { chats.last().id
)
} else if(placeholderToInsert == null && maxId != null && sinceId != null) {
chatsDao.removeAllPlaceholdersBetween(accountId, maxId, sinceId) chatsDao.removeAllPlaceholdersBetween(accountId, maxId, sinceId)
} }
} }
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.subscribe() .subscribe()
return resultChats return resultChats
} }
@ -200,62 +254,73 @@ private val emojisListTypeToken = object : TypeToken<List<Emoji>>() {}
fun Placeholder.toChatEntity(timelineUserId: Long): ChatEntity { fun Placeholder.toChatEntity(timelineUserId: Long): ChatEntity {
return ChatEntity( return ChatEntity(
localId = timelineUserId, localId = timelineUserId,
chatId = this.id, chatId = this.id,
accountId = "", accountId = "",
unread = 0L, unread = 0L,
updatedAt = 0L, updatedAt = 0L,
lastMessageId = null lastMessageId = null
) )
} }
fun ChatMessage.toEntity(timelineUserId: Long, gson: Gson) : ChatMessageEntity { fun ChatMessage.toEntity(timelineUserId: Long, gson: Gson): ChatMessageEntity {
return ChatMessageEntity( return ChatMessageEntity(
localId = timelineUserId, localId = timelineUserId,
messageId = this.id, messageId = this.id,
content = this.content?.toHtml(), content = this.content?.toHtml(),
chatId = this.chatId, chatId = this.chatId,
accountId = this.accountId, accountId = this.accountId,
createdAt = this.createdAt.time, createdAt = this.createdAt.time,
attachment = this.attachment?.let { gson.toJson(it, Attachment::class.java) }, attachment = this.attachment?.let { gson.toJson(it, Attachment::class.java) },
emojis = gson.toJson(this.emojis) emojis = gson.toJson(this.emojis)
) )
} }
fun Chat.toEntity(timelineUserId: Long, gson: Gson): Pair<ChatEntity, ChatMessageEntity?> { fun Chat.toEntity(timelineUserId: Long, gson: Gson): Pair<ChatEntity, ChatMessageEntity?> {
return Pair(ChatEntity( return Pair(
ChatEntity(
localId = timelineUserId, localId = timelineUserId,
chatId = this.id, chatId = this.id,
accountId = this.account.id, accountId = this.account.id,
unread = this.unread, unread = this.unread,
updatedAt = this.updatedAt.time, updatedAt = this.updatedAt.time,
lastMessageId = this.lastMessage?.id lastMessageId = this.lastMessage?.id
), this.lastMessage?.toEntity(timelineUserId, gson)) ), this.lastMessage?.toEntity(timelineUserId, gson)
}
fun ChatMessageEntity.toChatMessage(gson: Gson) : ChatMessage {
return ChatMessage(
id = this.messageId,
content = this.content?.let { it.parseAsHtml().trimTrailingWhitespace() },
chatId = this.chatId,
accountId = this.accountId,
createdAt = Date(this.createdAt),
attachment = this.attachment?.let { gson.fromJson(it, Attachment::class.java) },
emojis = gson.fromJson(this.emojis, object : TypeToken<List<Emoji>>() {}.type ),
card = null /* don't care about card */
) )
} }
fun ChatEntityWithAccount.toChat(gson: Gson) : ChatStatus { fun ChatMessageEntity.toChatMessage(gson: Gson): ChatMessage {
return ChatMessage(
id = this.messageId,
content = this.content?.let { it.parseAsHtml().trimTrailingWhitespace() },
chatId = this.chatId,
accountId = this.accountId,
createdAt = Date(this.createdAt),
attachment = this.attachment?.let { gson.fromJson(it, Attachment::class.java) },
emojis = gson.fromJson(this.emojis, object : TypeToken<List<Emoji>>() {}.type),
card = null /* don't care about card */
)
}
fun ChatEntityWithAccount.toChat(gson: Gson): ChatStatus {
if(account == null || chat.accountId.isEmpty() || chat.updatedAt == 0L) if(account == null || chat.accountId.isEmpty() || chat.updatedAt == 0L)
return Either.Left(Placeholder(chat.chatId)) return Either.Left(Placeholder(chat.chatId))
return Chat( return Chat(
account = this.account?.toAccount(gson) ?: Account("", "", "", "", SpannedString(""), "", "", "" ), account = this.account?.toAccount(gson) ?: Account(
id = this.chat.chatId, "",
unread = this.chat.unread, "",
updatedAt = Date(this.chat.updatedAt), "",
lastMessage = this.lastMessage?.toChatMessage(gson) "",
SpannedString(""),
"",
"",
""
),
id = this.chat.chatId,
unread = this.chat.unread,
updatedAt = Date(this.chat.updatedAt),
lastMessage = this.lastMessage?.toChatMessage(gson)
).lift() ).lift()
} }