Skip to content

Commit

Permalink
Merge pull request #687 from NoodleOfDeath/dev
Browse files Browse the repository at this point in the history
~sitemaps
  • Loading branch information
NoodleOfDeath authored Oct 9, 2023
2 parents deb308d + 3900c86 commit 4b1a3a8
Show file tree
Hide file tree
Showing 42 changed files with 1,770 additions and 319 deletions.
15 changes: 12 additions & 3 deletions src/core/src/client/contexts/session/SessionContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ReadingFormat,
RecapAttributes,
} from '~/api';
import { Locale, getLocale } from '~/locales';
import { useLocalStorage, usePlatformTools } from '~/utils';

export const SessionContext = React.createContext(DEFAULT_SESSION_CONTEXT);
Expand All @@ -40,6 +41,7 @@ export function SessionContextProvider({ children }: React.PropsWithChildren) {
const [lastRequestForReview, setLastRequestForReview] = React.useState(0);
const [categories, setCategories] = React.useState<Record<string, PublicCategoryAttributes>>();
const [publishers, setPublishers] = React.useState<Record<string, PublicPublisherAttributes>>();
const [loadedInitialUrl, setLoadedInitialUrl] = React.useState(false);

// user state
const [uuid, setUuid] = React.useState<string>();
Expand All @@ -51,6 +53,7 @@ export function SessionContextProvider({ children }: React.PropsWithChildren) {
const [bookmarkedSummaries, setBookmarkedSummaries] = React.useState<{ [key: number]: Bookmark<PublicSummaryGroup> }>();
const [readSummaries, setReadSummaries] = React.useState<{ [key: number]: Bookmark<boolean> }>();
const [removedSummaries, setRemovedSummaries] = React.useState<{ [key: number]: boolean }>();
const [locale, setLocale] = React.useState<Locale>();
const [summaryTranslations, setSummaryTranslations] = React.useState<{ [key: number]: { [key in keyof PublicSummaryGroup]?: string } }>();

// bookmark state
Expand Down Expand Up @@ -166,6 +169,9 @@ export function SessionContextProvider({ children }: React.PropsWithChildren) {
case 'lastRequestForReview':
setLastRequestForReview(newValue);
break;
case 'loadedInitialUrl':
setLoadedInitialUrl(newValue);
break;

// user state
case 'uuid':
Expand Down Expand Up @@ -545,11 +551,13 @@ export function SessionContextProvider({ children }: React.PropsWithChildren) {
setBookmarkedSummaries(await getPreference('bookmarkedSummaries'));
setReadSummaries(await getPreference('readSummaries'));
setRemovedSummaries(await getPreference('removedSummaries'));
setSummaryTranslations(await getPreference('summaryTranslations'));
const locale = await getPreference('locale');
setLocale(locale);
setSummaryTranslations(locale !== getLocale() ? {} : await getPreference('summaryTranslations'));

// recap state
setReadRecaps(await getPreference('readRecaps'));
setRecapTranslations(await getPreference('recapTranslations'));
setRecapTranslations(locale !== getLocale() ? {} : await getPreference('recapTranslations'));

// publisher states
setFollowedPublishers(await getPreference('followedPublishers'));
Expand All @@ -567,7 +575,7 @@ export function SessionContextProvider({ children }: React.PropsWithChildren) {
setFontSizeOffset(await getPreference('fontSizeOffset'));
setLetterSpacing(await getPreference('letterSpacing'));
setLineHeightMultiplier(await getPreference('lineHeightMultiplier'));

// summary preferences
setCompactSummaries(await getPreference('compactSummaries') ?? await getPreference('compactMode'));
setShowShortSummary(await getPreference('showShortSummary'));
Expand Down Expand Up @@ -629,6 +637,7 @@ export function SessionContextProvider({ children }: React.PropsWithChildren) {
latestVersion,
letterSpacing,
lineHeightMultiplier,
loadedInitialUrl,
preferredReadingFormat,
preferredShortPressFormat,
publisherIsFavorited,
Expand Down
5 changes: 5 additions & 0 deletions src/core/src/client/contexts/session/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
RecapAttributes,
RequestParams,
} from '~/api';
import { Locale } from '~/locales';

export type BookmarkConstructorProps = {
createdAt: Date;
Expand Down Expand Up @@ -117,6 +118,7 @@ export type Preferences = {
viewedFeatures?: { [key: string]: Bookmark<boolean> };
hasReviewed?: boolean;
lastRequestForReview: number;
loadedInitialUrl?: boolean;

// user state
uuid?: string;
Expand All @@ -130,6 +132,7 @@ export type Preferences = {
bookmarkCount: number;
unreadBookmarkCount: number;
removedSummaries?: { [key: number]: boolean };
locale?: Locale;
summaryTranslations?: { [key: number]: { [key in keyof PublicSummaryGroup]?: string } };

// recap state
Expand Down Expand Up @@ -189,6 +192,8 @@ export const PREFERENCE_TYPES: { [key in keyof Preferences]: 'boolean' | 'number
lastRequestForReview: 'number',
letterSpacing: 'number',
lineHeightMultiplier: 'number',
loadedInitialUrl: 'boolean',
locale: 'string',
preferredReadingFormat: 'string',
preferredShortPressFormat: 'string',
pushNotifications: 'object',
Expand Down
4 changes: 2 additions & 2 deletions src/mobile/android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ android {
applicationId "ai.readless.ReadLess"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 98
versionName "1.15.2"
versionCode 99
versionName "1.16.0"
missingDimensionStrategy "store", "play"
}

Expand Down
6 changes: 3 additions & 3 deletions src/mobile/ios/Extensions/Date.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,11 @@ extension Date {
func distanceFromNow() -> String {
let interval = Calendar.current.dateComponents([.minute, .hour, .day], from: self, to: Date())
if let day = interval.day, day > 0 {
return "\(day) day\(day == 1 ? "" : "s") ago"
return "\(day)d"
} else if let hour = interval.hour, hour > 0 {
return "\(hour) hour\(hour == 1 ? "" : "s") ago"
return "\(hour)h"
} else if let minute = interval.minute, minute > 0 {
return "\(minute) minute\(minute == 1 ? "" : "s") ago"
return "\(minute)m"
} else {
return "just now"
}
Expand Down
51 changes: 51 additions & 0 deletions src/mobile/ios/Extensions/Image.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// Image.swift
// ReadLess
//
// Created by thom on 10/4/23.
//

import Foundation
import CoreGraphics
import SwiftUI

public extension Image {

static func loadAsync(from string: String, maxWidth: CGFloat? = nil) async -> Image? {
guard let url = URL(string: string) else { return nil }
return await self.loadAsync(from: url, maxWidth: maxWidth)
}

static func loadAsync(from url: URL?, maxWidth: CGFloat? = nil) async -> Image? {
guard let url = url else { return nil }
let request = URLRequest(url: url)
guard let (data, _) = try? await URLSession.shared.data(for: request) else { return nil }
guard let image = UIImage(data: data) else { return nil }
if let maxWidth = maxWidth, let image = image.resized(toWidth: maxWidth) {
return Image(uiImage: image)
}
return Image(uiImage: image)
}

static func load(from string: String, maxWidth: CGFloat? = nil, completion: @escaping @Sendable (_ image: Image?) -> Void) {
guard let imageUrl = URL(string: string) else { return }
return self.load(from: imageUrl, completion: completion)
}

static func load(from url: URL?, maxWidth: CGFloat? = nil, completion: @escaping @Sendable (_ image: Image?) -> Void) {
guard let url = url else {
completion(nil)
return
}
URLSession.shared.dataTask(with: url) { data, _, error in
if let data = data, let image = UIImage(data: data) {
if let maxWidth = maxWidth, let image = image.resized(toWidth: maxWidth) {
completion(Image(uiImage: image))
} else {
completion(Image(uiImage: image))
}
}
}.resume()
}

}
25 changes: 25 additions & 0 deletions src/mobile/ios/Extensions/UIImage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// UIImage.swift
// ReadLess
//
// Created by thom on 10/6/23.
//

import Foundation
import UIKit

extension UIImage {

func resized(toWidth width: CGFloat, isOpaque: Bool = true) -> UIImage? {
let size = CGSize(width: width, height: width / size.width * size.height)
return self.resized(toSize: size, isOpaque: isOpaque)
}

func resized(toSize size: CGSize, isOpaque: Bool = true) -> UIImage? {
UIGraphicsBeginImageContextWithOptions(size, true, 1.0)
self.draw(in: CGRect(origin: .zero, size: size))
defer { UIGraphicsEndImageContext() }
return UIGraphicsGetImageFromCurrentImageContext()
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,31 @@ import Foundation
import AnyCodable
#endif

public struct BulkResponse<T: Codable, M: Codable>: Codable {
public struct BulkResponse<T: Codable>: Codable {

public var count: Int
public var rows: [T]

public init(count: Int, rows: [T]) {
self.count = count
self.rows = rows
}

public enum CodingKeys: String, CodingKey, CaseIterable {
case count
case rows
}

// Encodable protocol methods

public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(count, forKey: .count)
try container.encode(rows, forKey: .rows)
}
}

public struct BulkMetadataResponse<T: Codable, M: Codable>: Codable {

public var count: Int
public var rows: [T]
Expand Down
4 changes: 2 additions & 2 deletions src/mobile/ios/Models/PublicPublisherAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ public struct PublicPublisherAttributes: Codable, Hashable {
public var displayName: String
public var description: String?

public var icon: String {
return "https://readless.nyc3.cdn.digitaloceanspaces.com/img/pub/\(self.name).png"
public var icon: URL {
return URL(string: "https://readless.nyc3.cdn.digitaloceanspaces.com/img/pub/\(self.name).png")!
}

public init(name: String, displayName: String, description: String? = nil) {
Expand Down
101 changes: 100 additions & 1 deletion src/mobile/ios/Models/PublicSummaryAttributes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import Foundation
import SwiftUI
#if canImport(AnyCodable)
import AnyCodable
#endif
Expand Down Expand Up @@ -77,4 +78,102 @@ public struct PublicSummaryAttributes: Codable, Hashable {

}

public var MOCK_SUMMARY = PublicSummaryAttributes(id: 1, url: "https://readless.ai", title: "Summary Preview", shortSummary: "Short Summary", publisher: PublicPublisherAttributes(name: "cnn", displayName: "CNN"), category: PublicCategoryAttributes(name: "sports", displayName: "Sports", icon: "basketball"))
public class Summary {

public var root: PublicSummaryAttributes
public var id: Int
public var url: String
public var title: String
public var shortSummary: String?
public var publisher: PublicPublisherAttributes
public var category: PublicCategoryAttributes
public var imageUrl: String?
public var media: [String: String]?
public var originalDate: Date?
public var translations: [String: String]?

public var deeplink: URL {
return URL(string: "https://readless.ai/read/?s=\(id)")!
}

public var primaryImageUrl: URL? {
return URL(string: media?["imageArticle"] ?? media?["imageAi1"] ?? imageUrl ?? "")
}

public init(_ summary: PublicSummaryAttributes) {
self.root = summary
self.id = summary.id
self.url = summary.url
self.title = summary.title
self.shortSummary = summary.shortSummary
self.publisher = summary.publisher
self.category = summary.category
self.imageUrl = summary.imageUrl
self.media = summary.media
self.originalDate = summary.originalDate
self.translations = summary.translations
}

@Published public var image: Image?
@Published public var publisherIcon: Image?

public func loadImages() {
Image.load(from: primaryImageUrl, maxWidth: 400) { self.image = $0 }
Image.load(from: publisher.icon) { self.publisherIcon = $0 }
}

public func loadImagesAsync() async {
image = await Image.loadAsync(from: primaryImageUrl, maxWidth: 400)
publisherIcon = await Image.loadAsync(from: publisher.icon)
}

}


public var MOCK_SUMMARY_1 = Summary(
PublicSummaryAttributes(id: 1,
url: "https://readless.ai",
title: "Summary Preview",
shortSummary: "Short Summary",
publisher: PublicPublisherAttributes(name: "cnn",
displayName: "CNN"),
category: PublicCategoryAttributes(name: "sports",
displayName: "Sports",
icon: "basketball")
))

public var MOCK_SUMMARY_2 = Summary(
PublicSummaryAttributes(id: 2,
url: "https://readless.ai",
title: "Summary Preview",
shortSummary: "Short Summary",
publisher: PublicPublisherAttributes(name: "forbes",
displayName: "Forbes"),
category: PublicCategoryAttributes(name: "politics",
displayName: "politics",
icon: "bank")
))

public var MOCK_SUMMARY_3 = Summary(
PublicSummaryAttributes(id: 3,
url: "https://readless.ai",
title: "Summary Preview",
shortSummary: "Short Summary",
publisher: PublicPublisherAttributes(name: "forbes",
displayName: "Forbes"),
category: PublicCategoryAttributes(name: "politics",
displayName: "politics",
icon: "bank")
))

public var MOCK_SUMMARY_4 = Summary(
PublicSummaryAttributes(id: 4,
url: "https://readless.ai",
title: "Summary Preview",
shortSummary: "Short Summary",
publisher: PublicPublisherAttributes(name: "forbes",
displayName: "Forbes"),
category: PublicCategoryAttributes(name: "politics",
displayName: "politics",
icon: "bank")
))
Loading

0 comments on commit 4b1a3a8

Please sign in to comment.