diff --git a/clients/ios/Classes/FeedDetailCardView.swift b/clients/ios/Classes/FeedDetailCardView.swift index 10a4ff7d3..99fbfacc1 100644 --- a/clients/ios/Classes/FeedDetailCardView.swift +++ b/clients/ios/Classes/FeedDetailCardView.swift @@ -17,10 +17,9 @@ struct CardView: View { var body: some View { ZStack(alignment: .leading) { if story.isSelected || cache.isGrid { - RoundedRectangle(cornerRadius: 10).foregroundColor(.init(white: 0.9)) + RoundedRectangle(cornerRadius: 10).foregroundColor(highlightColor) CardFeedBarView(cache: cache, story: story) - .clipShape(RoundedRectangle(cornerRadius: 10)) .padding(.leading, 2) } else { CardFeedBarView(cache: cache, story: story) @@ -39,15 +38,16 @@ struct CardView: View { } HStack { - if !cache.isGrid, cache.settings.listPreview.isLeft, let previewImage { + if !cache.isGrid, cache.settings.preview.isLeft, let previewImage { listPreview(image: previewImage) } CardContentView(cache: cache, story: story) .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) - .padding(15) + .padding([.leading, .trailing], 15) + .padding([.top, .bottom], cache.settings.spacing == .compact ? 10 : 15) - if !cache.isGrid, !cache.settings.listPreview.isLeft, let previewImage { + if !cache.isGrid, !cache.settings.preview.isLeft, let previewImage { listPreview(image: previewImage) // Image(uiImage: previewImage) @@ -60,6 +60,9 @@ struct CardView: View { } } } + .if(cache.isGrid || story.isSelected) { view in + view.clipShape(RoundedRectangle(cornerRadius: 10)) + } .if(story.isSelected) { view in view.padding(10) } @@ -130,8 +133,16 @@ struct CardView: View { // } // } + var highlightColor: Color { + if cache.isGrid { + return Color.themed([0xFDFCFA, 0xFFFDEF, 0x4F4F4F, 0x292B2C]) + } else { + return Color.themed([0xFFFDEF, 0xEEECCD, 0x303A40, 0x303030]) + } + } + var previewImage: UIImage? { - guard cache.settings.listPreview != .none, let image = cache.appDelegate.cachedImage(forStoryHash: story.hash), image.isKind(of: UIImage.self) else { + guard cache.settings.preview != .none, let image = cache.appDelegate.cachedImage(forStoryHash: story.hash), image.isKind(of: UIImage.self) else { return nil } @@ -151,9 +162,9 @@ struct CardView: View { @ViewBuilder func listPreview(image: UIImage) -> some View { - let isLeft = cache.settings.listPreview.isLeft + let isLeft = cache.settings.preview.isLeft - if cache.settings.listPreview.isSmall { + if cache.settings.preview.isSmall { Image(uiImage: image) .resizable() .scaledToFill() @@ -167,7 +178,7 @@ struct CardView: View { .resizable() .scaledToFill() .frame(width: 90) - .cornerRadius(10, corners: isLeft || !story.isSelected ? [] : [.topRight, .bottomRight]) + .clipped() .padding(.leading, isLeft ? 8 : -10) .padding(.trailing, isLeft ? -10 : 0) } @@ -208,8 +219,9 @@ struct CardContentView: View { .padding(.leading, 24) Text(story.feedName) - .font(.custom("WhitneySSm-Medium", size: 10, relativeTo: .caption)) + .font(font(named: "WhitneySSm-Medium", size: 10)) .lineLimit(1) + .foregroundColor(feedColor) } } @@ -238,14 +250,28 @@ struct CardContentView: View { } Text(story.title) - .font(.custom("WhitneySSm-Medium", size: 18, relativeTo: .caption).bold()) + .font(font(named: "WhitneySSm-Medium", size: 18).bold()) + .foregroundColor(titleColor) + .lineLimit(cache.isGrid ? StorySettings.Content.titleLimit : cache.settings.content.limit) + .truncationMode(.tail) } - Text(story.content.prefix(400)) - .font(.custom("WhitneySSm-Book", size: 13, relativeTo: .caption)) - .padding(.top, 5) + .padding(.bottom, cache.settings.spacing == .compact ? -5 : 0) + + if cache.isGrid || cache.settings.content != .title { + Text(story.content) + .font(font(named: "WhitneySSm-Book", size: 13)) + .foregroundColor(contentColor) + .lineLimit(cache.isGrid ? StorySettings.Content.contentLimit : cache.settings.content.limit) + .truncationMode(.tail) + .padding(.top, 5) + .padding(.bottom, cache.settings.spacing == .compact ? -5 : 0) + } + Spacer() + Text(story.dateAndAuthor) - .font(.custom("WhitneySSm-Medium", size: 10, relativeTo: .caption)) + .font(font(named: "WhitneySSm-Medium", size: 10)) + .foregroundColor(dateAndAuthorColor) .padding(.top, 5) } } @@ -270,6 +296,40 @@ struct CardContentView: View { return UIImage(named: "indicator-unread") } } + + func font(named: String, size: CGFloat) -> Font { + return Font.custom(named, size: size + cache.settings.fontSize.offset, relativeTo: .caption) + } + + var feedColor: Color { + return contentColor + } + + var titleColor: Color { + if story.isSelected { + return Color.themed([0x686868, 0xA0A0A0]) + } else if story.isRead { + return Color.themed([0x585858, 0x585858, 0x989898, 0x888888]) + } else { + return Color.themed([0x111111, 0x333333, 0xD0D0D0, 0xCCCCCC]) + } + } + + var contentColor: Color { + if story.isSelected, story.isRead { + return Color.themed([0xB8B8B8, 0xB8B8B8, 0xA0A0A0, 0x707070]) + } else if story.isSelected { + return Color.themed([0x888785, 0x686868, 0xA9A9A9, 0x989898]) + } else if story.isRead { + return Color.themed([0xB8B8B8, 0xB8B8B8, 0xA0A0A0, 0x707070]) + } else { + return Color.themed([0x404040, 0x404040, 0xC0C0C0, 0xB0B0B0]) + } + } + + var dateAndAuthorColor: Color { + return contentColor + } } struct CardFeedBarView: View { diff --git a/clients/ios/Classes/FeedDetailGridView.swift b/clients/ios/Classes/FeedDetailGridView.swift index 323c46bdf..585f33e41 100644 --- a/clients/ios/Classes/FeedDetailGridView.swift +++ b/clients/ios/Classes/FeedDetailGridView.swift @@ -71,6 +71,7 @@ struct FeedDetailGridView: View { view.padding() } } + .background(Color.themed([0xF4F4F4, 0xFFFDEF, 0x4F4F4F, 0x101010])) } @ViewBuilder diff --git a/clients/ios/Classes/FeedDetailStoryView.swift b/clients/ios/Classes/FeedDetailStoryView.swift index 5df4c8d27..940f1bbb7 100644 --- a/clients/ios/Classes/FeedDetailStoryView.swift +++ b/clients/ios/Classes/FeedDetailStoryView.swift @@ -19,7 +19,7 @@ struct StoryView: View { var body: some View { VStack { ZStack { - Color(white: 0.9) + Color.themed([0xFFFDEF, 0xEEECCD, 0x303A40, 0x303030]) HStack { Text(story.title) @@ -36,7 +36,7 @@ struct StoryView: View { } } .font(.custom("WhitneySSm-Medium", size: 14, relativeTo: .body)) - .foregroundColor(.secondary) + .foregroundColor(Color.themed([0x686868, 0xA0A0A0])) .frame(height: 50) .clipShape(RoundedRectangle(cornerRadius: 10)) .onTapGesture { @@ -48,7 +48,7 @@ struct StoryView: View { } var previewImage: UIImage? { - guard cache.settings.listPreview != .none, let image = cache.appDelegate.cachedImage(forStoryHash: story.hash), image.isKind(of: UIImage.self) else { + guard cache.settings.preview != .none, let image = cache.appDelegate.cachedImage(forStoryHash: story.hash), image.isKind(of: UIImage.self) else { return nil } diff --git a/clients/ios/Classes/Story.swift b/clients/ios/Classes/Story.swift index d056fcce5..65c17969a 100644 --- a/clients/ios/Classes/Story.swift +++ b/clients/ios/Classes/Story.swift @@ -24,6 +24,7 @@ class Story: Identifiable { var content = "" var dateString = "" var timestamp = 0 + var isRead = false var isSaved = false var isShared = false var score = 0 @@ -99,6 +100,7 @@ class Story: Identifiable { score = Int(NewsBlurAppDelegate.computeStoryScore(intelligence)) } + isRead = score < 0 isRiverOrSocial = storiesCollection.isRiverOrSocial } @@ -107,9 +109,9 @@ class Story: Identifiable { let scanner = Scanner(string: hex) var color: Int64 = 0 scanner.scanHexInt64(&color) - let array = [NSNumber(value: color)] + let value = Int(color) - return ThemeManager.color(fromRGB: array) + return ThemeManager.shared.fixedColor(fromRGB: value) ?? UIColor.gray } } @@ -149,19 +151,44 @@ class StoryCache: ObservableObject { class StorySettings { let defaults = UserDefaults.standard - //TODO: 🚧 -// var listContent: Int { -// NSString *preferenceKey = @"story_list_preview_text_size"; -// NSArray *titles = @[@"Title", @"content_preview_small.png", @"content_preview_medium.png", @"content_preview_large.png"]; -// NSArray *values = @[@"title", @"short", @"medium", @"long"]; -// } + enum Content: String, RawRepresentable { + case title + case short + case medium + case long + + static let titleLimit = 6 + + static let contentLimit = 10 + + var limit: Int { + switch self { + case .title: + return 6 + case .short: + return 2 + case .medium: + return 4 + case .long: + return 6 + } + } + } - enum ListPreview { + var content: Content { + if let string = defaults.string(forKey: "story_list_preview_text_size"), let value = Content(rawValue: string) { + return value + } else { + return .short + } + } + + enum Preview: String, RawRepresentable { case none - case smallLeft - case largeLeft - case largeRight - case smallRight + case smallLeft = "small_left" + case largeLeft = "large_left" + case largeRight = "large_right" + case smallRight = "small_right" var isLeft: Bool { return [.smallLeft, .largeLeft].contains(self) @@ -172,31 +199,57 @@ class StorySettings { } } - var listPreview: ListPreview { - switch defaults.string(forKey: "story_list_preview_images_size") { - case "none": - return .none - case "small_left": - return .smallLeft - case "large_left": - return .largeLeft - case "large_right": - return .largeRight - default: + var preview: Preview { + if let string = defaults.string(forKey: "story_list_preview_images_size"), let value = Preview(rawValue: string) { + return value + } else { return .smallRight } } - //TODO: 🚧 -// NSString *preferenceKey = @"feed_list_font_size"; -// NSArray *titles = @[@"XS", @"S", @"M", @"L", @"XL"]; -// NSArray *values = @[@"xs", @"small", @"medium", @"large", @"xl"]; + enum FontSize: String, RawRepresentable { + case xs + case small + case medium + case large + case xl + + var offset: CGFloat { + switch self { + case .xs: + return -2 + case .small: + return -1 + case .medium: + return 0 + case .large: + return 1 + case .xl: + return 2 + } + } + } - //TODO: 🚧 -// preferenceKey = @"feed_list_spacing"; -// titles = @[@"Compact", @"Comfortable"]; -// values = @[@"compact", @"comfortable"]; + var fontSize: FontSize { + if let string = defaults.string(forKey: "feed_list_font_size"), let value = FontSize(rawValue: string) { + return value + } else { + return .medium + } + } + enum Spacing: String, RawRepresentable { + case compact + case comfortable + } + + var spacing: Spacing { + if let string = defaults.string(forKey: "feed_list_spacing"), let value = Spacing(rawValue: string) { + return value + } else { + return .comfortable + } + } // enum GridColumns: String { // case auto = "auto" diff --git a/clients/ios/Classes/SwiftUIUtilities.swift b/clients/ios/Classes/SwiftUIUtilities.swift index 40d06a9c0..86361de85 100644 --- a/clients/ios/Classes/SwiftUIUtilities.swift +++ b/clients/ios/Classes/SwiftUIUtilities.swift @@ -53,4 +53,8 @@ extension Color { return Color(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1) ) } + + static func themed(_ hex: [NSNumber]) -> Color { + return Color(ThemeManager.color(fromRGB: hex)) + } }