Skip to content

Commit 532818a

Browse files
committed
feat: experimental falback gradient for pure commons categories without main image
1 parent afeafaa commit 532818a

2 files changed

Lines changed: 136 additions & 22 deletions

File tree

CommonsFinder/Views/Reusable Views/CategoryTeaser.swift

Lines changed: 34 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -73,32 +73,44 @@ private struct CategoryTeaserBase: View {
7373
}
7474

7575

76+
@ViewBuilder
7677
private var background: some View {
77-
Color(.emptyWikiItemBackground)
78-
.overlay {
79-
if let imageRequest = categoryInfo.base.thumbnailImage {
80-
LazyImage(request: imageRequest, transaction: .init(animation: .linear)) { imageState in
81-
if let image = imageState.image {
82-
image.resizable()
83-
.aspectRatio(contentMode: .fill)
84-
.scaledToFill()
85-
} else {
86-
Color.clear
78+
let imageRequest = categoryInfo.base.thumbnailImage
79+
80+
ZStack {
81+
if let imageRequest {
82+
Color(.emptyWikiItemBackground)
83+
.overlay {
84+
LazyImage(request: imageRequest, transaction: .init(animation: .linear)) { imageState in
85+
if let image = imageState.image {
86+
image.resizable()
87+
.aspectRatio(contentMode: .fill)
88+
.scaledToFill()
89+
} else {
90+
Color.clear
91+
}
8792
}
8893
}
89-
}
90-
}
91-
.overlay {
92-
if categoryInfo.base.thumbnailImage != nil {
93-
LinearGradient(
94-
stops: [
95-
.init(color: .init(white: 0, opacity: 0), location: 0),
96-
.init(color: .init(white: 0, opacity: 0.1), location: 0.35),
97-
.init(color: .init(white: 0, opacity: 0.2), location: 0.5),
98-
.init(color: .init(white: 0, opacity: 0.8), location: 1),
99-
], startPoint: .top, endPoint: .bottom)
100-
}
94+
} else {
95+
ProceduralBackground(categoryName: categoryInfo.base.commonsCategory ?? categoryInfo.base.wikidataId ?? "")
96+
.blur(radius: 5)
97+
.clipped()
10198
}
99+
}
100+
.overlay {
101+
LinearGradient(
102+
stops: [
103+
.init(color: .init(white: 0, opacity: 0), location: 0),
104+
.init(color: .init(white: 0, opacity: 0.1), location: 0.35),
105+
.init(color: .init(white: 0, opacity: 0.2), location: 0.5),
106+
.init(color: .init(white: 0, opacity: 0.8), location: 1),
107+
], startPoint: .top, endPoint: .bottom)
108+
}
109+
110+
111+
112+
113+
102114
}
103115
}
104116

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
//
2+
// ProceduralBackground.swift
3+
// CommonsFinder
4+
//
5+
// Created by Tom Brewe on 27.08.25.
6+
//
7+
8+
import SwiftUI
9+
import RegexBuilder
10+
import NaturalLanguage
11+
import Algorithms
12+
13+
struct ProceduralBackground: View {
14+
let categoryName: String
15+
var body: some View {
16+
17+
let gradient = createGradient(from: categoryName)
18+
gradient.opacity(0.5)
19+
}
20+
}
21+
22+
@ViewBuilder
23+
private func createGradient(from string: String) -> some View {
24+
let colors = categoryColors(for: string)
25+
let hues = categoryHues(for: string)
26+
let points: [SIMD2<Float>] = hues.adjacentPairs().map({ (x,y) in
27+
.init(x: Float(x), y: Float(y))
28+
})
29+
let dim = Int(sqrt(Double(points.count)))
30+
31+
32+
MeshGradient(width: dim, height: dim, points: points, colors: colors, background: colors.first?.opacity(0.8) ?? .clear, smoothsColors: true)
33+
}
34+
35+
36+
private func categoryHues(for string: String) -> [Double] {
37+
string
38+
.lowercased()
39+
.adjacentPairs()
40+
.map { [$0.0, $0.1] }
41+
.map { $0.prefix(2).hashValue }
42+
.map { v in
43+
(Double(v) - Double(Int.min)) / Double(UInt.max)
44+
}
45+
}
46+
47+
private func categoryColors(for string: String) -> [Color] {
48+
let colors: [Color] = categoryHues(for: string)
49+
.map { hue in
50+
.init(hue: hue, saturation: 0.6, brightness: 0.5, opacity: 1)
51+
}
52+
return colors
53+
}
54+
55+
56+
#Preview {
57+
let categorieNames = [
58+
"apples of the forrest",
59+
"people of france",
60+
"people of Paris",
61+
"sheep walking",
62+
"people with sheep",
63+
"aaa aaa",
64+
"Ananas food",
65+
"Banana Food",
66+
"Banana dessert",
67+
"Berlin-Adlershof",
68+
"Berlin-Kreuzberg",
69+
"Berlin-Friedrichshain",
70+
"Berlin-Zehlendorf",
71+
"Potsdam-Babelsberg",
72+
"Potsdam-Jägervorstadt",
73+
"Potsdam-West",
74+
"Potsdam-Drewitz",
75+
"zzz zzz",
76+
"3 continents",
77+
"Streets of Berlin",
78+
"Streets of New York City in the 1990s",
79+
"Berlin",
80+
"Berlin Wall",
81+
"Sundown in Paris",
82+
"cities in Africa",
83+
"cities in America",
84+
"Cities in Europe",
85+
"Flux Compensator",
86+
"Fluvial geomorphology"
87+
88+
]
89+
90+
ScrollView(.vertical) {
91+
LazyVGrid(columns: [.init(), .init(), .init()]) {
92+
ForEach(categorieNames, id: \.self) { categoryName in
93+
ProceduralBackground(categoryName: categoryName)
94+
.frame(width: 100, height: 100)
95+
.overlay {
96+
Text(categoryName)
97+
}
98+
}
99+
}
100+
}
101+
}
102+

0 commit comments

Comments
 (0)