From 1c28dc3a007f9748e61a9171ba7d64658a6a4aea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9rgio=20Santos?= Date: Wed, 31 Mar 2021 22:25:49 +0100 Subject: [PATCH] feat: Add peer screen --- app/build.gradle | 1 + .../relaycorp/ping/test/ActivityAssertions.kt | 34 ++++++ .../relaycorp/ping/test/AppTestComponent.kt | 4 +- .../ping/test/BaseActivityTestRule.kt | 2 + .../tech/relaycorp/ping/test/IntentsRule.kt | 17 +++ .../ui/peers/AddPublicPeerActivityTest.kt | 89 +++++++++++++++ .../ui/{peer => peers}/PeerActivityTest.kt | 3 +- app/src/main/AndroidManifest.xml | 5 +- .../ping/common/di/ActivityComponent.kt | 4 +- .../java/tech/relaycorp/ping/data/ReadFile.kt | 16 +++ .../tech/relaycorp/ping/ui/BaseActivity.kt | 16 ++- .../ping/ui/common/ActivityResult.kt | 9 ++ .../ping/ui/common/SimpleTextWatcher.kt | 18 +++ .../ping/ui/peers/AddPublicPeerActivity.kt | 108 ++++++++++++++++++ .../ping/ui/peers/AddPublicPeerViewModel.kt | 105 +++++++++++++++++ .../ping/ui/{peer => peers}/PeerActivity.kt | 2 +- .../relaycorp/ping/ui/peers/PeerItemView.kt | 4 + .../ping/ui/{peer => peers}/PeerViewModel.kt | 2 +- .../relaycorp/ping/ui/peers/PeersActivity.kt | 8 ++ app/src/main/res/drawable/ic_clear.xml | 10 ++ .../res/layout/activity_add_public_peer.xml | 89 +++++++++++++++ app/src/main/res/layout/activity_peer.xml | 2 +- app/src/main/res/layout/item_peer.xml | 2 +- app/src/main/res/layout/view_field.xml | 6 +- app/src/main/res/menu/add_peer.xml | 8 ++ app/src/main/res/values/strings.xml | 8 ++ app/src/main/res/values/styles.xml | 18 +++ 27 files changed, 574 insertions(+), 16 deletions(-) create mode 100644 app/src/androidTest/java/tech/relaycorp/ping/test/ActivityAssertions.kt create mode 100644 app/src/androidTest/java/tech/relaycorp/ping/test/IntentsRule.kt create mode 100644 app/src/androidTest/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivityTest.kt rename app/src/androidTest/java/tech/relaycorp/ping/ui/{peer => peers}/PeerActivityTest.kt (95%) create mode 100644 app/src/main/java/tech/relaycorp/ping/data/ReadFile.kt create mode 100644 app/src/main/java/tech/relaycorp/ping/ui/common/ActivityResult.kt create mode 100644 app/src/main/java/tech/relaycorp/ping/ui/common/SimpleTextWatcher.kt create mode 100644 app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivity.kt create mode 100644 app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerViewModel.kt rename app/src/main/java/tech/relaycorp/ping/ui/{peer => peers}/PeerActivity.kt (98%) rename app/src/main/java/tech/relaycorp/ping/ui/{peer => peers}/PeerViewModel.kt (97%) create mode 100644 app/src/main/res/drawable/ic_clear.xml create mode 100644 app/src/main/res/layout/activity_add_public_peer.xml create mode 100644 app/src/main/res/menu/add_peer.xml diff --git a/app/build.gradle b/app/build.gradle index 30d215a6..5e5fcb89 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -129,6 +129,7 @@ dependencies { androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$kotlinCoroutinesVersion" androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-intents:3.3.0' androidTestImplementation('com.schibsted.spain:barista:3.7.0') { exclude group: 'org.jetbrains.kotlin' } diff --git a/app/src/androidTest/java/tech/relaycorp/ping/test/ActivityAssertions.kt b/app/src/androidTest/java/tech/relaycorp/ping/test/ActivityAssertions.kt new file mode 100644 index 00000000..6e19f78c --- /dev/null +++ b/app/src/androidTest/java/tech/relaycorp/ping/test/ActivityAssertions.kt @@ -0,0 +1,34 @@ +package tech.relaycorp.ping.test + +import android.app.Activity +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry +import androidx.test.runner.lifecycle.Stage +import org.junit.Assert +import tech.relaycorp.ping.test.WaitAssertions.waitFor +import kotlin.reflect.KClass + +object ActivityAssertions { + val currentActivity: Activity? + get() { + InstrumentationRegistry.getInstrumentation().waitForIdleSync() + val activity = arrayOfNulls(1) + InstrumentationRegistry.getInstrumentation().runOnMainSync { + val activities = + ActivityLifecycleMonitorRegistry.getInstance() + .getActivitiesInStage(Stage.RESUMED) + if (activities.iterator().hasNext()) { + activity[0] = activities.iterator().next() + } + } + return activity[0] + } + + fun assertCurrentActivity(activityKlass: KClass) { + Assert.assertEquals(activityKlass.java.name, currentActivity?.componentName?.className) + } + + fun waitForCurrentActivityToBe(activityKlass: KClass) { + waitFor { assertCurrentActivity(activityKlass) } + } +} diff --git a/app/src/androidTest/java/tech/relaycorp/ping/test/AppTestComponent.kt b/app/src/androidTest/java/tech/relaycorp/ping/test/AppTestComponent.kt index ede6702a..a4faafd0 100644 --- a/app/src/androidTest/java/tech/relaycorp/ping/test/AppTestComponent.kt +++ b/app/src/androidTest/java/tech/relaycorp/ping/test/AppTestComponent.kt @@ -5,7 +5,8 @@ import tech.relaycorp.ping.AppModule import tech.relaycorp.ping.awala.AwalaModule import tech.relaycorp.ping.common.di.AppComponent import tech.relaycorp.ping.ui.main.MainActivityTest -import tech.relaycorp.ping.ui.peer.PeerActivityTest +import tech.relaycorp.ping.ui.peers.AddPublicPeerActivityTest +import tech.relaycorp.ping.ui.peers.PeerActivityTest import tech.relaycorp.ping.ui.peers.PeersActivityTest import tech.relaycorp.ping.ui.ping.PingActivityTest import javax.inject.Singleton @@ -21,6 +22,7 @@ interface AppTestComponent : AppComponent { // Tests + fun inject(test: AddPublicPeerActivityTest) fun inject(test: MainActivityTest) fun inject(test: PeerActivityTest) fun inject(test: PeersActivityTest) diff --git a/app/src/androidTest/java/tech/relaycorp/ping/test/BaseActivityTestRule.kt b/app/src/androidTest/java/tech/relaycorp/ping/test/BaseActivityTestRule.kt index 0a488abe..10b59260 100644 --- a/app/src/androidTest/java/tech/relaycorp/ping/test/BaseActivityTestRule.kt +++ b/app/src/androidTest/java/tech/relaycorp/ping/test/BaseActivityTestRule.kt @@ -19,6 +19,7 @@ class BaseActivityTestRule( private val clearPreferencesRule: ClearPreferencesRule = ClearPreferencesRule() private val clearDatabaseRule: ClearTestDatabaseRule = ClearTestDatabaseRule() private val clearFilesRule: ClearFilesRule = ClearFilesRule() + private val intentsRule: IntentsRule = IntentsRule() private val activityTestRule: ActivityTestRule = ActivityTestRule( activityClass.java, true, @@ -30,6 +31,7 @@ class BaseActivityTestRule( .around(clearPreferencesRule) .around(clearDatabaseRule) .around(clearFilesRule) + .around(intentsRule) .apply(base, description) } diff --git a/app/src/androidTest/java/tech/relaycorp/ping/test/IntentsRule.kt b/app/src/androidTest/java/tech/relaycorp/ping/test/IntentsRule.kt new file mode 100644 index 00000000..64c92278 --- /dev/null +++ b/app/src/androidTest/java/tech/relaycorp/ping/test/IntentsRule.kt @@ -0,0 +1,17 @@ +package tech.relaycorp.ping.test + +import androidx.test.espresso.intent.Intents +import org.junit.rules.TestRule +import org.junit.runner.Description +import org.junit.runners.model.Statement + +class IntentsRule : TestRule { + override fun apply(base: Statement, description: Description?) = + object : Statement() { + override fun evaluate() { + Intents.init() + base.evaluate() + Intents.release() + } + } +} diff --git a/app/src/androidTest/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivityTest.kt b/app/src/androidTest/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivityTest.kt new file mode 100644 index 00000000..646dab5c --- /dev/null +++ b/app/src/androidTest/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivityTest.kt @@ -0,0 +1,89 @@ +package tech.relaycorp.ping.ui.peers + +import android.app.Activity +import android.app.Instrumentation +import android.content.Intent +import android.net.Uri +import androidx.test.espresso.intent.Intents.intending +import androidx.test.espresso.intent.matcher.IntentMatchers.hasAction +import androidx.test.ext.junit.runners.AndroidJUnit4 +import com.schibsted.spain.barista.assertion.BaristaEnabledAssertions.assertDisabled +import com.schibsted.spain.barista.assertion.BaristaEnabledAssertions.assertEnabled +import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed +import com.schibsted.spain.barista.interaction.BaristaClickInteractions.clickOn +import com.schibsted.spain.barista.interaction.BaristaEditTextInteractions.writeTo +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import tech.relaycorp.ping.R +import tech.relaycorp.ping.test.ActivityAssertions.waitForCurrentActivityToBe +import tech.relaycorp.ping.test.AppTestProvider.component +import tech.relaycorp.ping.test.AppTestProvider.context +import tech.relaycorp.ping.test.BaseActivityTestRule + +@RunWith(AndroidJUnit4::class) +class AddPublicPeerActivityTest { + + @Rule + @JvmField + val testRule = BaseActivityTestRule(PeersActivity::class, false) + + @Before + fun setUp() { + component.inject(this) + } + + @Test + fun addPublicPeerSuccessfully() { + testRule.start() + clickOn(R.id.addPeer) + clickOn(R.string.peer_public) + + assertDisabled(R.id.save) + + val address = "ping.awala.services" + writeTo(R.id.addressEdit, address) + + intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)) + .respondWith( + Instrumentation.ActivityResult( + Activity.RESULT_OK, + Intent().setData( + Uri.parse("android.resource://${context.packageName}/${R.raw.ping_awala_identity}") + ) + ) + ) + clickOn(R.string.peer_certificate_button) + + assertEnabled(R.id.save) + clickOn(R.id.save) + + waitForCurrentActivityToBe(PeersActivity::class) + assertDisplayed(address) + } + + @Test + fun addPublicPeerFailure() { + testRule.start() + clickOn(R.id.addPeer) + clickOn(R.string.peer_public) + + val address = "ping.awala.services" + writeTo(R.id.addressEdit, address) + + intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)) + .respondWith( + Instrumentation.ActivityResult( + Activity.RESULT_OK, + Intent().setData( + Uri.parse("android.resource://invalid") + ) + ) + ) + clickOn(R.string.peer_certificate_button) + + clickOn(R.id.save) + assertDisplayed(R.string.peer_add_error) + } +} diff --git a/app/src/androidTest/java/tech/relaycorp/ping/ui/peer/PeerActivityTest.kt b/app/src/androidTest/java/tech/relaycorp/ping/ui/peers/PeerActivityTest.kt similarity index 95% rename from app/src/androidTest/java/tech/relaycorp/ping/ui/peer/PeerActivityTest.kt rename to app/src/androidTest/java/tech/relaycorp/ping/ui/peers/PeerActivityTest.kt index ad3e16cd..196a8c6c 100644 --- a/app/src/androidTest/java/tech/relaycorp/ping/ui/peer/PeerActivityTest.kt +++ b/app/src/androidTest/java/tech/relaycorp/ping/ui/peers/PeerActivityTest.kt @@ -1,4 +1,4 @@ -package tech.relaycorp.ping.ui.peer +package tech.relaycorp.ping.ui.peers import androidx.test.ext.junit.runners.AndroidJUnit4 import com.schibsted.spain.barista.assertion.BaristaVisibilityAssertions.assertDisplayed @@ -18,6 +18,7 @@ import tech.relaycorp.ping.test.BaseActivityTestRule import tech.relaycorp.ping.test.PublicPeerEntityFactory import tech.relaycorp.ping.test.WaitAssertions.suspendWaitFor import tech.relaycorp.ping.test.WaitAssertions.waitFor +import tech.relaycorp.ping.ui.peers.PeerActivity import javax.inject.Inject @RunWith(AndroidJUnit4::class) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 064de170..ff291a46 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -29,7 +29,10 @@ android:name=".ui.peers.PeersActivity" android:label="@string/peers" /> + diff --git a/app/src/main/java/tech/relaycorp/ping/common/di/ActivityComponent.kt b/app/src/main/java/tech/relaycorp/ping/common/di/ActivityComponent.kt index 283f12ec..ca5a2fc5 100644 --- a/app/src/main/java/tech/relaycorp/ping/common/di/ActivityComponent.kt +++ b/app/src/main/java/tech/relaycorp/ping/common/di/ActivityComponent.kt @@ -3,7 +3,8 @@ package tech.relaycorp.ping.common.di import dagger.Subcomponent import tech.relaycorp.gateway.common.di.PerActivity import tech.relaycorp.ping.ui.main.MainActivity -import tech.relaycorp.ping.ui.peer.PeerActivity +import tech.relaycorp.ping.ui.peers.AddPublicPeerActivity +import tech.relaycorp.ping.ui.peers.PeerActivity import tech.relaycorp.ping.ui.peers.PeersActivity import tech.relaycorp.ping.ui.ping.PingActivity @@ -13,6 +14,7 @@ interface ActivityComponent { // Activities + fun inject(activity: AddPublicPeerActivity) fun inject(activity: MainActivity) fun inject(activity: PeerActivity) fun inject(activity: PeersActivity) diff --git a/app/src/main/java/tech/relaycorp/ping/data/ReadFile.kt b/app/src/main/java/tech/relaycorp/ping/data/ReadFile.kt new file mode 100644 index 00000000..43884516 --- /dev/null +++ b/app/src/main/java/tech/relaycorp/ping/data/ReadFile.kt @@ -0,0 +1,16 @@ +package tech.relaycorp.ping.data + +import android.content.Context +import android.net.Uri +import javax.inject.Inject + +class ReadFile +@Inject constructor( + private val context: Context +) { + + fun read(uri: Uri) = + context.contentResolver.openInputStream(uri)?.use { + it.readBytes() + } ?: ByteArray(0) +} diff --git a/app/src/main/java/tech/relaycorp/ping/ui/BaseActivity.kt b/app/src/main/java/tech/relaycorp/ping/ui/BaseActivity.kt index 8bdccead..74dd526d 100644 --- a/app/src/main/java/tech/relaycorp/ping/ui/BaseActivity.kt +++ b/app/src/main/java/tech/relaycorp/ping/ui/BaseActivity.kt @@ -1,17 +1,19 @@ package tech.relaycorp.ping.ui -import android.os.Build +import android.content.Intent import android.os.Bundle -import android.view.View -import android.view.WindowManager import androidx.annotation.DrawableRes import androidx.appcompat.app.AppCompatActivity import androidx.core.view.WindowCompat import dev.chrisbanes.insetter.applyInsetter import kotlinx.android.synthetic.main.common_app_bar.* +import kotlinx.coroutines.channels.sendBlocking +import kotlinx.coroutines.flow.asFlow import tech.relaycorp.gateway.ui.common.MessageManager import tech.relaycorp.ping.App import tech.relaycorp.ping.R +import tech.relaycorp.ping.common.PublishFlow +import tech.relaycorp.ping.ui.common.ActivityResult abstract class BaseActivity : AppCompatActivity() { @@ -20,6 +22,9 @@ abstract class BaseActivity : AppCompatActivity() { protected val messageManager by lazy { MessageManager(this) } + protected val results get() = _results.asFlow() + private val _results = PublishFlow() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -27,6 +32,11 @@ abstract class BaseActivity : AppCompatActivity() { WindowCompat.setDecorFitsSystemWindows(window, true) } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + _results.sendBlocking(ActivityResult(requestCode, resultCode, data)) + } + override fun setContentView(layoutResID: Int) { super.setContentView(layoutResID) toolbarTitle?.text = title diff --git a/app/src/main/java/tech/relaycorp/ping/ui/common/ActivityResult.kt b/app/src/main/java/tech/relaycorp/ping/ui/common/ActivityResult.kt new file mode 100644 index 00000000..335ca281 --- /dev/null +++ b/app/src/main/java/tech/relaycorp/ping/ui/common/ActivityResult.kt @@ -0,0 +1,9 @@ +package tech.relaycorp.ping.ui.common + +import android.content.Intent + +data class ActivityResult( + val requestCode: Int, + val resultCode: Int, + val data: Intent? +) diff --git a/app/src/main/java/tech/relaycorp/ping/ui/common/SimpleTextWatcher.kt b/app/src/main/java/tech/relaycorp/ping/ui/common/SimpleTextWatcher.kt new file mode 100644 index 00000000..c98ae470 --- /dev/null +++ b/app/src/main/java/tech/relaycorp/ping/ui/common/SimpleTextWatcher.kt @@ -0,0 +1,18 @@ +package tech.relaycorp.ping.ui.common + +import android.text.Editable +import android.text.TextWatcher + +class SimpleTextWatcher( + private val textChanged: (String) -> Unit +) : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) { + } + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + textChanged(s?.toString() ?: "") + } + + override fun afterTextChanged(s: Editable?) { + } +} diff --git a/app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivity.kt b/app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivity.kt new file mode 100644 index 00000000..9485a9fa --- /dev/null +++ b/app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerActivity.kt @@ -0,0 +1,108 @@ +package tech.relaycorp.ping.ui.peers + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import androidx.core.view.isVisible +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import kotlinx.android.synthetic.main.activity_add_public_peer.* +import kotlinx.android.synthetic.main.common_app_bar.* +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach +import tech.relaycorp.ping.R +import tech.relaycorp.ping.common.di.ViewModelFactory +import tech.relaycorp.ping.ui.BaseActivity +import tech.relaycorp.ping.ui.common.SimpleTextWatcher +import javax.inject.Inject + +class AddPublicPeerActivity : BaseActivity() { + + @Inject + lateinit var viewModelFactory: ViewModelFactory + + private val viewModel by lazy { + ViewModelProvider(this, viewModelFactory).get(AddPublicPeerViewModel::class.java) + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + component.inject(this) + setContentView(R.layout.activity_add_public_peer) + setupNavigation(R.drawable.ic_close) + + toolbar.inflateMenu(R.menu.add_peer) + toolbar.setOnMenuItemClickListener { + when (it.itemId) { + R.id.save -> viewModel.saveClicked() + } + true + } + + addressEdit.addTextChangedListener(SimpleTextWatcher { + viewModel.aliasChanged(it) + }) + certificateButton.setOnClickListener { + openFileDialog() + } + certificateClear.setOnClickListener { + viewModel.removeCertificateClicked() + } + + viewModel + .hasCertificate() + .onEach { + certificateButton.isVisible = !it + certificateName.isVisible = it + certificateClear.isVisible = it + } + .launchIn(lifecycleScope) + + viewModel + .saveEnabled() + .onEach { + toolbar.menu.findItem(R.id.save).isEnabled = it + } + .launchIn(lifecycleScope) + + viewModel + .errors() + .onEach { + messageManager.showError( + when (it) { + AddPublicPeerViewModel.Error.Save -> R.string.peer_add_error + } + ) + } + .launchIn(lifecycleScope) + + viewModel + .finish() + .onEach { finish() } + .launchIn(lifecycleScope) + + results + .onEach { + if (it.requestCode == PICK_CERTIFICATE && it.resultCode == Activity.RESULT_OK) { + it.data?.data?.let { uri -> viewModel.certificatePicked(uri) } + } + } + .launchIn(lifecycleScope) + } + + private fun openFileDialog() { + startActivityForResult( + Intent(Intent.ACTION_OPEN_DOCUMENT).apply { + type = "*/*" + }, + PICK_CERTIFICATE + ) + } + + companion object { + private const val PICK_CERTIFICATE = 11 + + fun getIntent(context: Context) = Intent(context, AddPublicPeerActivity::class.java) + } +} diff --git a/app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerViewModel.kt b/app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerViewModel.kt new file mode 100644 index 00000000..ec61e377 --- /dev/null +++ b/app/src/main/java/tech/relaycorp/ping/ui/peers/AddPublicPeerViewModel.kt @@ -0,0 +1,105 @@ +package tech.relaycorp.ping.ui.peers + +import android.net.Uri +import kotlinx.coroutines.flow.* +import tech.relaycorp.gateway.ui.BaseViewModel +import tech.relaycorp.ping.common.PublishFlow +import tech.relaycorp.ping.data.ReadFile +import tech.relaycorp.ping.domain.AddPublicPeer +import tech.relaycorp.ping.ui.common.Click +import tech.relaycorp.ping.ui.common.Finish +import tech.relaycorp.ping.ui.common.clicked +import tech.relaycorp.ping.ui.common.finish +import java.util.* +import javax.inject.Inject + +class AddPublicPeerViewModel +@Inject constructor( + addPublicPeer: AddPublicPeer, + readFile: ReadFile +) : BaseViewModel() { + + // Inputs + + private val alias = MutableStateFlow("") + fun aliasChanged(value: String) { + alias.value = value + } + + private val certificatePicks = MutableStateFlow(Optional.empty()) + fun certificatePicked(value: Uri) { + certificatePicks.value = Optional.of(value) + } + + private val removeCertificateClicks = PublishFlow() + fun removeCertificateClicked() { + removeCertificateClicks.clicked() + } + + private val saveClicks = PublishFlow() + fun saveClicked() { + saveClicks.clicked() + } + + // Outputs + + private val _hasCertificate = MutableStateFlow(false) + fun hasCertificate(): Flow = _hasCertificate.asStateFlow() + + private val _saveEnabled = MutableStateFlow(false) + fun saveEnabled(): Flow = _saveEnabled.asStateFlow() + + private val _errors = PublishFlow() + fun errors(): Flow = _errors.asFlow() + + private val _finish = PublishFlow() + fun finish(): Flow = _finish.asFlow() + + init { + val form = alias.combine(certificatePicks, ::Form) + + form + .onEach { _saveEnabled.value = it.isValid() } + .launchIn(backgroundScope) + + certificatePicks + .onEach { _hasCertificate.value = it.isPresent } + .launchIn(backgroundScope) + + removeCertificateClicks + .asFlow() + .onEach { + certificatePicks.value = Optional.empty() + _hasCertificate.value = false + } + .launchIn(backgroundScope) + + saveClicks + .asFlow() + .flatMapLatest { form.take(1) } + .filter { it.isValid() } + .onEach { f -> + val cert = readFile.read(f.certificateUri.get()) + addPublicPeer.add(f.alias, cert) + _finish.finish() + } + .catch { + _errors.send(Error.Save) + } + .launchIn(backgroundScope) + } + + enum class Error { + Save + } + + data class Form( + val alias: String, + val certificateUri: Optional + ) { + fun isValid() = + alias.isNotBlank() + && alias.matches(Regex("(\\w+\\.)+\\w+")) + && certificateUri.isPresent + } +} diff --git a/app/src/main/java/tech/relaycorp/ping/ui/peer/PeerActivity.kt b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerActivity.kt similarity index 98% rename from app/src/main/java/tech/relaycorp/ping/ui/peer/PeerActivity.kt rename to app/src/main/java/tech/relaycorp/ping/ui/peers/PeerActivity.kt index 90136aca..88d41640 100644 --- a/app/src/main/java/tech/relaycorp/ping/ui/peer/PeerActivity.kt +++ b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerActivity.kt @@ -1,4 +1,4 @@ -package tech.relaycorp.ping.ui.peer +package tech.relaycorp.ping.ui.peers import android.content.Context import android.content.Intent diff --git a/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerItemView.kt b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerItemView.kt index bafa1ca3..8085eb77 100644 --- a/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerItemView.kt +++ b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerItemView.kt @@ -25,5 +25,9 @@ constructor( @ModelProp fun setItem(item: Peer) { alias.text = item.alias + + setOnClickListener { + context.startActivity(PeerActivity.getIntent(context, item.privateAddress)) + } } } diff --git a/app/src/main/java/tech/relaycorp/ping/ui/peer/PeerViewModel.kt b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerViewModel.kt similarity index 97% rename from app/src/main/java/tech/relaycorp/ping/ui/peer/PeerViewModel.kt rename to app/src/main/java/tech/relaycorp/ping/ui/peers/PeerViewModel.kt index 72864df5..9e3d1869 100644 --- a/app/src/main/java/tech/relaycorp/ping/ui/peer/PeerViewModel.kt +++ b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeerViewModel.kt @@ -1,4 +1,4 @@ -package tech.relaycorp.ping.ui.peer +package tech.relaycorp.ping.ui.peers import kotlinx.coroutines.flow.* import tech.relaycorp.gateway.ui.BaseViewModel diff --git a/app/src/main/java/tech/relaycorp/ping/ui/peers/PeersActivity.kt b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeersActivity.kt index 6b3f2b39..d05124ac 100644 --- a/app/src/main/java/tech/relaycorp/ping/ui/peers/PeersActivity.kt +++ b/app/src/main/java/tech/relaycorp/ping/ui/peers/PeersActivity.kt @@ -65,6 +65,14 @@ class PeersActivity : BaseActivity() { .setLabel(R.string.peer_private) .create() ) + addPeer.setOnActionSelectedListener { + when (it.id) { + PUBLIC_PEER_ITEM_ID -> + startActivity(AddPublicPeerActivity.getIntent(this)) + } + addPeer.close() + true + } } private fun updateList(peers: List) { diff --git a/app/src/main/res/drawable/ic_clear.xml b/app/src/main/res/drawable/ic_clear.xml new file mode 100644 index 00000000..1ab7bacb --- /dev/null +++ b/app/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_add_public_peer.xml b/app/src/main/res/layout/activity_add_public_peer.xml new file mode 100644 index 00000000..a549cc28 --- /dev/null +++ b/app/src/main/res/layout/activity_add_public_peer.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_peer.xml b/app/src/main/res/layout/activity_peer.xml index d8638d6c..6dba79d5 100644 --- a/app/src/main/res/layout/activity_peer.xml +++ b/app/src/main/res/layout/activity_peer.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context=".ui.peer.PeerActivity"> + tools:context=".ui.peers.PeerActivity"> diff --git a/app/src/main/res/layout/item_peer.xml b/app/src/main/res/layout/item_peer.xml index a4aee849..1d1160f4 100644 --- a/app/src/main/res/layout/item_peer.xml +++ b/app/src/main/res/layout/item_peer.xml @@ -24,7 +24,7 @@ android:id="@+id/alias" android:layout_width="match_parent" android:layout_height="wrap_content" - android:textSize="20sp" + android:textSize="18sp" tools:text="example.org" /> diff --git a/app/src/main/res/layout/view_field.xml b/app/src/main/res/layout/view_field.xml index 396c0b0d..0ade9094 100644 --- a/app/src/main/res/layout/view_field.xml +++ b/app/src/main/res/layout/view_field.xml @@ -8,11 +8,7 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 956cea3a..2480865d 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,6 +6,8 @@ Copied to clipboard Delete Cancel + Clear + Save Ping @@ -24,5 +26,11 @@ Add Endpoint Private Address Are you sure you want to delete this Endpoint? + Address * + Type an address + Identity Certificate * + Upload certificate + Certificate picked + Error saving new peer diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index e1f1b7d2..72ccd45e 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -1,5 +1,7 @@ + @@ -28,6 +30,9 @@ + + + +