Skip to content

Commit 6ea3772

Browse files
authored
Merge branch 'main' into bg/deprecate-composite
2 parents 6034364 + e6876ca commit 6ea3772

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

52 files changed

+4988
-928
lines changed

.github/workflows/test.yml

+19-21
Original file line numberDiff line numberDiff line change
@@ -24,37 +24,35 @@ jobs:
2424
uses: gradle/gradle-build-action@v2
2525
- name: Validate Gradle Wrapper
2626
uses: gradle/wrapper-validation-action@v1
27-
- name: Start local test server
28-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml up -d
27+
- name: Start Docker containers
28+
run: dev/up
2929
- name: Gradle Run Unit Tests
3030
run: ./gradlew library:testDebug
31-
- name: Stop local test server
32-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml down
3331
library-integration:
3432
name: Library (Integration Tests)
35-
runs-on: macos-latest
33+
runs-on: ubuntu-latest
3634
steps:
37-
- name: Checkout project sources
38-
uses: actions/checkout@v3
39-
- uses: actions/setup-java@v3
35+
- name: Checkout
36+
uses: actions/checkout@v4
4037
with:
41-
distribution: 'adopt'
42-
java-version: '11'
43-
- name: Setup Gradle
38+
fetch-depth: 0
39+
- name: Configure JDK
40+
uses: actions/setup-java@v4
41+
with:
42+
distribution: 'zulu'
43+
java-version: 11
44+
- name: Enable KVM group perms
45+
run: |
46+
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules
47+
sudo udevadm control --reload-rules
48+
sudo udevadm trigger --name-match=kvm
49+
- name: Gradle cache
4450
uses: gradle/gradle-build-action@v2
45-
- name: Validate Gradle Wrapper
46-
uses: gradle/wrapper-validation-action@v1
47-
- name: Set up Docker
48-
run: brew install docker docker-compose
49-
- name: Start Colima
50-
run: colima start
51-
- name: Start local test server
52-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml up -d
51+
- name: Start Docker containers
52+
run: dev/up
5353
- name: Gradle Run Integration Tests
5454
uses: reactivecircus/android-emulator-runner@v2
5555
with:
5656
api-level: 29
5757
script: ./gradlew connectedCheck
58-
- name: Stop local test server
59-
run: docker-compose -p xmtp -f dev/local/docker-compose.yml down
6058

dev/local/compose

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
4+
docker-compose -f dev/local/docker-compose.yml -p "xmtp-android" "$@"

dev/local/docker-compose.yml

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
services:
2-
waku-node:
2+
node:
33
image: xmtp/node-go:latest
44
platform: linux/amd64
55
environment:
@@ -8,14 +8,27 @@ services:
88
- --store.enable
99
- --store.db-connection-string=postgres://postgres:xmtp@db:5432/postgres?sslmode=disable
1010
- --store.reader-db-connection-string=postgres://postgres:xmtp@db:5432/postgres?sslmode=disable
11+
- --mls-store.db-connection-string=postgres://postgres:xmtp@mlsdb:5432/postgres?sslmode=disable
12+
- --mls-validation.grpc-address=validation:50051
13+
- --api.enable-mls
1114
- --wait-for-db=30s
1215
- --api.authn.enable
1316
ports:
14-
- 9001:9001
1517
- 5555:5555
18+
- 5556:5556
1619
depends_on:
1720
- db
21+
22+
validation:
23+
image: xmtp/mls-validation-service:latest
24+
platform: linux/amd64
25+
1826
db:
1927
image: postgres:13
2028
environment:
21-
POSTGRES_PASSWORD: xmtp
29+
POSTGRES_PASSWORD: xmtp
30+
31+
mlsdb:
32+
image: postgres:13
33+
environment:
34+
POSTGRES_PASSWORD: xmtp

dev/local/up

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
script_dir="$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
4+
5+
"${script_dir}"/compose pull
6+
"${script_dir}"/compose up -d --build

dev/up

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#!/bin/bash
2+
set -eou pipefail
3+
4+
if [[ "${OSTYPE}" == "darwin"* ]]; then
5+
if ! which buf &>/dev/null; then brew install buf; fi
6+
if ! which shellcheck &>/dev/null; then brew install shellcheck; fi
7+
if ! which markdownlint &>/dev/null; then brew install markdownlint-cli; fi
8+
if ! java -version &>/dev/null; then
9+
brew install java
10+
sudo ln -sfn /opt/homebrew/opt/openjdk/libexec/openjdk.jdk \
11+
/Library/Java/JavaVirtualMachines/
12+
fi
13+
if ! kotlinc -version &>/dev/null; then brew install kotlin; fi
14+
fi
15+
16+
rustup update
17+
18+
dev/local/up

example/src/main/java/org/xmtp/android/example/ClientManager.kt

+16-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.xmtp.android.example
22

3+
import android.content.Context
34
import androidx.annotation.UiThread
45
import kotlinx.coroutines.Dispatchers
56
import kotlinx.coroutines.GlobalScope
@@ -10,10 +11,21 @@ import org.xmtp.android.library.Client
1011
import org.xmtp.android.library.ClientOptions
1112
import org.xmtp.android.library.XMTPEnvironment
1213
import org.xmtp.android.library.messages.PrivateKeyBundleV1Builder
14+
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChangeCodec
1315

1416
object ClientManager {
1517

16-
val CLIENT_OPTIONS = ClientOptions(api = ClientOptions.Api(XMTPEnvironment.DEV, appVersion = "XMTPAndroidExample/v1.0.0"))
18+
fun clientOptions(appContext: Context?): ClientOptions {
19+
return ClientOptions(
20+
api = ClientOptions.Api(
21+
XMTPEnvironment.LOCAL,
22+
appVersion = "XMTPAndroidExample/v1.0.0",
23+
isSecure = false
24+
),
25+
enableAlphaMls = true,
26+
appContext = appContext
27+
)
28+
}
1729

1830
private val _clientState = MutableStateFlow<ClientState>(ClientState.Unknown)
1931
val clientState: StateFlow<ClientState> = _clientState
@@ -28,13 +40,14 @@ object ClientManager {
2840
}
2941

3042
@UiThread
31-
fun createClient(encodedPrivateKeyData: String) {
43+
fun createClient(encodedPrivateKeyData: String, appContext: Context) {
3244
if (clientState.value is ClientState.Ready) return
3345
GlobalScope.launch(Dispatchers.IO) {
3446
try {
3547
val v1Bundle =
3648
PrivateKeyBundleV1Builder.fromEncodedData(data = encodedPrivateKeyData)
37-
_client = Client().buildFrom(v1Bundle, CLIENT_OPTIONS)
49+
_client = Client().buildFrom(v1Bundle, clientOptions(appContext))
50+
Client.register(codec = GroupMembershipChangeCodec())
3851
_clientState.value = ClientState.Ready
3952
} catch (e: Exception) {
4053
_clientState.value = ClientState.Error(e.localizedMessage.orEmpty())

example/src/main/java/org/xmtp/android/example/MainActivity.kt

+16-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import org.xmtp.android.example.conversation.ConversationDetailActivity
2222
import org.xmtp.android.example.conversation.ConversationsAdapter
2323
import org.xmtp.android.example.conversation.ConversationsClickListener
2424
import org.xmtp.android.example.conversation.NewConversationBottomSheet
25+
import org.xmtp.android.example.conversation.NewGroupBottomSheet
2526
import org.xmtp.android.example.databinding.ActivityMainBinding
2627
import org.xmtp.android.example.pushnotifications.PushNotificationTokenManager
2728
import org.xmtp.android.example.utils.KeyUtil
@@ -35,6 +36,7 @@ class MainActivity : AppCompatActivity(),
3536
private lateinit var accountManager: AccountManager
3637
private lateinit var adapter: ConversationsAdapter
3738
private var bottomSheet: NewConversationBottomSheet? = null
39+
private var groupBottomSheet: NewGroupBottomSheet? = null
3840

3941
override fun onCreate(savedInstanceState: Bundle?) {
4042
super.onCreate(savedInstanceState)
@@ -48,7 +50,7 @@ class MainActivity : AppCompatActivity(),
4850
return
4951
}
5052

51-
ClientManager.createClient(keys)
53+
ClientManager.createClient(keys, this)
5254

5355
binding = ActivityMainBinding.inflate(layoutInflater)
5456
setContentView(binding.root)
@@ -67,6 +69,10 @@ class MainActivity : AppCompatActivity(),
6769
openConversationDetail()
6870
}
6971

72+
binding.groupFab.setOnClickListener {
73+
openGroupDetail()
74+
}
75+
7076
lifecycleScope.launch {
7177
repeatOnLifecycle(Lifecycle.State.STARTED) {
7278
ClientManager.clientState.collect(::ensureClientState)
@@ -86,6 +92,7 @@ class MainActivity : AppCompatActivity(),
8692

8793
override fun onDestroy() {
8894
bottomSheet?.dismiss()
95+
groupBottomSheet?.dismiss()
8996
super.onDestroy()
9097
}
9198

@@ -127,6 +134,7 @@ class MainActivity : AppCompatActivity(),
127134
is ClientManager.ClientState.Ready -> {
128135
viewModel.fetchConversations()
129136
binding.fab.visibility = View.VISIBLE
137+
binding.groupFab.visibility = View.VISIBLE
130138
}
131139
is ClientManager.ClientState.Error -> showError(clientState.message)
132140
is ClientManager.ClientState.Unknown -> Unit
@@ -193,4 +201,11 @@ class MainActivity : AppCompatActivity(),
193201
NewConversationBottomSheet.TAG
194202
)
195203
}
204+
private fun openGroupDetail() {
205+
groupBottomSheet = NewGroupBottomSheet.newInstance()
206+
groupBottomSheet?.show(
207+
supportFragmentManager,
208+
NewGroupBottomSheet.TAG
209+
)
210+
}
196211
}

example/src/main/java/org/xmtp/android/example/MainViewModel.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ class MainViewModel : ViewModel() {
4242
viewModelScope.launch(Dispatchers.IO) {
4343
val listItems = mutableListOf<MainListItem>()
4444
try {
45-
val conversations = ClientManager.client.conversations.list()
45+
val conversations = ClientManager.client.conversations.list(includeGroups = true)
4646
PushNotificationTokenManager.xmtpPush.subscribe(conversations.map { it.topic })
4747
listItems.addAll(
4848
conversations.map { conversation ->

example/src/main/java/org/xmtp/android/example/account/WalletConnectV2Account.kt

+28-3
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package org.xmtp.android.example.account
33
import android.net.Uri
44
import com.walletconnect.wcmodal.client.Modal
55
import kotlinx.coroutines.flow.first
6+
import kotlinx.coroutines.runBlocking
67
import org.web3j.crypto.Keys
78
import org.xmtp.android.example.connect.getPersonalSignBody
89
import org.xmtp.android.example.extension.requestMethod
@@ -14,7 +15,7 @@ import org.xmtp.proto.message.contents.SignatureOuterClass
1415
data class WalletConnectV2Account(
1516
val session: Modal.Model.ApprovedSession,
1617
val chain: String,
17-
private val sendSessionRequestDeepLink: (Uri) -> Unit
18+
private val sendSessionRequestDeepLink: (Uri) -> Unit,
1819
) :
1920
SigningKey {
2021
override val address: String
@@ -25,10 +26,34 @@ data class WalletConnectV2Account(
2526
)
2627

2728
override suspend fun sign(data: ByteArray): SignatureOuterClass.Signature? {
28-
return sign(String(data))
29+
return signLegacy(String(data))
2930
}
3031

31-
override suspend fun sign(message: String): SignatureOuterClass.Signature? {
32+
override fun sign(text: String): ByteArray {
33+
val (parentChain, chainId, account) = session.namespaces.getValue(chain).accounts[0].split(":")
34+
val requestParams = session.namespaces.getValue(chain).methods.find { method ->
35+
method == "personal_sign"
36+
}?.let { method ->
37+
Modal.Params.Request(
38+
sessionTopic = session.topic,
39+
method = method,
40+
params = getPersonalSignBody(text, account),
41+
chainId = "$parentChain:$chainId"
42+
)
43+
}
44+
45+
runCatching {
46+
runBlocking {
47+
requestMethod(requestParams!!, sendSessionRequestDeepLink).first().getOrThrow()
48+
}
49+
}.onSuccess {
50+
return it
51+
}.onFailure {}
52+
53+
return byteArrayOf()
54+
}
55+
56+
override suspend fun signLegacy(message: String): SignatureOuterClass.Signature? {
3257
val (parentChain, chainId, account) = session.namespaces.getValue(chain).accounts[0].split(":")
3358
val requestParams = session.namespaces.getValue(chain).methods.find { method ->
3459
method == "personal_sign"

example/src/main/java/org/xmtp/android/example/connect/ConnectWalletViewModel.kt

+8-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
package org.xmtp.android.example.connect
22

3+
import android.app.Application
34
import android.net.Uri
45
import androidx.annotation.UiThread
5-
import androidx.lifecycle.ViewModel
6+
import androidx.lifecycle.AndroidViewModel
67
import androidx.lifecycle.viewModelScope
78
import com.walletconnect.wcmodal.client.Modal
89
import kotlinx.coroutines.Dispatchers
@@ -20,8 +21,9 @@ import org.xmtp.android.library.Client
2021
import org.xmtp.android.library.XMTPException
2122
import org.xmtp.android.library.messages.PrivateKeyBuilder
2223
import org.xmtp.android.library.messages.PrivateKeyBundleV1Builder
24+
import uniffi.xmtpv3.org.xmtp.android.library.codecs.GroupMembershipChangeCodec
2325

24-
class ConnectWalletViewModel : ViewModel() {
26+
class ConnectWalletViewModel(application: Application) : AndroidViewModel(application) {
2527

2628
private val chains: List<ChainSelectionUi> =
2729
Chains.values().map { it.toChainUiState() }
@@ -84,7 +86,8 @@ class ConnectWalletViewModel : ViewModel() {
8486
_uiState.value = ConnectUiState.Loading
8587
try {
8688
val wallet = PrivateKeyBuilder()
87-
val client = Client().create(wallet, ClientManager.CLIENT_OPTIONS)
89+
val client = Client().create(wallet, ClientManager.clientOptions(getApplication()))
90+
Client.register(codec = GroupMembershipChangeCodec())
8891
_uiState.value = ConnectUiState.Success(
8992
wallet.address,
9093
PrivateKeyBundleV1Builder.encodeData(client.privateKeyBundleV1)
@@ -108,7 +111,8 @@ class ConnectWalletViewModel : ViewModel() {
108111
it.copy(showWallet = true, uri = uri)
109112
}
110113
}
111-
val client = Client().create(wallet, ClientManager.CLIENT_OPTIONS)
114+
val client = Client().create(wallet, ClientManager.clientOptions(getApplication()))
115+
Client.register(codec = GroupMembershipChangeCodec())
112116
_uiState.value = ConnectUiState.Success(
113117
wallet.address,
114118
PrivateKeyBundleV1Builder.encodeData(client.privateKeyBundleV1)

example/src/main/java/org/xmtp/android/example/conversation/ConversationDetailActivity.kt

+14-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,14 @@ class ConversationDetailActivity : AppCompatActivity() {
5454
setContentView(binding.root)
5555
setSupportActionBar(binding.toolbar)
5656
supportActionBar?.setDisplayHomeAsUpEnabled(true)
57-
supportActionBar?.subtitle = peerAddress?.truncatedAddress()
57+
supportActionBar?.subtitle = if (peerAddress != null && peerAddress!!.contains(",")) {
58+
val addresses = peerAddress?.split(",")?.toMutableList()
59+
addresses?.joinToString(" & ") {
60+
it.truncatedAddress()
61+
}
62+
} else {
63+
peerAddress?.truncatedAddress()
64+
}
5865

5966
adapter = MessageAdapter()
6067
binding.list.layoutManager =
@@ -106,10 +113,12 @@ class ConversationDetailActivity : AppCompatActivity() {
106113
finish()
107114
true
108115
}
116+
109117
R.id.copy_address -> {
110118
copyWalletAddress()
111119
true
112120
}
121+
113122
else -> super.onOptionsItemSelected(item)
114123
}
115124
}
@@ -130,10 +139,12 @@ class ConversationDetailActivity : AppCompatActivity() {
130139
adapter.setData(uiState.listItems)
131140
}
132141
}
142+
133143
is ConversationDetailViewModel.UiState.Success -> {
134144
binding.refresh.isRefreshing = false
135145
adapter.setData(uiState.listItems)
136146
}
147+
137148
is ConversationDetailViewModel.UiState.Error -> {
138149
binding.refresh.isRefreshing = false
139150
showError(uiState.message)
@@ -146,10 +157,12 @@ class ConversationDetailActivity : AppCompatActivity() {
146157
is ConversationDetailViewModel.SendMessageState.Error -> {
147158
showError(sendState.message)
148159
}
160+
149161
ConversationDetailViewModel.SendMessageState.Loading -> {
150162
binding.sendButton.isEnabled = false
151163
binding.messageEditText.isEnabled = false
152164
}
165+
153166
ConversationDetailViewModel.SendMessageState.Success -> {
154167
binding.messageEditText.text.clear()
155168
binding.messageEditText.isEnabled = true

0 commit comments

Comments
 (0)