Skip to content

Commit fce69f8

Browse files
committed
feat: add reveal identity support to chat if previously established
Signed-off-by: Brandon McAnsh <git@bmcreations.dev>
1 parent e35a536 commit fce69f8

10 files changed

Lines changed: 292 additions & 92 deletions

File tree

api/src/main/java/com/getcode/db/ConversationDao.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,13 @@ interface ConversationDao {
4545
return hasInteracted(conversationId.base58)
4646
}
4747

48+
@Query("UPDATE conversations SET hasRevealedIdentity = 1 WHERE idBase58 = :conversationId")
49+
suspend fun revealIdentity(conversationId: String)
50+
51+
suspend fun revealIdentity(conversationId: ID) {
52+
revealIdentity(conversationId.base58)
53+
}
54+
4855
// @Query("SELECT EXISTS (SELECT * FROM messages WHERE conversationIdBase58 = :messageId AND content LIKE '%4|%')")
4956
// suspend fun hasRevealedIdentity(messageId: String): Boolean
5057
//

api/src/main/java/com/getcode/model/chat/Platform.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,9 @@ enum class Platform {
1010
operator fun invoke(proto: ChatService.Platform): Platform {
1111
return runCatching { entries[proto.ordinal] }.getOrNull() ?: Unknown
1212
}
13+
14+
fun named(name: String): Platform {
15+
return entries.firstOrNull { it.name.lowercase() == name.lowercase() } ?: Unknown
16+
}
1317
}
1418
}

api/src/main/java/com/getcode/network/ConversationController.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import com.getcode.model.ID
1616
import com.getcode.model.MessageStatus
1717
import com.getcode.model.chat.ChatType
1818
import com.getcode.model.chat.OutgoingMessageContent
19+
import com.getcode.model.chat.Platform
1920
import com.getcode.model.chat.selfId
2021
import com.getcode.model.uuid
2122
import com.getcode.network.client.ChatMessageStreamReference
@@ -38,7 +39,7 @@ interface ConversationController {
3839
fun openChatStream(scope: CoroutineScope, conversation: Conversation)
3940
fun closeChatStream()
4041
suspend fun hasInteracted(messageId: ID): Boolean
41-
suspend fun revealIdentity(messageId: ID)
42+
suspend fun revealIdentity(conversationId: ID, platform: Platform, username: String): Result<Unit>
4243
suspend fun advanceReadPointer(conversationId: ID, messageId: ID, status: MessageStatus)
4344
suspend fun sendMessage(conversationId: ID, message: String): Result<ID>
4445
fun conversationPagingData(conversationId: ID): Flow<PagingData<ConversationMessageWithContent>>
@@ -169,7 +170,19 @@ class ConversationStreamController @Inject constructor(
169170
return false
170171
}
171172

172-
override suspend fun revealIdentity(messageId: ID) {
173+
override suspend fun revealIdentity(conversationId: ID, platform: Platform, username: String): Result<Unit> {
174+
val owner = SessionManager.getOrganizer()?.ownerKeyPair ?: return Result.failure(Throwable("owner not found"))
175+
val chat = historyController.chats.value?.firstOrNull {
176+
it.id == conversationId
177+
} ?: return Result.failure(Throwable("Chat not found"))
178+
179+
val memberId = chat.selfId ?: return Result.failure(Throwable("Not member of chat"))
180+
181+
return chatService.revealIdentity(owner, chat, memberId, platform, username)
182+
.map { }
183+
.onSuccess {
184+
db.conversationDao().revealIdentity(conversationId)
185+
}
173186
}
174187

175188
override suspend fun advanceReadPointer(conversationId: ID, messageId: ID, status: MessageStatus) {

api/src/main/java/com/getcode/network/api/ChatApiV2.kt

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,20 @@
11
package com.getcode.network.api
22

33
import com.codeinc.gen.chat.v2.ChatService
4+
import com.codeinc.gen.chat.v2.ChatService.ChatMemberIdentity
45
import com.codeinc.gen.chat.v2.ChatService.Content
56
import com.codeinc.gen.chat.v2.ChatService.PointerType
7+
import com.codeinc.gen.chat.v2.ChatService.RevealIdentityRequest
8+
import com.codeinc.gen.chat.v2.ChatService.RevealIdentityResponse
69
import com.codeinc.gen.chat.v2.ChatService.SendMessageRequest
710
import com.codeinc.gen.chat.v2.ChatService.SendMessageResponse
811
import com.codeinc.gen.common.v1.Model
912
import com.getcode.ed25519.Ed25519.KeyPair
1013
import com.getcode.model.Cursor
1114
import com.getcode.model.ID
15+
import com.getcode.model.chat.Chat
1216
import com.getcode.model.chat.OutgoingMessageContent
17+
import com.getcode.model.chat.Platform
1318
import com.getcode.model.chat.StartChatRequest
1419
import com.getcode.model.chat.StartChatResponse
1520
import com.getcode.model.description
@@ -225,31 +230,32 @@ class ChatApiV2 @Inject constructor(
225230
.apply { setSignature(sign(owner)) }
226231
.build()
227232

228-
229-
// val observer = object : StreamObserver<SendMessageResponse> {
230-
// override fun onNext(value: SendMessageResponse?) {
231-
// val result = value?.result
232-
// if (result == null) {
233-
// trace(
234-
// message = "SendMessage Server sent empty message. This is unexpected.",
235-
// type = TraceType.Error
236-
// )
237-
// onResult(Result.failure(Throwable()))
238-
// return
239-
// }
240-
//
241-
// onResult(Res)
242-
// }
243-
//
244-
// override fun onError(t: Throwable?) {
245-
// TODO("Not yet implemented")
246-
// }
247-
//
248-
// override fun onCompleted() {
249-
// TODO("Not yet implemented")
250-
// }
251-
//
252-
// }
253233
api.sendMessage(request, observer)
254234
}
235+
236+
fun revealIdentity(
237+
owner: KeyPair,
238+
chatId: ID,
239+
memberId: UUID,
240+
platform: Platform,
241+
username: String,
242+
observer: StreamObserver<RevealIdentityResponse>
243+
) {
244+
val request = RevealIdentityRequest.newBuilder()
245+
.setChatId(ChatId.newBuilder()
246+
.setValue(chatId.toByteArray().toByteString())
247+
)
248+
.setMemberId(ChatService.ChatMemberId.newBuilder()
249+
.setValue(memberId.bytes.toByteString())
250+
)
251+
.setIdentity(ChatMemberIdentity.newBuilder()
252+
.setPlatformValue(platform.ordinal)
253+
.setUsername(username)
254+
)
255+
.setOwner(owner.publicKeyBytes.toSolanaAccount())
256+
.apply { setSignature(sign(owner)) }
257+
.build()
258+
259+
api.revealIdentity(request, observer)
260+
}
255261
}

api/src/main/java/com/getcode/network/service/ChatServiceV2.kt

Lines changed: 84 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.codeinc.gen.chat.v2.ChatService
44
import com.codeinc.gen.chat.v2.ChatService.ChatMemberId
55
import com.codeinc.gen.chat.v2.ChatService.OpenChatEventStream
66
import com.codeinc.gen.chat.v2.ChatService.PointerType
7+
import com.codeinc.gen.chat.v2.ChatService.RevealIdentityResponse
78
import com.codeinc.gen.chat.v2.ChatService.SendMessageResponse
89
import com.codeinc.gen.chat.v2.ChatService.StreamChatEventsRequest
910
import com.codeinc.gen.chat.v2.ChatService.StreamChatEventsResponse
@@ -21,6 +22,7 @@ import com.getcode.model.chat.Chat
2122
import com.getcode.model.chat.ChatStreamEventUpdate
2223
import com.getcode.model.chat.ChatType
2324
import com.getcode.model.chat.OutgoingMessageContent
25+
import com.getcode.model.chat.Platform
2426
import com.getcode.model.description
2527
import com.getcode.network.api.ChatApiV2
2628
import com.getcode.network.client.ChatMessageStreamReference
@@ -237,8 +239,8 @@ class ChatServiceV2 @Inject constructor(
237239

238240
val type = when (status) {
239241
MessageStatus.Sent -> PointerType.SENT
240-
MessageStatus.Delivered -> PointerType.DELIVERED
241-
MessageStatus.Read -> PointerType.READ
242+
MessageStatus.Delivered -> PointerType.DELIVERED
243+
MessageStatus.Read -> PointerType.READ
242244
MessageStatus.Unknown -> return Result.failure(Throwable("Can't update a pointer to Unknown"))
243245
}
244246
return try {
@@ -531,6 +533,86 @@ class ChatServiceV2 @Inject constructor(
531533
Timber.e(t = error)
532534
Result.failure(error)
533535
}
536+
537+
else -> {
538+
val error = Throwable("Error: Unknown")
539+
Timber.e(t = error)
540+
Result.failure(error)
541+
}
542+
}
543+
544+
cont.resume(result)
545+
}
546+
547+
override fun onError(t: Throwable?) {
548+
val error = t ?: Throwable("Error: Hit a snag")
549+
ErrorUtils.handleError(error)
550+
cont.resume(Result.failure(error))
551+
}
552+
553+
override fun onCompleted() {
554+
555+
}
556+
557+
}
558+
)
559+
} catch (e: Exception) {
560+
ErrorUtils.handleError(e)
561+
cont.resume(Result.failure(e))
562+
}
563+
}
564+
565+
suspend fun revealIdentity(
566+
owner: KeyPair,
567+
chat: Chat,
568+
memberId: UUID,
569+
platform: Platform,
570+
username: String,
571+
): Result<ChatMessage> = suspendCancellableCoroutine { cont ->
572+
val chatId = chat.id
573+
try {
574+
api.revealIdentity(
575+
owner,
576+
chatId,
577+
memberId,
578+
platform,
579+
username,
580+
observer = object : StreamObserver<RevealIdentityResponse> {
581+
override fun onNext(value: RevealIdentityResponse?) {
582+
val requestResult = value?.result
583+
if (requestResult == null) {
584+
trace(
585+
message = "Chat SendMessage Server returned empty message. This is unexpected.",
586+
type = TraceType.Error
587+
)
588+
return
589+
}
590+
591+
val result = when (requestResult) {
592+
RevealIdentityResponse.Result.OK -> {
593+
trace("Chat message sent =: ${value.message.messageId.value.toList().description}")
594+
val message = messageMapper.map(chat to value.message)
595+
Result.success(message)
596+
}
597+
598+
RevealIdentityResponse.Result.DENIED -> {
599+
val error = Throwable("Error: Send Message: Denied")
600+
Timber.e(t = error)
601+
Result.failure(error)
602+
}
603+
604+
RevealIdentityResponse.Result.CHAT_NOT_FOUND -> {
605+
val error = Throwable("Error: Send Message: chat not found $chatId")
606+
Timber.e(t = error)
607+
Result.failure(error)
608+
}
609+
610+
RevealIdentityResponse.Result.UNRECOGNIZED -> {
611+
val error = Throwable("Error: Send Message: Unrecognized request.")
612+
Timber.e(t = error)
613+
Result.failure(error)
614+
}
615+
534616
else -> {
535617
val error = Throwable("Error: Unknown")
536618
Timber.e(t = error)
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.getcode.domain
2+
3+
import com.getcode.analytics.AnalyticsManager
4+
import com.getcode.ed25519.Ed25519
5+
import com.getcode.model.KinAmount
6+
import com.getcode.network.repository.SendTransactionRepository
7+
import com.getcode.solana.organizer.Organizer
8+
import com.getcode.utils.ErrorUtils
9+
import io.reactivex.rxjava3.disposables.Disposable
10+
import java.util.Timer
11+
import java.util.TimerTask
12+
import javax.inject.Inject
13+
import javax.inject.Singleton
14+
import kotlin.concurrent.schedule
15+
16+
@Singleton
17+
class CashLinkManager @Inject constructor(
18+
private val sendTransactionRepository: SendTransactionRepository,
19+
) {
20+
private var billDismissTimer: TimerTask? = null
21+
private var sendTransactionDisposable: Disposable? = null
22+
23+
fun awaitBillGrab(
24+
amount: KinAmount,
25+
organizer: Organizer,
26+
owner: Ed25519.KeyPair,
27+
onGrabbed: () -> Unit,
28+
onTimeout: () -> Unit,
29+
onError: (Throwable) -> Unit
30+
) {
31+
// this should not be in the view model
32+
sendTransactionDisposable?.dispose()
33+
sendTransactionRepository.init(amount, organizer, owner)
34+
sendTransactionDisposable =
35+
sendTransactionRepository.startTransaction()
36+
.subscribe({
37+
onGrabbed()
38+
}, {
39+
ErrorUtils.handleError(it)
40+
onError(it)
41+
})
42+
43+
presentSend(onTimeout)
44+
}
45+
46+
private fun presentSend(onTimeout: () -> Unit) {
47+
billDismissTimer?.cancel()
48+
billDismissTimer = Timer().schedule((1000 * 50).toLong()) {
49+
onTimeout()
50+
}
51+
}
52+
53+
fun cancelSend() {
54+
cancelBillTimeout()
55+
sendTransactionDisposable?.dispose()
56+
}
57+
58+
fun cancelBillTimeout() {
59+
billDismissTimer?.cancel()
60+
}
61+
}

app/src/main/java/com/getcode/util/IntentUtils.kt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import android.content.Intent
55
import android.net.Uri
66
import android.provider.Settings
77
import com.getcode.BuildConfig
8+
import com.getcode.R
9+
import com.getcode.model.Currency
10+
import com.getcode.model.KinAmount
11+
import com.getcode.model.Username
12+
import com.getcode.network.repository.replaceParam
813
import com.getcode.network.repository.urlEncode
14+
import com.getcode.solana.organizer.GiftCardAccount
915
import com.getcode.utils.makeE164
1016

1117
object IntentUtils {
@@ -40,4 +46,39 @@ object IntentUtils {
4046

4147
return shareIntent
4248
}
49+
50+
fun tipCard(username: String): Intent {
51+
val url = "https://tipcard.getcode.com/x/$username"
52+
53+
val sendIntent: Intent = Intent().apply {
54+
action = Intent.ACTION_SEND
55+
putExtra(Intent.EXTRA_TEXT, url)
56+
type = "text/plain"
57+
}
58+
59+
val shareIntent = Intent.createChooser(sendIntent, null).apply {
60+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
61+
}
62+
63+
return shareIntent
64+
}
65+
66+
fun cashLink(
67+
entropy: String,
68+
formattedAmount: String,
69+
): Intent {
70+
val url = "https://cash.getcode.com/c/#/e=$entropy"
71+
val text = "$formattedAmount $url"
72+
73+
val sendIntent: Intent = Intent().apply {
74+
action = Intent.ACTION_SEND
75+
putExtra(Intent.EXTRA_TEXT, text)
76+
type = "text/plain"
77+
}
78+
val shareIntent = Intent.createChooser(sendIntent, null).apply {
79+
flags = Intent.FLAG_ACTIVITY_NEW_TASK
80+
}
81+
82+
return shareIntent
83+
}
4384
}

app/src/main/java/com/getcode/view/main/chat/conversation/ChatConversationScreen.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ private fun IdentityRevealHeader(
9898
if (!state.identityRevealed) {
9999
delay(500)
100100
}
101-
showRevealHeader = !state.identityRevealed && state.user != null
101+
showRevealHeader = !state.identityRevealed
102102
}
103103

104104
AnimatedContent(

0 commit comments

Comments
 (0)