Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

EuphonyTxPanel Component & Documentation #262

Merged
merged 12 commits into from
Oct 1, 2022
51 changes: 51 additions & 0 deletions EUPHONY_UI.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Euphony Ui

## Introduction

Euphony provides basic UI components for easy use. These work based on Jetpack Compose.

## How to use

1. [Add Jetpack Compose](https://developer.android.com/jetpack/compose/interop/adding) to your
project.
2. Declare Euphony UI where necessary
```kotlin
@Composable
fun MainScreen() {
Column(
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
.padding(horizontal = 16.dp, vertical = 40.dp)
) {
EuphonyTxPanel(
hint = "hint text",
textColor = Red,
panelHeight = 60.dp
)
}
}
```

## Components

### EuphonyTxPanel

Write text to transmit and click button to create sound waves.

#### Preview

<img src='https://github.com/zion830/euphony/blob/master/assets/eutxpanel_screenshot.png?raw=true' width='350px' />

#### Options

Comment on lines +40 to +41
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WoW! So nice options 👍 It will help to customize EuphonyTxPanel!

|Name|Type|Description|
|---|---|---|
|hint|String|Hint text in the the text field.|
|textColor|Color|Text color of the text field. Default color is Black.|
|hintTextColor|Color|Text color of the hint text. Default color is LightGray.|
|textFieldBackgroundColor|Color|Background color of the text field. Default color is LightSkyBlue.|
|buttonBackgroundColor|Color|Background color of the right side button. Default color is LightBlue.|
|panelHeight|Dp|Height of this component.|
|cornerRadius|Dp|Corner radius of this component.|
|iconTintColor|Color|Icon color of the right side button. Default color is White.|
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ mRxManager.listen(); //Listening Start
Below links are detail guides.
- [Getting Started Guide](GETTING_STARTED.md)
- [How To Build & Unit Test Guide](HOWTOBUILD.md)
- [Euphony Ui](EUPHONY_UI.md)

## Architecture
<p align='center'> <img src='https://github.com/euphony-io/euphony/raw/master/assets/euphony_architecture.png' alt='euphony architecture'> </p>
Expand Down
Binary file added assets/eutxpanel_screenshot.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
47 changes: 47 additions & 0 deletions euphony/src/androidTest/java/co/euphony/ui/EuphonyTxPanelKtTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package co.euphony.ui

import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.test.*
import androidx.compose.ui.test.junit4.createComposeRule
import co.euphony.common.Constants
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner


@ExperimentalMaterialApi
@ExperimentalComposeUiApi
@RunWith(MockitoJUnitRunner::class)
class EuphonyTxPanelKtTest {

@get:Rule
val composeTestRule = createComposeRule()

@Before
fun setup() {
composeTestRule.setContent {
EuphonyTxPanel(
hint = "hint text"
)
}
}

@Test
fun haveEmptyValueAtFirst() {
composeTestRule.onNodeWithText("hint text").assertExists()
composeTestRule.onNodeWithContentDescription(Constants.TAG_TX_ICON_START).assertExists()
}

@Test
fun startSendTextAndFinishSending() {
composeTestRule.onNodeWithText("").performTextInput("Text")
composeTestRule.onNodeWithTag(Constants.TAG_TX_BTN).performClick()
composeTestRule.onNodeWithContentDescription(Constants.TAG_TX_ICON_STOP).assertExists()

composeTestRule.onNodeWithTag(Constants.TAG_TX_BTN).performClick()
composeTestRule.onNodeWithContentDescription(Constants.TAG_TX_ICON_START).assertExists()
}
}
9 changes: 7 additions & 2 deletions euphony/src/main/java/co/euphony/common/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,17 @@ public class Constants {
public static final int START_SIGNAL_FREQ = STANDARD_FREQ - CHANNEL_INTERVAL;

public static final int BUNDLE_INTERVAL = CHANNEL_INTERVAL * CHANNEL;

// RX
public static final int MAX_REF = 4000;
public static final int MIN_REF = 50;

public static final int DEFAULT_REF = 500; // BASE Reference value
public static final int DEFAULT_REF = 500; // BASE Reference value

// EuphonyTxPanel Test Tags
public static final String TAG_TX_BTN = "EuphonyTxPanelButton";
public static final String TAG_TX_ICON_STOP = "EuphonyTxPanelIcon_Stop";
public static final String TAG_TX_ICON_START = "EuphonyTxPanelIcon_Start";

// TxRxChecker Test Tags
public static final String TAG_INPUT = "TxRxCheckerInput";
Expand Down
100 changes: 100 additions & 0 deletions euphony/src/main/java/co/euphony/ui/EuphonyTxPanel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package co.euphony.ui

import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Send
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Color.Companion.Black
import androidx.compose.ui.graphics.Color.Companion.LightGray
import androidx.compose.ui.graphics.Color.Companion.White
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewmodel.compose.viewModel
import co.euphony.common.Constants.*
import co.euphony.tx.EuTxManager
import co.euphony.ui.theme.LightBlue
import co.euphony.ui.theme.LightSkyBlue
import co.euphony.ui.viewmodel.TxPanelViewModel
import co.euphony.ui.viewmodel.TxPanelViewModelFactory

@Composable
fun EuphonyTxPanel(
hint: String = "",
textColor: Color = Black,
hintTextColor: Color = LightGray,
textFieldBackgroundColor: Color = LightSkyBlue,
buttonBackgroundColor: Color = LightBlue,
panelHeight: Dp = 54.dp,
cornerRadius: Dp = 8.dp,
iconTintColor: Color = White
) {
val viewModel: TxPanelViewModel =
viewModel(factory = TxPanelViewModelFactory(EuTxManager.getInstance()))

val btnStatus = viewModel.isProcessing.collectAsState()
var textData by remember { mutableStateOf("") }

Row(
modifier = Modifier
.fillMaxWidth()
.height(panelHeight),
verticalAlignment = Alignment.CenterVertically
) {
TextField(
modifier = Modifier
.weight(1f)
.height(panelHeight),
value = textData,
onValueChange = {
textData = it
},
shape = RoundedCornerShape(topStart = cornerRadius, bottomStart = cornerRadius),
colors = TextFieldDefaults.textFieldColors(
textColor = textColor,
backgroundColor = textFieldBackgroundColor,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent,
),
placeholder = {
Text(
text = hint,
color = hintTextColor
)
}
)
Button(
onClick = {
viewModel.onBtnClick(textData)
},
modifier = Modifier
.height(panelHeight)
.testTag(TAG_TX_BTN),
shape = RoundedCornerShape(bottomEnd = cornerRadius, topEnd = cornerRadius),
colors = ButtonDefaults.buttonColors(backgroundColor = buttonBackgroundColor),
elevation = null,
) {
Icon(
imageVector = if (btnStatus.value) {
Icons.Default.Close
} else {
Icons.Default.Send
},
contentDescription = if (btnStatus.value) {
TAG_TX_ICON_STOP
} else {
TAG_TX_ICON_START
},
tint = iconTintColor,
)
}
}
}
51 changes: 51 additions & 0 deletions euphony/src/main/java/co/euphony/ui/viewmodel/TxPanelViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package co.euphony.ui.viewmodel

import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import co.euphony.tx.EuTxManager
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

class TxPanelViewModel(
private val txManager: EuTxManager
) : ViewModel() {

private val _isProcessing = MutableStateFlow(false)
val isProcessing: StateFlow<Boolean> = _isProcessing

fun onBtnClick(data: String) {
if (_isProcessing.value) {
stop()
} else {
start(data)
}
}

fun stop() {
txManager.stop()
if (_isProcessing.value) {
_isProcessing.value = false
}
}

private fun start(data: String) {
txManager.code = data
txManager.play(-1)
_isProcessing.value = true
}

override fun onCleared() {
super.onCleared()
txManager.stop()
_isProcessing.value = false
}
}

class TxPanelViewModelFactory(
private val euTxManager: EuTxManager
) : ViewModelProvider.NewInstanceFactory() {

@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T =
TxPanelViewModel(euTxManager) as T
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package co.euphony.ui.viewmodel

import co.euphony.tx.EuTxManager
import org.junit.After
import org.junit.Assert.assertFalse
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnitRunner

@RunWith(MockitoJUnitRunner::class)
class TxPanelViewModelTest {

private lateinit var viewModel: TxPanelViewModel

@Mock
private lateinit var txManager: EuTxManager

@Before
fun setUp() {
viewModel = TxPanelViewModel(txManager)
}

@After
fun tearDown() {
viewModel.stop()
}

@Test
fun `if onBtnClick starts, isProcessing becomes true`() {
assertFalse(viewModel.isProcessing.value)
viewModel.onBtnClick("")
assertTrue(viewModel.isProcessing.value)
}

@Test
fun `if stop() is called, isProcessing becomes false`() {
viewModel.onBtnClick("")
assertTrue(viewModel.isProcessing.value)

viewModel.stop()
assertFalse(viewModel.isProcessing.value)
}
}