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

[Voice to Content] Update waveform #20981

Merged
merged 6 commits into from
Jun 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package org.wordpress.android.ui.voicetocontent

import android.annotation.SuppressLint
import android.app.Dialog
import android.content.Intent
import android.net.Uri
import android.os.Bundle
Expand All @@ -16,7 +18,10 @@ import org.wordpress.android.ui.compose.theme.AppTheme
import org.wordpress.android.R
import org.wordpress.android.util.audio.IAudioRecorder.Companion.REQUIRED_RECORDING_PERMISSIONS
import android.provider.Settings
import android.widget.FrameLayout
import androidx.compose.material.ExperimentalMaterialApi
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import org.wordpress.android.ui.ActivityNavigator
import javax.inject.Inject

Expand Down Expand Up @@ -46,6 +51,25 @@ class VoiceToContentDialogFragment : BottomSheetDialogFragment() {
viewModel.start()
}

@SuppressLint("ClickableViewAccessibility")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = super.onCreateDialog(savedInstanceState) as BottomSheetDialog
dialog.setOnShowListener {
val bottomSheet: FrameLayout = dialog.findViewById(
com.google.android.material.R.id.design_bottom_sheet
) ?: return@setOnShowListener

val behavior = BottomSheetBehavior.from(bottomSheet)
behavior.isDraggable = true
behavior.skipCollapsed = true
behavior.state = BottomSheetBehavior.STATE_EXPANDED

// Disable touch interception by the bottom sheet to allow nested scrolling
bottomSheet.setOnTouchListener { _, _ -> false }
}
return dialog
}

private fun observeViewModel() {
viewModel.requestPermission.observe(viewLifecycleOwner) {
requestAllPermissionsForRecording()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.CircularProgressIndicator
import androidx.compose.material.ContentAlpha
import androidx.compose.material.Icon
Expand All @@ -34,7 +36,9 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.TextStyle
Expand All @@ -48,29 +52,42 @@ import androidx.constraintlayout.compose.Dimension
import org.wordpress.android.R
import org.wordpress.android.ui.compose.components.buttons.Drawable
import org.wordpress.android.ui.compose.theme.AppTheme
import org.wordpress.android.util.audio.RecordingUpdate

@Composable
fun VoiceToContentScreen(
viewModel: VoiceToContentViewModel
) {
val state by viewModel.state.collectAsState()
val amplitudes by viewModel.amplitudes.observeAsState(initial = listOf())
val recordingUpdate by viewModel.recordingUpdate.observeAsState(initial = RecordingUpdate())
val configuration = LocalConfiguration.current
val screenHeight = configuration.screenHeightDp.dp
val bottomSheetHeight = screenHeight * 0.6f // Set to 60% of screen height - but how can it be dynamic?
// Adjust the bottom sheet height based on orientation
val bottomSheetHeight = if (configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
screenHeight // Full height in landscape
} else {
screenHeight * 0.6f // 60% height in portrait
}

Surface(
modifier = Modifier
.fillMaxWidth()
.height(bottomSheetHeight),
color = MaterialTheme.colors.surface
) {
VoiceToContentView(state, amplitudes)
Box(
modifier = Modifier
.fillMaxSize()
.nestedScroll(rememberNestedScrollInteropConnection()) // Enable nested scrolling for the bottom sheet
.verticalScroll(rememberScrollState()) // Enable vertical scrolling for the bottom sheet
) {
VoiceToContentView(state, recordingUpdate)
}
}
}

@Composable
fun VoiceToContentView(state: VoiceToContentUiState, amplitudes: List<Float>) {
fun VoiceToContentView(state: VoiceToContentUiState, recordingUpdate: RecordingUpdate) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
Expand All @@ -84,7 +101,7 @@ fun VoiceToContentView(state: VoiceToContentUiState, amplitudes: List<Float>) {
else -> {
Header(state.header)
SecondaryHeader(state.secondaryHeader)
RecordingPanel(state, amplitudes)
RecordingPanel(state, recordingUpdate)
}
}
}
Expand Down Expand Up @@ -167,7 +184,7 @@ fun SecondaryHeader(model: SecondaryHeaderUIModel?) {
}

@Composable
fun RecordingPanel(model: VoiceToContentUiState, amplitudes: List<Float>) {
fun RecordingPanel(model: VoiceToContentUiState, recordingUpdate: RecordingUpdate) {
model.recordingPanel?.let {
Row(
verticalAlignment = Alignment.CenterVertically,
Expand All @@ -189,14 +206,7 @@ fun RecordingPanel(model: VoiceToContentUiState, amplitudes: List<Float>) {
.height(IntrinsicSize.Max)
.padding(48.dp)
) {
WaveformVisualizer(
amplitudes = amplitudes,
modifier = Modifier
.fillMaxWidth()
.height(40.dp)
.padding(16.dp),
color = MaterialTheme.colors.primary
)
ScrollingWaveformVisualizer(recordingUpdate = recordingUpdate)
}
} else if (model.uiStateType == VoiceToContentUIStateType.INELIGIBLE_FOR_FEATURE) {
InEligible(model = it)
Expand Down Expand Up @@ -338,7 +348,7 @@ fun PreviewInitializingView() {
hasPermission = false
)
)
VoiceToContentView(state = state, amplitudes = listOf())
VoiceToContentView(state = state, recordingUpdate = RecordingUpdate())
}
}

Expand All @@ -360,7 +370,7 @@ fun PreviewReadyToRecordView() {
isEligibleForFeature = true
)
)
VoiceToContentView(state = state, amplitudes = listOf())
VoiceToContentView(state = state, recordingUpdate = RecordingUpdate())
}
}

Expand All @@ -381,7 +391,7 @@ fun PreviewNotEligibleToRecordView() {
upgradeUrl = "https://www.wordpress.com"
)
)
VoiceToContentView(state = state, amplitudes = listOf())
VoiceToContentView(state = state, recordingUpdate = RecordingUpdate())
}
}

Expand All @@ -404,18 +414,7 @@ fun PreviewRecordingView() {
isEligibleForFeature = true
)
)
VoiceToContentView(
state = state,
amplitudes = listOf(
1.1f,
2.2f,
3.3f,
4.4f,
2.2f,
3.3f,
1.1f
)
)
VoiceToContentView(state = state, recordingUpdate = RecordingUpdate())
}
}

Expand All @@ -428,6 +427,6 @@ fun PreviewProcessingView() {
uiStateType = VoiceToContentUIStateType.PROCESSING,
header = HeaderUIModel(label = R.string.voice_to_content_processing_label, onClose = { })
)
VoiceToContentView(state = state, amplitudes = listOf())
VoiceToContentView(state = state, recordingUpdate = RecordingUpdate())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import org.wordpress.android.ui.voicetocontent.VoiceToContentUIStateType.RECORDI
import org.wordpress.android.util.audio.IAudioRecorder
import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Error
import org.wordpress.android.util.audio.IAudioRecorder.AudioRecorderResult.Success
import org.wordpress.android.util.audio.RecordingUpdate
import org.wordpress.android.viewmodel.ContextProvider
import org.wordpress.android.viewmodel.ScopedViewModel
import java.io.File
Expand All @@ -49,8 +50,8 @@ class VoiceToContentViewModel @Inject constructor(
private val _dismiss = MutableLiveData<Unit>()
val dismiss = _dismiss as LiveData<Unit>

private val _amplitudes = MutableLiveData<List<Float>>()
val amplitudes: LiveData<List<Float>> get() = _amplitudes
private val _recordingUpdate = MutableLiveData<RecordingUpdate>()
val recordingUpdate: LiveData<RecordingUpdate> get() = _recordingUpdate

private val _onIneligibleForVoiceToContent = MutableLiveData<String>()
val onIneligibleForVoiceToContent = _onIneligibleForVoiceToContent as LiveData<String>
Expand Down Expand Up @@ -104,11 +105,8 @@ class VoiceToContentViewModel @Inject constructor(
}

// Recording
// todo: This doesn't work as expected
@Suppress("MagicNumber")
private fun updateAmplitudes(newAmplitudes: List<Float>) {
_amplitudes.value = listOf(1.1f, 2.2f, 4.4f, 3.2f, 1.1f, 2.2f, 1.0f, 3.5f)
Log.d(javaClass.simpleName, "Update amplitudes: $newAmplitudes")
private fun updateRecordingData(recordingUpdate: RecordingUpdate) {
_recordingUpdate.value = recordingUpdate
}

private fun observeRecordingUpdates() {
Expand All @@ -117,7 +115,7 @@ class VoiceToContentViewModel @Inject constructor(
if (update.fileSizeLimitExceeded) {
stopRecording()
} else {
updateAmplitudes(update.amplitudes)
updateRecordingData(update)
// todo: Handle other updates if needed when UI is ready, e.g., elapsed time and file size
Log.d("AudioRecorder", "Recording update: $update")
}
Expand Down
Loading
Loading