From 271ba5a0856e5b90162fc2b671815ad9cc641dc7 Mon Sep 17 00:00:00 2001 From: noodleofdeath Date: Fri, 13 Oct 2023 11:41:53 -0400 Subject: [PATCH 1/5] feat(RL-118): wordle updates --- .../components/common/OnscreenKeyboard.tsx | 84 ++++++++++ src/mobile/src/components/common/index.ts | 1 + src/mobile/src/hooks/useNavigation.tsx | 3 + .../src/screens/games/PlayGameScreen.tsx | 16 +- src/mobile/src/screens/games/WordleGame.tsx | 105 ------------ src/mobile/src/screens/games/index.ts | 2 +- .../src/screens/games/wordle/WordleGame.tsx | 155 ++++++++++++++++++ src/mobile/src/screens/games/wordle/index.ts | 1 + 8 files changed, 257 insertions(+), 110 deletions(-) create mode 100644 src/mobile/src/components/common/OnscreenKeyboard.tsx delete mode 100644 src/mobile/src/screens/games/WordleGame.tsx create mode 100644 src/mobile/src/screens/games/wordle/WordleGame.tsx create mode 100644 src/mobile/src/screens/games/wordle/index.ts diff --git a/src/mobile/src/components/common/OnscreenKeyboard.tsx b/src/mobile/src/components/common/OnscreenKeyboard.tsx new file mode 100644 index 00000000..f6c63961 --- /dev/null +++ b/src/mobile/src/components/common/OnscreenKeyboard.tsx @@ -0,0 +1,84 @@ +import React from 'react'; + +import { + Button, + ChildlessViewProps, + View, +} from '~/components'; +import { Icon } from '~/components/common/Icon'; +import { useTheme } from '~/hooks'; + +type Key = { + value: string; + label?: React.ReactNode; + bg?: string; +}; + +export const DEFAULT_KEYS: Key[][] = [ + 'qwertyuiop'.split('').map((k) => ({ label: k.toUpperCase(), value: k })), + 'asdfghjkl'.split('').map((k) => ({ label: k.toUpperCase(), value: k })), + '#zxcvbnm$'.split('').map((k) => k === '#' ? ({ label: 'ENTER', value: 'enter' }) : k === '$' ? ({ label: , value: 'delete' }) : ({ label: k.toUpperCase(), value: k })), +]; + +type KeyboardRowProps = ChildlessViewProps & { + keys: Key[]; + onKeyPress?: (key: Key) => void, +}; + +function KeyboardRow({ + keys, + onKeyPress, + ...props +}: KeyboardRowProps) { + + const theme = useTheme(); + + return ( + + {keys.map((key, index) => ( + + ))} + + ); +} + +export type OnscreenKeyboardProps = ChildlessViewProps & { + keys?: Key[][]; + onKeyPress?: (key: Key) => void; +}; + +export function OnscreenKeyboard({ + keys = DEFAULT_KEYS, + onKeyPress, + ...props +}: OnscreenKeyboardProps ) { + return ( + + {keys.map((row, index) => ( + + ))} + + ); +} \ No newline at end of file diff --git a/src/mobile/src/components/common/index.ts b/src/mobile/src/components/common/index.ts index 91c3b993..5aa54c37 100644 --- a/src/mobile/src/components/common/index.ts +++ b/src/mobile/src/components/common/index.ts @@ -22,6 +22,7 @@ export * from './Highlighter'; export * from './Icon'; export * from './Image'; export * from './Markdown'; +export * from './OnscreenKeyboard'; export * from './Popover'; export * from './Pulse'; export * from './MeterDial'; diff --git a/src/mobile/src/hooks/useNavigation.tsx b/src/mobile/src/hooks/useNavigation.tsx index 721128d0..3bf7ee54 100644 --- a/src/mobile/src/hooks/useNavigation.tsx +++ b/src/mobile/src/hooks/useNavigation.tsx @@ -81,6 +81,9 @@ export function useNavigation() { if (route === 'top') { navigate('topStories'); } else + if (route === 'live') { + navigate('liveFeed'); + } else if (route === 'search') { const filter = params['filter']?.trim(); if (!filter) { diff --git a/src/mobile/src/screens/games/PlayGameScreen.tsx b/src/mobile/src/screens/games/PlayGameScreen.tsx index 004cd3d6..6a1dba56 100644 --- a/src/mobile/src/screens/games/PlayGameScreen.tsx +++ b/src/mobile/src/screens/games/PlayGameScreen.tsx @@ -1,16 +1,24 @@ import React from 'react'; -import { WordleGame } from './WordleGame'; +import { useFocusEffect } from '@react-navigation/native'; + +import { WordleGame } from './wordle'; import { ScreenComponent } from '../types'; -import { Screen } from '~/components'; +import { Screen, View } from '~/components'; -export function PlayGameScreen({ route }: ScreenComponent<'play'>) { +export function PlayGameScreen({ route, navigation }: ScreenComponent<'play'>) { + + useFocusEffect(React.useCallback(() => { + navigation?.setOptions({ headerTitle: route?.params.name }); + }, [route, navigation])); if (route?.params.name === 'wordle') { return ( - + + + ); } diff --git a/src/mobile/src/screens/games/WordleGame.tsx b/src/mobile/src/screens/games/WordleGame.tsx deleted file mode 100644 index a22b96ac..00000000 --- a/src/mobile/src/screens/games/WordleGame.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import React from 'react'; - -import { - Button, - ChildlessViewProps, - NativeTextInput, - TextInput, - View, -} from '~/components'; -import { LayoutContext } from '~/contexts'; - -type WordleRowProps = ChildlessViewProps & { - text: string; - maxLength: number; - correctAnswer: string; - revealed?: boolean; -}; - -function WordleRow({ - text, maxLength, correctAnswer, revealed, ...props -}: WordleRowProps) { - const { screenHeight } = React.useContext(LayoutContext); - return ( - - {[...Array(maxLength).keys()].map((_, index) => { - const bg = revealed && text.length === maxLength ? text[index] === correctAnswer[index] ? 'green' : correctAnswer.includes(text[index]) ? 'orange' : 'red' : 'gray'; - const color = revealed && text.length === maxLength ? text[index] === correctAnswer[index] ? 'white' : 'black' : 'black'; - return ( - - ); - })} - - ); -} - -export type WordleGameProps = { - word?: string; - maxGuesses?: number; - maxLength?: number; -}; - -export function WordleGame({ - word = 'fuckme', - maxGuesses = 6, - maxLength = word.length, - ...props -}: WordleGameProps) { - - const [guesses, setGuesses] = React.useState([...Array(maxGuesses).keys()].map(() => '')); - const [currentGuess, setCurrentGuess] = React.useState(''); - - const textRef = React.useRef(null); - - return ( - - - textRef.current?.focus() } /> - {guesses.map((_, index) => ( - index < guesses.length - 1 && ( - textRef.current?.focus() } /> - ) - ))} - - { - const text = e.nativeEvent.text; - if (text.length <= maxLength) { - setCurrentGuess(text); - } - } } - onSubmitEditing={ (e) => { - const text = e.nativeEvent.text; - if (text.length === maxLength) { - setGuesses((prev) => { - const state = [...prev]; - state[prev.filter((g) => g.length).length] = text; - return (prev = state); - }); - setCurrentGuess(''); - } - } } /> - - ); -} \ No newline at end of file diff --git a/src/mobile/src/screens/games/index.ts b/src/mobile/src/screens/games/index.ts index 0c6f2ae4..1f61747a 100644 --- a/src/mobile/src/screens/games/index.ts +++ b/src/mobile/src/screens/games/index.ts @@ -1,2 +1,2 @@ export * from './GamesSelectionScreen'; -export * from './WordleGame'; +export * from './wordle'; diff --git a/src/mobile/src/screens/games/wordle/WordleGame.tsx b/src/mobile/src/screens/games/wordle/WordleGame.tsx new file mode 100644 index 00000000..1a58c268 --- /dev/null +++ b/src/mobile/src/screens/games/wordle/WordleGame.tsx @@ -0,0 +1,155 @@ +import React from 'react'; + +import { + Button, + ChildlessViewProps, + DEFAULT_KEYS, + OnscreenKeyboard, + View, +} from '~/components'; +import { LayoutContext } from '~/contexts'; + +type LetterClue = 'exact' | 'close' | 'wrong'; + +const LETTER_COLORS = { + close: { + bg: 'orange', + color: 'white', + }, + exact: { + bg: 'green', + color: 'white', + }, + unknown: { + bg: 'gray', + color: 'white', + }, + wrong: { + bg: 'red', + color: 'white', + }, +}; + +function letterClue(answer: string, guess: string, index: number): LetterClue { + if (answer.length < index || guess.length < index) { + return 'wrong'; + } + const letter = guess[index]; + return letter === answer[index] ? 'exact' : answer.includes(letter) ? 'close' : 'wrong'; +} + +type WordleRowProps = ChildlessViewProps & { + text: string; + maxLength: number; + correctAnswer: string; + revealed?: boolean; +}; + +function WordleRow({ + text, maxLength, correctAnswer, revealed, ...props +}: WordleRowProps) { + const { screenHeight } = React.useContext(LayoutContext); + return ( + + {[...Array(maxLength).keys()].map((_, index) => { + const clue = letterClue(correctAnswer, text, index); + const bg = revealed ? LETTER_COLORS[clue].bg : LETTER_COLORS.unknown.bg; + const color = revealed ? LETTER_COLORS[clue].color : LETTER_COLORS.unknown.color; + return ( + + ); + })} + + ); +} + +export type WordleGameProps = { + word: string; + maxGuesses?: number; + maxLength?: number; +}; + +export function WordleGame({ + word, + maxGuesses = 6, + maxLength = word.length, + ...props +}: WordleGameProps) { + + const { screenHeight } = React.useContext(LayoutContext); + + const [guesses, setGuesses] = React.useState([]); + const [currentGuess, setCurrentGuess] = React.useState(''); + const [keys, setKeys] = React.useState(DEFAULT_KEYS); + + return ( + + + + {[...Array(maxGuesses).keys()].map((_, index) => { + const text = index < guesses.length ? guesses[index] : index === guesses.length ? currentGuess : ''; + return ( + + ); + })} + + + { + if (key.value === 'enter') { + if (currentGuess.length === maxLength) { + setGuesses((prev) => [...prev, currentGuess]); + setKeys((prev) => { + const state = prev; + alert(state); + for (const [index, k] in currentGuess.split('').entries()) { + const clue = letterClue(correctAnswer, currentGuess, index); + for (const row in state) { + const key = row.find((r) => r.value === k); + if (!key) { + continue; + } + key.bg = LETTER_COLORS[clue].bg; + key.color = LETTER_COLORS[clue].color; + } + } + alert(JSON.stringify(state)); + return (prev = state); + }); + setCurrentGuess(''); + } + } else + if (key.value === 'delete') { + setCurrentGuess((prev) => { + if (prev.length < 1) { + return prev; + } + return (prev = prev.substring(0, prev.length - 1)); + }); + } else { + setCurrentGuess((prev) => { + if (prev.length + 1 > maxLength) { + return prev; + } + return prev + key.value; + }); + } + } } /> + + ); +} \ No newline at end of file diff --git a/src/mobile/src/screens/games/wordle/index.ts b/src/mobile/src/screens/games/wordle/index.ts new file mode 100644 index 00000000..212c2847 --- /dev/null +++ b/src/mobile/src/screens/games/wordle/index.ts @@ -0,0 +1 @@ +export * from './WordleGame'; \ No newline at end of file From 85ee7bbb2b41ad06f1987392c71197362b413f17 Mon Sep 17 00:00:00 2001 From: noodleofdeath Date: Fri, 13 Oct 2023 14:34:40 -0400 Subject: [PATCH 2/5] fix(RL-121): reduces memory footprint of widget --- src/mobile/ios/Extensions/Image.swift | 25 +++++------ src/mobile/ios/Extensions/UIImage.swift | 44 ++++++++++++++----- .../ios/Models/PublicSummaryAttributes.swift | 26 +++++------ .../ios/ReadLess.xcodeproj/project.pbxproj | 4 +- .../ios/ReadLessWidget/ReadLessWidget.swift | 19 +++++--- src/mobile/ios/Services/APIClient.swift | 10 ++--- src/mobile/ios/Views/SummaryCard.swift | 13 +++--- 7 files changed, 81 insertions(+), 60 deletions(-) diff --git a/src/mobile/ios/Extensions/Image.swift b/src/mobile/ios/Extensions/Image.swift index 98617e97..b45fd802 100644 --- a/src/mobile/ios/Extensions/Image.swift +++ b/src/mobile/ios/Extensions/Image.swift @@ -6,44 +6,41 @@ // import Foundation -import CoreGraphics import SwiftUI public extension Image { - static func loadAsync(from string: String, maxWidth: CGFloat? = nil) async -> Image? { + static func load(from imageURL: URL, maxWidth: CGFloat) -> Image? { + guard let uiImage = UIImage.load(from: imageURL, maxWidth: maxWidth) else { return nil } + return Image(uiImage: uiImage) + } + + static func loadAsync(from string: String) async -> Image? { guard let url = URL(string: string) else { return nil } - return await self.loadAsync(from: url, maxWidth: maxWidth) + return await self.loadAsync(from: url) } - static func loadAsync(from url: URL?, maxWidth: CGFloat? = nil) async -> Image? { + static func loadAsync(from url: URL?) 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) { + static func load(from string: String, 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) { + static func load(from url: URL?, 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)) - } + completion(Image(uiImage: image)) } }.resume() } diff --git a/src/mobile/ios/Extensions/UIImage.swift b/src/mobile/ios/Extensions/UIImage.swift index 51e64044..edab76bc 100644 --- a/src/mobile/ios/Extensions/UIImage.swift +++ b/src/mobile/ios/Extensions/UIImage.swift @@ -6,20 +6,44 @@ // import Foundation +import ImageIO 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() + static func load(from imageURL: URL, maxWidth: CGFloat) -> UIImage? { + + // Create an CGImageSource that represent an image + let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary + guard let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions) else { + return nil + } + + guard let imageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as Dictionary?, + let pixelWidth = imageProperties[kCGImagePropertyPixelWidth]?.floatValue, + let pixelHeight = imageProperties[kCGImagePropertyPixelHeight]?.floatValue + else { + return nil + } + + let size = CGSize(width: maxWidth, height: maxWidth / CGFloat(pixelWidth) * CGFloat(pixelHeight)) + + // Calculate the desired dimension + let maxDimensionInPixels = max(size.width, size.height) + + // Perform downsampling + let options = [ + kCGImageSourceCreateThumbnailFromImageAlways: true, + kCGImageSourceCreateThumbnailFromImageIfAbsent: true, + kCGImageSourceShouldCacheImmediately: true, + kCGImageSourceCreateThumbnailWithTransform: true, + kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels + ] as CFDictionary + + guard let imgRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options) else { return nil } + return UIImage(cgImage: imgRef) } } + + diff --git a/src/mobile/ios/Models/PublicSummaryAttributes.swift b/src/mobile/ios/Models/PublicSummaryAttributes.swift index 184d6663..bb134717 100644 --- a/src/mobile/ios/Models/PublicSummaryAttributes.swift +++ b/src/mobile/ios/Models/PublicSummaryAttributes.swift @@ -13,7 +13,7 @@ import AnyCodable /** From T, pick a set of properties whose keys are in the union K */ public struct PublicSummaryAttributes: Codable, Hashable { - + public var id: Int public var url: String public var title: String @@ -24,7 +24,7 @@ public struct PublicSummaryAttributes: Codable, Hashable { public var media: [String: String]? public var originalDate: Date? public var translations: [String: String]? - + public init(id: Int, url: String, title: String, @@ -46,7 +46,7 @@ public struct PublicSummaryAttributes: Codable, Hashable { self.originalDate = originalDate self.translations = translations } - + public enum CodingKeys: String, CodingKey, CaseIterable { case id case url @@ -59,9 +59,9 @@ public struct PublicSummaryAttributes: Codable, Hashable { case originalDate case translations } - + // Encodable protocol methods - + public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(id, forKey: .id) @@ -118,13 +118,10 @@ public class Summary { @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) + if let url = primaryImageUrl { + image = Image.load(from: url, maxWidth: 100) + } + publisherIcon = Image.load(from: publisher.icon, maxWidth: 40) } } @@ -135,9 +132,9 @@ public var MOCK_SUMMARY_1 = Summary( url: "https://readless.ai", title: "Summary Preview", shortSummary: "Short Summary", - publisher: PublicPublisherAttributes(name: "cnn", + publisher: PublicPublisherAttributes(name: "cnn", displayName: "CNN"), - category: PublicCategoryAttributes(name: "sports", + category: PublicCategoryAttributes(name: "sports", displayName: "Sports", icon: "basketball") )) @@ -177,3 +174,4 @@ public var MOCK_SUMMARY_4 = Summary( displayName: "politics", icon: "bank") )) + diff --git a/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj b/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj index 2f381ac9..48cea8a9 100644 --- a/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj +++ b/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj @@ -1722,7 +1722,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.0; + MARKETING_VERSION = 1.16.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", @@ -1763,7 +1763,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.17.0; + MARKETING_VERSION = 1.16.4; OTHER_LDFLAGS = ( "$(inherited)", "-ObjC", diff --git a/src/mobile/ios/ReadLessWidget/ReadLessWidget.swift b/src/mobile/ios/ReadLessWidget/ReadLessWidget.swift index a5a8f4a1..2abd97fe 100644 --- a/src/mobile/ios/ReadLessWidget/ReadLessWidget.swift +++ b/src/mobile/ios/ReadLessWidget/ReadLessWidget.swift @@ -12,8 +12,13 @@ import SwiftUI let DEFAULT_TIMELINE_INTERVAL: Double = 10 +struct DEEPLINKS { + static let topStories = "https://readless.ai/top" + static let liveFeed = "https://readless.ai/live" +} + struct CustomWidgetConfiguration { - var channel: Channel = .liveFeed + var channel: Channel = .topStories var topic: String? var updateInterval: Measurement? } @@ -41,7 +46,7 @@ var WidgetPlaceholders: Dictionary = [ func buildEntries(in context: TimelineProviderContext, for configuration: CustomWidgetConfiguration) async -> [SummaryEntry] { - let endpoint = configuration.channel == .topStories ? Endpoints.GetTopStories : Endpoints.GetSummaries + let endpoint = configuration.channel == .topStories ? ENDPOINTS.GetTopStories : ENDPOINTS.GetSummaries let filter = configuration.channel == .topStories ? "" : configuration.channel == .liveFeed ? "" : configuration.topic let summaries = Array(await APIClient().fetchAsync(endpoint: endpoint, filter: filter).reversed()) @@ -50,13 +55,13 @@ func buildEntries(in context: TimelineProviderContext, for i in stride(from: 0, to: summaries.count, by: pageSize) { let first = summaries[i] if context.family != .systemSmall { - await first.loadImagesAsync() + first.loadImages() } var subset = [first] for j in 1 ..< pageSize { if let next = i + j < summaries.count ? summaries[i + j] : nil { if context.family != .systemSmall { - await next.loadImagesAsync() + next.loadImages() } subset.insert(next, at: 0) } @@ -182,13 +187,13 @@ struct ReadLessWidgetEntryView : View { var deeplink: URL { if entry.context?.family == .systemSmall { - return entry.summaries.first?.deeplink ?? URL(string: "https://readless.ai/top")! + return entry.summaries.first?.deeplink ?? URL(string: DEEPLINKS.topStories)! } if entry.config?.channel == .liveFeed { - return URL(string: "https://readless.ai/live")! + return URL(string: DEEPLINKS.liveFeed)! } if entry.config?.channel == .topStories { - return URL(string: "https://readless.ai/top")! + return URL(string: DEEPLINKS.topStories)! } return URL(string: "https://readless.ai/search?filter=\(entry.config?.topic ?? "")")! } diff --git a/src/mobile/ios/Services/APIClient.swift b/src/mobile/ios/Services/APIClient.swift index e51bbd61..e0b4b118 100644 --- a/src/mobile/ios/Services/APIClient.swift +++ b/src/mobile/ios/Services/APIClient.swift @@ -7,14 +7,14 @@ import SwiftUI -struct Endpoints { +struct ENDPOINTS { static let Root = "https://api.readless.ai/v1" static let GetSummaries = "\(Root)/summary" static let GetTopStories = "\(Root)/summary/top" static let GetCategories = "\(Root)/category" } -func parseQuery(endpoint: String = Endpoints.GetSummaries, filter: String?) -> URL? { +func parseQuery(endpoint: String = ENDPOINTS.GetSummaries, filter: String?) -> URL? { guard let filter = filter?.addingPercentEncoding(withAllowedCharacters: .urlHostAllowed) else { return URL(string: endpoint) } return URL(string: endpoint + "?filter=\(filter)") @@ -67,7 +67,7 @@ class APIClient: ObservableObject { return self.fetchSync(nil) } - @Sendable func fetchSync(endpoint: String = Endpoints.GetSummaries, filter: String? = "", _ callback: ((_ summaries: [Summary]) -> ())?) { + @Sendable func fetchSync(endpoint: String = ENDPOINTS.GetSummaries, filter: String? = "", _ callback: ((_ summaries: [Summary]) -> ())?) { guard let url = parseQuery(endpoint: endpoint, filter: filter) else { return } @@ -82,7 +82,7 @@ class APIClient: ObservableObject { }.resume() } - @Sendable func fetchAsync(endpoint: String = Endpoints.GetSummaries, filter: String? = "") async -> [Summary] { + @Sendable func fetchAsync(endpoint: String = ENDPOINTS.GetSummaries, filter: String? = "") async -> [Summary] { guard let url = parseQuery(endpoint: endpoint, filter: filter) else { return [] } @@ -96,7 +96,7 @@ class APIClient: ObservableObject { } @Sendable func getCategories() async -> [PublicCategoryAttributes] { - guard let url = URL(string: Endpoints.GetCategories) else { return [] } + guard let url = URL(string: ENDPOINTS.GetCategories) else { return [] } print("fetching categories") let request = URLRequest(url: url) guard let (data, _) = try? await URLSession.shared.data(for: request) else { diff --git a/src/mobile/ios/Views/SummaryCard.swift b/src/mobile/ios/Views/SummaryCard.swift index 45f74335..60ea56a5 100644 --- a/src/mobile/ios/Views/SummaryCard.swift +++ b/src/mobile/ios/Views/SummaryCard.swift @@ -18,9 +18,6 @@ struct SummaryCard: View { var expanded: Bool = false var deeplink: Bool = false - @State var image: Image? = nil - @State var publisherIcon: Image? = nil - @Environment(\.colorScheme) private var colorScheme var backdrop: Color { @@ -50,7 +47,7 @@ struct SummaryCard: View { var HEADER: some View { HStack { if let summary = summary { - if let image = summary.publisherIcon ?? publisherIcon { + if let image = summary.publisherIcon { image .resizable() .frame(width: headerHeight, height: headerHeight) @@ -59,7 +56,7 @@ struct SummaryCard: View { Text("") .frame(width: headerHeight, height: headerHeight) .onAppear { - Image.load(from: summary.publisher.icon) { image in DispatchQueue.main.async { self.publisherIcon = image } } + Image.load(from: summary.publisher.icon) { image in DispatchQueue.main.async { self.summary?.publisherIcon = image } } } } Text(summary.publisher.displayName) @@ -104,18 +101,18 @@ struct SummaryCard: View { var IMAGE: some View { VStack { if let summary = summary { - if let image = summary.image ?? image { + if let image = summary.image { image .resizable() .scaledToFill() .frame(width: imageHeight, height: imageHeight) } else { - Text("Loading Image...") + Text("") .font(headerFont) .frame(width: imageHeight, height: imageHeight) .onAppear { if let url = summary.primaryImageUrl { - Image.load(from: url) { image in DispatchQueue.main.async { self.image = image } } + Image.load(from: url) { image in DispatchQueue.main.async { self.summary?.image = image } } } } } From 762218e08ef2a9dd91724028a9d36f0b6275e725 Mon Sep 17 00:00:00 2001 From: noodleofdeath Date: Fri, 13 Oct 2023 15:28:58 -0400 Subject: [PATCH 3/5] v1.16.4 --- src/mobile/ios/Extensions/Image.swift | 26 +-- .../ios/ReadLess.xcodeproj/project.pbxproj | 105 +++++++++- .../Base.lproj/LaunchScreen.storyboard | 52 ++--- .../ios/ReadLess/LaunchScreen.storyboard | 186 ------------------ .../ReadLess/ar.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/ca.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/cs.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/da.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/de.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/el.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/en-AU.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/en-GB.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/en-IN.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/en.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/es.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/fa.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/fi.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/fr-CA.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/fr.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/he.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/hi.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/hr.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/hu.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/id.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/it-IT.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/it.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/ja.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/ko.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/ms.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/nb.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/nl.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/pl.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/pt-BR.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/pt-PT.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/ro.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/ru.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/sk.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/sv.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/th.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/tr.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/uk.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/vi.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/zh-HK.lproj/LaunchScreen.strings | 63 ++++++ .../zh-Hans-CN.lproj/LaunchScreen.strings | 63 ++++++ .../zh-Hans.lproj/LaunchScreen.strings | 63 ++++++ .../zh-Hant-TW.lproj/LaunchScreen.strings | 63 ++++++ .../zh-Hant.lproj/LaunchScreen.strings | 63 ++++++ .../ReadLess/zh.lproj/LaunchScreen.strings | 63 ++++++ .../Views/ContentView.swift | 34 +++- src/mobile/ios/ReadLessWidget/Channel.swift | 2 +- .../ios/ReadLessWidget/ReadLessWidget.swift | 53 +++-- .../WidgetTopicConfiguration.swift | 2 +- src/mobile/ios/Services/APIClient.swift | 10 +- src/mobile/ios/Views/SummaryCard.swift | 11 +- 54 files changed, 2983 insertions(+), 270 deletions(-) delete mode 100644 src/mobile/ios/ReadLess/LaunchScreen.storyboard create mode 100644 src/mobile/ios/ReadLess/ar.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/ca.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/cs.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/da.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/de.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/el.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/en-AU.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/en-GB.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/en-IN.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/en.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/es.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/fa.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/fi.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/fr-CA.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/fr.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/he.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/hi.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/hr.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/hu.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/id.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/it-IT.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/it.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/ja.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/ko.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/ms.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/nb.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/nl.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/pl.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/pt-BR.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/pt-PT.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/ro.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/ru.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/sk.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/sv.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/th.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/tr.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/uk.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/vi.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/zh-HK.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/zh-Hans-CN.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/zh-Hans.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/zh-Hant-TW.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/zh-Hant.lproj/LaunchScreen.strings create mode 100644 src/mobile/ios/ReadLess/zh.lproj/LaunchScreen.strings diff --git a/src/mobile/ios/Extensions/Image.swift b/src/mobile/ios/Extensions/Image.swift index b45fd802..aa36d572 100644 --- a/src/mobile/ios/Extensions/Image.swift +++ b/src/mobile/ios/Extensions/Image.swift @@ -15,19 +15,6 @@ public extension Image { return Image(uiImage: uiImage) } - static func loadAsync(from string: String) async -> Image? { - guard let url = URL(string: string) else { return nil } - return await self.loadAsync(from: url) - } - - static func loadAsync(from url: URL?) 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 } - return Image(uiImage: image) - } - static func load(from string: String, completion: @escaping @Sendable (_ image: Image?) -> Void) { guard let imageUrl = URL(string: string) else { return } return self.load(from: imageUrl, completion: completion) @@ -45,4 +32,17 @@ public extension Image { }.resume() } + static func loadAsync(from string: String) async -> Image? { + guard let url = URL(string: string) else { return nil } + return await self.loadAsync(from: url) + } + + static func loadAsync(from url: URL?) 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 } + return Image(uiImage: image) + } + } diff --git a/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj b/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj index 48cea8a9..33183fff 100644 --- a/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj +++ b/src/mobile/ios/ReadLess.xcodeproj/project.pbxproj @@ -66,13 +66,13 @@ 7699B88040F8A987B510C191 /* libPods-ReadLess-ReadLessTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 19F6CBCC0A4E27FBF8BF4A61 /* libPods-ReadLess-ReadLessTests.a */; }; CD4BF5A52AD074F7006BDC35 /* WidgetTopicConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4BF5A42AD074F7006BDC35 /* WidgetTopicConfiguration.swift */; }; CD4BF5A62AD0797C006BDC35 /* WidgetTopicConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD4BF5A42AD074F7006BDC35 /* WidgetTopicConfiguration.swift */; }; + CD777A222AD9D16600804569 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CD777A242AD9D16600804569 /* LaunchScreen.storyboard */; }; CD78217A2A28FA2100F3B33A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CD7821792A28FA2100F3B33A /* StoreKit.framework */; }; CD92BEB22AD093940096D6DA /* Intents.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = CDE160082ACC8CDB0036F0A4 /* Intents.intentdefinition */; }; CDA6AD302AD22FD900514CD1 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA6AD2F2AD22FD900514CD1 /* Channel.swift */; }; CDA6AD312AD22FD900514CD1 /* Channel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDA6AD2F2AD22FD900514CD1 /* Channel.swift */; }; CDE11B1F2A7701BE00C0BC39 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = CDE11B1E2A7701BE00C0BC39 /* GoogleService-Info.plist */; }; CDE11B212A786FF500C0BC39 /* PublicPublisherAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE11B202A786FF500C0BC39 /* PublicPublisherAttributes.swift */; }; - CDE15EEA2ACB27A00036F0A4 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CDE15EEB2ACB27A00036F0A4 /* LaunchScreen.storyboard */; }; CDE15FB92ACB497E0036F0A4 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0150ACA429F9577400875CF7 /* WidgetKit.framework */; }; CDE15FBA2ACB497E0036F0A4 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 0150ACA629F9577400875CF7 /* SwiftUI.framework */; }; CDE15FBD2ACB497E0036F0A4 /* ReadLessWidgetBundle.swift in Sources */ = {isa = PBXBuildFile; fileRef = CDE15FBC2ACB497E0036F0A4 /* ReadLessWidgetBundle.swift */; }; @@ -287,6 +287,51 @@ 5DCACB8F33CDC322A6C60F78 /* libPods-ReadLess.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-ReadLess.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 89C6BE57DB24E9ADA2F236DE /* Pods-ReadLess-ReadLessTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ReadLess-ReadLessTests.release.xcconfig"; path = "Target Support Files/Pods-ReadLess-ReadLessTests/Pods-ReadLess-ReadLessTests.release.xcconfig"; sourceTree = ""; }; CD4BF5A42AD074F7006BDC35 /* WidgetTopicConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = WidgetTopicConfiguration.swift; path = /Users/thom/git/noodleofdeath/readless/src/mobile/ios/ReadLessWidget/WidgetTopicConfiguration.swift; sourceTree = ""; }; + CD777A232AD9D16600804569 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; + CD777A272AD9D18800804569 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A282AD9D18D00804569 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A292AD9D19000804569 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A2A2AD9D19100804569 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A2B2AD9D19400804569 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-HK"; path = "zh-HK.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A2C2AD9D19600804569 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A2D2AD9D19800804569 /* zh-Hans-CN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans-CN"; path = "zh-Hans-CN.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A2E2AD9D19A00804569 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A2F2AD9D19C00804569 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A302AD9D19E00804569 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A312AD9D1A000804569 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A322AD9D1A200804569 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A332AD9D1A400804569 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A342AD9D1A500804569 /* en-AU */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-AU"; path = "en-AU.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A352AD9D1A700804569 /* en-IN */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-IN"; path = "en-IN.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A362AD9D1A900804569 /* en-GB */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "en-GB"; path = "en-GB.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A372AD9D1AB00804569 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A382AD9D1AE00804569 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A392AD9D1AF00804569 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "fr-CA"; path = "fr-CA.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A3A2AD9D1B100804569 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A3B2AD9D1B300804569 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A3C2AD9D1B500804569 /* he */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = he; path = he.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A3D2AD9D1B700804569 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A3E2AD9D1B900804569 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A3F2AD9D1BB00804569 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A402AD9D1BD00804569 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A412AD9D1BF00804569 /* it-IT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "it-IT"; path = "it-IT.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A422AD9D1C100804569 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A432AD9D1C300804569 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A442AD9D1C500804569 /* ms */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ms; path = ms.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A452AD9D1C700804569 /* nb */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nb; path = nb.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A462AD9D1CB00804569 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A472AD9D1CD00804569 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A482AD9D1CF00804569 /* pt-BR */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-BR"; path = "pt-BR.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A4A2AD9D1D100804569 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/LaunchScreen.strings"; sourceTree = ""; }; + CD777A4B2AD9D1D300804569 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A4C2AD9D1D500804569 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A4D2AD9D1D700804569 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A4E2AD9D1DA00804569 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A4F2AD9D1DC00804569 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A502AD9D1DE00804569 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A512AD9D1E000804569 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A522AD9D1E100804569 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/LaunchScreen.strings; sourceTree = ""; }; + CD777A532AD9D1E300804569 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/LaunchScreen.strings; sourceTree = ""; }; CD7821792A28FA2100F3B33A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; }; CD84722A2ACB266C00683959 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Base; path = Base.lproj/Info.plist; sourceTree = ""; }; CD84722C2ACB268E00683959 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = en.lproj/Info.plist; sourceTree = ""; }; @@ -336,7 +381,6 @@ CDA6AD2F2AD22FD900514CD1 /* Channel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Channel.swift; sourceTree = ""; }; CDE11B1E2A7701BE00C0BC39 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; CDE11B202A786FF500C0BC39 /* PublicPublisherAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PublicPublisherAttributes.swift; sourceTree = ""; }; - CDE15EEB2ACB27A00036F0A4 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = LaunchScreen.storyboard; path = ReadLess/LaunchScreen.storyboard; sourceTree = ""; }; CDE15FB82ACB497E0036F0A4 /* ReadLessWidgetExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = ReadLessWidgetExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; CDE15FBC2ACB497E0036F0A4 /* ReadLessWidgetBundle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadLessWidgetBundle.swift; sourceTree = ""; }; CDE15FC02ACB497E0036F0A4 /* ReadLessWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReadLessWidget.swift; sourceTree = ""; }; @@ -565,7 +609,7 @@ 13B07FB71A68108700A75B9A /* main.m */, CD84722B2ACB266C00683959 /* Info.plist */, 13B07FB51A68108700A75B9A /* Images.xcassets */, - CDE15EEB2ACB27A00036F0A4 /* LaunchScreen.storyboard */, + CD777A242AD9D16600804569 /* LaunchScreen.storyboard */, ); name = ReadLess; sourceTree = ""; @@ -995,7 +1039,7 @@ CDE9EB502A48A3770057217B /* Faustina-Italic.ttf in Resources */, 0125BD3D29D791ED00EAC61C /* Feather.ttf in Resources */, CDE9EB642A48A3770057217B /* Newsreader72pt-Italic.ttf in Resources */, - CDE15EEA2ACB27A00036F0A4 /* LaunchScreen.storyboard in Resources */, + CD777A222AD9D16600804569 /* LaunchScreen.storyboard in Resources */, CDE9EB522A48A3770057217B /* AnekLatin-Regular.ttf in Resources */, CDE9EB662A48A3770057217B /* Faustina-Bold.ttf in Resources */, 13B07FBF1A68108700A75B9A /* Images.xcassets in Resources */, @@ -1368,6 +1412,59 @@ /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ + CD777A242AD9D16600804569 /* LaunchScreen.storyboard */ = { + isa = PBXVariantGroup; + children = ( + CD777A232AD9D16600804569 /* Base */, + CD777A272AD9D18800804569 /* en */, + CD777A282AD9D18D00804569 /* ar */, + CD777A292AD9D19000804569 /* ca */, + CD777A2A2AD9D19100804569 /* zh */, + CD777A2B2AD9D19400804569 /* zh-HK */, + CD777A2C2AD9D19600804569 /* zh-Hans */, + CD777A2D2AD9D19800804569 /* zh-Hans-CN */, + CD777A2E2AD9D19A00804569 /* zh-Hant */, + CD777A2F2AD9D19C00804569 /* zh-Hant-TW */, + CD777A302AD9D19E00804569 /* hr */, + CD777A312AD9D1A000804569 /* cs */, + CD777A322AD9D1A200804569 /* da */, + CD777A332AD9D1A400804569 /* nl */, + CD777A342AD9D1A500804569 /* en-AU */, + CD777A352AD9D1A700804569 /* en-IN */, + CD777A362AD9D1A900804569 /* en-GB */, + CD777A372AD9D1AB00804569 /* fi */, + CD777A382AD9D1AE00804569 /* fr */, + CD777A392AD9D1AF00804569 /* fr-CA */, + CD777A3A2AD9D1B100804569 /* de */, + CD777A3B2AD9D1B300804569 /* el */, + CD777A3C2AD9D1B500804569 /* he */, + CD777A3D2AD9D1B700804569 /* hi */, + CD777A3E2AD9D1B900804569 /* hu */, + CD777A3F2AD9D1BB00804569 /* id */, + CD777A402AD9D1BD00804569 /* it */, + CD777A412AD9D1BF00804569 /* it-IT */, + CD777A422AD9D1C100804569 /* ja */, + CD777A432AD9D1C300804569 /* ko */, + CD777A442AD9D1C500804569 /* ms */, + CD777A452AD9D1C700804569 /* nb */, + CD777A462AD9D1CB00804569 /* fa */, + CD777A472AD9D1CD00804569 /* pl */, + CD777A482AD9D1CF00804569 /* pt-BR */, + CD777A4A2AD9D1D100804569 /* pt-PT */, + CD777A4B2AD9D1D300804569 /* ro */, + CD777A4C2AD9D1D500804569 /* ru */, + CD777A4D2AD9D1D700804569 /* sk */, + CD777A4E2AD9D1DA00804569 /* es */, + CD777A4F2AD9D1DC00804569 /* sv */, + CD777A502AD9D1DE00804569 /* th */, + CD777A512AD9D1E000804569 /* tr */, + CD777A522AD9D1E100804569 /* uk */, + CD777A532AD9D1E300804569 /* vi */, + ); + name = LaunchScreen.storyboard; + path = ReadLess; + sourceTree = ""; + }; CD84722B2ACB266C00683959 /* Info.plist */ = { isa = PBXVariantGroup; children = ( diff --git a/src/mobile/ios/ReadLess/Base.lproj/LaunchScreen.storyboard b/src/mobile/ios/ReadLess/Base.lproj/LaunchScreen.storyboard index 33ce5c87..3deda00c 100644 --- a/src/mobile/ios/ReadLess/Base.lproj/LaunchScreen.storyboard +++ b/src/mobile/ios/ReadLess/Base.lproj/LaunchScreen.storyboard @@ -1,9 +1,9 @@ - + - + @@ -13,11 +13,11 @@ - + - + @@ -32,130 +32,130 @@ - +