Skip to content

Commit 5dd4607

Browse files
authored
Merge pull request #5605 from wikimedia/T410641_edit-impact-data
Activity Tab - Fetch your impact data
2 parents b7a2e77 + 406fb90 commit 5dd4607

File tree

16 files changed

+781
-222
lines changed

16 files changed

+781
-222
lines changed
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import WMFData
2+
import SwiftUI
3+
4+
@MainActor
5+
final class AllTimeImpactViewModel: ObservableObject {
6+
let totalEdits: Int?
7+
let bestStreak: Int?
8+
let thanksCount: Int?
9+
let lastEdited: Date?
10+
11+
init(data: WMFUserImpactData) {
12+
self.totalEdits = data.totalEditsCount
13+
self.bestStreak = data.longestEditingStreak
14+
self.thanksCount = data.receivedThanksCount
15+
self.lastEdited = data.lastEditTimestamp
16+
}
17+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import WMFData
2+
import SwiftUI
3+
4+
@MainActor
5+
final class ArticleViewsViewModel: ObservableObject {
6+
7+
struct View: Identifiable {
8+
public let date: Date
9+
public let count: Int
10+
11+
public var id: Date { date }
12+
}
13+
14+
let totalViewsCount: Int
15+
let views: [View]
16+
17+
init?(data: WMFUserImpactData) {
18+
let calendar = Calendar.current
19+
20+
// Normalize to start-of-day
21+
let endDate = calendar.startOfDay(for: Date())
22+
let startDate = calendar.date(byAdding: .day, value: -29, to: endDate)!
23+
24+
// Normalize response dates
25+
let normalizedCounts: [Date: Int] = Dictionary(
26+
data.dailyTotalViews.map { date, count in
27+
(calendar.startOfDay(for: date), count)
28+
},
29+
uniquingKeysWith: +
30+
)
31+
32+
var views: [View] = []
33+
var total = 0
34+
35+
// Generate last 30 days, filling missing days with 0
36+
for offset in 0..<30 {
37+
let date = calendar.date(byAdding: .day, value: offset, to: startDate)!
38+
let count = normalizedCounts[date] ?? 0
39+
40+
views.append(View(date: date, count: count))
41+
total += count
42+
}
43+
44+
guard total > 0 else {
45+
return nil
46+
}
47+
48+
self.views = views
49+
self.totalViewsCount = total
50+
}
51+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import WMFData
2+
import SwiftUI
3+
4+
@MainActor
5+
final class ContributionsViewModel: ObservableObject {
6+
let thisMonthCount: Int
7+
let lastMonthCount: Int
8+
9+
init(data: WMFUserImpactData) {
10+
let calendar = Calendar.current
11+
let now = Date()
12+
13+
// Current month components
14+
let thisMonthComponents = calendar.dateComponents([.year, .month], from: now)
15+
16+
// Last month date + components
17+
let lastMonthDate = calendar.date(byAdding: .month, value: -1, to: now)!
18+
let lastMonthComponents = calendar.dateComponents([.year, .month], from: lastMonthDate)
19+
20+
var thisMonthCount: Int = 0
21+
var lastMonthCount: Int = 0
22+
23+
for (date, count) in data.editCountByDay {
24+
let components = calendar.dateComponents([.year, .month], from: date)
25+
26+
if components.year == thisMonthComponents.year &&
27+
components.month == thisMonthComponents.month {
28+
thisMonthCount += count
29+
} else if components.year == lastMonthComponents.year &&
30+
components.month == lastMonthComponents.month {
31+
lastMonthCount += count
32+
}
33+
}
34+
35+
self.thisMonthCount = thisMonthCount
36+
self.lastMonthCount = lastMonthCount
37+
}
38+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import WMFData
2+
import SwiftUI
3+
4+
@MainActor
5+
final class MostViewedArticlesViewModel: ObservableObject {
6+
let topViewedArticles: [WMFUserImpactData.TopViewedArticle]
7+
8+
init?(data: WMFUserImpactData) {
9+
let topViewedArticles = Array(data.topViewedArticles.prefix(3))
10+
guard !topViewedArticles.isEmpty else {
11+
return nil
12+
}
13+
14+
self.topViewedArticles = topViewedArticles
15+
}
16+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import WMFData
2+
import SwiftUI
3+
4+
@MainActor
5+
final class RecentActivityViewModel: ObservableObject {
6+
7+
struct Edit: Identifiable {
8+
public let date: Date
9+
public let count: Int
10+
11+
public var id: Date { date }
12+
}
13+
14+
let editCount: Int
15+
let startDate: Date
16+
let endDate: Date
17+
let edits: [Edit]
18+
19+
init?(data: WMFUserImpactData) {
20+
let calendar = Calendar.current
21+
22+
// Normalize to start-of-day so keys line up
23+
let endDate = calendar.startOfDay(for: Date())
24+
let startDate = calendar.date(byAdding: .day, value: -29, to: endDate)!
25+
26+
// Normalize response dates to start-of-day
27+
let normalizedCounts: [Date: Int] = Dictionary(
28+
data.editCountByDay.map { key, value in
29+
(calendar.startOfDay(for: key), value)
30+
},
31+
uniquingKeysWith: +
32+
)
33+
34+
// Generate last 30 days, filling missing days with 0
35+
var edits: [Edit] = []
36+
var totalCount = 0
37+
38+
for offset in 0..<30 {
39+
let date = calendar.date(byAdding: .day, value: offset, to: startDate)!
40+
let count = normalizedCounts[date] ?? 0
41+
42+
edits.append(Edit(date: date, count: count))
43+
totalCount += count
44+
}
45+
46+
guard totalCount > 0 else {
47+
return nil
48+
}
49+
50+
self.startDate = startDate
51+
self.endDate = endDate
52+
self.edits = edits
53+
self.editCount = totalCount
54+
}
55+
}

WMFComponents/Sources/WMFComponents/Components/Activity Tab/WMFActivityTabInfoCardView.swift

Lines changed: 9 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,22 @@ struct WMFActivityTabInfoCardView<Content: View>: View {
44
private let icon: UIImage?
55
private let title: String
66
private let dateText: String?
7-
private let amount: Int
8-
private let onTapModule: () -> Void
7+
private let additionalAccessibilityLabel: String?
8+
private let onTapModule: (() -> Void)?
99
private let content: () -> Content
10-
private let contentAccessibilityLabels: [String]
1110

1211
init(
1312
icon: UIImage?,
1413
title: String,
1514
dateText: String?,
16-
amount: Int = 0,
17-
contentAccessibilityLabels: [String] = [],
18-
onTapModule: @escaping () -> Void,
15+
additionalAccessibilityLabel: String?,
16+
onTapModule: (() -> Void)?,
1917
@ViewBuilder content: @escaping () -> Content = { EmptyView() }
2018
) {
2119
self.icon = icon
2220
self.title = title
2321
self.dateText = dateText
24-
self.amount = amount
25-
self.contentAccessibilityLabels = contentAccessibilityLabels
22+
self.additionalAccessibilityLabel = additionalAccessibilityLabel
2623
self.content = content
2724
self.onTapModule = onTapModule
2825
}
@@ -31,7 +28,7 @@ struct WMFActivityTabInfoCardView<Content: View>: View {
3128
private var theme: WMFTheme { appEnvironment.theme }
3229

3330
var body: some View {
34-
Button(action: onTapModule) {
31+
Button(action: { onTapModule?() }) {
3532
VStack(spacing: 24) {
3633
HStack {
3734
if let icon {
@@ -55,17 +52,9 @@ struct WMFActivityTabInfoCardView<Content: View>: View {
5552
}
5653
}
5754

58-
HStack(alignment: .bottom) {
59-
Text("\(amount)")
60-
.foregroundStyle(Color(theme.text))
61-
.font(Font(WMFFont.for(.boldTitle1)))
62-
Spacer()
63-
content()
64-
}
65-
.padding(.bottom, 16)
55+
content()
6656
}
67-
.padding(.horizontal, 16)
68-
.padding(.top, 16)
57+
.padding(16)
6958
.background(Color(theme.paperBackground))
7059
.cornerRadius(10)
7160
.overlay(
@@ -80,16 +69,9 @@ struct WMFActivityTabInfoCardView<Content: View>: View {
8069
}
8170

8271
private var accessibilityString: String {
83-
let numberFormatter = NumberFormatter()
84-
numberFormatter.numberStyle = .decimal
85-
86-
let formattedAmount = numberFormatter.string(from: NSNumber(value: amount)) ?? "\(amount)"
87-
8872
var parts = [title]
8973
if let dateText { parts.append(dateText) }
90-
parts.append(formattedAmount)
91-
parts.append(contentsOf: contentAccessibilityLabels)
74+
if let additionalAccessibilityLabel { parts.append(additionalAccessibilityLabel)}
9275
return parts.joined(separator: ", ")
9376
}
94-
9577
}

0 commit comments

Comments
 (0)