Merge remote-tracking branch 'origin/WIP' into WIP

# Conflicts:
#	app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt
#	app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt
#	app/src/main/java/com/isolaatti/profile/ui/components/ProfileHeader.kt
#	app/src/main/res/values/strings.xml
This commit is contained in:
erik 2025-03-17 22:38:57 -06:00
commit 34326cc8d4
16 changed files with 298 additions and 82 deletions

View File

@ -159,7 +159,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.5'
implementation 'androidx.compose.runtime:runtime-livedata'
implementation("com.google.accompanist:accompanist-permissions:0.36.0")
implementation "androidx.work:work-runtime-ktx:2.10.0"
}

View File

@ -20,6 +20,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp
@ -48,7 +49,7 @@ fun AudioPlayer(
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()}) {
if(isPlaying) {
Icon(painterResource(R.drawable.baseline_pause_24), null)

View File

@ -19,8 +19,10 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@ -40,6 +42,35 @@ import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
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)
@Composable
fun AudioRecorder(
@ -49,13 +80,7 @@ fun AudioRecorder(
onStopRecording: () -> Unit,
onPauseRecording: () -> Unit,
onStartRecording: (fromPaused: Boolean) -> Unit,
isRecording: Boolean,
recordIsStopped: Boolean,
recordIsPaused: Boolean,
isPlaying: Boolean,
isLoading: Boolean,
durationSeconds: Long,
position: Long,
state: AudioRecorderState = AudioRecorderState(),
onSeek: (position: Float) -> Unit = {},
modifier: Modifier = Modifier
) {
@ -68,9 +93,9 @@ fun AudioRecorder(
var duration by remember { mutableLongStateOf(0L) }
LaunchedEffect(isRecording, recordIsPaused) {
LaunchedEffect(state.isRecording, state.recordIsPaused) {
coroutineScope {
while(isRecording && !recordIsPaused) {
while(state.isRecording && !state.recordIsPaused) {
duration += 500
delay(500.milliseconds)
}
@ -94,7 +119,7 @@ fun AudioRecorder(
when {
audioRecordPermissionState.status.isGranted -> {
when {
!isRecording && !recordIsStopped-> {
!state.isRecording && !state.recordIsStopped-> {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.Center
@ -113,7 +138,7 @@ fun AudioRecorder(
}
}
isRecording && !recordIsStopped -> {
state.isRecording && !state.recordIsStopped -> {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(horizontal = 8.dp)
@ -121,7 +146,7 @@ fun AudioRecorder(
Text(duration.milliseconds.clockFormat(), modifier = Modifier.padding(horizontal = 4.dp))
Spacer(Modifier.weight(1f))
IconButton(onClick = {
if(recordIsPaused) {
if(state.recordIsPaused) {
onStartRecording(true)
} else {
@ -129,7 +154,7 @@ fun AudioRecorder(
}
}) {
if(recordIsPaused) {
if(state.recordIsPaused) {
Icon(painterResource(R.drawable.baseline_circle_24), null, tint = Color.Red)
} else {
Icon(painterResource(R.drawable.baseline_pause_24), null)
@ -146,10 +171,10 @@ fun AudioRecorder(
else -> {
AudioPlayer(
modifier = Modifier.fillMaxWidth(),
positionMs = position,
isLoading = isLoading,
isPlaying = isPlaying,
durationMs = durationSeconds,
positionMs = state.position,
isLoading = state.isLoading,
isPlaying = state.isPlaying,
durationMs = state.durationSeconds,
onPlay = onPlay,
onPause = onPause,
onSeek = onSeek

View File

@ -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
)

View File

@ -15,6 +15,7 @@ import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilledTonalIconButton
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@ -33,10 +34,14 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import coil3.compose.AsyncImage
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.posting.posts.domain.entity.Post
import com.isolaatti.utils.UrlGen.userProfileImage
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PostComponent(
post: Post,
@ -50,6 +55,7 @@ fun PostComponent(
onUsernameClick: () -> Unit = {},
onImageClick: (images: List<RemoteImage>, index: Int) -> Unit = {_, _ -> },
onHashtagClick: (hashtag: String) -> Unit = {},
onPlay: (audio: Audio, positionMs: Long) -> Unit = {_, _ -> },
modifier: Modifier = Modifier
) {
Card(modifier = modifier.padding(8.dp).clickable { onClick() }) {
@ -77,7 +83,22 @@ fun PostComponent(
}
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()) {
@ -167,5 +188,5 @@ fun PostComponent(
@Composable
@Preview(device = Devices.PIXEL_5)
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))
}

View File

@ -4,6 +4,7 @@ import android.os.Parcel
import android.os.Parcelable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import com.isolaatti.audio.common.domain.Audio
@ -35,6 +36,10 @@ class Post(
var numberOfLikes by mutableIntStateOf(numberOfLikes)
var numberOfComments by mutableStateOf(numberOfComments)
var playing by mutableStateOf(false)
var durationMs by mutableLongStateOf(0L)
var progressMs by mutableLongStateOf(0L)
constructor(parcel: Parcel) : this(
parcel.readLong(),
parcel.readString()!!,

View File

@ -11,6 +11,7 @@ import androidx.compose.runtime.setValue
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.audio.common.components.AudioRecorderState
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.common.domain.Playable
import com.isolaatti.images.common.domain.entity.Image
@ -61,19 +62,9 @@ class CreatePostViewModel @Inject constructor(
private var audioDraft: Long? = null
private var audioId: String? = null
// region Player state
val isPlaying: MutableStateFlow<Boolean> = MutableStateFlow(false)
var position: MutableStateFlow<Long> = MutableStateFlow(0L)
var duration: MutableStateFlow<Long> = MutableStateFlow(0L)
var isLoading: MutableStateFlow<Boolean> = MutableStateFlow(false)
val audioRecorderState = AudioRecorderState()
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
/**

View File

@ -4,6 +4,9 @@ import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
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.posts.data.remote.FeedDto
import com.isolaatti.posting.posts.domain.entity.Post
@ -40,8 +43,14 @@ abstract class PostListingViewModelBase : ViewModel() {
val errorLoading: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
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 }
val audioRecorderState = AudioRecorderState()
abstract fun getFeed(refresh: Boolean, hashtag: String?)
@ -115,4 +124,16 @@ abstract class PostListingViewModelBase : ViewModel() {
fun onPostAddedAtTheBeginning(post: Post) {
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)
}
}

View File

@ -124,21 +124,21 @@ class CreatePostActivity : IsolaattiBaseActivity() {
private val playerListener: Player.Listener = object: Player.Listener {
override fun onIsPlayingChanged(isPlaying: Boolean) {
viewModel.isPlaying.value = isPlaying
viewModel.audioRecorderState.isPlaying = isPlaying
}
override fun onPlaybackStateChanged(playbackState: Int) {
when(playbackState) {
Player.STATE_READY -> {
viewModel.ended = false
viewModel.isLoading.value = false
viewModel.duration.value = this@CreatePostActivity.mediaController.duration
viewModel.audioRecorderState.isLoading = false
viewModel.audioRecorderState.durationSeconds = this@CreatePostActivity.mediaController.duration
}
Player.STATE_ENDED -> {
viewModel.ended = true
}
Player.STATE_BUFFERING -> {
viewModel.isLoading.value = true
viewModel.audioRecorderState.isLoading = true
}
Player.STATE_IDLE -> {}
@ -291,19 +291,11 @@ class CreatePostActivity : IsolaattiBaseActivity() {
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()
val recordIsPaused by viewModel.recordingIsPaused.collectAsState()
val recordIsStopped by viewModel.recodingIsStopped.collectAsState()
LaunchedEffect(isPlaying) {
while(isPlaying) {
LaunchedEffect(viewModel.audioRecorderState.isPlaying) {
while(viewModel.audioRecorderState.isPlaying) {
delay(500.milliseconds)
viewModel.position.value = mediaController.currentPosition
viewModel.audioRecorderState.position = mediaController.currentPosition
}
}
@ -327,12 +319,7 @@ class CreatePostActivity : IsolaattiBaseActivity() {
mediaRecorder.release()
// clear references
viewModel.recodingIsStopped.value = false
viewModel.isRecording.value = false
viewModel.recordingIsPaused.value = false
viewModel.isPlaying.value = false
viewModel.duration.value = 0L
viewModel.position.value = 0L
viewModel.audioRecorderState.clearState()
viewModel.audioToUpload = null
},
onPlay = {
@ -367,18 +354,19 @@ class CreatePostActivity : IsolaattiBaseActivity() {
start()
}
}
viewModel.recodingIsStopped.value = false
viewModel.isRecording.value = true
viewModel.recordingIsPaused.value = false
viewModel.audioRecorderState.recordIsStopped = false
viewModel.audioRecorderState.isRecording = true
viewModel.audioRecorderState.recordIsPaused = false
},
onPauseRecording = {
mediaRecorder.pause()
viewModel.recodingIsStopped.value = false
viewModel.isRecording.value = true
viewModel.recordingIsPaused.value = true
viewModel.audioRecorderState.recordIsStopped = false
viewModel.audioRecorderState.isRecording = true
viewModel.audioRecorderState.recordIsPaused = true
},
onStopRecording = {
mediaRecorder.stop()
@ -392,17 +380,11 @@ class CreatePostActivity : IsolaattiBaseActivity() {
.build()
)
viewModel.audioToUpload = audioUri
viewModel.recodingIsStopped.value = true
viewModel.isRecording.value = false
viewModel.recordingIsPaused.value = false
viewModel.audioRecorderState.recordIsStopped = true
viewModel.audioRecorderState.isRecording = false
viewModel.audioRecorderState.recordIsPaused = false
},
isPlaying = isPlaying,
position = position,
durationSeconds = duration,
isRecording = isRecording,
recordIsPaused = recordIsPaused,
recordIsStopped = recordIsStopped,
isLoading = isLoading,
state = viewModel.audioRecorderState,
onSeek = { ms ->
mediaController.seekTo(ms.toLong())
}

View File

@ -26,6 +26,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import com.isolaatti.BuildConfig
import com.isolaatti.R
import com.isolaatti.audio.common.components.AudioRecorderState
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.common.Dialogs
import com.isolaatti.common.ErrorMessageViewModel
@ -63,6 +64,7 @@ class PostListingFragment : Fragment() {
private val viewModel: PostListingViewModel by viewModels()
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d(LOG_TAG, "onCreate ; hashtag: $hashtag")

View File

@ -57,6 +57,8 @@ class ProfileViewModel @Inject constructor(
val uploadingProfilePicture: MutableStateFlow<Boolean> = MutableStateFlow(false)
val errorProfilePicture: MutableStateFlow<Resource.Error<RemoteImage>?> = MutableStateFlow(null)
val settingUpAudio: MutableStateFlow<Boolean> = MutableStateFlow(false)
// runs the lists of "Runnable" one by one and clears list. After this is executed,
// caller should report as handled

View File

@ -3,6 +3,8 @@ package com.isolaatti.profile.ui
import android.content.ComponentName
import android.content.Intent
import android.media.MediaRecorder
import android.os.Build
import android.media.MediaRecorder
import android.net.Uri
import android.os.Build
import android.os.Bundle
@ -16,14 +18,18 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.slideInVertically
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.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.Add
import androidx.compose.material3.Card
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
@ -31,6 +37,7 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.ModalBottomSheet
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.pulltorefresh.PullToRefreshBox
import androidx.compose.material3.rememberModalBottomSheetState
@ -43,6 +50,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@ -70,7 +78,9 @@ import com.google.common.util.concurrent.ListenableFuture
import com.isolaatti.BuildConfig
import com.isolaatti.MyApplication
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.common.Dialogs
import com.isolaatti.common.ErrorMessageViewModel
@ -100,8 +110,11 @@ import com.isolaatti.profile.ui.components.ProfilePictureState
import com.isolaatti.reports.data.ContentType
import com.isolaatti.reports.ui.NewReportBottomSheetDialogFragment
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Calendar
@ -159,9 +172,12 @@ class ProfileMainFragment : Fragment() {
private lateinit var mediaController: MediaController
private lateinit var mediaRecorder: MediaRecorder
private var mediaRecorder: MediaRecorder? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.mediaControllerIsAvailable.value = false
val sessionToken = SessionToken(requireContext(), ComponentName(requireContext(), MediaService::class.java))
mediaControllerFuture = MediaController.Builder(requireContext(), sessionToken).buildAsync()
@ -169,6 +185,58 @@ class ProfileMainFragment : Fragment() {
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) {
}
@ -187,6 +255,14 @@ class ProfileMainFragment : Fragment() {
val lazyColumState = rememberLazyListState()
val profile by viewModel.profile.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 {
derivedStateOf {
lazyColumState.firstVisibleItemIndex == 0 && lazyColumState.firstVisibleItemScrollOffset == 0
@ -254,8 +330,7 @@ class ProfileMainFragment : Fragment() {
}
val posts by viewModel.posts.collectAsState()
val loadingProfile by viewModel.loadingProfile.collectAsState()
IsolaattiTheme {
Scaffold(
@ -415,8 +490,77 @@ class ProfileMainFragment : Fragment() {
onFollowChange = { follow ->
viewModel.followUser()
},
profileAudioProgress = 0.4f,
showProfileAudioProgress = true,
audioPlayer = {
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 = {
editProfile.launch(it)
},
@ -489,11 +633,26 @@ class ProfileMainFragment : Fragment() {
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewLifecycleOwner.lifecycleScope.launch {
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 {

View File

@ -69,8 +69,6 @@ fun ProfileHeader(
followerCount: Int,
followingCount: Int,
onImageClick: () -> Unit,
showProfileAudioProgress: Boolean,
profileAudioProgress: Float,
onFollowersClick: () -> Unit,
onEditProfileClick: () -> Unit,
onFollowChange: (Boolean) -> Unit,
@ -190,8 +188,6 @@ fun ProfileHeaderPreview() {
isOwnUser = true,
followingThisUser = true,
onImageClick = {},
profileAudioProgress = 0.6f,
showProfileAudioProgress = true,
onFollowersClick = {},
onFollowChange = {},
onEditProfileClick = {},

View File

@ -234,6 +234,7 @@
<string name="change_profile_photo_question">Change profile picture?</string>
<string name="uploading_photo">Uploading photo</string>
<string name="you_have_now_audio">You have no audio description</string>
<string name="setup_your_profile_audio">Setup your profile audio</string>
<string-array name="report_reasons">
<item>Spam</item>
<item>Explicit content</item>

View File

@ -14,8 +14,8 @@ buildscript {
}
}
plugins {
id 'com.android.application' version '8.7.3' apply false
id 'com.android.library' version '8.7.3' apply false
id 'com.android.application' version '8.8.2' apply false
id 'com.android.library' version '8.8.2' 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.gms.google-services' version '4.4.2' apply false

View File

@ -1,6 +1,6 @@
#Thu Jul 27 21:16:21 CST 2023
distributionBase=GRADLE_USER_HOME
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
zipStorePath=wrapper/dists