diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index af79f73a..eddae504 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -35,6 +35,7 @@ 1F1295812989B51F0048FCB9 /* ExpandableText in Frameworks */ = {isa = PBXBuildFile; productRef = 1F1295802989B51F0048FCB9 /* ExpandableText */; }; 1F180F92298E7A1B00D1C98B /* StoreApp+Trusted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */; }; 1F180F94298E7A2500D1C98B /* Source+Trusted.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F180F93298E7A2500D1C98B /* Source+Trusted.swift */; }; + 1F1D669E29A234CE0095BFCD /* WriteAppReviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1D669D29A234CE0095BFCD /* WriteAppReviewView.swift */; }; 1F2EF787297C4D40002FD839 /* LicensesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F2EF786297C4D40002FD839 /* LicensesView.swift */; }; 1F44634529744E570070E514 /* HintView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F44634429744E570070E514 /* HintView.swift */; }; 1F545E83298D79E400589F68 /* ErrorLogView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F545E82298D79E400589F68 /* ErrorLogView.swift */; }; @@ -587,6 +588,7 @@ 1F0DD8442936B3FE007608A4 /* FilledButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FilledButtonStyle.swift; sourceTree = ""; }; 1F180F91298E7A1B00D1C98B /* StoreApp+Trusted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "StoreApp+Trusted.swift"; sourceTree = ""; }; 1F180F93298E7A2500D1C98B /* Source+Trusted.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Source+Trusted.swift"; sourceTree = ""; }; + 1F1D669D29A234CE0095BFCD /* WriteAppReviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteAppReviewView.swift; sourceTree = ""; }; 1F2EF786297C4D40002FD839 /* LicensesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LicensesView.swift; sourceTree = ""; }; 1F44634429744E570070E514 /* HintView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HintView.swift; sourceTree = ""; }; 1F545E82298D79E400589F68 /* ErrorLogView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLogView.swift; sourceTree = ""; }; @@ -1180,6 +1182,7 @@ 1F0DD8202933B749007608A4 /* AppPermissionsGrid.swift */, 1F07F56A2955F11500F7BE95 /* AppScreenshotsPreview.swift */, 1FFEF103298552DB0098374C /* AppVersionHistoryView.swift */, + 1F1D669D29A234CE0095BFCD /* WriteAppReviewView.swift */, ); path = "App Detail"; sourceTree = ""; @@ -2864,6 +2867,7 @@ BF9ABA4B22DD1380008935CF /* NavigationBar.swift in Sources */, BF6C8FAC242935ED00125131 /* NSAttributedString+Markdown.m in Sources */, BFF00D322501BDA100746320 /* BackgroundRefreshAppsOperation.swift in Sources */, + 1F1D669E29A234CE0095BFCD /* WriteAppReviewView.swift in Sources */, 1F66F5BC2938F03700A910CA /* Modifiers.swift in Sources */, 1FA5A6CA298E8B2F007BA946 /* RefreshAttemptsView.swift in Sources */, 1F5DF9D82974426300DDAA47 /* AppScreenshot.swift in Sources */, diff --git a/AltStore/Views/App Detail/AppDetailView.swift b/AltStore/Views/App Detail/AppDetailView.swift index 741010ab..cae06961 100644 --- a/AltStore/Views/App Detail/AppDetailView.swift +++ b/AltStore/Views/App Detail/AppDetailView.swift @@ -25,7 +25,7 @@ struct AppDetailView: View { let maxContentCornerRadius: CGFloat = 24 let headerViewHeight: CGFloat = 140 let permissionColumns = 4 - + var headerBlurRadius: CGFloat { min(20, max(0, 20 - (scrollOffset / -150) * 20)) } @@ -35,6 +35,11 @@ struct AppDetailView: View { var contentCornerRadius: CGFloat { max(CGFloat.zero, min(maxContentCornerRadius, maxContentCornerRadius * (1 - self.scrollOffset / self.headerViewHeight))) } + + var canRateApp: Bool { + self.storeApp.installedApp != nil + } + var body: some View { ObservableScrollView(scrollOffset: $scrollOffset) { proxy in @@ -86,7 +91,7 @@ struct AppDetailView: View { } var contentView: some View { - VStack(alignment: .leading) { + VStack(alignment: .leading, spacing: 24) { VStack(alignment: .leading, spacing: 32) { if storeApp.isFromOfficialSource { officialAppBadge @@ -145,7 +150,7 @@ struct AppDetailView: View { } - VStack(spacing: 16) { + VStack(spacing: 24) { Divider() currentVersionView @@ -179,23 +184,29 @@ struct AppDetailView: View { } var officialAppBadge: some View { - HStack { - Spacer() - Image(systemSymbol: .checkmarkSealFill) - Text(L10n.AppDetailView.Badge.official) - Spacer() + HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) { + HStack { + Spacer() + Image(systemSymbol: .checkmarkSealFill) + Text(L10n.AppDetailView.Badge.official) + Spacer() + } + .foregroundColor(.accentColor) } - .foregroundColor(.accentColor) + .padding(.horizontal) } var trustedAppBadge: some View { - HStack { - Spacer() - Image(systemSymbol: .shieldLefthalfFill) - Text(L10n.AppDetailView.Badge.trusted) - Spacer() + HintView(backgroundColor: Color(UIColor.secondarySystemBackground)) { + HStack { + Spacer() + Image(systemSymbol: .shieldLefthalfFill) + Text(L10n.AppDetailView.Badge.trusted) + Spacer() + } + .foregroundColor(.accentColor) } - .foregroundColor(.accentColor) + .padding(.horizontal) } var currentVersionView: some View { @@ -318,7 +329,7 @@ struct AppDetailView: View { .foregroundColor(.secondary) } - RatingStars(rating: i + 1) + RatingStars(rating: 5 - i) .frame(height: 12) .foregroundColor(.yellow) } @@ -337,6 +348,17 @@ struct AppDetailView: View { .tabViewStyle(PageTabViewStyle(indexDisplayMode: .never)) .frame(height: 150) .padding(.horizontal, -16) + + if self.canRateApp { + ModalNavigationLink { + NavigationView { + WriteAppReviewView(storeApp: self.storeApp) + } + } label: { + Label("Write a Review", systemSymbol: .squareAndPencil) + } + .frame(maxWidth: .infinity, alignment: .leading) + } } } @@ -353,9 +375,8 @@ struct AppDetailView: View { } else { AppPermissionsGrid(permissions: storeApp.permissions) } - - Spacer() } + .frame(maxWidth: .infinity, alignment: .leading) } var informationData: [(title: String, content: String)] { diff --git a/AltStore/Views/App Detail/WriteAppReviewView.swift b/AltStore/Views/App Detail/WriteAppReviewView.swift new file mode 100644 index 00000000..e5aa1605 --- /dev/null +++ b/AltStore/Views/App Detail/WriteAppReviewView.swift @@ -0,0 +1,102 @@ +// +// WriteAppReviewView.swift +// SideStore +// +// Created by Fabian Thies on 19.02.23. +// Copyright © 2023 SideStore. All rights reserved. +// + +import SwiftUI +import AltStoreCore + +struct WriteAppReviewView: View { + @Environment(\.dismiss) var dismiss + + let storeApp: StoreApp + + @State var currentRating = 0 + @State var reviewText = "" + + var canSendReview: Bool { + // Only allow the user to send the review if a rating has been set and + // the review text is either empty or doesn't contain only whitespaces. + self.currentRating > 0 && ( + self.reviewText.isEmpty || !self.reviewText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty + ) + } + + var body: some View { + List { + // App Information + HStack { + AppIconView(iconUrl: storeApp.iconURL, size: 50) + VStack(alignment: .leading) { + Text(storeApp.name) + .bold() + Text(storeApp.developerName) + .font(.callout) + .foregroundColor(.secondary) + } + } + + // Rating + Section { + HStack { + Spacer() + ForEach(1...5) { rating in + SwiftUI.Button { + self.currentRating = rating + } label: { + Image(systemSymbol: rating > self.currentRating ? .star : .starFill) + .resizable() + .aspectRatio(contentMode: .fit) + } + .buttonStyle(PlainButtonStyle()) + .frame(maxHeight: 40) + } + Spacer() + } + .foregroundColor(.yellow) + } header: { + Text("Rate the App") + } + + // Review + Section { + TextEditor(text: self.$reviewText) + .frame(minHeight: 100, maxHeight: 250) + } header: { + Text("Leave a Review (optional)") + } + } + .navigationTitle("Write a Review") + .toolbar { + ToolbarItem(placement: .cancellationAction) { + SwiftUI.Button("Cancel", action: self.dismiss) + } + + ToolbarItem(placement: .confirmationAction) { + SwiftUI.Button("Send", action: self.sendReview) + .disabled(!self.canSendReview) + } + } + } + + + private func sendReview() { + NotificationManager.shared.showNotification(title: "Feature not Implemented") + self.dismiss() + } +} + +struct WriteAppReviewView_Previews: PreviewProvider { + + static let context = DatabaseManager.shared.viewContext + static let app = StoreApp.makeAltStoreApp(in: context) + + static var previews: some View { + NavigationView { + WriteAppReviewView(storeApp: app) + } + } +}