Skip to content
This repository was archived by the owner on Aug 21, 2025. It is now read-only.

Commit a16ae6b

Browse files
committed
Implemented VCard and VCard.Builder classes
With those classes, we kept the VCard generation isolated and easy to test.
1 parent 6520aa3 commit a16ae6b

File tree

5 files changed

+258
-211
lines changed

5 files changed

+258
-211
lines changed

homeUi/src/main/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ internal fun ShareScreen(uiState: ShareUiState, onEvent: (ShareEvent) -> Unit) {
4545
) {
4646
ShareHeader(
4747
avatarUrl = uiState.avatarUrl.orEmpty(),
48-
vCardQrCodeData = uiState.vCardQrCodeData,
48+
vCardQrCodeData = uiState.vCardQrCodeData.toString(),
4949
modifier = Modifier
5050
.fillMaxWidth()
5151
)
Lines changed: 12 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,24 @@
11
package com.gravatar.app.homeUi.presentation.home.share
22

3+
import com.gravatar.app.homeUi.presentation.home.share.model.VCard
34
import com.gravatar.restapi.models.Profile
45

56
internal data class ShareUiState(
67
val profile: Profile? = null,
78
val avatarUrl: String? = null,
89
val privateContactInfo: PrivateContactInfo = PrivateContactInfo(),
910
) {
10-
val vCardQrCodeData: String = generateVCardData(profile, privateContactInfo)
11+
val vCardQrCodeData: VCard = VCard.Builder()
12+
.firstName(profile?.firstName)
13+
.lastName(profile?.lastName)
14+
.nickname(profile?.displayName)
15+
.organization(profile?.company)
16+
.title(profile?.jobTitle)
17+
.profileUrl(profile?.profileUrl.toString())
18+
.note(profile?.description)
19+
.phoneNumber(privateContactInfo.phoneValue.takeIf { privateContactInfo.isPhoneShared })
20+
.email(privateContactInfo.emailValue.takeIf { privateContactInfo.isEmailShared })
21+
.build()
1122
}
1223

1324
internal data class PrivateContactInfo(
@@ -16,51 +27,3 @@ internal data class PrivateContactInfo(
1627
val phoneValue: String = "",
1728
val isPhoneShared: Boolean = true,
1829
)
19-
20-
private fun generateVCardData(profile: Profile?, privateContactInfo: PrivateContactInfo): String {
21-
val vCardBuilder = StringBuilder()
22-
.append("BEGIN:VCARD\n")
23-
.append("VERSION:3.0\n")
24-
.append("PRODID:Gravatar Android\n")
25-
26-
// Add name information if available
27-
if (profile != null) {
28-
val firstName = profile.firstName.orEmpty()
29-
val lastName = profile.lastName.orEmpty()
30-
if (firstName.isNotEmpty() || lastName.isNotEmpty()) {
31-
vCardBuilder.append("N:$lastName;$firstName;;;\n")
32-
.append("FN:${("$firstName $lastName".trim()).ifEmpty { profile.displayName }}\n")
33-
.append("NICKNAME:${profile.displayName.ifEmpty { "$firstName $lastName".trim() }}\n")
34-
}
35-
36-
// Add organization information if available
37-
if (profile.company.isNotEmpty()) {
38-
vCardBuilder.append("ORG:${profile.company}\n")
39-
}
40-
41-
// Add job title if available
42-
if (profile.jobTitle.isNotEmpty()) {
43-
vCardBuilder.append("TITLE:${profile.jobTitle}\n")
44-
}
45-
46-
// Add URL
47-
vCardBuilder.append("URL:${profile.profileUrl}\n")
48-
49-
// Add Note
50-
if (profile.description.isNotEmpty()) {
51-
vCardBuilder.append("NOTE:${profile.description}\n")
52-
}
53-
}
54-
55-
// Add private contact info if shared
56-
if (privateContactInfo.isPhoneShared && privateContactInfo.phoneValue.isNotEmpty()) {
57-
vCardBuilder.append("TEL;TYPE=cell:${privateContactInfo.phoneValue}\n")
58-
}
59-
60-
if (privateContactInfo.isEmailShared && privateContactInfo.emailValue.isNotEmpty()) {
61-
vCardBuilder.append("EMAIL:${privateContactInfo.emailValue}\n")
62-
}
63-
64-
vCardBuilder.append("END:VCARD")
65-
return vCardBuilder.toString()
66-
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.gravatar.app.homeUi.presentation.home.share.model
2+
3+
internal class VCard private constructor(
4+
val firstName: String? = null,
5+
val lastName: String? = null,
6+
val nickname: String? = null,
7+
val organization: String? = null,
8+
val title: String? = null,
9+
val profileUrl: String? = null,
10+
val note: String? = null,
11+
val phoneNumber: String? = null,
12+
val email: String? = null,
13+
) {
14+
15+
override fun toString(): String {
16+
val contentBuilder = StringBuilder().append("BEGIN:VCARD\n")
17+
.append("VERSION:3.0\n")
18+
.append("PRODID:Gravatar Android\n")
19+
20+
val firstName = firstName.orEmpty()
21+
val lastName = lastName.orEmpty()
22+
val nickname = nickname.orEmpty()
23+
if (firstName.isNotEmpty() || lastName.isNotEmpty()) {
24+
contentBuilder.append("N:$lastName;$firstName;;;\n")
25+
.append("FN:${("$firstName $lastName".trim()).ifEmpty { nickname }}\n")
26+
}
27+
val calculatedNickname = nickname.ifEmpty { "$firstName $lastName".trim() }
28+
29+
calculatedNickname.takeIf { it.isNotEmpty() }?.let { contentBuilder.append("NICKNAME:$it\n") }
30+
organization?.takeIf { it.isNotEmpty() }?.let { contentBuilder.append("ORG:$it\n") }
31+
title?.takeIf { it.isNotEmpty() }?.let { contentBuilder.append("TITLE:$it\n") }
32+
profileUrl?.takeIf { it.isNotEmpty() }?.let { contentBuilder.append("URL:$it\n") }
33+
note?.takeIf { it.isNotEmpty() }?.let { contentBuilder.append("NOTE:$it\n") }
34+
phoneNumber?.takeIf { it.isNotEmpty() }?.let { contentBuilder.append("TEL;TYPE=cell:$it\n") }
35+
email?.takeIf { it.isNotEmpty() }?.let { contentBuilder.append("EMAIL:$it\n") }
36+
37+
contentBuilder.append("END:VCARD")
38+
return contentBuilder.toString()
39+
}
40+
41+
class Builder(
42+
private var firstName: String? = null,
43+
private var lastName: String? = null,
44+
private var nickname: String? = null,
45+
private var organization: String? = null,
46+
private var title: String? = null,
47+
private var profileUrl: String? = null,
48+
private var note: String? = null,
49+
private var phoneNumber: String? = null,
50+
private var email: String? = null,
51+
) {
52+
fun firstName(firstName: String?) = apply { this.firstName = firstName }
53+
fun lastName(lastName: String?) = apply { this.lastName = lastName }
54+
fun nickname(nickname: String?) = apply { this.nickname = nickname }
55+
fun organization(organization: String?) = apply { this.organization = organization }
56+
fun title(title: String?) = apply { this.title = title }
57+
fun profileUrl(url: String?) = apply { this.profileUrl = url }
58+
fun note(description: String?) = apply { this.note = description }
59+
fun phoneNumber(phone: String?) = apply { this.phoneNumber = phone }
60+
fun email(email: String?) = apply { this.email = email }
61+
62+
fun build() = VCard(
63+
firstName = firstName,
64+
lastName = lastName,
65+
nickname = nickname,
66+
organization = organization,
67+
title = title,
68+
profileUrl = profileUrl,
69+
note = note,
70+
phoneNumber = phoneNumber,
71+
email = email
72+
)
73+
}
74+
}

homeUi/src/test/kotlin/com/gravatar/app/homeUi/presentation/home/share/ShareUiStateTest.kt

Lines changed: 0 additions & 161 deletions
This file was deleted.

0 commit comments

Comments
 (0)