Add double star for Purple members that have been active for over a year
This commit adds a special badge for purple members who have been active for more than one entire year. Closes: https://github.com/damus-io/damus/issues/2831 Changelog-Added: Purple members who have been active for more than a year now get a special badge Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -12,6 +12,14 @@ struct SupporterBadge: View {
|
|||||||
let purple_account: DamusPurple.Account?
|
let purple_account: DamusPurple.Account?
|
||||||
let style: Style
|
let style: Style
|
||||||
let text_color: Color
|
let text_color: Color
|
||||||
|
var badge_variant: BadgeVariant {
|
||||||
|
if purple_account?.attributes.contains(.memberForMoreThanOneYear) == true {
|
||||||
|
return .oneYearSpecial
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return .normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
init(percent: Int?, purple_account: DamusPurple.Account? = nil, style: Style, text_color: Color = .secondary) {
|
init(percent: Int?, purple_account: DamusPurple.Account? = nil, style: Style, text_color: Color = .secondary) {
|
||||||
self.percent = percent
|
self.percent = percent
|
||||||
@@ -26,13 +34,18 @@ struct SupporterBadge: View {
|
|||||||
HStack {
|
HStack {
|
||||||
if let purple_account, purple_account.active == true {
|
if let purple_account, purple_account.active == true {
|
||||||
HStack(spacing: 1) {
|
HStack(spacing: 1) {
|
||||||
Image("star.fill")
|
switch self.badge_variant {
|
||||||
.resizable()
|
case .normal:
|
||||||
.frame(width:size, height:size)
|
StarShape()
|
||||||
.foregroundStyle(GoldGradient)
|
.frame(width:size, height:size)
|
||||||
if self.style == .full {
|
.foregroundStyle(GoldGradient)
|
||||||
let date = format_date(date: purple_account.created_at, time_style: .none)
|
case .oneYearSpecial:
|
||||||
Text(date)
|
DoubleStar(size: size)
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.style == .full,
|
||||||
|
let ordinal = self.purple_account?.ordinal() {
|
||||||
|
Text(ordinal)
|
||||||
.foregroundStyle(text_color)
|
.foregroundStyle(text_color)
|
||||||
.font(.caption)
|
.font(.caption)
|
||||||
}
|
}
|
||||||
@@ -56,8 +69,102 @@ struct SupporterBadge: View {
|
|||||||
case full // Shows the entire badge with a purple subscriber number if present
|
case full // Shows the entire badge with a purple subscriber number if present
|
||||||
case compact // Does not show purple subscriber number. Only shows the star (if applicable)
|
case compact // Does not show purple subscriber number. Only shows the star (if applicable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum BadgeVariant {
|
||||||
|
/// A normal badge that people are used to
|
||||||
|
case normal
|
||||||
|
/// A special badge for users who have been members for more than a year
|
||||||
|
case oneYearSpecial
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
struct StarShape: Shape {
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
var path = Path()
|
||||||
|
let center = CGPoint(x: rect.midX, y: rect.midY)
|
||||||
|
let radius: CGFloat = min(rect.width, rect.height) / 2
|
||||||
|
let points = 5
|
||||||
|
let adjustment: CGFloat = .pi / 2
|
||||||
|
|
||||||
|
for i in 0..<points * 2 {
|
||||||
|
let angle = (CGFloat(i) * .pi / CGFloat(points)) - adjustment
|
||||||
|
let pointRadius = i % 2 == 0 ? radius : radius * 0.4
|
||||||
|
let point = CGPoint(x: center.x + pointRadius * cos(angle), y: center.y + pointRadius * sin(angle))
|
||||||
|
if i == 0 {
|
||||||
|
path.move(to: point)
|
||||||
|
} else {
|
||||||
|
path.addLine(to: point)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
path.closeSubpath()
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct DoubleStar: View {
|
||||||
|
let size: CGFloat
|
||||||
|
var starOffset: CGFloat = 5
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
DoubleStarShape(starOffset: starOffset)
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.foregroundStyle(GoldGradient)
|
||||||
|
.padding(.trailing, starOffset)
|
||||||
|
} else {
|
||||||
|
Fallback(size: size, starOffset: starOffset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@available(iOS 17.0, *)
|
||||||
|
struct DoubleStarShape: Shape {
|
||||||
|
var strokeSize: CGFloat = 3
|
||||||
|
var starOffset: CGFloat
|
||||||
|
|
||||||
|
func path(in rect: CGRect) -> Path {
|
||||||
|
let normalSizedStarPath = StarShape().path(in: rect)
|
||||||
|
let largerStarPath = StarShape().path(in: rect.insetBy(dx: -strokeSize, dy: -strokeSize))
|
||||||
|
|
||||||
|
let finalPath = normalSizedStarPath
|
||||||
|
.subtracting(
|
||||||
|
largerStarPath.offsetBy(dx: starOffset, dy: 0)
|
||||||
|
)
|
||||||
|
.union(
|
||||||
|
normalSizedStarPath.offsetBy(dx: starOffset, dy: 0)
|
||||||
|
)
|
||||||
|
|
||||||
|
return finalPath
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A fallback view for those who cannot run iOS 17
|
||||||
|
struct Fallback: View {
|
||||||
|
var size: CGFloat
|
||||||
|
var starOffset: CGFloat
|
||||||
|
|
||||||
|
var body: some View {
|
||||||
|
HStack {
|
||||||
|
StarShape()
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.foregroundStyle(GoldGradient)
|
||||||
|
|
||||||
|
StarShape()
|
||||||
|
.fill(GoldGradient)
|
||||||
|
.overlay(
|
||||||
|
StarShape()
|
||||||
|
.stroke(Color.damusAdaptableWhite, lineWidth: 1)
|
||||||
|
)
|
||||||
|
.frame(width: size + 1, height: size + 1)
|
||||||
|
.padding(.leading, -size - starOffset)
|
||||||
|
}
|
||||||
|
.padding(.trailing, -3)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
func support_level_color(_ percent: Int) -> Color {
|
func support_level_color(_ percent: Int) -> Color {
|
||||||
if percent == 0 {
|
if percent == 0 {
|
||||||
return .gray
|
return .gray
|
||||||
@@ -86,7 +193,7 @@ struct SupporterBadge_Previews: PreviewProvider {
|
|||||||
HStack(alignment: .center) {
|
HStack(alignment: .center) {
|
||||||
SupporterBadge(
|
SupporterBadge(
|
||||||
percent: nil,
|
percent: nil,
|
||||||
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: subscriber_number, active: true),
|
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: subscriber_number, active: true, attributes: []),
|
||||||
style: .full
|
style: .full
|
||||||
)
|
)
|
||||||
.frame(width: 100)
|
.frame(width: 100)
|
||||||
@@ -118,4 +225,52 @@ struct SupporterBadge_Previews: PreviewProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#Preview("1 yr badge") {
|
||||||
|
VStack {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
SupporterBadge(
|
||||||
|
percent: nil,
|
||||||
|
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: 3, active: true, attributes: []),
|
||||||
|
style: .full
|
||||||
|
)
|
||||||
|
.frame(width: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
SupporterBadge(
|
||||||
|
percent: nil,
|
||||||
|
purple_account: DamusPurple.Account(pubkey: test_pubkey, created_at: .now, expiry: .now.addingTimeInterval(10000), subscriber_number: 3, active: true, attributes: [.memberForMoreThanOneYear]),
|
||||||
|
style: .full
|
||||||
|
)
|
||||||
|
.frame(width: 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Double star (just shape itself, with alt background color, to show it adapts to background well)")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
if #available(iOS 17.0, *) {
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
DoubleStar.DoubleStarShape(starOffset: 5)
|
||||||
|
.frame(width: 17, height: 17)
|
||||||
|
.padding(.trailing, -8)
|
||||||
|
}
|
||||||
|
.background(Color.blue)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Double star (fallback for iOS 16 and below)")
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
DoubleStar.Fallback(size: 17, starOffset: 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text("Double star (fallback for iOS 16 and below, with alt color limitation shown)")
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
|
||||||
|
HStack(alignment: .center) {
|
||||||
|
DoubleStar.Fallback(size: 17, starOffset: 5)
|
||||||
|
}
|
||||||
|
.background(Color.blue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -418,6 +418,13 @@ class DamusPurple: StoreObserverDelegate {
|
|||||||
let expiry: Date
|
let expiry: Date
|
||||||
let subscriber_number: Int
|
let subscriber_number: Int
|
||||||
let active: Bool
|
let active: Bool
|
||||||
|
let attributes: PurpleAccountAttributes
|
||||||
|
|
||||||
|
struct PurpleAccountAttributes: OptionSet {
|
||||||
|
let rawValue: Int
|
||||||
|
|
||||||
|
static let memberForMoreThanOneYear = PurpleAccountAttributes(rawValue: 1 << 0)
|
||||||
|
}
|
||||||
|
|
||||||
func ordinal() -> String? {
|
func ordinal() -> String? {
|
||||||
let number = Int(self.subscriber_number)
|
let number = Int(self.subscriber_number)
|
||||||
@@ -438,7 +445,8 @@ class DamusPurple: StoreObserverDelegate {
|
|||||||
created_at: Date.init(timeIntervalSince1970: TimeInterval(payload.created_at)),
|
created_at: Date.init(timeIntervalSince1970: TimeInterval(payload.created_at)),
|
||||||
expiry: Date.init(timeIntervalSince1970: TimeInterval(payload.expiry)),
|
expiry: Date.init(timeIntervalSince1970: TimeInterval(payload.expiry)),
|
||||||
subscriber_number: Int(payload.subscriber_number),
|
subscriber_number: Int(payload.subscriber_number),
|
||||||
active: payload.active
|
active: payload.active,
|
||||||
|
attributes: (payload.attributes?.member_for_more_than_one_year ?? false) ? [.memberForMoreThanOneYear] : []
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -448,6 +456,11 @@ class DamusPurple: StoreObserverDelegate {
|
|||||||
let expiry: UInt64 // Unix timestamp
|
let expiry: UInt64 // Unix timestamp
|
||||||
let subscriber_number: UInt
|
let subscriber_number: UInt
|
||||||
let active: Bool
|
let active: Bool
|
||||||
|
let attributes: Attributes?
|
||||||
|
|
||||||
|
struct Attributes: Codable {
|
||||||
|
let member_for_more_than_one_year: Bool
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,7 +136,8 @@ struct DamusPurpleAccountView: View {
|
|||||||
created_at: Date.now,
|
created_at: Date.now,
|
||||||
expiry: Date.init(timeIntervalSinceNow: 60 * 60 * 24 * 30),
|
expiry: Date.init(timeIntervalSinceNow: 60 * 60 * 24 * 30),
|
||||||
subscriber_number: 7,
|
subscriber_number: 7,
|
||||||
active: true
|
active: true,
|
||||||
|
attributes: []
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -149,7 +150,8 @@ struct DamusPurpleAccountView: View {
|
|||||||
created_at: Date.init(timeIntervalSinceNow: -60 * 60 * 24 * 37),
|
created_at: Date.init(timeIntervalSinceNow: -60 * 60 * 24 * 37),
|
||||||
expiry: Date.init(timeIntervalSinceNow: -60 * 60 * 24 * 7),
|
expiry: Date.init(timeIntervalSinceNow: -60 * 60 * 24 * 7),
|
||||||
subscriber_number: 7,
|
subscriber_number: 7,
|
||||||
active: false
|
active: false,
|
||||||
|
attributes: []
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user