Skip to content
This repository was archived by the owner on Jan 20, 2021. It is now read-only.

Commit 069c24e

Browse files
Julian KahnertJulian Kahnert
authored andcommitted
statistics added
1 parent 4987b95 commit 069c24e

File tree

15 files changed

+296
-24
lines changed

15 files changed

+296
-24
lines changed

ArchiveCore/Package.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ let package = Package(
6666
],
6767
resources: [
6868
.copy("assets")
69+
]),
70+
.testTarget(name: "ArchiveViewsTests",
71+
dependencies: [
72+
"ArchiveViews"
6973
])
7074
]
7175
)

ArchiveCore/Sources/ArchiveBackend/ArchiveStore.swift

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import Combine
99
import Foundation
1010

1111
public protocol ArchiveStoreAPI: class {
12+
var documents: [Document] { get }
13+
var documentsPublisher: AnyPublisher<[Document], Never> { get }
14+
1215
func update(archiveFolder: URL, untaggedFolders: [URL])
1316
func archive(_ document: Document, slugify: Bool) throws
1417
func download(_ document: Document) throws
@@ -38,6 +41,10 @@ public final class ArchiveStore: ObservableObject, ArchiveStoreAPI, Log {
3841
@Published public var documents: [Document] = []
3942
@Published public var years: Set<String> = []
4043

44+
public var documentsPublisher: AnyPublisher<[Document], Never> {
45+
$documents.eraseToAnyPublisher()
46+
}
47+
4148
private var archiveFolder: URL!
4249
private var untaggedFolders: [URL] = []
4350

ArchiveCore/Sources/ArchiveBackend/Models/Document.swift

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,5 +277,14 @@ extension Document {
277277
let name = UUID().uuidString.prefix(5)
278278
return Document(path: URL(string: "~/test-\(name).pdf")!, taggingStatus: taggingStatus, downloadStatus: .downloading(percent: 0.33), byteSize: 512)
279279
}
280+
281+
public static func createWithInfo(taggingStatus: TaggingStatus, tags: Set<String>, folderName: String) -> Document {
282+
let name = UUID().uuidString.prefix(5)
283+
let url = URL(string: "~/\(folderName)/2020-12-27--\(name)__\(tags.sorted().joined(separator: "_")).pdf")!
284+
let document = Document(path: url, taggingStatus: taggingStatus, downloadStatus: .downloading(percent: 0.33), byteSize: 512)
285+
document.tags = tags
286+
287+
return document
288+
}
280289
}
281290
#endif

ArchiveCore/Sources/ArchiveBackend/PDFRendering/PDFProcessing.swift

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension NSImage {
3131
#endif
3232

3333
public enum PDFProcessingError: Error {
34-
case unttaggedDocumentsPathNotFound
34+
case untaggedDocumentsPathNotFound
3535
case pdfNotFound
3636
}
3737

@@ -289,6 +289,8 @@ public final class PDFProcessing: Operation, Log {
289289
document.insert(page, at: index)
290290
}
291291

292+
let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String
293+
document.documentAttributes?[PDFDocumentAttribute.creatorAttribute] = "PDF Archiver " + (version ?? "")
292294
return document
293295
}
294296

ArchiveCore/Sources/ArchiveViews/Helper/MissingViews/TagListView.swift

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,17 @@
99
import SwiftUI
1010

1111
struct TagListView: View {
12-
private static let minColumnWidth: CGFloat = 100
1312

1413
@Binding var tags: [String]
1514
let isEditable: Bool
1615
let isMultiLine: Bool
1716
let tapHandler: ((String) -> Void)?
1817

19-
var columns: [GridItem] = [GridItem(.adaptive(minimum: Self.minColumnWidth), spacing: 4)]
20-
2118
@ViewBuilder
2219
var body: some View {
2320
if isMultiLine {
24-
LazyVGrid(columns: columns, spacing: 6) {
25-
ForEach(tags, id: \.self) { tag in
26-
TagView(tagName: tag, isEditable: self.isEditable, tapHandler: self.tapHandler)
27-
}
21+
WrappingHStack(items: tags) { tag in
22+
TagView(tagName: tag, isEditable: self.isEditable, tapHandler: self.tapHandler)
2823
}
2924
} else {
3025
singleLineView

ArchiveCore/Sources/ArchiveViews/MoreTab/MoreTabView.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ struct MoreTabView: View {
1818
Form {
1919
preferences
2020
subscription
21+
statistics
2122
moreInformation
2223
}
23-
.listStyle(GroupedListStyle())
2424
.foregroundColor(.primary)
2525
.navigationTitle("Preferences & More")
2626
.navigationViewStyle(StackNavigationViewStyle())
@@ -66,6 +66,12 @@ struct MoreTabView: View {
6666
}
6767
}
6868

69+
private var statistics: some View {
70+
Section(header: Text("🧾 Statistics")) {
71+
StatisticsView(viewModel: viewModel.statisticsViewModel)
72+
}
73+
}
74+
6975
private var moreInformation: some View {
7076
Section(header: Text("⁉️ More Information"), footer: Text("Version \(MoreTabViewModel.appVersion)")) {
7177
NavigationLink(destination: AboutMeView()) {

ArchiveCore/Sources/ArchiveViews/MoreTab/MoreTabViewModel.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public final class MoreTabViewModel: ObservableObject, Log {
2626
@Published var selectedArchiveType = StorageType.getCurrent()
2727
@Published var showArchiveTypeSelection = false
2828
@Published var subscriptionStatus: LocalizedStringKey = "Inactive ❌"
29+
@Published var statisticsViewModel: StatisticsViewModel
2930

3031
var manageSubscriptionUrl: URL {
3132
URL(string: "https://apps.apple.com/account/subscriptions")!
@@ -42,6 +43,14 @@ public final class MoreTabViewModel: ObservableObject, Log {
4243
public init(iapService: IAPServiceAPI, archiveStore: ArchiveStoreAPI) {
4344
self.iapService = iapService
4445
self.archiveStore = archiveStore
46+
self.statisticsViewModel = StatisticsViewModel(documents: archiveStore.documents)
47+
48+
archiveStore.documentsPublisher
49+
.receive(on: DispatchQueue.global(qos: .utility))
50+
.map(StatisticsViewModel.init(documents: ))
51+
.receive(on: DispatchQueue.main)
52+
.assign(to: &$statisticsViewModel)
53+
4554
$selectedQualityIndex
4655
.sink { selectedQuality in
4756
UserDefaults.appGroup.pdfQuality = UserDefaults.PDFQuality.allCases[selectedQuality]
@@ -144,6 +153,10 @@ extension MoreTabViewModel {
144153
}
145154

146155
private class MockArchiveStoreAPI: ArchiveStoreAPI {
156+
var documents: [Document] { [] }
157+
var documentsPublisher: AnyPublisher<[Document], Never> {
158+
Just([]).eraseToAnyPublisher()
159+
}
147160
func update(archiveFolder: URL, untaggedFolders: [URL]) {}
148161
func archive(_ document: Document, slugify: Bool) throws {}
149162
func download(_ document: Document) throws {}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
//
2+
// SwiftUIView.swift
3+
//
4+
//
5+
// Created by Julian Kahnert on 27.12.20.
6+
//
7+
8+
import SwiftUI
9+
10+
struct StatisticsView: View {
11+
12+
@Environment(\.horizontalSizeClass) private var sizeClass
13+
14+
var viewModel: StatisticsViewModel
15+
16+
var body: some View {
17+
VStack {
18+
HStack(alignment: .top, spacing: 12) {
19+
documentsView
20+
if !viewModel.topTags.isEmpty && sizeClass != .compact {
21+
tagView
22+
}
23+
if !viewModel.topYears.isEmpty && sizeClass != .compact {
24+
yearView
25+
}
26+
}
27+
28+
if sizeClass == .compact && (!viewModel.topTags.isEmpty || !viewModel.topYears.isEmpty) {
29+
HStack(alignment: .top, spacing: 12) {
30+
if !viewModel.topTags.isEmpty {
31+
tagView
32+
}
33+
if !viewModel.topYears.isEmpty {
34+
yearView
35+
}
36+
}
37+
}
38+
}
39+
}
40+
41+
private var documentsView: some View {
42+
VStack(alignment: .leading) {
43+
Label("Documents", systemImage: "doc.text")
44+
.font(.headline)
45+
.padding(.bottom, 4)
46+
HStack {
47+
Text("\(viewModel.taggedDocumentCount)")
48+
.foregroundColor(.gray)
49+
Text("tagged")
50+
}
51+
.font(.subheadline)
52+
HStack {
53+
Text("\(viewModel.untaggedDocumentCount)")
54+
.foregroundColor(.gray)
55+
Text("untagged")
56+
}
57+
.font(.subheadline)
58+
}
59+
.padding()
60+
}
61+
62+
private var tagView: some View {
63+
VStack(alignment: .leading, spacing: 8) {
64+
Label("Top Tags", systemImage: "tag")
65+
.font(.headline)
66+
.padding(.bottom, 4)
67+
ForEach(viewModel.topTags, id: \.0) { (tag, count) in
68+
HStack {
69+
Text("\(count)")
70+
.foregroundColor(.gray)
71+
Text(tag.localizedCapitalized)
72+
.lineLimit(1)
73+
.minimumScaleFactor(0.95)
74+
}
75+
}
76+
.font(.subheadline)
77+
}
78+
.padding()
79+
}
80+
81+
private var yearView: some View {
82+
VStack(alignment: .leading, spacing: 8) {
83+
Label("Top Years", systemImage: "calendar")
84+
.font(.headline)
85+
.padding(.bottom, 4)
86+
ForEach(viewModel.topYears, id: \.0) { (year, count) in
87+
HStack {
88+
Text("\(count)")
89+
.foregroundColor(.gray)
90+
Text(year)
91+
.lineLimit(1)
92+
.minimumScaleFactor(0.95)
93+
}
94+
}
95+
.font(.subheadline)
96+
}
97+
.padding()
98+
}
99+
}
100+
101+
struct StatisticsView_Previews: PreviewProvider {
102+
static var previews: some View {
103+
StatisticsView(viewModel: StatisticsViewModel.previewViewModel)
104+
}
105+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//
2+
// StatisticsViewModel.swift
3+
//
4+
//
5+
// Created by Julian Kahnert on 27.12.20.
6+
//
7+
8+
import Foundation
9+
10+
public struct StatisticsViewModel: Log {
11+
12+
private let documents: [Document]
13+
public var topTags: [(String, Int)]
14+
public var topYears: [(String, Int)]
15+
16+
public init(documents: [Document]) {
17+
self.documents = documents
18+
19+
let taggedDocuments = documents.filter { $0.taggingStatus == .tagged }
20+
21+
let tmpTopTags = taggedDocuments
22+
.map(\.tags)
23+
.reduce(into: [String: Int]()) { (counts, documentTags) in
24+
for documentTag in documentTags {
25+
counts[documentTag, default: 0] += 1
26+
}
27+
}
28+
.sorted { $0.value > $1.value }
29+
.prefix(3)
30+
self.topTags = Array(tmpTopTags)
31+
32+
let tmpTopYears = taggedDocuments
33+
.map(\.folder)
34+
.reduce(into: [String: Int]()) { (counts, year) in
35+
counts[year, default: 0] += 1
36+
}
37+
.sorted { $0.value > $1.value }
38+
.prefix(3)
39+
self.topYears = Array(tmpTopYears)
40+
}
41+
42+
var taggedDocumentCount: Int {
43+
documents.filter { $0.taggingStatus == .tagged }
44+
.count
45+
}
46+
47+
var untaggedDocumentCount: Int {
48+
documents.filter { $0.taggingStatus == .untagged }
49+
.count
50+
}
51+
}
52+
53+
#if DEBUG
54+
import Combine
55+
import InAppPurchases
56+
import StoreKit
57+
58+
extension StatisticsViewModel {
59+
60+
private class MockArchiveStoreAPI: ArchiveStoreAPI {
61+
var documents: [Document] { [] }
62+
var documentsPublisher: AnyPublisher<[Document], Never> {
63+
Just([]).eraseToAnyPublisher()
64+
}
65+
func update(archiveFolder: URL, untaggedFolders: [URL]) {}
66+
func archive(_ document: Document, slugify: Bool) throws {}
67+
func download(_ document: Document) throws {}
68+
func delete(_ document: Document) throws {}
69+
func getCreationDate(of url: URL) throws -> Date? { nil }
70+
}
71+
72+
static var previewViewModel = StatisticsViewModel(documents: [Document]())
73+
}
74+
#endif

ArchiveCore/Sources/ArchiveViews/TagTab/DocumentList.swift

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,20 +21,25 @@ struct DocumentList: View {
2121
Text("Tagged: \(taggedUntaggedDocuments)")
2222
.font(Font.headline)
2323
.padding()
24-
List {
25-
ForEach(documents) { document in
26-
HStack {
27-
Circle()
28-
.fill(Color.systemBlue)
29-
.frame(width: 8, height: 8)
30-
.opacity(document == currentDocument ? 1 : 0)
31-
DocumentView(viewModel: document, showTagStatus: true, multilineTagList: false)
32-
}
33-
.contentShape(Rectangle())
34-
.onTapGesture {
35-
self.currentDocument = document
36-
}
24+
List(documents) { document in
25+
HStack {
26+
Circle()
27+
.fill(Color.systemBlue)
28+
.frame(width: 8, height: 8)
29+
.opacity(document == currentDocument ? 1 : 0)
30+
Text(document.filename)
31+
.lineLimit(1)
32+
.truncationMode(.middle)
3733
}
34+
.frame(maxWidth: .infinity, alignment: .leading)
35+
.padding(.vertical, 5)
36+
.padding(.horizontal, 4)
37+
.contentShape(Rectangle())
38+
.onTapGesture {
39+
self.currentDocument = document
40+
}
41+
.background((documents.firstIndex(of: document) ?? 2) % 2 == 0 ? .clear : .paSecondaryBackground)
42+
.cornerRadius(5)
3843
}
3944
}
4045
}

ArchiveCore/Sources/InAppPurchases/IAPService.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import Combine
1010
import StoreKit
1111
import TPInAppReceipt
1212

13-
public final class IAPService: NSObject, ObservableObject, Log {
13+
public final class IAPService: NSObject, Log {
1414

1515
private static var isInitialized = false
1616

0 commit comments

Comments
 (0)