mirror of
https://github.com/SideStore/SideStore.git
synced 2026-02-11 07:43:28 +01:00
[CHANGE] Overhaul of the AppDetailView with version history, reviews & ratings, and app information
This commit is contained in:
@@ -55,16 +55,24 @@ internal enum L10n {
|
||||
internal static let restoreBackup = L10n.tr("Localizable", "AppAction.restoreBackup", fallback: "Restore backup")
|
||||
}
|
||||
internal enum AppDetailView {
|
||||
///
|
||||
internal static let information = L10n.tr("Localizable", "AppDetailView.information", fallback: "")
|
||||
/// More...
|
||||
internal static let more = L10n.tr("Localizable", "AppDetailView.more", fallback: "More...")
|
||||
/// The app requires no permissions.
|
||||
internal static let noPermissions = L10n.tr("Localizable", "AppDetailView.noPermissions", fallback: "The app requires no permissions.")
|
||||
/// No screenshots available for this app.
|
||||
internal static let noScreenshots = L10n.tr("Localizable", "AppDetailView.noScreenshots", fallback: "No screenshots available for this app.")
|
||||
/// No version information
|
||||
internal static let noVersionInformation = L10n.tr("Localizable", "AppDetailView.noVersionInformation", fallback: "No version information")
|
||||
/// Permissions
|
||||
internal static let permissions = L10n.tr("Localizable", "AppDetailView.permissions", fallback: "Permissions")
|
||||
/// Version
|
||||
internal static let version = L10n.tr("Localizable", "AppDetailView.version", fallback: "Version")
|
||||
/// Ratings & Reviews
|
||||
internal static let reviews = L10n.tr("Localizable", "AppDetailView.reviews", fallback: "Ratings & Reviews")
|
||||
/// Version %@
|
||||
internal static func version(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.version", String(describing: p1), fallback: "Version %@")
|
||||
}
|
||||
/// What's New
|
||||
internal static let whatsNew = L10n.tr("Localizable", "AppDetailView.whatsNew", fallback: "What's New")
|
||||
internal enum Badge {
|
||||
@@ -73,6 +81,46 @@ internal enum L10n {
|
||||
/// From Trusted Source
|
||||
internal static let trusted = L10n.tr("Localizable", "AppDetailView.Badge.trusted", fallback: "From Trusted Source")
|
||||
}
|
||||
internal enum Information {
|
||||
/// Compatibility
|
||||
internal static let compatibility = L10n.tr("Localizable", "AppDetailView.Information.compatibility", fallback: "Compatibility")
|
||||
/// Requires iOS %@ or higher
|
||||
internal static func compatibilityAtLeast(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Information.compatibilityAtLeast", String(describing: p1), fallback: "Requires iOS %@ or higher")
|
||||
}
|
||||
/// Requires iOS %@ or lower
|
||||
internal static func compatibilityOrLower(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Information.compatibilityOrLower", String(describing: p1), fallback: "Requires iOS %@ or lower")
|
||||
}
|
||||
/// Unknown
|
||||
internal static let compatibilityUnknown = L10n.tr("Localizable", "AppDetailView.Information.compatibilityUnknown", fallback: "Unknown")
|
||||
/// Developer
|
||||
internal static let developer = L10n.tr("Localizable", "AppDetailView.Information.developer", fallback: "Developer")
|
||||
/// Latest Version
|
||||
internal static let latestVersion = L10n.tr("Localizable", "AppDetailView.Information.latestVersion", fallback: "Latest Version")
|
||||
/// Size
|
||||
internal static let size = L10n.tr("Localizable", "AppDetailView.Information.size", fallback: "Size")
|
||||
/// Source
|
||||
internal static let source = L10n.tr("Localizable", "AppDetailView.Information.source", fallback: "Source")
|
||||
}
|
||||
internal enum Reviews {
|
||||
/// out of %d
|
||||
internal static func outOf(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Reviews.outOf", p1, fallback: "out of %d")
|
||||
}
|
||||
/// %d Ratings
|
||||
internal static func ratings(_ p1: Int) -> String {
|
||||
return L10n.tr("Localizable", "AppDetailView.Reviews.ratings", p1, fallback: "%d Ratings")
|
||||
}
|
||||
/// See All
|
||||
internal static let seeAll = L10n.tr("Localizable", "AppDetailView.Reviews.seeAll", fallback: "See All")
|
||||
}
|
||||
internal enum WhatsNew {
|
||||
/// Show project on GitHub
|
||||
internal static let showOnGithub = L10n.tr("Localizable", "AppDetailView.WhatsNew.showOnGithub", fallback: "Show project on GitHub")
|
||||
/// Version History
|
||||
internal static let versionHistory = L10n.tr("Localizable", "AppDetailView.WhatsNew.versionHistory", fallback: "Version History")
|
||||
}
|
||||
}
|
||||
internal enum AppIDsView {
|
||||
/// Each app and app extension installed with SideStore must register an App ID with Apple.
|
||||
|
||||
@@ -16,6 +16,20 @@ struct DateFormatterHelper {
|
||||
dateComponentsFormatter.collapsesLargestUnit = false
|
||||
return dateComponentsFormatter
|
||||
}()
|
||||
|
||||
private static let relativeDateFormatter: RelativeDateTimeFormatter = {
|
||||
let formatter = RelativeDateTimeFormatter()
|
||||
formatter.unitsStyle = .abbreviated
|
||||
return formatter
|
||||
}()
|
||||
|
||||
private static let mediumDateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
|
||||
|
||||
static func string(forExpirationDate date: Date) -> String {
|
||||
@@ -32,10 +46,11 @@ struct DateFormatterHelper {
|
||||
self.appExpirationDateFormatter.unitsStyle = .full
|
||||
self.appExpirationDateFormatter.allowedUnits = [.day]
|
||||
}
|
||||
|
||||
return self.appExpirationDateFormatter.string(from: startDate, to: date) ?? ""
|
||||
}
|
||||
|
||||
return self.appExpirationDateFormatter.string(from: startDate, to: date) ?? ""
|
||||
}
|
||||
|
||||
static func string(forRelativeDate date: Date, to referenceDate: Date = Date()) -> String {
|
||||
self.relativeDateFormatter.localizedString(for: date, relativeTo: referenceDate)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -119,12 +119,28 @@
|
||||
/* AppDetailView*/
|
||||
"AppDetailView.Badge.official" = "Official App";
|
||||
"AppDetailView.Badge.trusted" = "From Trusted Source";
|
||||
"AppDetailView.noScreenshots" = "No screenshots available for this app.";
|
||||
"AppDetailView.more" = "More...";
|
||||
"AppDetailView.whatsNew" = "What's New";
|
||||
"AppDetailView.version" = "Version";
|
||||
"AppDetailView.WhatsNew.versionHistory" = "Version History";
|
||||
"AppDetailView.WhatsNew.showOnGithub" = "Show project on GitHub";
|
||||
"AppDetailView.reviews" = "Ratings & Reviews";
|
||||
"AppDetailView.Reviews.outOf" = "out of %d";
|
||||
"AppDetailView.Reviews.ratings" = "%d Ratings";
|
||||
"AppDetailView.Reviews.seeAll" = "See All";
|
||||
"AppDetailView.version" = "Version %@";
|
||||
"AppDetailView.noVersionInformation" = "No version information";
|
||||
"AppDetailView.noPermissions" = "The app requires no permissions.";
|
||||
"AppDetailView.permissions" = "Permissions";
|
||||
"AppDetailView.information" = "Information";
|
||||
"AppDetailView.Information.source" = "Source";
|
||||
"AppDetailView.Information.developer" = "Developer";
|
||||
"AppDetailView.Information.size" = "Size";
|
||||
"AppDetailView.Information.latestVersion" = "Latest Version";
|
||||
"AppDetailView.Information.compatibility" = "Compatibility";
|
||||
"AppDetailView.Information.compatibilityUnknown" = "Unknown";
|
||||
"AppDetailView.Information.compatibilityAtLeast" = "Requires iOS %@ or higher";
|
||||
"AppDetailView.Information.compatibilityOrLower" = "Requires iOS %@ or lower";
|
||||
|
||||
/* AppPermissionGrid */
|
||||
"AppPermissionGrid.usageDescription" = "Usage Description";
|
||||
|
||||
@@ -16,14 +16,7 @@ struct AppDetailView: View {
|
||||
|
||||
let storeApp: StoreApp
|
||||
|
||||
var dateFormatter: DateFormatter = {
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateStyle = .medium
|
||||
dateFormatter.timeStyle = .none
|
||||
return dateFormatter
|
||||
}()
|
||||
|
||||
var byteCountFormatter: ByteCountFormatter = {
|
||||
let byteCountFormatter: ByteCountFormatter = {
|
||||
let formatter = ByteCountFormatter()
|
||||
return formatter
|
||||
}()
|
||||
@@ -93,38 +86,81 @@ struct AppDetailView: View {
|
||||
}
|
||||
|
||||
var contentView: some View {
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
if storeApp.sourceIdentifier == Source.altStoreIdentifier {
|
||||
officialAppBadge
|
||||
}
|
||||
|
||||
if let subtitle = storeApp.subtitle {
|
||||
Text(subtitle)
|
||||
.multilineTextAlignment(.center)
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 32) {
|
||||
if storeApp.sourceIdentifier == Source.altStoreIdentifier {
|
||||
officialAppBadge
|
||||
}
|
||||
|
||||
if let subtitle = storeApp.subtitle {
|
||||
VStack {
|
||||
if #available(iOS 15.0, *) {
|
||||
Image(systemSymbol: .quoteOpening)
|
||||
.foregroundColor(.secondary.opacity(0.5))
|
||||
.imageScale(.large)
|
||||
.transformEffect(CGAffineTransform(a: 1, b: 0, c: -0.3, d: 1, tx: 0, ty: 0))
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
.offset(x: 30)
|
||||
}
|
||||
|
||||
Text(subtitle)
|
||||
.bold()
|
||||
.italic()
|
||||
.multilineTextAlignment(.center)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
if #available(iOS 15.0, *) {
|
||||
Image(systemSymbol: .quoteClosing)
|
||||
.foregroundColor(.secondary.opacity(0.5))
|
||||
.imageScale(.large)
|
||||
.transformEffect(CGAffineTransform(a: 1, b: 0, c: -0.3, d: 1, tx: 0, ty: 0))
|
||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||
.offset(x: -30)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
if !storeApp.screenshotURLs.isEmpty {
|
||||
// Equatable: Only reload the view if the screenshots change.
|
||||
// This prevents unnecessary redraws on scroll.
|
||||
AppScreenshotsScrollView(urls: storeApp.screenshotURLs)
|
||||
.equatable()
|
||||
} else {
|
||||
VStack() {
|
||||
Text(L10n.AppDetailView.noScreenshots)
|
||||
.italic()
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
ExpandableText(text: storeApp.localizedDescription)
|
||||
.lineLimit(6)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
.padding(.horizontal)
|
||||
}
|
||||
|
||||
if !storeApp.screenshotURLs.isEmpty {
|
||||
// Equatable: Only reload the view if the screenshots change.
|
||||
// This prevents unnecessary redraws on scroll.
|
||||
AppScreenshotsScrollView(urls: storeApp.screenshotURLs)
|
||||
.equatable()
|
||||
|
||||
|
||||
VStack(spacing: 16) {
|
||||
Divider()
|
||||
|
||||
currentVersionView
|
||||
|
||||
Divider()
|
||||
|
||||
ratingsView
|
||||
|
||||
Divider()
|
||||
|
||||
permissionsView
|
||||
|
||||
Divider()
|
||||
|
||||
informationView
|
||||
}
|
||||
|
||||
ExpandableText(text: storeApp.localizedDescription)
|
||||
.lineLimit(6)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
.padding(.horizontal)
|
||||
|
||||
currentVersionView
|
||||
.padding(.horizontal)
|
||||
|
||||
permissionsView
|
||||
.padding(.horizontal)
|
||||
|
||||
// TODO: Add review view
|
||||
// Only let users rate the app if it is currently installed!
|
||||
.padding(.horizontal)
|
||||
}
|
||||
.padding(.vertical)
|
||||
.background(
|
||||
@@ -156,25 +192,26 @@ struct AppDetailView: View {
|
||||
|
||||
var currentVersionView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .bottom) {
|
||||
VStack(alignment: .leading) {
|
||||
VStack {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(L10n.AppDetailView.whatsNew)
|
||||
.bold()
|
||||
.font(.title3)
|
||||
|
||||
if let version = storeApp.latestVersion?.version {
|
||||
Text("\(L10n.AppDetailView.version) \(version)")
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
|
||||
Spacer()
|
||||
|
||||
NavigationLink {
|
||||
AppVersionHistoryView(storeApp: self.storeApp)
|
||||
} label: {
|
||||
Text(L10n.AppDetailView.WhatsNew.versionHistory)
|
||||
}
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
if let versionDate = storeApp.versionDate, let versionSize = storeApp.size {
|
||||
VStack(alignment: .trailing) {
|
||||
Text(dateFormatter.string(from: versionDate))
|
||||
Text(byteCountFormatter.string(fromByteCount: Int64(versionSize)))
|
||||
if let latestVersion = storeApp.latestVersion {
|
||||
HStack {
|
||||
Text(L10n.AppDetailView.version(latestVersion.version))
|
||||
Spacer()
|
||||
Text(DateFormatterHelper.string(forRelativeDate: latestVersion.date))
|
||||
}
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
@@ -189,6 +226,109 @@ struct AppDetailView: View {
|
||||
Text(L10n.AppDetailView.noVersionInformation)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
|
||||
if true {
|
||||
SwiftUI.Button {
|
||||
UIApplication.shared.open(URL(string: "https://github.com/SideStore/SideStore")!) { _ in }
|
||||
} label: {
|
||||
HStack {
|
||||
Text(L10n.AppDetailView.WhatsNew.showOnGithub)
|
||||
Image(systemSymbol: .arrowUpForwardSquare)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var ratingsView: some View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(L10n.AppDetailView.whatsNew)
|
||||
.bold()
|
||||
.font(.title3)
|
||||
|
||||
Spacer()
|
||||
|
||||
NavigationLink {
|
||||
AppVersionHistoryView(storeApp: self.storeApp)
|
||||
} label: {
|
||||
Text(L10n.AppDetailView.Reviews.seeAll)
|
||||
}
|
||||
}
|
||||
|
||||
HStack(spacing: 40) {
|
||||
VStack {
|
||||
Text("3.0")
|
||||
.font(.system(size: 48, weight: .bold, design: .rounded))
|
||||
.opacity(0.8)
|
||||
Text(L10n.AppDetailView.Reviews.outOf(5))
|
||||
.bold()
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
VStack(alignment: .trailing) {
|
||||
LazyVGrid(columns: [GridItem(.fixed(48), alignment: .trailing), GridItem(.flexible())], spacing: 2) {
|
||||
ForEach(Array(1...5).reversed(), id: \.self) { rating in
|
||||
HStack(spacing: 2) {
|
||||
ForEach(0..<rating) { _ in
|
||||
Image(systemSymbol: .starFill)
|
||||
.resizable()
|
||||
.aspectRatio(contentMode: .fit)
|
||||
.frame(height: 8)
|
||||
}
|
||||
}
|
||||
|
||||
ProgressView(value: 0.5)
|
||||
.frame(maxWidth: .infinity)
|
||||
.progressViewStyle(LinearProgressViewStyle(tint: .secondary))
|
||||
}
|
||||
}
|
||||
.foregroundColor(.secondary)
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
Text(L10n.AppDetailView.Reviews.ratings(5))
|
||||
.font(.callout)
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
|
||||
TabView {
|
||||
ForEach(0..<5) { i in
|
||||
HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) {
|
||||
VStack(alignment: .leading) {
|
||||
VStack(alignment: .leading, spacing: 4) {
|
||||
HStack {
|
||||
Text("Review \(i + 1)")
|
||||
.bold()
|
||||
.lineLimit(1)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(DateFormatterHelper.string(forRelativeDate: Date().addingTimeInterval(-60*60)))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
RatingStars(rating: i + 1)
|
||||
.frame(height: 12)
|
||||
.foregroundColor(.yellow)
|
||||
}
|
||||
|
||||
ExpandableText(text: "Long review text content here.\nMultiple lines.\nAt least three are shown.\nBut are there more?")
|
||||
.lineLimit(3)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
|
||||
}
|
||||
.tag(i)
|
||||
.padding(.horizontal, 16)
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
.frame(height: 150)
|
||||
.padding(.horizontal, -16)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,4 +349,53 @@ struct AppDetailView: View {
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
|
||||
var informationData: [(title: String, content: String)] {
|
||||
var data: [(title: String, content: String)] = [
|
||||
(L10n.AppDetailView.Information.source, self.storeApp.source?.name ?? ""),
|
||||
(L10n.AppDetailView.Information.developer, self.storeApp.developerName),
|
||||
// ("Category", self.storeApp.category),
|
||||
]
|
||||
|
||||
if let latestVersion = self.storeApp.latestVersion {
|
||||
data += [
|
||||
(L10n.AppDetailView.Information.size, self.byteCountFormatter.string(fromByteCount: latestVersion.size)),
|
||||
(L10n.AppDetailView.Information.latestVersion, self.storeApp.latestVersion?.version ?? ""),
|
||||
]
|
||||
|
||||
var compatibility: String = L10n.AppDetailView.Information.compatibilityUnknown
|
||||
let iOSVersion = ProcessInfo.processInfo.operatingSystemVersion
|
||||
|
||||
if let minOSVersion = latestVersion.minOSVersion, ProcessInfo.processInfo.isOperatingSystemAtLeast(minOSVersion) == false {
|
||||
compatibility = L10n.AppDetailView.Information.compatibilityAtLeast(minOSVersion.stringValue)
|
||||
}
|
||||
|
||||
if let maxOSVersion = latestVersion.maxOSVersion,
|
||||
(!ProcessInfo.processInfo.isOperatingSystemAtLeast(maxOSVersion) || maxOSVersion.stringValue.compare(iOSVersion.stringValue, options: .numeric) == .orderedSame) {
|
||||
compatibility = L10n.AppDetailView.Information.compatibilityOrLower(maxOSVersion.stringValue)
|
||||
}
|
||||
|
||||
data.append((L10n.AppDetailView.Information.compatibility, compatibility))
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
var informationView: some View {
|
||||
VStack(alignment: .leading) {
|
||||
Text(L10n.AppDetailView.information)
|
||||
.bold()
|
||||
.font(.title3)
|
||||
|
||||
LazyVGrid(columns: [GridItem(.flexible(), alignment: .leading), GridItem(.flexible(), alignment: .trailing)], spacing: 8) {
|
||||
ForEach(informationData, id: \.title) { title, content in
|
||||
Text(title)
|
||||
.foregroundColor(.secondary)
|
||||
.fixedSize(horizontal: false, vertical: true)
|
||||
|
||||
Text(content)
|
||||
.multilineTextAlignment(.trailing)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ struct AppScreenshotsPreview: View {
|
||||
}
|
||||
}
|
||||
.tabViewStyle(PageTabViewStyle(indexDisplayMode: .never))
|
||||
.navigationTitle("\(index + 1) of \(self.urls.count)")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbar {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
SwiftUI.Button {
|
||||
|
||||
49
AltStore/Views/App Detail/AppVersionHistoryView.swift
Normal file
49
AltStore/Views/App Detail/AppVersionHistoryView.swift
Normal file
@@ -0,0 +1,49 @@
|
||||
//
|
||||
// AppVersionHistoryView.swift
|
||||
// SideStore
|
||||
//
|
||||
// Created by Fabian Thies on 28.01.23.
|
||||
// Copyright © 2023 SideStore. All rights reserved.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import AltStoreCore
|
||||
import ExpandableText
|
||||
|
||||
struct AppVersionHistoryView: View {
|
||||
let storeApp: StoreApp
|
||||
|
||||
var body: some View {
|
||||
List {
|
||||
ForEach(storeApp.versions.sorted(by: { $0.date < $1.date }), id: \.version) { version in
|
||||
VStack(spacing: 8) {
|
||||
HStack {
|
||||
Text(version.version).bold()
|
||||
Spacer()
|
||||
Text(DateFormatterHelper.string(forRelativeDate: version.date))
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
|
||||
if let versionDescription = version.localizedDescription {
|
||||
ExpandableText(text: versionDescription)
|
||||
.lineLimit(3)
|
||||
.expandButton(TextSet(text: L10n.AppDetailView.more, font: .callout, color: .accentColor))
|
||||
.buttonStyle(.plain)
|
||||
} else {
|
||||
Text("No version desciption available")
|
||||
.italic()
|
||||
.foregroundColor(.secondary)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.listStyle(PlainListStyle())
|
||||
.navigationTitle("Version History")
|
||||
}
|
||||
}
|
||||
|
||||
//struct AppVersionHistoryView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// AppVersionHistoryView(storeApp: )
|
||||
// }
|
||||
//}
|
||||
@@ -50,13 +50,15 @@ struct NewsItemView: View {
|
||||
.bold()
|
||||
.foregroundColor(.white)
|
||||
|
||||
VStack(alignment: .leading) {
|
||||
HStack(spacing: 0) {
|
||||
if let sourceName = newsItem.source?.name {
|
||||
Text(sourceName)
|
||||
.italic()
|
||||
}
|
||||
|
||||
if let externalURL = newsItem.externalURL {
|
||||
Text(" • ")
|
||||
|
||||
HStack(spacing: 0) {
|
||||
Image(systemSymbol: .link)
|
||||
Text(externalURL.host ?? "")
|
||||
|
||||
@@ -61,8 +61,10 @@ struct RootView: View {
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
.background(Color(UIColor.altPrimary))
|
||||
.foregroundColor(.white)
|
||||
.background(Color.accentColor)
|
||||
.clipShape(RoundedRectangle(cornerRadius: 16))
|
||||
.shadow(radius: 15)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
@@ -83,7 +85,7 @@ extension RootView {
|
||||
switch self {
|
||||
case .news: return .newspaper
|
||||
case .browse: return .booksVertical
|
||||
case .myApps: return .appBadge
|
||||
case .myApps: return .squareStack
|
||||
case .settings: return .gearshape
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user