Merge pull request #106 from VernissageApp/merge/2.0.4

Merge version 2.0.4 into main
This commit is contained in:
Marcin Czachurski 2023-12-10 12:02:06 +01:00 committed by GitHub
commit 8d546096f8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 2892 additions and 558 deletions

View File

@ -13,7 +13,7 @@ extension Client {
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40,
includeReblogs: Bool? = nil) async throws -> [Status] {
includeReblogs: Bool? = nil) async throws -> Linkable<[Status]> {
return try await pixelfedClient.getHomeTimeline(maxId: maxId, sinceId: sinceId, minId: minId, limit: limit, includeReblogs: includeReblogs)
}
@ -22,7 +22,7 @@ extension Client {
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
limit: Int = 40) async throws -> Linkable<[Status]> {
return try await pixelfedClient.getPublicTimeline(local: local,
remote: remote,
onlyMedia: true,
@ -38,7 +38,7 @@ extension Client {
maxId: String? = nil,
sinceId: String? = nil,
minId: String? = nil,
limit: Int = 40) async throws -> [Status] {
limit: Int = 40) async throws -> Linkable<[Status]> {
return try await pixelfedClient.getTagTimeline(tag: tag,
local: local,
remote: remote,

View File

@ -64,7 +64,7 @@ class AccountDataHandler {
}
}
func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, statuses: [Status]? = nil, applicationState: ApplicationState, modelContext: ModelContext) throws {
func update(lastSeenStatusId: String?, lastLoadedStatusId: String?, statuses: Linkable<[Status]>? = nil, applicationState: ApplicationState, modelContext: ModelContext) throws {
guard let accountId = applicationState.account?.id else {
return
}

View File

@ -44,13 +44,13 @@ class ViewedStatusHandler {
let statusId = status.id
var fetchDescriptor = FetchDescriptor<ViewedStatus>(
// Here we are finding status which is other then checked status AND orginal status has been visible OR same reblogged by different user status has been visible.
predicate: #Predicate { $0.pixelfedAccount?.id == accountId && $0.id != statusId && ($0.id == reblogId || $0.reblogId == reblogId) }
// Here we are finding status which is older then checked status AND orginal status has been visible OR same reblogged by different user status has been visible.
predicate: #Predicate { $0.pixelfedAccount?.id == accountId && $0.id < statusId && ($0.id == reblogId || $0.reblogId == reblogId) }
)
fetchDescriptor.fetchLimit = 1
fetchDescriptor.includePendingChanges = true
guard let first = try modelContext.fetch(fetchDescriptor).first else {
guard try modelContext.fetch(fetchDescriptor).first != nil else {
return false
}

File diff suppressed because it is too large Load Diff

View File

@ -1,371 +0,0 @@
// MARK: Common strings.
"global.title.contentWarning" = "Contenido sensible";
"global.title.seePost" = "Ver publicación";
"global.title.refresh" = "Actualizar";
"global.title.momentsAgo" = "hace uno momento";
"global.title.success" = "Éxito";
"global.title.photoSaved" = "La foto se ha guardado.";
"global.title.ok" = "Aceptar";
"global.title.showMore" = "Mostrar más";
"global.title.showLess" = "Mostrar menos";
"global.title.close" = "Cerrar";
"global.error.refreshingCredentialsTitle" = "Error al actualizar las credenciales.";
"global.error.refreshingCredentialsSubtitle" = "Por favor, vuelve a iniciar sesión en Pixelfed.";
// MARK: Global errors.
"global.error.unexpected" = "Error inesperado.";
"global.error.statusesNotRetrieved" = "No se pudieron obtener los estados.";
"global.error.errorDuringDownloadStatuses" = "Error durante la descarga de estados del servidor.";
"global.error.errorDuringDownloadHashtag" = "Error durante la descarga de etiquetas del servidor.";
"global.error.hashtagNotExists" = "La etiqueta no existe.";
"global.error.errorDuringImageDownload" = "No se puede descargar la imagen.";
"global.error.canceledImageDownload" = "La descarga de la imagen ha sido cancelada.";
"global.error.errorDuringDataLoad" = "Error al cargar los datos.";
"global.error.errorDuringUserRead" = "No se puede recuperar la cuenta de usuario.";
"global.error.badUrlServer" = "URL incorrecta del servidor.";
"global.error.accessTokenNotFound" = "Token de acceso no encontrado.";
"global.error.errorDuringDownloadStatus" = "Error durante la descarga del estado del servidor.";
"global.error.errorDuringPurchaseVerification" = "Fallo en la verificación de la compra.";
// MARK: Main view (main navigation bar).
"mainview.tab.homeTimeline" = "Inicio";
"mainview.tab.localTimeline" = "Local";
"mainview.tab.federatedTimeline" = "Federado";
"mainview.tab.trendingPhotos" = "Fotos";
"mainview.tab.trendingTags" = "Etiquetas";
"mainview.tab.trendingAccounts" = "Cuentas";
"mainview.tab.userProfile" = "Perfil";
"mainview.tab.notifications" = "Notificaciones";
"mainview.tab.search" = "Buscar";
"mainview.tab.trending" = "Tendencias";
// MARK: Main view (leading navigation bar).
"mainview.menu.settings" = "Ajustes";
// MARK: Main view (error notifications).
"mainview.error.switchAccounts" = "No se pueden cambiar las cuentas.";
// MARK: Home timeline.
"home.title.allCaughtUp" = "Estás al día";
"home.title.noPhotos" = "Desafortunadamente, no hay fotos aquí.";
// MARK: Statuses timeline (local/federated/favourite/bookmarks etc.).
"statuses.navigationBar.localTimeline" = "Local";
"statuses.navigationBar.federatedTimeline" = "Federado";
"statuses.navigationBar.favourites" = "Favoritos";
"statuses.navigationBar.bookmarks" = "Marcadores";
"statuses.title.noPhotos" = "Desafortunadamente, no hay fotos aquí.";
"statuses.title.tagFollowed" = "Estás siguiendo la etiqueta.";
"statuses.title.tagUnfollowed" = "Se ha dejado de seguir la etiqueta.";
"statuses.error.loadingStatusesFailed" = "Error al cargar los estados.";
"statuses.error.tagFollowFailed" = "Error al seguir la etiqueta.";
"statuses.error.tagUnfollowFailed" = "Error al dejar de seguir la etiqueta.";
// Mark: Search view.
"search.navigationBar.title" = "Buscar";
"search.title.placeholder" = "Buscar...";
"search.title.usersWith" = "Usuarios con %@";
"search.title.goToUser" = "Ir al usuario %@";
"search.title.hashtagWith" = "Etiquetas con %@";
"search.title.goToHashtag" = "Ir a la etiqueta %@";
// Mark: Trending statuses.
"trendingStatuses.navigationBar.title" = "Fotos";
"trendingStatuses.title.daily" = "Diario";
"trendingStatuses.title.monthly" = "Mensual";
"trendingStatuses.title.yearly" = "Anual";
"trendingStatuses.error.loadingStatusesFailed" = "Error al cargar los estados.";
"trendingStatuses.title.noPhotos" = "Desafortunadamente, no hay fotos aquí.";
// Mark: Trending tags.
"trendingTags.navigationBar.title" = "Etiquetas";
"trendingTags.title.noTags" = "Desafortunadamente, no hay etiquetas aquí.";
"trendingTags.title.amountOfPosts" = "%d publicaciones";
"trendingTags.error.loadingTagsFailed" = "Error al cargar las etiquetas.";
// Mark: Trending accounts.
"trendingAccounts.navigationBar.title" = "Cuentas";
"trendingAccounts.title.noAccounts" = "Desafortunadamente, aquí no hay nadie.";
"trendingAccounts.error.loadingAccountsFailed" = "Error al cargar las cuentas.";
// Mark: User profile view.
"userProfile.title.openInBrowser" = "Abrir en el navegador";
"userProfile.title.share" = "Compartir";
"userProfile.title.unmute" = "Quitar silencio";
"userProfile.title.mute" = "Silenciar";
"userProfile.title.unblock" = "Desbloquear";
"userProfile.title.block" = "Bloquear";
"userProfile.title.favourites" = "Favoritos";
"userProfile.title.bookmarks" = "Marcadores";
"userProfile.title.posts" = "Publicaciones";
"userProfile.title.followers" = "Seguidores";
"userProfile.title.following" = "Siguiendo";
"userProfile.title.joined" = "Unido a %@";
"userProfile.title.unfollow" = "Dejar de seguir";
"userProfile.title.follow" = "Seguir";
"userProfile.title.instance" = "Información de la instancia";
"userProfile.title.blocks" = "Cuentas bloqueadas";
"userProfile.title.mutes" = "Cuentas silenciadas";
"userProfile.title.muted" = "Cuenta silenciada";
"userProfile.title.unmuted" = "Cuenta sin silencio";
"userProfile.title.blocked" = "Cuenta bloqueada";
"userProfile.title.unblocked" = "Cuenta desbloqueada";
"userProfile.title.report" = "Informar";
"userProfile.title.followsYou" = "Te sigue";
"userProfile.title.requestFollow" = "Solicitar seguir";
"userProfile.title.cancelRequestFollow" = "Cancelar solicitud";
"userProfile.title.followRequests" = "Solicitudes de seguimiento";
"userProfile.title.privateProfileTitle" = "Este perfil es privado.";
"userProfile.title.privateProfileSubtitle" = "Solo los seguidores aprobados pueden ver las fotos.";
"userProfile.error.notExists" = "La cuenta no existe.";
"userProfile.error.loadingAccountFailed" = "Error al descargar la cuenta del servidor.";
"userProfile.error.muting" = "Error al silenciar/quitar silencio.";
"userProfile.error.block" = "Error al bloquear/desbloquear.";
"userProfile.error.relationship" = "Error en la acción de relación.";
"userProfile.title.edit" = "Editar";
"userProfile.title.muted" = "Silenciado";
"userProfile.title.blocked" = "Bloqueado";
// Mark: Notifications view.
"notifications.navigationBar.title" = "Notificaciones";
"notifications.title.noNotifications" = "Desafortunadamente, aquí no hay nada.";
"notifications.title.followedYou" = "te ha seguido";
"notifications.title.mentionedYou" = "te ha mencionado";
"notifications.title.boosted" = "ha compartido";
"notifications.title.favourited" = "le ha gustado";
"notifications.title.postedStatus" = "ha publicado un estado";
"notifications.title.followRequest" = "solicitud de seguimiento";
"notifications.title.poll" = "encuesta";
"notifications.title.updatedStatus" = "estado actualizado";
"notifications.title.signedUp" = "se ha registrado";
"notifications.title.newReport" = "nuevo informe";
"notifications.error.loadingNotificationsFailed" = "Error al cargar las notificaciones.";
// Mark: Compose view.
"compose.navigationBar.title" = "Componer";
"compose.title.everyone" = "Todos";
"compose.title.unlisted" = "No listado";
"compose.title.followers" = "Seguidores";
"compose.title.attachPhotoFull" = "Adjunta una foto y escribe lo que piensas";
"compose.title.attachPhotoMini" = "Escribe lo que piensas";
"compose.title.publish" = "Publicar";
"compose.title.cancel" = "Cancelar";
"compose.title.writeContentWarning" = "Escribir aviso de contenido";
"compose.title.commentsWillBeDisabled" = "Los comentarios estarán desactivados";
"compose.title.statusPublished" = "Estado publicado";
"compose.title.tryToUpload" = "Intentar subir";
"compose.title.delete" = "Eliminar";
"compose.title.edit" = "Editar";
"compose.title.photos" = "Fototeca";
"compose.title.camera" = "Hacer una foto";
"compose.title.files" = "Explorar archivos";
"compose.title.missingAltTexts" = "Textos ALT faltantes";
"compose.title.missingAltTextsWarning" = "No todas las imágenes han sido descritas para personas con discapacidad visual. ¿Te gustaría enviar las fotos de todos modos?";
"compose.error.loadingPhotosFailed" = "No se puede recuperar la imagen de la biblioteca.";
"compose.error.postingPhotoFailed" = "Error al publicar la foto.";
"compose.error.postingStatusFailed" = "Error al publicar el estado.";
// Mark: Photo editor view.
"photoEdit.navigationBar.title" = "Detalles de la foto";
"photoEdit.title.photo" = "Foto";
"photoEdit.title.accessibility" = "Accesibilidad";
"photoEdit.title.accessibilityDescription" = "Descripción para personas con discapacidad visual";
"photoEdit.title.save" = "Guardar";
"photoEdit.title.cancel" = "Cancelar";
"photoEdit.error.updatePhotoFailed" = "Error al actualizar la foto.";
// Mark: Place selector view.
"placeSelector.navigationBar.title" = "Lugares";
"placeSelector.title.search" = "Buscar...";
"placeSelector.title.buttonSearch" = "Buscar";
"placeSelector.title.cancel" = "Cancelar";
"placeSelector.error.loadingPlacesFailed" = "Error al cargar las notificaciones.";
// Mark: Settings view.
"settings.navigationBar.title" = "Ajustes";
"settings.title.close" = "Cerrar";
"settings.title.version" = "Versión";
"settings.title.accounts" = "Cuentas";
"settings.title.newAccount" = "Nueva cuenta";
"settings.title.accent" = "Acento";
"settings.title.theme" = "Tema";
"settings.title.system" = "Sistema";
"settings.title.light" = "Claro";
"settings.title.dark" = "Oscuro";
"settings.title.avatar" = "Avatar";
"settings.title.circle" = "Círculo";
"settings.title.rounderRectangle" = "Rectángulo redondeado";
"settings.title.other" = "Otros";
"settings.title.thirdParty" = "Terceros";
"settings.title.reportBug" = "Informar de un error";
"settings.title.githubIssues" = "Problemas en Github";
"settings.title.follow" = "Sígueme";
"settings.title.support" = "Soporte";
"settings.title.thankYouTitle" = "Gracias 💕";
"settings.title.thankYouMessage" = "Gracias por tu compra. Tanto las compras grandes como las pequeñas nos ayudan a mantener nuestro sueño de proporcionar productos de la mejor calidad a nuestros clientes. Esperamos que estés disfrutando de Vernissage.";
"settings.title.thankYouClose" = "Cerrar";
"settings.title.haptics" = "Respuesta háptica";
"settings.title.hapticsTabSelection" = "Selección de pestaña";
"settings.title.hapticsButtonPress" = "Pulsación de botón";
"settings.title.hapticsListRefresh" = "Actualizar lista";
"settings.title.hapticsAnimationFinished" = "Animación finalizada";
"settings.title.mediaSettings" = "Ajustes multimedia";
"settings.title.alwaysShowSensitiveTitle" = "Mostrar siempre NSFW";
"settings.title.alwaysShowSensitiveDescription" = "Mostrar siempre todos los medios NSFW (sensibles) sin advertencias";
"settings.title.alwaysShowAltTitle" = "Mostrar texto alternativo";
"settings.title.alwaysShowAltDescription" = "Mostrar el texto alternativo si está presente en la pantalla de detalles del estado";
"settings.title.general" = "General";
"settings.title.applicationIcon" = "Icono de la aplicación";
"settings.title.followVernissage" = "Sígueme en Vernissage";
"settings.title.mastodonAccount" = "Cuenta de Mastodon";
"settings.title.pixelfedAccount" = "Cuenta de Pixelfed";
"settings.title.openPage" = "Abrir";
"settings.title.privacyPolicy" = "Política de privacidad";
"settings.title.terms" = "Términos y condiciones";
"settings.title.sourceCode" = "Código fuente";
"settings.title.rate" = "Puntúa Vernissage";
"settings.title.socials" = "Redes sociales";
"settings.title.menuPosition" = "Posición del menú";
"settings.title.topMenu" = "Barra de navegación";
"settings.title.bottomRightMenu" = "Abajo a la derecha";
"settings.title.bottomLeftMenu" = "Abajo a la izquierda";
"settings.title.showAvatars" = "Mostrar avatares";
"settings.title.showAvatarsOnTimeline" = "Los avatares se mostrarán en las cronologías";
"settings.title.showFavourite" = "Mostrar favoritos";
"settings.title.showFavouriteOnTimeline" = "Los favoritos se mostrarán en las cronologías";
"settings.title.showAltText" = "Mostrar icono ALT";
"settings.title.showAltTextOnTimeline" = "El icono ALT se mostrará en las cronologíasS";
"settings.title.warnAboutMissingAltTitle" = "Advertir sobre el texto ALT faltante";
"settings.title.warnAboutMissingAltDescription" = "Se mostrará una advertencia sobre los textos ALT faltantes antes de publicar una nueva entrada.";
// Mark: Signin view.
"signin.navigationBar.title" = "Iniciar sesión en Pixelfed";
"signin.title.serverAddress" = "Dirección del servidor";
"signin.title.signIn" = "Iniciar sesión";
"signin.title.enterServerAddress" = "Introducir la dirección del servidor";
"signin.title.howToJoinLink" = "Cómo unirse a Pixelfed";
"signin.title.chooseServer" = "O elige un servidor Pixelfed";
"signin.title.amountOfUsers" = "%d usuarios";
"signin.title.amountOStatuses" = "%d estados";
"signin.error.communicationFailed" = "Error de comunicación con el servidor.";
// Mark: Status view.
"status.navigationBar.title" = "Detalles";
"status.title.uploaded" = "Subido";
"status.title.via" = "a través de %@";
"status.title.reboostedBy" = "Compartido por";
"status.title.favouritedBy" = "Le ha gustado a";
"status.title.openInBrowser" = "Abrir en el navegador";
"status.title.shareStatus" = "Compartir estado";
"status.title.yourStatus" = "Tu estado";
"status.title.delete" = "Eliminar";
"status.title.reboosted" = "Compartido";
"status.title.unreboosted" = "No compartido";
"status.title.favourited" = "Le ha gustado";
"status.title.unfavourited" = "No le ha gustado";
"status.title.bookmarked" = "Marcado";
"status.title.unbookmarked" = "No marcado";
"status.title.statusDeleted" = "Estado eliminado";
"status.title.reboost" = "Compartir";
"status.title.unreboost" = "No compartir";
"status.title.favourite" = "Me gusta";
"status.title.unfavourite" = "No me gusta";
"status.title.bookmark" = "Marcar";
"status.title.unbookmark" = "Desmarcar";
"status.title.comment" = "Comentario";
"status.title.report" = "Informar";
"status.title.saveImage" = "Guardar imagen";
"status.title.showMediaDescription" = "Mostrar descripción de medios";
"status.title.mediaDescription" = "Descripción de medios";
"status.title.shareImage" = "Compartir imagen";
"status.title.altText" = "ALT";
"status.error.loadingStatusFailed" = "Error al cargar el estado.";
"status.error.notFound" = "El estado ya no existe.";
"status.error.loadingCommentsFailed" = "No se pueden cargar los comentarios.";
"status.error.reboostFailed" = "Error al compartir.";
"status.error.favouriteFailed" = "Error al dar me gusta.";
"status.error.bookmarkFailed" = "Error al marcar.";
"status.error.deleteFailed" = "Error al eliminar.";
// Mark: Accounts view.
"accounts.navigationBar.followers" = "Seguidores";
"accounts.navigationBar.following" = "Siguiendo";
"accounts.navigationBar.favouritedBy" = "Le ha gustado a";
"accounts.navigationBar.reboostedBy" = "Compartido por";
"accounts.navigationBar.blocked" = "Cuentas bloqueadas";
"accounts.navigationBar.mutes" = "Cuentas silenciadas";
"accounts.title.noAccounts" = "Desafortunadamente, aquí no hay nadie.";
"accounts.error.loadingAccountsFailed" = "Error al cargar las cuentas.";
// Mark: Third party view.
"thirdParty.navigationBar.title" = "Terceros";
// Mark: Widget view.
"widget.title.photoDescription" = "Widget con fotos de Pixelfed.";
"widget.title.qrCodeDescription" = "Widget con el código QR de tu perfil de Pixelfed.";
// Mark: In-app purchases.
"purchase.donut.title" = "Rosquilla";
"purchase.donut.description" = "Dame una rosquilla.";
"purchase.coffee.title" = "Café";
"purchase.coffee.description" = "Dame un café.";
"purchase.cake.title" = "Café y pastel";
"purchase.cake.description" = "Dame un café y un pastel.";
// Mark: Edit profile.
"editProfile.navigationBar.title" = "Editar perfil";
"editProfile.title.displayName" = "Nombre para mostrar";
"editProfile.title.bio" = "Biografía";
"editProfile.title.website" = "Sitio web";
"editProfile.title.save" = "Guardar";
"editProfile.title.accountSaved" = "Perfil actualizado.";
"editProfile.title.photoInfo" = "La foto cambiada se mostrará en la aplicación y en el sitio web con un pequeño retraso.";
"editProfile.title.privateAccount" = "Cuenta privada";
"editProfile.title.privateAccountInfo" = "Cuando tu cuenta es privada, sólo las personas a las que apruebas pueden ver tus fotos y vídeos en Pixelfed. Tus seguidores existentes no se verán afectados.";
"editProfile.error.saveAccountFailed" = "Error al guardar el perfil.";
"editProfile.error.loadingAvatarFailed" = "Error al cargar el avatar.";
"editProfile.error.noProfileData" = "No se pueden mostrar los datos del perfil.";
"editProfile.error.loadingAccountFailed" = "Error al cargar los datos de la cuenta desde el servidor.";
// Mark: Instance information.
"instance.navigationBar.title" = "Instancia";
"instance.title.instanceInfo" = "Información de la instancia";
"instance.title.name" = "Nombre";
"instance.title.address" = "Dirección";
"instance.title.email" = "Correo electrónico";
"instance.title.version" = "Versión";
"instance.title.users" = "Usuarios";
"instance.title.posts" = "Publicaciones";
"instance.title.domains" = "Dominios";
"instance.title.registrations" = "Registros";
"instance.title.approvalRequired" = "Se requiere aprobación";
"instance.title.rules" = "Reglas de la instancia";
"instance.title.contact" = "Contacto";
"instance.title.pixelfedAccount" = "Cuenta de Pixelfed";
"instance.error.noInstanceData" = "No se pueden mostrar los datos de la instancia.";
"instance.error.loadingDataFailed" = "Error al cargar los datos de la instancia desde el servidor.";
// Mark: Report screen.
"report.navigationBar.title" = "Informe";
"report.title.close" = "Cerrar";
"report.title.send" = "Enviar";
"report.title.userReported" = "El usuario ha sido denunciado";
"report.title.postReported" = "La publicación ha sido denunciada";
"report.title.reportType" = "Tipo de abuso";
"report.title.spam" = "No deseado (spam)";
"report.title.sensitive" = "Desnudos o actividad sexual";
"report.title.abusive" = "Discurso o símbolos de odio";
"report.title.underage" = "Cuenta de menor de edad";
"report.title.violence" = "Violencia u organizaciones peligrosas";
"report.title.copyright" = "Infracción de derechos de autor";
"report.title.impersonation" = "Suplantación de identidad";
"report.title.scam" = "Acoso u hostigamiento";
"report.title.terrorism" = "Terrorismo";
"report.error.notReported" = "Error al enviar el informe.";
// Mark: Following requests.
"followingRequests.navigationBar.title" = "Solicitudes de seguimiento";
"followingRequests.title.approve" = "Aprobar";
"followingRequests.title.reject" = "Rechazar";
"followingRequests.error.approve" = "Error al aprobar la solicitud.";
"followingRequests.error.reject" = "Error al rechazar la solicitud.";

View File

@ -8,7 +8,7 @@ import Foundation
import RegexBuilder
/// Link returned in header for paging feature/
public struct Link {
public struct Link: Codable {
/// Raw value of header link.
public let rawLink: String

View File

@ -7,7 +7,7 @@
import Foundation
/// Some of endpoint returns JSON data and additional information in header, like link for paging functionality.
public struct Linkable<T> where T: Codable {
public struct Linkable<T> : Codable where T: Codable {
/// Data retunred in HTTP reponse body (mostly JSON data/entities).
public let data: T
@ -20,3 +20,29 @@ public struct Linkable<T> where T: Codable {
self.link = link
}
}
public extension Linkable<[Status]> {
func getMinId() -> String? {
if let link = self.link {
return link.minId
}
if let firstItemId = self.data.first?.id {
return firstItemId
}
return nil
}
func getMaxId() -> String? {
if let link = self.link {
return link.maxId
}
if let lastItemId = self.data.last?.id {
return lastItemId
}
return nil
}
}

View File

@ -4,6 +4,12 @@
"global.error.notSuccessResponse" : {
"comment" : "It's error returned from remote server. Request URL: '(response.url?.string ?? \"unknown\")'.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Serverantwort: %@."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -39,6 +45,12 @@
"global.error.unknownError" : {
"comment" : "Response doesn't contains any information about request status.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unbekannter Fehler."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -74,6 +86,12 @@
"report.error.duplicate" : {
"comment" : "The report has already been sent.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Der Bericht wurde bereits gesendet."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -109,6 +127,12 @@
"report.error.invalidObject" : {
"comment" : "Invalid object type.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ungültiges Objekt."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -144,6 +168,12 @@
"report.error.invalidObjectId" : {
"comment" : "Incorrect object Id.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fehlerhafte ID."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -179,6 +209,12 @@
"report.error.invalidParameters" : {
"comment" : "Invalid report parameters.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ungültige Parameter."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -214,6 +250,12 @@
"report.error.invalidType" : {
"comment" : "Invalid report type.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Ungültiger Berichtstyp."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -249,6 +291,12 @@
"report.error.noSelfReports" : {
"comment" : "Self-reporting is not allowed.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Du darfst Dich nicht selbst melden."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",

View File

@ -13,7 +13,7 @@ public extension PixelfedClientAuthenticated {
minId: EntityId? = nil,
limit: Int? = nil,
includeReblogs: Bool? = nil,
timeoutInterval: Double? = nil) async throws -> [Status] {
timeoutInterval: Double? = nil) async throws -> Linkable<[Status]> {
let request = try Self.request(
for: baseURL,
@ -22,7 +22,7 @@ public extension PixelfedClientAuthenticated {
timeoutInterval: timeoutInterval
)
return try await downloadJson([Status].self, request: request)
return try await downloadJsonWithLink([Status].self, request: request)
}
func getPublicTimeline(local: Bool? = nil,
@ -31,7 +31,7 @@ public extension PixelfedClientAuthenticated {
maxId: EntityId? = nil,
sinceId: EntityId? = nil,
minId: EntityId? = nil,
limit: Limit? = nil) async throws -> [Status] {
limit: Limit? = nil) async throws -> Linkable<[Status]> {
let request = try Self.request(
for: baseURL,
@ -39,7 +39,7 @@ public extension PixelfedClientAuthenticated {
withBearerToken: token
)
return try await downloadJson([Status].self, request: request)
return try await downloadJsonWithLink([Status].self, request: request)
}
func getTagTimeline(tag: String,
@ -49,7 +49,7 @@ public extension PixelfedClientAuthenticated {
maxId: EntityId? = nil,
sinceId: EntityId? = nil,
minId: EntityId? = nil,
limit: Int? = nil) async throws -> [Status] {
limit: Int? = nil) async throws -> Linkable<[Status]> {
let request = try Self.request(
for: baseURL,
@ -57,7 +57,7 @@ public extension PixelfedClientAuthenticated {
withBearerToken: token
)
return try await downloadJson([Status].self, request: request)
return try await downloadJsonWithLink([Status].self, request: request)
}
func setMarkers(_ markers: [Pixelfed.Markers.Timeline: EntityId]) async throws -> Markers {

View File

@ -2,10 +2,47 @@
"sourceLanguage" : "en",
"strings" : {
"" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
},
"eu" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
}
}
},
"global.error.downloadingImageFailed" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Das Herunterladen des Bildes in den Cache ist fehlgeschlagen."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -18,6 +55,18 @@
"value" : "Error al descargar la imagen en la caché."
}
},
"eu" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Ezin izan da irudia cachean deskargatu."
}
},
"fr" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Le téléchargement de l'image dans le cache a échoué."
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
@ -25,41 +74,6 @@
}
}
}
},
"global.error.unexpected" : {
"extractionState" : "stale",
"localizations" : {
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Unexpected error."
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Error inesperado."
}
},
"eu" : {
"stringUnit" : {
"state" : "translated",
"value" : "Espero ez zen errorea."
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Erreur inattendue."
}
},
"pl" : {
"stringUnit" : {
"state" : "translated",
"value" : "Wystąpił nieoczekiwany błąd."
}
}
}
}
},
"version" : "1.0"

View File

@ -957,6 +957,7 @@
eu,
fr,
es,
de,
);
mainGroup = F88C245F295C37B80006098B;
packageReferences = (
@ -1209,7 +1210,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.4;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1243,7 +1244,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.4;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.widget;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1276,7 +1277,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.4;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1308,7 +1309,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.4;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage.share;
PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES;
@ -1474,7 +1475,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.4;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -1517,7 +1518,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 2.0.0;
MARKETING_VERSION = 2.0.4;
PRODUCT_BUNDLE_IDENTIFIER = dev.mczachurski.vernissage;
PRODUCT_NAME = "$(TARGET_NAME)";
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";

View File

@ -77,8 +77,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/kean/Nuke",
"state" : {
"revision" : "3f666f120b63ea7de57d42e9a7c9b47f8e7a290b",
"version" : "12.1.6"
"revision" : "1694798e876113d44f6ec6ead965d7286695981d",
"version" : "12.2.0"
}
},
{

View File

@ -20,7 +20,7 @@ public class HomeTimelineService {
public static let shared = HomeTimelineService()
private init() { }
private let maximumAmountOfDownloadedStatuses = 80
private let maximumAmountOfDownloadedStatuses = 40
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
private let semaphore = AsyncSemaphore(value: 1)
@ -43,27 +43,49 @@ public class HomeTimelineService {
let client = PixelfedClient(baseURL: accountData.serverUrl).getAuthenticated(token: accessToken)
var statuses: [Status] = []
var newestStatusId = lastSeenStatusId
var latestStatusId: String? = nil
var breakProcesssing = false;
// There can be more then 80 newest statuses, that's why we have to sometimes send more then one request.
// There can be more then 40 newest statuses, that's why we have to sometimes send more then one request.
while true {
do {
let downloadedStatuses = try await client.getHomeTimeline(minId: newestStatusId,
// Download statuses from the top or the list.
let downloadedStatuses = try await client.getHomeTimeline(maxId: latestStatusId,
limit: self.maximumAmountOfDownloadedStatuses,
includeReblogs: includeReblogs)
guard let firstStatus = downloadedStatuses.first else {
break
// Iterate througt the list until we go to already visible status by the user.
var temporaryList: [Status] = []
for downloadedStatus in downloadedStatuses.data {
guard downloadedStatus.id != lastSeenStatusId else {
breakProcesssing = true
break
}
temporaryList.append(downloadedStatus)
}
// Remove from the list duplicated statuses.
let visibleStatuses = self.getVisibleStatuses(accountId: accountData.id,
statuses: downloadedStatuses,
statuses: temporaryList,
hideStatusesWithoutAlt: hideStatusesWithoutAlt,
modelContext: modelContext)
// Add statuses to the list.
statuses.append(contentsOf: visibleStatuses)
newestStatusId = firstStatus.id
// Break when we go to the already visible status.
if breakProcesssing {
break
}
// When we discovered more then 100 statuses we can break.
if statuses.count > 100 {
break
}
// Set status Id which should be used to download next portion of the statuses.
latestStatusId = downloadedStatuses.getMaxId()
} catch {
ErrorService.shared.handle(error, message: "global.error.errorDuringDownloadingNewStatuses")
break
@ -80,9 +102,10 @@ public class HomeTimelineService {
public func getVisibleStatuses(accountId: String, statuses: [Status], hideStatusesWithoutAlt: Bool, modelContext: ModelContext) -> [Status] {
// We have to include in the counter only statuses with images.
let statusesWithImagesOnly = statuses.getStatusesWithImagesOnly()
let statusesFromOldestToNewest = statusesWithImagesOnly.reversed()
var visibleStatuses: [Status] = []
for status in statusesWithImagesOnly {
for status in statusesFromOldestToNewest {
// We have to hide statuses without ALT text.
if hideStatusesWithoutAlt && status.statusContainsAltText() == false {
@ -112,7 +135,8 @@ public class HomeTimelineService {
visibleStatuses.append(status)
}
return visibleStatuses
// Return statuses from newest to oldest.
return visibleStatuses.reversed()
}
private func hasBeenAlreadyOnTimeline(accountId: String, status: Status, modelContext: ModelContext) -> Bool {

View File

@ -27,7 +27,7 @@ struct HomeTimelineView: View {
@State private var opacity = 0.0
@State private var offset = -50.0
private let defaultLimit = 80
private let defaultLimit = 40
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
private let timelineDoubleTapTip = TimelineDoubleTapTip()
@ -143,8 +143,14 @@ struct HomeTimelineView: View {
HStack {
Image(systemName: "arrow.up")
.fontWeight(.light)
Text("\(self.applicationState.amountOfNewStatuses)")
.fontWeight(.semibold)
if self.applicationState.amountOfNewStatuses < 100 {
Text("\(self.applicationState.amountOfNewStatuses)")
.fontWeight(.semibold)
} else {
Text("+99")
.fontWeight(.semibold)
}
}
.padding(.vertical, 12)
.padding(.horizontal, 18)
@ -183,28 +189,28 @@ struct HomeTimelineView: View {
// Download statuses from API (which are older then last visible status).
let statuses = try await self.loadFromCacheOrApi(timelineCache: accountData.timelineCache)
if statuses.isEmpty {
if statuses.data.isEmpty {
self.allItemsLoaded = true
return
}
// Remember last status id returned by API.
self.lastStatusId = statuses.last?.id
self.lastStatusId = statuses.getMaxId()
// Get only visible statuses.
let visibleStatuses = HomeTimelineService.shared.getVisibleStatuses(accountId: accountId,
statuses: statuses,
statuses: statuses.data,
hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt,
modelContext: modelContext)
// Remeber first status returned by API in user context (when it's newer then remembered).
try AccountDataHandler.shared.update(lastSeenStatusId: nil,
lastLoadedStatusId: statuses.first?.id,
lastLoadedStatusId: statuses.getMinId(),
applicationState: self.applicationState,
modelContext: modelContext)
// Append statuses to viewed.
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
try ViewedStatusHandler.shared.append(contentsOf: statuses.data, accountId: accountId, modelContext: modelContext)
// Map to view models.
let statusModels = visibleStatuses.map({ StatusModel(status: $0) })
@ -222,24 +228,24 @@ struct HomeTimelineView: View {
// Download statuses from API.
let statuses = try await self.loadFromApi(maxId: lastStatusId)
if statuses.isEmpty {
if statuses.data.isEmpty {
self.allItemsLoaded = true
return
}
// Now we have new last status.
if let lastStatusId = statuses.last?.id {
if let lastStatusId = statuses.getMaxId() {
self.lastStatusId = lastStatusId
}
// Get only visible statuses.
let visibleStatuses = HomeTimelineService.shared.getVisibleStatuses(accountId: accountId,
statuses: statuses,
statuses: statuses.data,
hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt,
modelContext: modelContext)
// Append statuses to viewed.
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
try ViewedStatusHandler.shared.append(contentsOf: statuses.data, accountId: accountId, modelContext: modelContext)
// Map to view models.
let statusModels = visibleStatuses.map({ StatusModel(status: $0) })
@ -260,29 +266,29 @@ struct HomeTimelineView: View {
// Download statuses from API.
let statuses = try await self.loadFromApi()
if statuses.isEmpty {
if statuses.data.isEmpty {
self.allItemsLoaded = true
return
}
// Remember last status id returned by API.
self.lastStatusId = statuses.last?.id
self.lastStatusId = statuses.getMaxId()
// Get only visible statuses.
let visibleStatuses = HomeTimelineService.shared.getVisibleStatuses(accountId: accountId,
statuses: statuses,
statuses: statuses.data,
hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt,
modelContext: modelContext)
// Remeber first status returned by API in user context (when it's newer then remembered).
try AccountDataHandler.shared.update(lastSeenStatusId: self.statusViewModels.first?.id,
lastLoadedStatusId: statuses.first?.id,
lastLoadedStatusId: statuses.getMinId(),
statuses: statuses,
applicationState: self.applicationState,
modelContext: modelContext)
// Append statuses to viewed.
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
try ViewedStatusHandler.shared.append(contentsOf: statuses.data, accountId: accountId, modelContext: modelContext)
// Map to view models.
let statusModels = visibleStatuses.map({ StatusModel(status: $0) })
@ -297,24 +303,22 @@ struct HomeTimelineView: View {
self.applicationState.amountOfNewStatuses = 0
}
private func loadFromCacheOrApi(timelineCache: String?) async throws -> [Status] {
if let timelineCache, let timelineCacheData = timelineCache.data(using: .utf8) {
let statusesFromCache = try? JSONDecoder().decode([Status].self, from: timelineCacheData)
if let statusesFromCache {
return statusesFromCache
}
private func loadFromCacheOrApi(timelineCache: String?) async throws -> Linkable<[Status]> {
if let timelineCache, let timelineCacheData = timelineCache.data(using: .utf8),
let statusesFromCache = try? JSONDecoder().decode(Linkable<[Status]>.self, from: timelineCacheData) {
return statusesFromCache
}
return try await self.loadFromApi()
}
private func loadFromApi(maxId: String? = nil, sinceId: String? = nil, minId: String? = nil) async throws -> [Status] {
private func loadFromApi(maxId: String? = nil, sinceId: String? = nil, minId: String? = nil) async throws -> Linkable<[Status]> {
return try await self.client.publicTimeline?.getHomeTimeline(
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit,
includeReblogs: self.applicationState.showReboostedStatuses) ?? []
includeReblogs: self.applicationState.showReboostedStatuses) ?? Linkable(data: [])
}
private func calculateOffset() {

View File

@ -61,7 +61,7 @@ struct StatusesView: View {
@State private var containerWidth: Double = UIDevice.isIPad ? UIScreen.main.bounds.width / 3 : UIScreen.main.bounds.width
@State private var containerHeight: Double = UIDevice.isIPad ? UIScreen.main.bounds.height / 3 : UIScreen.main.bounds.height
private let defaultLimit = 80
private let defaultLimit = 40
private let imagePrefetcher = ImagePrefetcher(destination: .diskCache)
var body: some View {
@ -182,29 +182,29 @@ struct StatusesView: View {
let statuses = try await self.loadFromApi()
if statuses.isEmpty {
if statuses.data.isEmpty {
self.allItemsLoaded = true
return
}
// Remember last status id returned by API.
self.lastStatusId = statuses.last?.id
self.lastStatusId = statuses.getMaxId()
// Get only visible statuses.
let visibleStatuses = HomeTimelineService.shared.getVisibleStatuses(accountId: accountId,
statuses: statuses,
statuses: statuses.data,
hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt,
modelContext: modelContext)
if self.listType == .home {
// Remeber first status returned by API in user context (when it's newer then remembered).
try AccountDataHandler.shared.update(lastSeenStatusId: nil,
lastLoadedStatusId: statuses.first?.id,
lastLoadedStatusId: statuses.getMinId(),
applicationState: self.applicationState,
modelContext: modelContext)
// Append statuses to viewed.
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
try ViewedStatusHandler.shared.append(contentsOf: statuses.data, accountId: accountId, modelContext: modelContext)
}
// Map to view models.
@ -221,25 +221,25 @@ struct StatusesView: View {
if let lastStatusId = self.lastStatusId, let accountId = self.applicationState.account?.id {
let statuses = try await self.loadFromApi(maxId: lastStatusId)
if statuses.isEmpty {
if statuses.data.isEmpty {
self.allItemsLoaded = true
return
}
// Now we have new last status.
if let lastStatusId = statuses.last?.id {
if let lastStatusId = statuses.getMaxId() {
self.lastStatusId = lastStatusId
}
// Get only visible statuses.
let visibleStatuses = HomeTimelineService.shared.getVisibleStatuses(accountId: accountId,
statuses: statuses,
statuses: statuses.data,
hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt,
modelContext: modelContext)
if self.listType == .home {
// Append statuses to viewed.
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
try ViewedStatusHandler.shared.append(contentsOf: statuses.data, accountId: accountId, modelContext: modelContext)
}
// Map to view models.
@ -260,29 +260,29 @@ struct StatusesView: View {
let statuses = try await self.loadFromApi()
if statuses.isEmpty {
if statuses.data.isEmpty {
self.allItemsLoaded = true
return
}
// Remember last status id returned by API.
self.lastStatusId = statuses.last?.id
self.lastStatusId = statuses.getMaxId()
// Get only visible statuses.
let visibleStatuses = HomeTimelineService.shared.getVisibleStatuses(accountId: accountId,
statuses: statuses,
statuses: statuses.data,
hideStatusesWithoutAlt: self.applicationState.hideStatusesWithoutAlt,
modelContext: modelContext)
if self.listType == .home {
// Remeber first status returned by API in user context (when it's newer then remembered).
try AccountDataHandler.shared.update(lastSeenStatusId: self.statusViewModels.first?.id,
lastLoadedStatusId: statuses.first?.id,
lastLoadedStatusId: statuses.getMinId(),
applicationState: self.applicationState,
modelContext: modelContext)
// Append statuses to viewed.
try ViewedStatusHandler.shared.append(contentsOf: statuses, accountId: accountId, modelContext: modelContext)
try ViewedStatusHandler.shared.append(contentsOf: statuses.data, accountId: accountId, modelContext: modelContext)
}
// Map to view models.
@ -296,7 +296,7 @@ struct StatusesView: View {
self.statusViewModels = statusModels
}
private func loadFromApi(maxId: String? = nil, sinceId: String? = nil, minId: String? = nil) async throws -> [Status] {
private func loadFromApi(maxId: String? = nil, sinceId: String? = nil, minId: String? = nil) async throws -> Linkable<[Status]> {
switch self.listType {
case .home:
return try await self.client.publicTimeline?.getHomeTimeline(
@ -304,40 +304,44 @@ struct StatusesView: View {
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit,
includeReblogs: self.applicationState.showReboostedStatuses) ?? []
includeReblogs: self.applicationState.showReboostedStatuses) ?? Linkable(data: [])
case .local:
return try await self.client.publicTimeline?.getStatuses(
local: true,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit) ?? []
limit: self.defaultLimit) ?? Linkable(data: [])
case .federated:
return try await self.client.publicTimeline?.getStatuses(
remote: true,
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit) ?? []
limit: self.defaultLimit) ?? Linkable(data: [])
case .favourites:
return try await self.client.accounts?.favourites(
let favourites = try await self.client.accounts?.favourites(
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit) ?? []
return Linkable(data: favourites)
case .bookmarks:
return try await self.client.accounts?.bookmarks(
let bookmarks = try await self.client.accounts?.bookmarks(
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit) ?? []
return Linkable(data: bookmarks)
case .hashtag(let tag):
let hashtagsFromApi = try await self.client.search?.search(query: tag, resultsType: .hashtags)
guard let hashtagsFromApi = hashtagsFromApi, hashtagsFromApi.hashtags.isEmpty == false else {
ToastrService.shared.showError(title: LocalizedStringResource("global.error.hashtagNotExists"), imageSystemName: "exclamationmark.octagon")
dismiss()
return []
return Linkable(data: [])
}
return try await self.client.publicTimeline?.getTagStatuses(
@ -345,7 +349,7 @@ struct StatusesView: View {
maxId: maxId,
sinceId: sinceId,
minId: minId,
limit: self.defaultLimit) ?? []
limit: self.defaultLimit) ?? Linkable(data: [])
}
}

View File

@ -75,7 +75,7 @@ struct ImagesGrid: View {
do {
let statusesFromApi = try await self.loadStatuses()
let statusesWithImages = statusesFromApi.getStatusesWithImagesOnly()
let statusesWithImages = statusesFromApi.data.getStatusesWithImagesOnly()
let photoUrls = self.getPhotoUrls(statuses: statusesWithImages)
self.prefetch(photoUrls: photoUrls)
@ -119,15 +119,21 @@ struct ImagesGrid: View {
}
}
private func loadStatuses() async throws -> [Status] {
private func loadStatuses() async throws -> Linkable<[Status]> {
switch self.gridType {
case .hashtag(let name):
return try await self.client.publicTimeline?.getTagStatuses(
tag: name,
local: true,
limit: 10) ?? []
limit: 10) ?? Linkable(data: [])
case .account(let accountId, _, _):
return try await self.client.accounts?.statuses(createdBy: accountId, onlyMedia: true, limit: 10) ?? []
let accountStatuses = try await self.client.accounts?.statuses(
createdBy: accountId,
onlyMedia: true,
limit: 10
) ?? []
return Linkable(data: accountStatuses)
}
}

View File

@ -33,7 +33,7 @@ public class StatusFetcher {
let client = PixelfedClient(baseURL: account.serverUrl).getAuthenticated(token: accessToken)
let statuses = try await client.getHomeTimeline(limit: 20, includeReblogs: defaultSettings.showReboostedStatuses, timeoutInterval: 5.0)
let widgetEntries = await self.prepare(statuses: statuses, length: length)
let widgetEntries = await self.prepare(statuses: statuses.data, length: length)
return widgetEntries
}
@ -49,11 +49,11 @@ public class StatusFetcher {
let accountData = AccountDataHandler.shared.getAccountData(accountId: accountId, modelContext: modelContext)
guard let timelineCache = accountData?.timelineCache,
let timelineCacheData = timelineCache.data(using: .utf8),
let statusesFromCache = try? JSONDecoder().decode([Status].self, from: timelineCacheData) else {
let statusesFromCache = try? JSONDecoder().decode(Linkable<[Status]>.self, from: timelineCacheData) else {
return [self.placeholder()]
}
let widgetEntries = await self.prepare(statuses: statusesFromCache, length: length)
let widgetEntries = await self.prepare(statuses: statusesFromCache.data, length: length)
return widgetEntries
}

View File

@ -3,6 +3,12 @@
"strings" : {
"" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : ""
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
@ -31,6 +37,12 @@
},
"@%@" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "@%@"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
@ -59,6 +71,12 @@
},
"#%@" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "#%@"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
@ -87,6 +105,12 @@
},
"%@" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "%@"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
@ -115,6 +139,12 @@
},
"%@, %@" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "%1$@, %2$@"
}
},
"en" : {
"stringUnit" : {
"state" : "new",
@ -149,6 +179,12 @@
},
"%lld" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "%lld"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
@ -177,6 +213,12 @@
},
"compose.error.cannotLoadImageFromExternalLibrary" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Bild kann nicht aus externer Bibliothek geladen werden."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -199,6 +241,12 @@
},
"compose.error.errorDuringComposingAttributeString" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Fehler beim Zusammenstellen der Attributzeichenfolge."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -221,6 +269,12 @@
},
"compose.error.errorDuringDownloadingAutocomplete" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Fehler beim Herunterladen von Autocomplete."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -243,6 +297,12 @@
},
"compose.error.loadingPhotosFailed" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kann das Foto nicht aus der Bibliothek laden."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -277,6 +337,12 @@
},
"compose.error.postingStatusFailed" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fehler beim Veröffentlichen des Beitrags."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -311,6 +377,12 @@
},
"compose.title.attachPhotoFull" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Füge ein Foto hinzu und beschreibe, was Du denkst."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -345,6 +417,12 @@
},
"compose.title.attachPhotoMini" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Beschreibe was Du denkst."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -380,6 +458,12 @@
"compose.title.camera" : {
"comment" : "Camera",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Foto aufnehmen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -415,6 +499,12 @@
"compose.title.cancel" : {
"comment" : "Cancel",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abbrechen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -450,6 +540,12 @@
"compose.title.commentsWillBeDisabled" : {
"comment" : "Comments disabled",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Kommentare werden nicht möglich sein"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -484,6 +580,12 @@
},
"compose.title.delete" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Löschen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -518,6 +620,12 @@
},
"compose.title.edit" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Bearbeiten"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -552,6 +660,12 @@
},
"compose.title.everyone" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Öffentlich"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -587,6 +701,12 @@
"compose.title.files" : {
"comment" : "Files",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Dateien durchsuchen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -621,6 +741,12 @@
},
"compose.title.followers" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "für Follower"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -656,6 +782,12 @@
"compose.title.missingAltTexts" : {
"comment" : "Missing ALT texts",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fehlende ALT-Texte"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -691,6 +823,12 @@
"compose.title.missingAltTextsWarning" : {
"comment" : "Missing ALT texts warning",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Es haben nicht alle Fotos eine Beschreibung für Sehbehinderte. Möchtest Du trotzdem senden?"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -726,6 +864,12 @@
"compose.title.photos" : {
"comment" : "Photo",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fotobibliothek"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -761,6 +905,12 @@
"compose.title.publish" : {
"comment" : "Publish",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Veröffentlichen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -796,6 +946,12 @@
"compose.title.tryToUpload" : {
"comment" : "Try to upload",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Versuche hochzuladen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -830,6 +986,12 @@
},
"compose.title.unlisted" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "nicht gelistet"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -865,6 +1027,12 @@
"compose.title.writeContentWarning" : {
"comment" : "Content warning",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Inhaltswarnung hinzufügen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -899,6 +1067,12 @@
},
"global.error.errorDuringDeletingFileFromTmpDirectory" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Fehler beim Löschen einer Datei aus dem tmp-Verzeichnis."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -921,6 +1095,12 @@
},
"global.error.errorDuringGettingTmpDirectoryContents" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Fehler beim Abrufen des Inhalts des tmp-Verzeichnisses."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -943,6 +1123,12 @@
},
"global.error.errorDuringRemovingTransferredImage" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Fehler beim Entfernen des übertragenen Bildes aus dem tmp-Verzeichnis."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -966,6 +1152,12 @@
"global.title.contentWarning" : {
"comment" : "Sensitive content",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Inhaltswarnung"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1001,6 +1193,12 @@
"global.title.momentsAgo" : {
"comment" : "moments ago",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "gerade eben"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1036,6 +1234,12 @@
"global.title.refresh" : {
"comment" : "Refresh",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Aktualisieren"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1071,6 +1275,12 @@
"global.title.seePost" : {
"comment" : "See post",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Beitrag ansehen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1105,6 +1315,12 @@
},
"global.title.showLess" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Weniger anzeigen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1139,6 +1355,12 @@
},
"global.title.showMore" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Mehr anzeigen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1173,6 +1395,12 @@
},
"photoEdit.error.updatePhotoFailed" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fehler beim Aktualisieren des Fotos."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1208,6 +1436,12 @@
"photoEdit.navigationBar.title" : {
"comment" : "Title",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Fotodetails"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1243,6 +1477,12 @@
"photoEdit.title.accessibility" : {
"comment" : "Accessibility",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Barrierefreiheit"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1278,6 +1518,12 @@
"photoEdit.title.accessibilityDescription" : {
"comment" : "Accesibility",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Beschreibung für Sehbehinderte"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1313,6 +1559,12 @@
"photoEdit.title.cancel" : {
"comment" : "Cancel",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abbrechen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1348,6 +1600,12 @@
"photoEdit.title.photo" : {
"comment" : "Photo",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Foto"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1383,6 +1641,12 @@
"photoEdit.title.save" : {
"comment" : "Save",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Speichern"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1417,6 +1681,12 @@
},
"placeSelector.error.loadingPlacesFailed" : {
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Benachrichtigungen konnten nicht geladen werden."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1452,6 +1722,12 @@
"placeSelector.navigationBar.title" : {
"comment" : "Title",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Orte"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1487,6 +1763,12 @@
"placeSelector.title.buttonSearch" : {
"comment" : "Search",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suchen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1522,6 +1804,12 @@
"placeSelector.title.cancel" : {
"comment" : "Cancel",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Abbrechen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1557,6 +1845,12 @@
"placeSelector.title.search" : {
"comment" : "Search",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suchen..."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1592,6 +1886,12 @@
"status.title.altText" : {
"comment" : "ALT",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "ALT"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1627,6 +1927,12 @@
"tip.mainNavigation.message" : {
"comment" : "Main navigation tip message.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sie können die Position des Hauptnavigationsmenüs in den Einstellungen der App anpassen."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1644,6 +1950,12 @@
"tip.mainNavigation.title" : {
"comment" : "Main navigation tip title.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Position im Menü"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1661,6 +1973,12 @@
"tip.menuCustomizable.message" : {
"comment" : "Menu customizable tip message.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Sie können die Standardoptionen im Menü ändern, indem Sie lange auf die jeweilige Option drücken."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1678,6 +1996,12 @@
"tip.menuCustomizable.title" : {
"comment" : "Menu customizable tip title.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Ändern der Standardoptionen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1695,6 +2019,12 @@
"tip.timelineDoubleTap.message" : {
"comment" : "Timeline double tip message.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Ein Doppeltippen auf ein Foto führt dazu, dass es favorisiert wird."
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
@ -1712,6 +2042,12 @@
"tip.timelineDoubleTap.title" : {
"comment" : "Timeline double tip title.",
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "needs_review",
"value" : "Als Favorit markieren"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",