This commit is contained in:
erik-everardo 2024-01-29 00:07:36 -06:00
parent 96fd70556b
commit 3ff49d73ce
17 changed files with 211 additions and 32 deletions

View File

@ -3,14 +3,16 @@ package com.isolaatti.audio.audios_list.presentation
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder import androidx.recyclerview.widget.RecyclerView.ViewHolder
import coil.load import coil.load
import com.isolaatti.R
import com.isolaatti.audio.common.domain.Audio import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.databinding.AudioListItemBinding import com.isolaatti.databinding.AudioListItemBinding
class AudiosAdapter( class AudiosAdapter(
private val onClick: ((audio: Audio) -> Unit), private val onPlayClick: ((audio: Audio) -> Unit),
private val onOptionsClick: ((audio: Audio, button: View) -> Boolean) private val onOptionsClick: ((audio: Audio, button: View) -> Boolean)
) : RecyclerView.Adapter<AudiosAdapter.AudiosViewHolder>() { ) : RecyclerView.Adapter<AudiosAdapter.AudiosViewHolder>() {
@ -39,12 +41,18 @@ class AudiosAdapter(
holder.binding.audioAuthor.text = audio.userName holder.binding.audioAuthor.text = audio.userName
holder.binding.thumbnail.load(audio.thumbnail) holder.binding.thumbnail.load(audio.thumbnail)
holder.binding.root.setOnClickListener {
onClick(audio)
}
holder.binding.audioItemOptionsButton.setOnClickListener { holder.binding.audioItemOptionsButton.setOnClickListener {
onOptionsClick(audio, it) onOptionsClick(audio, it)
} }
holder.binding.playButton.icon = ResourcesCompat.getDrawable(
holder.itemView.resources,
if(audio.isPlaying) R.drawable.baseline_pause_24 else R.drawable.baseline_play_arrow_24,
null
)
holder.binding.playButton.setOnClickListener {
onPlayClick(audio)
}
} }
} }

View File

@ -18,6 +18,7 @@ import com.isolaatti.common.ErrorMessageViewModel
import com.isolaatti.databinding.FragmentAudiosBinding import com.isolaatti.databinding.FragmentAudiosBinding
import com.isolaatti.utils.Resource import com.isolaatti.utils.Resource
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlin.properties.Delegates
@AndroidEntryPoint @AndroidEntryPoint
class AudiosFragment : Fragment() { class AudiosFragment : Fragment() {
@ -26,6 +27,7 @@ class AudiosFragment : Fragment() {
private val errorViewModel: ErrorMessageViewModel by activityViewModels() private val errorViewModel: ErrorMessageViewModel by activityViewModels()
private val arguments: AudiosFragmentArgs by navArgs() private val arguments: AudiosFragmentArgs by navArgs()
private lateinit var adapter: AudiosAdapter private lateinit var adapter: AudiosAdapter
private var privilegedUserId by Delegates.notNull<Int>()
private var loadedFirstTime = false private var loadedFirstTime = false
override fun onCreateView( override fun onCreateView(
@ -42,20 +44,25 @@ class AudiosFragment : Fragment() {
val popup = PopupMenu(requireContext(), button) val popup = PopupMenu(requireContext(), button)
popup.menuInflater.inflate(R.menu.audio_item_menu, popup.menu) popup.menuInflater.inflate(R.menu.audio_item_menu, popup.menu)
if(audio.userId != privilegedUserId) {
popup.menu.removeItem(R.id.rename_item)
popup.menu.removeItem(R.id.delete_item)
popup.menu.removeItem(R.id.set_as_profile_audio)
}
popup.show() popup.show()
true true
} }
private val onAudioClick: ((audio: Audio) -> Unit) = { private val onAudioPlayClick: ((audio: Audio) -> Unit) = {
// Play audio // Play audio
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
adapter = AudiosAdapter(onClick = onAudioClick, onOptionsClick = onOptionsClick) adapter = AudiosAdapter(onPlayClick = onAudioPlayClick, onOptionsClick = onOptionsClick)
viewBinding.recyclerAudios.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) viewBinding.recyclerAudios.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
viewBinding.recyclerAudios.adapter = adapter viewBinding.recyclerAudios.adapter = adapter
@ -63,7 +70,8 @@ class AudiosFragment : Fragment() {
setupObservers() setupObservers()
if(arguments.source == SOURCE_PROFILE) { if(arguments.source == SOURCE_PROFILE) {
viewModel.loadAudios(arguments.sourceId.toInt()) privilegedUserId = arguments.sourceId.toInt()
viewModel.loadAudios(privilegedUserId)
} }
} }

View File

@ -1,5 +1,7 @@
package com.isolaatti.audio.common.domain package com.isolaatti.audio.common.domain
import android.net.Uri
import androidx.core.net.toUri
import com.isolaatti.audio.common.data.AudioDto import com.isolaatti.audio.common.data.AudioDto
import com.isolaatti.common.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.connectivity.RetrofitClient.Companion.BASE_URL import com.isolaatti.connectivity.RetrofitClient.Companion.BASE_URL
@ -13,17 +15,16 @@ data class Audio(
val creationTime: ZonedDateTime, val creationTime: ZonedDateTime,
override val userId: Int, override val userId: Int,
val userName: String val userName: String
): Ownable, Serializable { ): Ownable, Playable(), Serializable {
var playing: Boolean = false
val downloadUrl: String get() { override val uri: Uri get() {
return "${BASE_URL}audios/$id.webm" return "${BASE_URL}audios/$id.webm".toUri()
} }
val thumbnail: String get() { override val thumbnail: String get() {
return UrlGen.userProfileImage(userId) return UrlGen.userProfileImage(userId)
} }
companion object { companion object {
fun fromDto(audioDto: AudioDto): Audio { fun fromDto(audioDto: AudioDto): Audio {
return Audio( return Audio(

View File

@ -0,0 +1,13 @@
package com.isolaatti.audio.common.domain
import android.net.Uri
abstract class Playable {
var isPlaying: Boolean = false
abstract val uri: Uri
/**
* Image url, null indicating no image should be shown
*/
abstract val thumbnail: String?
}

View File

@ -1,4 +1,4 @@
package com.isolaatti.audio.recorder.data package com.isolaatti.audio.drafts.data
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey

View File

@ -1,4 +1,4 @@
package com.isolaatti.audio.recorder.data package com.isolaatti.audio.drafts.data
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete

View File

@ -0,0 +1,18 @@
package com.isolaatti.audio.drafts.domain
import android.net.Uri
import androidx.core.net.toUri
import com.isolaatti.MyApplication
import com.isolaatti.audio.common.domain.Playable
import java.io.File
data class AudioDraft(val id: Long, val name: String, val localStorageRelativePath: String) : Playable() {
override val thumbnail: String?
get() = null
override val uri: Uri
get() {
val appFiles = MyApplication.myApp.applicationContext.filesDir
return File(appFiles, localStorageRelativePath).toUri()
}
}

View File

@ -0,0 +1,55 @@
package com.isolaatti.audio.drafts.presentation
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.res.ResourcesCompat
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.isolaatti.R
import com.isolaatti.audio.drafts.domain.AudioDraft
import com.isolaatti.databinding.AudioListItemBinding
class AudioDraftsAdapter(
private val onOptionsClicked: (item: AudioDraft, view: View) -> Unit = { _,_ -> },
private val onPlayClicked: (item: AudioDraft , view: View) -> Unit = { _,_ -> },
private val onItemClicked: (item: AudioDraft , view: View) -> Unit = { _,_ -> }
) : ListAdapter<AudioDraft, AudioDraftsAdapter.AudioDraftViewHolder>(diffCallback) {
inner class AudioDraftViewHolder(val audioListItemBinding: AudioListItemBinding) : ViewHolder(audioListItemBinding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AudioDraftViewHolder {
return AudioDraftViewHolder(
AudioListItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
)
}
override fun onBindViewHolder(holder: AudioDraftViewHolder, position: Int) {
val item = getItem(position)
holder.audioListItemBinding.apply {
audioName.text = item.name
audioItemOptionsButton.setOnClickListener { onOptionsClicked(item, it) }
playButton.icon = ResourcesCompat.getDrawable(
holder.itemView.resources,
if(item.isPlaying) R.drawable.baseline_pause_24 else R.drawable.baseline_play_arrow_24,
null
)
}
}
companion object {
val diffCallback = object: DiffUtil.ItemCallback<AudioDraft>() {
override fun areItemsTheSame(oldItem: AudioDraft, newItem: AudioDraft): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: AudioDraft, newItem: AudioDraft): Boolean {
return oldItem == newItem
}
}
}
}

View File

@ -0,0 +1,6 @@
package com.isolaatti.audio.drafts.presentation
import androidx.lifecycle.ViewModel
class AudioDraftsViewModel : ViewModel() {
}

View File

@ -0,0 +1,49 @@
package com.isolaatti.audio.drafts.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import com.isolaatti.audio.drafts.domain.AudioDraft
import com.isolaatti.audio.drafts.presentation.AudioDraftsAdapter
import com.isolaatti.audio.drafts.presentation.AudioDraftsViewModel
import com.isolaatti.databinding.FragmentAudioDraftsBinding
class AudioDraftsFragment : Fragment() {
private lateinit var binding: FragmentAudioDraftsBinding
private val viewModel: AudioDraftsViewModel by viewModels()
private var adapter: AudioDraftsAdapter? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentAudioDraftsBinding.inflate(inflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = AudioDraftsAdapter(
onPlayClicked = { item: AudioDraft, view: View ->
},
onOptionsClicked = { item, button ->
}
)
binding.recycler.adapter = adapter
binding.recycler.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
}
}

View File

@ -157,7 +157,7 @@ class AudioPlayerConnector(
return return
} }
this.audio = audio this.audio = audio
mediaItem = MediaItem.fromUri(Uri.parse(audio.downloadUrl)) mediaItem = MediaItem.fromUri(audio.uri)
player?.setMediaItem(mediaItem!!) player?.setMediaItem(mediaItem!!)
} }

View File

@ -1,11 +0,0 @@
package com.isolaatti.audio.recorder.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
class AudioDraftsFragment : Fragment() {
}

View File

@ -2,8 +2,8 @@ package com.isolaatti.database
import androidx.room.Database import androidx.room.Database
import androidx.room.RoomDatabase import androidx.room.RoomDatabase
import com.isolaatti.audio.recorder.data.AudioDraftEntity import com.isolaatti.audio.drafts.data.AudioDraftEntity
import com.isolaatti.audio.recorder.data.AudiosDraftsDao import com.isolaatti.audio.drafts.data.AudiosDraftsDao
import com.isolaatti.auth.data.local.UserInfoDao import com.isolaatti.auth.data.local.UserInfoDao
import com.isolaatti.auth.data.local.UserInfoEntity import com.isolaatti.auth.data.local.UserInfoEntity
import com.isolaatti.settings.data.KeyValueDao import com.isolaatti.settings.data.KeyValueDao

View File

@ -11,12 +11,23 @@
<androidx.constraintlayout.widget.ConstraintLayout <androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/play_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Widget.Material3.Button.IconButton"
android:layout_marginStart="4dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"/>
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/thumbnail" android:id="@+id/thumbnail"
android:layout_width="40dp" android:layout_width="40dp"
android:layout_height="40dp" android:layout_height="40dp"
android:layout_margin="16dp" android:layout_marginStart="4dp"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toEndOf="@id/play_button"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
tools:src="@drawable/baseline_image_24" /> tools:src="@drawable/baseline_image_24" />

View File

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -6,4 +6,12 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:title="@string/delete" /> android:title="@string/delete" />
<item
android:id="@+id/rename_item"
android:title="@string/rename"/>
<item
android:id="@+id/set_as_profile_audio"
android:title="@string/set_as_profile_audio"/>
</menu> </menu>

View File

@ -177,4 +177,6 @@
<string name="recover_password">Recover password</string> <string name="recover_password">Recover password</string>
<string name="old_password_not_correct">Password not changed, old password did not match. If you don\'t remember your password, you can always recover it.</string> <string name="old_password_not_correct">Password not changed, old password did not match. If you don\'t remember your password, you can always recover it.</string>
<string name="new_password_is_invalid">New password is invalid. Please check it meets the requirements</string> <string name="new_password_is_invalid">New password is invalid. Please check it meets the requirements</string>
<string name="rename">Rename</string>
<string name="set_as_profile_audio">Set as profile audio</string>
</resources> </resources>