WIP
This commit is contained in:
parent
b019c79bcb
commit
eb6026e1d9
@ -159,7 +159,7 @@ dependencies {
|
|||||||
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
|
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
|
||||||
implementation 'androidx.compose.runtime:runtime-livedata'
|
implementation 'androidx.compose.runtime:runtime-livedata'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
implementation("com.google.accompanist:accompanist-permissions:0.36.0")
|
implementation("com.google.accompanist:accompanist-permissions:0.36.0")
|
||||||
|
|
||||||
|
implementation "androidx.work:work-runtime-ktx:2.10.0"
|
||||||
}
|
}
|
||||||
@ -20,6 +20,7 @@ import androidx.compose.runtime.getValue
|
|||||||
import androidx.compose.runtime.mutableFloatStateOf
|
import androidx.compose.runtime.mutableFloatStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.painterResource
|
import androidx.compose.ui.res.painterResource
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
@ -48,7 +49,7 @@ fun AudioPlayer(
|
|||||||
|
|
||||||
Card(modifier = modifier) {
|
Card(modifier = modifier) {
|
||||||
|
|
||||||
Row(modifier = Modifier.padding(16.dp)) {
|
Row(modifier = Modifier.padding(16.dp), verticalAlignment = Alignment.CenterVertically) {
|
||||||
IconButton(onClick = {if(isPlaying) onPause() else onPlay()}) {
|
IconButton(onClick = {if(isPlaying) onPause() else onPlay()}) {
|
||||||
if(isPlaying) {
|
if(isPlaying) {
|
||||||
Icon(painterResource(R.drawable.baseline_pause_24), null)
|
Icon(painterResource(R.drawable.baseline_pause_24), null)
|
||||||
|
|||||||
@ -19,8 +19,10 @@ import androidx.compose.material3.IconButton
|
|||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.MutableState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableLongStateOf
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
@ -40,6 +42,35 @@ import kotlinx.coroutines.coroutineScope
|
|||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
|
|
||||||
|
class AudioRecorderState(
|
||||||
|
isRecording: Boolean = false,
|
||||||
|
recordIsStopped: Boolean = false,
|
||||||
|
recordIsPaused: Boolean = false,
|
||||||
|
isPlaying: Boolean = false,
|
||||||
|
isLoading: Boolean = false,
|
||||||
|
durationSeconds: Long = 0L,
|
||||||
|
position: Long = 0L,
|
||||||
|
|
||||||
|
) {
|
||||||
|
var isRecording by mutableStateOf(isRecording)
|
||||||
|
var recordIsStopped by mutableStateOf(recordIsStopped)
|
||||||
|
var recordIsPaused by mutableStateOf(recordIsPaused)
|
||||||
|
var isPlaying by mutableStateOf(isPlaying)
|
||||||
|
var isLoading by mutableStateOf(isLoading)
|
||||||
|
var durationSeconds by mutableStateOf(durationSeconds)
|
||||||
|
var position by mutableStateOf(position)
|
||||||
|
|
||||||
|
fun clearState() {
|
||||||
|
recordIsStopped = false
|
||||||
|
isRecording = false
|
||||||
|
recordIsPaused = false
|
||||||
|
isPlaying = false
|
||||||
|
durationSeconds = 0L
|
||||||
|
position = 0L
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun AudioRecorder(
|
fun AudioRecorder(
|
||||||
@ -49,13 +80,7 @@ fun AudioRecorder(
|
|||||||
onStopRecording: () -> Unit,
|
onStopRecording: () -> Unit,
|
||||||
onPauseRecording: () -> Unit,
|
onPauseRecording: () -> Unit,
|
||||||
onStartRecording: (fromPaused: Boolean) -> Unit,
|
onStartRecording: (fromPaused: Boolean) -> Unit,
|
||||||
isRecording: Boolean,
|
state: AudioRecorderState = AudioRecorderState(),
|
||||||
recordIsStopped: Boolean,
|
|
||||||
recordIsPaused: Boolean,
|
|
||||||
isPlaying: Boolean,
|
|
||||||
isLoading: Boolean,
|
|
||||||
durationSeconds: Long,
|
|
||||||
position: Long,
|
|
||||||
onSeek: (position: Float) -> Unit = {},
|
onSeek: (position: Float) -> Unit = {},
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
@ -68,9 +93,9 @@ fun AudioRecorder(
|
|||||||
var duration by remember { mutableLongStateOf(0L) }
|
var duration by remember { mutableLongStateOf(0L) }
|
||||||
|
|
||||||
|
|
||||||
LaunchedEffect(isRecording, recordIsPaused) {
|
LaunchedEffect(state.isRecording, state.recordIsPaused) {
|
||||||
coroutineScope {
|
coroutineScope {
|
||||||
while(isRecording && !recordIsPaused) {
|
while(state.isRecording && !state.recordIsPaused) {
|
||||||
duration += 500
|
duration += 500
|
||||||
delay(500.milliseconds)
|
delay(500.milliseconds)
|
||||||
}
|
}
|
||||||
@ -94,7 +119,7 @@ fun AudioRecorder(
|
|||||||
when {
|
when {
|
||||||
audioRecordPermissionState.status.isGranted -> {
|
audioRecordPermissionState.status.isGranted -> {
|
||||||
when {
|
when {
|
||||||
!isRecording && !recordIsStopped-> {
|
!state.isRecording && !state.recordIsStopped-> {
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.Center
|
horizontalArrangement = Arrangement.Center
|
||||||
@ -113,7 +138,7 @@ fun AudioRecorder(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isRecording && !recordIsStopped -> {
|
state.isRecording && !state.recordIsStopped -> {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = Modifier.padding(horizontal = 8.dp)
|
modifier = Modifier.padding(horizontal = 8.dp)
|
||||||
@ -121,7 +146,7 @@ fun AudioRecorder(
|
|||||||
Text(duration.milliseconds.clockFormat(), modifier = Modifier.padding(horizontal = 4.dp))
|
Text(duration.milliseconds.clockFormat(), modifier = Modifier.padding(horizontal = 4.dp))
|
||||||
Spacer(Modifier.weight(1f))
|
Spacer(Modifier.weight(1f))
|
||||||
IconButton(onClick = {
|
IconButton(onClick = {
|
||||||
if(recordIsPaused) {
|
if(state.recordIsPaused) {
|
||||||
onStartRecording(true)
|
onStartRecording(true)
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -129,7 +154,7 @@ fun AudioRecorder(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}) {
|
}) {
|
||||||
if(recordIsPaused) {
|
if(state.recordIsPaused) {
|
||||||
Icon(painterResource(R.drawable.baseline_circle_24), null, tint = Color.Red)
|
Icon(painterResource(R.drawable.baseline_circle_24), null, tint = Color.Red)
|
||||||
} else {
|
} else {
|
||||||
Icon(painterResource(R.drawable.baseline_pause_24), null)
|
Icon(painterResource(R.drawable.baseline_pause_24), null)
|
||||||
@ -146,10 +171,10 @@ fun AudioRecorder(
|
|||||||
else -> {
|
else -> {
|
||||||
AudioPlayer(
|
AudioPlayer(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
positionMs = position,
|
positionMs = state.position,
|
||||||
isLoading = isLoading,
|
isLoading = state.isLoading,
|
||||||
isPlaying = isPlaying,
|
isPlaying = state.isPlaying,
|
||||||
durationMs = durationSeconds,
|
durationMs = state.durationSeconds,
|
||||||
onPlay = onPlay,
|
onPlay = onPlay,
|
||||||
onPause = onPause,
|
onPause = onPause,
|
||||||
onSeek = onSeek
|
onSeek = onSeek
|
||||||
|
|||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.isolaatti.audio.player
|
||||||
|
|
||||||
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
|
|
||||||
|
data class AudioPlaybackState(
|
||||||
|
val currentAudio: Audio? = null,
|
||||||
|
var isPlaying: Boolean = false,
|
||||||
|
val duration: Long = 0L,
|
||||||
|
val progress: Long = 0L
|
||||||
|
)
|
||||||
@ -15,6 +15,7 @@ import androidx.compose.foundation.pager.HorizontalPager
|
|||||||
import androidx.compose.foundation.pager.rememberPagerState
|
import androidx.compose.foundation.pager.rememberPagerState
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FilledTonalIconButton
|
import androidx.compose.material3.FilledTonalIconButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
@ -33,10 +34,14 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.audio.common.components.AudioPlayer
|
||||||
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
|
import com.isolaatti.audio.player.AudioPlaybackState
|
||||||
import com.isolaatti.images.common.domain.entity.RemoteImage
|
import com.isolaatti.images.common.domain.entity.RemoteImage
|
||||||
import com.isolaatti.posting.posts.domain.entity.Post
|
import com.isolaatti.posting.posts.domain.entity.Post
|
||||||
import com.isolaatti.utils.UrlGen.userProfileImage
|
import com.isolaatti.utils.UrlGen.userProfileImage
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable
|
@Composable
|
||||||
fun PostComponent(
|
fun PostComponent(
|
||||||
post: Post,
|
post: Post,
|
||||||
@ -50,6 +55,7 @@ fun PostComponent(
|
|||||||
onUsernameClick: () -> Unit = {},
|
onUsernameClick: () -> Unit = {},
|
||||||
onImageClick: (images: List<RemoteImage>, index: Int) -> Unit = {_, _ -> },
|
onImageClick: (images: List<RemoteImage>, index: Int) -> Unit = {_, _ -> },
|
||||||
onHashtagClick: (hashtag: String) -> Unit = {},
|
onHashtagClick: (hashtag: String) -> Unit = {},
|
||||||
|
onPlay: (audio: Audio, positionMs: Long) -> Unit = {_, _ -> },
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
Card(modifier = modifier.padding(8.dp).clickable { onClick() }) {
|
Card(modifier = modifier.padding(8.dp).clickable { onClick() }) {
|
||||||
@ -77,7 +83,22 @@ fun PostComponent(
|
|||||||
}
|
}
|
||||||
|
|
||||||
post.audio?.let {
|
post.audio?.let {
|
||||||
// TODO audio player here
|
AudioPlayer(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(80.dp),
|
||||||
|
positionMs = 2000L,
|
||||||
|
isPlaying = false,
|
||||||
|
durationMs = 30_000L,
|
||||||
|
isLoading = false,
|
||||||
|
onPlay = {
|
||||||
|
|
||||||
|
},
|
||||||
|
onPause = {
|
||||||
|
|
||||||
|
},
|
||||||
|
onSeek = {
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if(post.textContent.isNotBlank()) {
|
if(post.textContent.isNotBlank()) {
|
||||||
@ -167,5 +188,5 @@ fun PostComponent(
|
|||||||
@Composable
|
@Composable
|
||||||
@Preview(device = Devices.PIXEL_5)
|
@Preview(device = Devices.PIXEL_5)
|
||||||
fun PostPreview() {
|
fun PostPreview() {
|
||||||
PostComponent(Post(id = 0L, textContent = "Test", userId = 1, privacy = 2, date = "Date", images = emptyList(), liked = false, userName = "Username", numberOfLikes = 1, numberOfComments = 2))
|
PostComponent(Post(id = 0L, textContent = "Test", userId = 1, privacy = 2, date = "Date", images = emptyList(), liked = false, userName = "Username", numberOfLikes = 2, numberOfComments = 3))
|
||||||
}
|
}
|
||||||
@ -4,6 +4,7 @@ import android.os.Parcel
|
|||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableIntStateOf
|
import androidx.compose.runtime.mutableIntStateOf
|
||||||
|
import androidx.compose.runtime.mutableLongStateOf
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import com.isolaatti.audio.common.domain.Audio
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
@ -35,6 +36,10 @@ class Post(
|
|||||||
var numberOfLikes by mutableIntStateOf(numberOfLikes)
|
var numberOfLikes by mutableIntStateOf(numberOfLikes)
|
||||||
var numberOfComments by mutableStateOf(numberOfComments)
|
var numberOfComments by mutableStateOf(numberOfComments)
|
||||||
|
|
||||||
|
var playing by mutableStateOf(false)
|
||||||
|
var durationMs by mutableLongStateOf(0L)
|
||||||
|
var progressMs by mutableLongStateOf(0L)
|
||||||
|
|
||||||
constructor(parcel: Parcel) : this(
|
constructor(parcel: Parcel) : this(
|
||||||
parcel.readLong(),
|
parcel.readLong(),
|
||||||
parcel.readString()!!,
|
parcel.readString()!!,
|
||||||
|
|||||||
@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.isolaatti.audio.common.components.AudioRecorderState
|
||||||
import com.isolaatti.audio.common.domain.Audio
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
import com.isolaatti.audio.common.domain.Playable
|
import com.isolaatti.audio.common.domain.Playable
|
||||||
import com.isolaatti.images.common.domain.entity.Image
|
import com.isolaatti.images.common.domain.entity.Image
|
||||||
@ -61,19 +62,9 @@ class CreatePostViewModel @Inject constructor(
|
|||||||
private var audioDraft: Long? = null
|
private var audioDraft: Long? = null
|
||||||
private var audioId: String? = null
|
private var audioId: String? = null
|
||||||
|
|
||||||
// region Player state
|
val audioRecorderState = AudioRecorderState()
|
||||||
val isPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
var position: MutableStateFlow<Long> = MutableStateFlow(0L)
|
|
||||||
var duration: MutableStateFlow<Long> = MutableStateFlow(0L)
|
|
||||||
var isLoading: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
var ended: Boolean = false
|
var ended: Boolean = false
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region Recorder state
|
|
||||||
var isRecording: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
var recordingIsPaused: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
var recodingIsStopped: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -4,6 +4,9 @@ import android.util.Log
|
|||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.isolaatti.audio.common.components.AudioRecorderState
|
||||||
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
|
import com.isolaatti.audio.player.AudioPlaybackState
|
||||||
import com.isolaatti.posting.likes.domain.repository.LikesRepository
|
import com.isolaatti.posting.likes.domain.repository.LikesRepository
|
||||||
import com.isolaatti.posting.posts.data.remote.FeedDto
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
import com.isolaatti.posting.posts.domain.entity.Post
|
import com.isolaatti.posting.posts.domain.entity.Post
|
||||||
@ -40,8 +43,14 @@ abstract class PostListingViewModelBase : ViewModel() {
|
|||||||
val errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
|
val errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
|
||||||
var isLoadingFromScrolling = false
|
var isLoadingFromScrolling = false
|
||||||
|
|
||||||
|
val mediaControllerIsAvailable: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val audioState: MutableStateFlow<AudioPlaybackState> = MutableStateFlow(AudioPlaybackState())
|
||||||
|
|
||||||
fun getLastId(): Long = try { posts.value?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
|
fun getLastId(): Long = try { posts.value?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
|
||||||
|
|
||||||
|
val audioRecorderState = AudioRecorderState()
|
||||||
|
|
||||||
|
|
||||||
abstract fun getFeed(refresh: Boolean, hashtag: String?)
|
abstract fun getFeed(refresh: Boolean, hashtag: String?)
|
||||||
|
|
||||||
@ -115,4 +124,16 @@ abstract class PostListingViewModelBase : ViewModel() {
|
|||||||
fun onPostAddedAtTheBeginning(post: Post) {
|
fun onPostAddedAtTheBeginning(post: Post) {
|
||||||
posts.value = listOf(post) + posts.value
|
posts.value = listOf(post) + posts.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setAudioToPlay(audio: Audio) {
|
||||||
|
audioState.value = AudioPlaybackState(currentAudio = audio)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setIsPlaying(isPlaying: Boolean) {
|
||||||
|
audioState.value = audioState.value.copy(isPlaying = isPlaying)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateProgress(progress: Long, duration: Long) {
|
||||||
|
audioState.value = audioState.value.copy(progress = progress, duration = duration)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -124,21 +124,21 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
|||||||
|
|
||||||
private val playerListener: Player.Listener = object: Player.Listener {
|
private val playerListener: Player.Listener = object: Player.Listener {
|
||||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||||
viewModel.isPlaying.value = isPlaying
|
viewModel.audioRecorderState.isPlaying = isPlaying
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPlaybackStateChanged(playbackState: Int) {
|
override fun onPlaybackStateChanged(playbackState: Int) {
|
||||||
when(playbackState) {
|
when(playbackState) {
|
||||||
Player.STATE_READY -> {
|
Player.STATE_READY -> {
|
||||||
viewModel.ended = false
|
viewModel.ended = false
|
||||||
viewModel.isLoading.value = false
|
viewModel.audioRecorderState.isLoading = false
|
||||||
viewModel.duration.value = this@CreatePostActivity.mediaController.duration
|
viewModel.audioRecorderState.durationSeconds = this@CreatePostActivity.mediaController.duration
|
||||||
}
|
}
|
||||||
Player.STATE_ENDED -> {
|
Player.STATE_ENDED -> {
|
||||||
viewModel.ended = true
|
viewModel.ended = true
|
||||||
}
|
}
|
||||||
Player.STATE_BUFFERING -> {
|
Player.STATE_BUFFERING -> {
|
||||||
viewModel.isLoading.value = true
|
viewModel.audioRecorderState.isLoading = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Player.STATE_IDLE -> {}
|
Player.STATE_IDLE -> {}
|
||||||
@ -291,19 +291,11 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
|||||||
|
|
||||||
AnimatedVisibility(audioRecorderIsVisible) {
|
AnimatedVisibility(audioRecorderIsVisible) {
|
||||||
|
|
||||||
val isPlaying by viewModel.isPlaying.collectAsState()
|
|
||||||
val position by viewModel.position.collectAsState()
|
|
||||||
val duration by viewModel.duration.collectAsState()
|
|
||||||
val isLoading by viewModel.isLoading.collectAsState()
|
|
||||||
|
|
||||||
val isRecording by viewModel.isRecording.collectAsState()
|
LaunchedEffect(viewModel.audioRecorderState.isPlaying) {
|
||||||
val recordIsPaused by viewModel.recordingIsPaused.collectAsState()
|
while(viewModel.audioRecorderState.isPlaying) {
|
||||||
val recordIsStopped by viewModel.recodingIsStopped.collectAsState()
|
|
||||||
|
|
||||||
LaunchedEffect(isPlaying) {
|
|
||||||
while(isPlaying) {
|
|
||||||
delay(500.milliseconds)
|
delay(500.milliseconds)
|
||||||
viewModel.position.value = mediaController.currentPosition
|
viewModel.audioRecorderState.position = mediaController.currentPosition
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,12 +319,7 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
|||||||
mediaRecorder.release()
|
mediaRecorder.release()
|
||||||
|
|
||||||
// clear references
|
// clear references
|
||||||
viewModel.recodingIsStopped.value = false
|
viewModel.audioRecorderState.clearState()
|
||||||
viewModel.isRecording.value = false
|
|
||||||
viewModel.recordingIsPaused.value = false
|
|
||||||
viewModel.isPlaying.value = false
|
|
||||||
viewModel.duration.value = 0L
|
|
||||||
viewModel.position.value = 0L
|
|
||||||
viewModel.audioToUpload = null
|
viewModel.audioToUpload = null
|
||||||
},
|
},
|
||||||
onPlay = {
|
onPlay = {
|
||||||
@ -367,18 +354,19 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
|||||||
start()
|
start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
viewModel.recodingIsStopped.value = false
|
|
||||||
viewModel.isRecording.value = true
|
viewModel.audioRecorderState.recordIsStopped = false
|
||||||
viewModel.recordingIsPaused.value = false
|
viewModel.audioRecorderState.isRecording = true
|
||||||
|
viewModel.audioRecorderState.recordIsPaused = false
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
},
|
},
|
||||||
onPauseRecording = {
|
onPauseRecording = {
|
||||||
mediaRecorder.pause()
|
mediaRecorder.pause()
|
||||||
viewModel.recodingIsStopped.value = false
|
viewModel.audioRecorderState.recordIsStopped = false
|
||||||
viewModel.isRecording.value = true
|
viewModel.audioRecorderState.isRecording = true
|
||||||
viewModel.recordingIsPaused.value = true
|
viewModel.audioRecorderState.recordIsPaused = true
|
||||||
},
|
},
|
||||||
onStopRecording = {
|
onStopRecording = {
|
||||||
mediaRecorder.stop()
|
mediaRecorder.stop()
|
||||||
@ -392,17 +380,11 @@ class CreatePostActivity : IsolaattiBaseActivity() {
|
|||||||
.build()
|
.build()
|
||||||
)
|
)
|
||||||
viewModel.audioToUpload = audioUri
|
viewModel.audioToUpload = audioUri
|
||||||
viewModel.recodingIsStopped.value = true
|
viewModel.audioRecorderState.recordIsStopped = true
|
||||||
viewModel.isRecording.value = false
|
viewModel.audioRecorderState.isRecording = false
|
||||||
viewModel.recordingIsPaused.value = false
|
viewModel.audioRecorderState.recordIsPaused = false
|
||||||
},
|
},
|
||||||
isPlaying = isPlaying,
|
state = viewModel.audioRecorderState,
|
||||||
position = position,
|
|
||||||
durationSeconds = duration,
|
|
||||||
isRecording = isRecording,
|
|
||||||
recordIsPaused = recordIsPaused,
|
|
||||||
recordIsStopped = recordIsStopped,
|
|
||||||
isLoading = isLoading,
|
|
||||||
onSeek = { ms ->
|
onSeek = { ms ->
|
||||||
mediaController.seekTo(ms.toLong())
|
mediaController.seekTo(ms.toLong())
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,6 +26,7 @@ import androidx.fragment.app.viewModels
|
|||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.audio.common.components.AudioRecorderState
|
||||||
import com.isolaatti.audio.common.domain.Audio
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
import com.isolaatti.common.Dialogs
|
import com.isolaatti.common.Dialogs
|
||||||
import com.isolaatti.common.ErrorMessageViewModel
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
@ -63,6 +64,7 @@ class PostListingFragment : Fragment() {
|
|||||||
private val viewModel: PostListingViewModel by viewModels()
|
private val viewModel: PostListingViewModel by viewModels()
|
||||||
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
Log.d(LOG_TAG, "onCreate ; hashtag: $hashtag")
|
Log.d(LOG_TAG, "onCreate ; hashtag: $hashtag")
|
||||||
|
|||||||
@ -45,6 +45,8 @@ class ProfileViewModel @Inject constructor(
|
|||||||
|
|
||||||
val isRefreshing: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
val isRefreshing: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
|
||||||
|
val settingUpAudio: MutableStateFlow<Boolean> = MutableStateFlow(false)
|
||||||
|
|
||||||
|
|
||||||
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
// runs the lists of "Runnable" one by one and clears list. After this is executed,
|
||||||
// caller should report as handled
|
// caller should report as handled
|
||||||
|
|||||||
@ -2,6 +2,8 @@ package com.isolaatti.profile.ui
|
|||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.media.MediaRecorder
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -11,20 +13,25 @@ import android.widget.Toast
|
|||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.slideInVertically
|
import androidx.compose.animation.slideInVertically
|
||||||
import androidx.compose.animation.slideOutVertically
|
import androidx.compose.animation.slideOutVertically
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.consumeWindowInsets
|
import androidx.compose.foundation.layout.consumeWindowInsets
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
import androidx.compose.material.icons.automirrored.filled.ArrowBack
|
||||||
import androidx.compose.material.icons.filled.Add
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.ExperimentalMaterial3Api
|
import androidx.compose.material3.ExperimentalMaterial3Api
|
||||||
import androidx.compose.material3.FloatingActionButton
|
import androidx.compose.material3.FloatingActionButton
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.Scaffold
|
import androidx.compose.material3.Scaffold
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBar
|
import androidx.compose.material3.TopAppBar
|
||||||
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
@ -35,6 +42,7 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.geometry.Offset
|
import androidx.compose.ui.geometry.Offset
|
||||||
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
|
||||||
@ -58,6 +66,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.audio.common.components.AudioPlayer
|
||||||
|
import com.isolaatti.audio.common.components.AudioRecorder
|
||||||
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
import com.isolaatti.audio.player.MediaService
|
import com.isolaatti.audio.player.MediaService
|
||||||
import com.isolaatti.common.Dialogs
|
import com.isolaatti.common.Dialogs
|
||||||
import com.isolaatti.common.ErrorMessageViewModel
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
@ -81,8 +92,12 @@ import com.isolaatti.profile.ui.components.ProfileHeader
|
|||||||
import com.isolaatti.reports.data.ContentType
|
import com.isolaatti.reports.data.ContentType
|
||||||
import com.isolaatti.reports.ui.NewReportBottomSheetDialogFragment
|
import com.isolaatti.reports.ui.NewReportBottomSheetDialogFragment
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.guava.await
|
import kotlinx.coroutines.guava.await
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class ProfileMainFragment : Fragment() {
|
class ProfileMainFragment : Fragment() {
|
||||||
@ -120,9 +135,12 @@ class ProfileMainFragment : Fragment() {
|
|||||||
private lateinit var mediaControllerFuture: ListenableFuture<MediaController>
|
private lateinit var mediaControllerFuture: ListenableFuture<MediaController>
|
||||||
private lateinit var mediaController: MediaController
|
private lateinit var mediaController: MediaController
|
||||||
|
|
||||||
|
private var mediaRecorder: MediaRecorder? = null
|
||||||
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
viewModel.mediaControllerIsAvailable.value = false
|
||||||
val sessionToken = SessionToken(requireContext(), ComponentName(requireContext(), MediaService::class.java))
|
val sessionToken = SessionToken(requireContext(), ComponentName(requireContext(), MediaService::class.java))
|
||||||
mediaControllerFuture = MediaController.Builder(requireContext(), sessionToken).buildAsync()
|
mediaControllerFuture = MediaController.Builder(requireContext(), sessionToken).buildAsync()
|
||||||
|
|
||||||
@ -130,6 +148,58 @@ class ProfileMainFragment : Fragment() {
|
|||||||
getData()
|
getData()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private lateinit var recordOutputFile: File
|
||||||
|
|
||||||
|
// initialized in onViewCreated
|
||||||
|
private lateinit var directory: File
|
||||||
|
|
||||||
|
private fun startRecording(callback: () -> Unit) {
|
||||||
|
recordOutputFile = File(directory, "${System.currentTimeMillis()}.3gp")
|
||||||
|
mediaRecorder = if (Build.VERSION.SDK_INT >= 31) MediaRecorder(requireContext()) else MediaRecorder()
|
||||||
|
mediaRecorder?.run {
|
||||||
|
setAudioSource(MediaRecorder.AudioSource.MIC)
|
||||||
|
setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP)
|
||||||
|
setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB)
|
||||||
|
if (Build.VERSION.SDK_INT >= 26) {
|
||||||
|
setOutputFile(recordOutputFile)
|
||||||
|
} else {
|
||||||
|
setOutputFile(recordOutputFile.path)
|
||||||
|
}
|
||||||
|
|
||||||
|
prepare()
|
||||||
|
start()
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pauseRecording() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun stopRecording() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playRecordedAudio() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pauseRecordedAudio() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun seekTo(ms: Float) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun playAudio(descriptionAudio: Audio) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pauseAudio(descriptionAudio: Audio) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
@ -146,6 +216,14 @@ class ProfileMainFragment : Fragment() {
|
|||||||
val lazyColumState = rememberLazyListState()
|
val lazyColumState = rememberLazyListState()
|
||||||
val profile by viewModel.profile.observeAsState()
|
val profile by viewModel.profile.observeAsState()
|
||||||
val followingState by viewModel.followingState.observeAsState()
|
val followingState by viewModel.followingState.observeAsState()
|
||||||
|
val posts by viewModel.posts.collectAsState()
|
||||||
|
val loadingProfile by viewModel.loadingProfile.collectAsState()
|
||||||
|
val settingUpAudio by viewModel.settingUpAudio.collectAsState()
|
||||||
|
|
||||||
|
val audioPlaybackState by viewModel.audioState.collectAsState()
|
||||||
|
|
||||||
|
// use this to show or hide all audio related UI
|
||||||
|
val mediaControllerIsAvailable by viewModel.mediaControllerIsAvailable.collectAsState()
|
||||||
val isAtTop by remember {
|
val isAtTop by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
lazyColumState.firstVisibleItemIndex == 0 && lazyColumState.firstVisibleItemScrollOffset == 0
|
lazyColumState.firstVisibleItemIndex == 0 && lazyColumState.firstVisibleItemScrollOffset == 0
|
||||||
@ -196,10 +274,8 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
val posts by viewModel.posts.collectAsState()
|
|
||||||
val loadingProfile by viewModel.loadingProfile.collectAsState()
|
|
||||||
|
|
||||||
val playingAudio by remember { mutableStateOf(true) }
|
|
||||||
IsolaattiTheme {
|
IsolaattiTheme {
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = {
|
topBar = {
|
||||||
@ -286,8 +362,77 @@ class ProfileMainFragment : Fragment() {
|
|||||||
onFollowChange = { follow ->
|
onFollowChange = { follow ->
|
||||||
viewModel.followUser()
|
viewModel.followUser()
|
||||||
},
|
},
|
||||||
profileAudioProgress = 0.4f,
|
audioPlayer = {
|
||||||
showProfileAudioProgress = true,
|
if(mediaControllerIsAvailable) {
|
||||||
|
when {
|
||||||
|
settingUpAudio -> {
|
||||||
|
AudioRecorder(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
onDismiss = {
|
||||||
|
viewModel.settingUpAudio.value = false
|
||||||
|
},
|
||||||
|
onPlay = {
|
||||||
|
playRecordedAudio()
|
||||||
|
},
|
||||||
|
onPause = {
|
||||||
|
pauseRecordedAudio()
|
||||||
|
},
|
||||||
|
onStopRecording = {
|
||||||
|
stopRecording()
|
||||||
|
},
|
||||||
|
onPauseRecording = {
|
||||||
|
pauseRecording()
|
||||||
|
},
|
||||||
|
onStartRecording = {
|
||||||
|
startRecording {
|
||||||
|
|
||||||
|
}
|
||||||
|
},
|
||||||
|
state = viewModel.audioRecorderState,
|
||||||
|
onSeek = {
|
||||||
|
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
it.descriptionAudio != null -> {
|
||||||
|
AudioPlayer(
|
||||||
|
modifier = Modifier.fillMaxWidth().height(120.dp),
|
||||||
|
positionMs = 2000L,
|
||||||
|
isPlaying = false,
|
||||||
|
durationMs = 30_000L,
|
||||||
|
isLoading = false,
|
||||||
|
onPlay = {
|
||||||
|
playAudio(it.descriptionAudio)
|
||||||
|
},
|
||||||
|
onPause = {
|
||||||
|
pauseAudio(it.descriptionAudio)
|
||||||
|
},
|
||||||
|
onSeek = { ms ->
|
||||||
|
seekTo(ms)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
it.isUserItself -> {
|
||||||
|
Card(Modifier.fillMaxWidth().height(120.dp).padding(16.dp)) {
|
||||||
|
Column(modifier = Modifier.fillMaxWidth().padding(8.dp),
|
||||||
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
|
verticalArrangement = Arrangement.Center
|
||||||
|
) {
|
||||||
|
Text(stringResource(R.string.setup_your_profile_audio))
|
||||||
|
TextButton(
|
||||||
|
onClick = {
|
||||||
|
viewModel.settingUpAudio.value = true
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Text("Record audio")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
onEditProfileClick = {
|
onEditProfileClick = {
|
||||||
editProfile.launch(it)
|
editProfile.launch(it)
|
||||||
}
|
}
|
||||||
@ -355,11 +500,26 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
mediaController = mediaControllerFuture.await()
|
mediaController = mediaControllerFuture.await()
|
||||||
|
viewModel.mediaControllerIsAvailable.value = true
|
||||||
|
|
||||||
|
// check audios directory
|
||||||
|
directory = File(requireContext().filesDir, "recordings")
|
||||||
|
|
||||||
|
withContext(Dispatchers.IO) {
|
||||||
|
if(!directory.isDirectory) {
|
||||||
|
if(directory.isFile) {
|
||||||
|
directory.delete()
|
||||||
|
}
|
||||||
|
directory.mkdir()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
|||||||
@ -65,11 +65,10 @@ fun ProfileHeader(
|
|||||||
followerCount: Int,
|
followerCount: Int,
|
||||||
followingCount: Int,
|
followingCount: Int,
|
||||||
onImageClick: () -> Unit,
|
onImageClick: () -> Unit,
|
||||||
showProfileAudioProgress: Boolean,
|
|
||||||
profileAudioProgress: Float,
|
|
||||||
onFollowersClick: () -> Unit,
|
onFollowersClick: () -> Unit,
|
||||||
onEditProfileClick: () -> Unit,
|
onEditProfileClick: () -> Unit,
|
||||||
onFollowChange: (Boolean) -> Unit,
|
onFollowChange: (Boolean) -> Unit,
|
||||||
|
audioPlayer: @Composable () -> Unit = {},
|
||||||
loading: Boolean = false,
|
loading: Boolean = false,
|
||||||
isOwnUser: Boolean,
|
isOwnUser: Boolean,
|
||||||
followingThisUser: Boolean,
|
followingThisUser: Boolean,
|
||||||
@ -121,6 +120,8 @@ fun ProfileHeader(
|
|||||||
Text(it, modifier = Modifier.padding(top = 8.dp))
|
Text(it, modifier = Modifier.padding(top = 8.dp))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
audioPlayer()
|
||||||
|
|
||||||
TextButton(onClick = onFollowersClick, modifier = Modifier.padding(top = 4.dp)) {
|
TextButton(onClick = onFollowersClick, modifier = Modifier.padding(top = 4.dp)) {
|
||||||
Text(stringResource(R.string.go_to_followers_btn_text, followerCount, followingCount))
|
Text(stringResource(R.string.go_to_followers_btn_text, followerCount, followingCount))
|
||||||
}
|
}
|
||||||
@ -164,8 +165,6 @@ fun ProfileHeaderPreview() {
|
|||||||
isOwnUser = true,
|
isOwnUser = true,
|
||||||
followingThisUser = true,
|
followingThisUser = true,
|
||||||
onImageClick = {},
|
onImageClick = {},
|
||||||
profileAudioProgress = 0.6f,
|
|
||||||
showProfileAudioProgress = true,
|
|
||||||
onFollowersClick = {},
|
onFollowersClick = {},
|
||||||
onFollowChange = {},
|
onFollowChange = {},
|
||||||
onEditProfileClick = {}
|
onEditProfileClick = {}
|
||||||
|
|||||||
@ -230,6 +230,7 @@
|
|||||||
<string name="profile">Profile</string>
|
<string name="profile">Profile</string>
|
||||||
<string name="add_post_button_desc">Add post button</string>
|
<string name="add_post_button_desc">Add post button</string>
|
||||||
<string name="you_will_see_what_user_posts_here">When %s posts something you will see it here</string>
|
<string name="you_will_see_what_user_posts_here">When %s posts something you will see it here</string>
|
||||||
|
<string name="setup_your_profile_audio">Setup your profile audio</string>
|
||||||
<string-array name="report_reasons">
|
<string-array name="report_reasons">
|
||||||
<item>Spam</item>
|
<item>Spam</item>
|
||||||
<item>Explicit content</item>
|
<item>Explicit content</item>
|
||||||
|
|||||||
@ -14,8 +14,8 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.7.3' apply false
|
id 'com.android.application' version '8.8.2' apply false
|
||||||
id 'com.android.library' version '8.7.3' apply false
|
id 'com.android.library' version '8.8.2' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '2.1.0' apply false
|
id 'org.jetbrains.kotlin.android' version '2.1.0' apply false
|
||||||
id 'com.google.dagger.hilt.android' version '2.53.1' apply false
|
id 'com.google.dagger.hilt.android' version '2.53.1' apply false
|
||||||
id 'com.google.gms.google-services' version '4.4.2' apply false
|
id 'com.google.gms.google-services' version '4.4.2' apply false
|
||||||
|
|||||||
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,6 @@
|
|||||||
#Thu Jul 27 21:16:21 CST 2023
|
#Thu Jul 27 21:16:21 CST 2023
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user