This commit is contained in:
Erik Everardo 2023-12-11 23:10:57 -06:00
parent b91a58c8c0
commit d082d7c4d6
25 changed files with 338 additions and 75 deletions

View File

@ -308,7 +308,7 @@
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
<map> <map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/download/baseline_download_24.xml" /> <entry key="url" value="jar:file:/home/erik/Descargas/android-studio/plugins/android/lib/android.jar!/images/material/icons/materialicons/play_circle/baseline_play_circle_24.xml" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>
@ -318,8 +318,8 @@
</option> </option>
<option name="values"> <option name="values">
<map> <map>
<entry key="outputName" value="baseline_download_24" /> <entry key="outputName" value="baseline_play_circle_24" />
<entry key="sourceFile" value="C:\Users\erike\Downloads\isolaatti.svg" /> <entry key="sourceFile" value="$USER_HOME$/C:\Users\erike\Downloads\isolaatti.svg" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>

5
.idea/gradle.xml generated
View File

@ -4,16 +4,15 @@
<component name="GradleSettings"> <component name="GradleSettings">
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" /> <option name="gradleJvm" value="#GRADLE_LOCAL_JAVA_HOME" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" /> <option value="$PROJECT_DIR$/app" />
</set> </set>
</option> </option>
<option name="resolveExternalAnnotations" value="false" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View File

@ -55,6 +55,7 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
implementation "androidx.preference:preference-ktx:1.2.1"
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5' androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
@ -106,15 +107,19 @@ dependencies {
exclude group: 'org.json', module: 'json' exclude group: 'org.json', module: 'json'
} }
// Room Database
def room_version = "2.5.2" def room_version = "2.5.2"
implementation "androidx.room:room-runtime:$room_version" implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version" annotationProcessor "androidx.room:room-compiler:$room_version"
kapt("androidx.room:room-compiler:$room_version") kapt("androidx.room:room-compiler:$room_version")
implementation "androidx.room:room-ktx:2.5.2" implementation "androidx.room:room-ktx:2.5.2"
implementation "androidx.preference:preference-ktx:1.2.1" // Image viewer
implementation 'com.github.MikeOrtiz:TouchImageView:3.5' implementation 'com.github.MikeOrtiz:TouchImageView:3.5'
// Media 3
implementation 'androidx.media3:media3-session:1.2.0'
implementation 'androidx.media3:media3-exoplayer:1.2.0'
implementation "androidx.media3:media3-ui:1.2.0"
} }

View File

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools"> xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<application <application
android:name=".MyApplication" android:name=".MyApplication"
android:allowBackup="true" android:allowBackup="true"

View File

@ -1,5 +1,6 @@
package com.isolaatti.audio package com.isolaatti.audio
import androidx.media3.common.Player
import com.isolaatti.audio.common.data.AudiosApi import com.isolaatti.audio.common.data.AudiosApi
import com.isolaatti.audio.common.data.AudiosRepositoryImpl import com.isolaatti.audio.common.data.AudiosRepositoryImpl
import com.isolaatti.audio.common.domain.AudiosRepository import com.isolaatti.audio.common.domain.AudiosRepository

View File

@ -15,7 +15,7 @@ data class Audio(
): Ownable { ): Ownable {
var playing: Boolean = false var playing: Boolean = false
val downloadUrl: String get() { val downloadUrl: String get() {
return "${BASE_URL}/$id" return "${BASE_URL}audios/$id.webm"
} }
val thumbnail: String get() { val thumbnail: String get() {

View File

@ -0,0 +1,61 @@
package com.isolaatti.audio.player
import android.content.Context
import android.net.Uri
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.lifecycle.LifecycleOwner
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.exoplayer.ExoPlayer
import com.isolaatti.audio.common.domain.Audio
class AudioPlayerConnector(
private val context: Context
): LifecycleEventObserver {
private var player: Player? = null
private var mediaItem: MediaItem? = null
private val playerListener: Player.Listener = object: Player.Listener {
}
private fun initializePlayer() {
player = ExoPlayer.Builder(context).build()
player?.playWhenReady = true
player?.addListener(playerListener)
player?.prepare()
}
private fun releasePlayer() {
player?.run {
release()
}
player = null
}
override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
when(event) {
Lifecycle.Event.ON_START -> {
initializePlayer()
}
Lifecycle.Event.ON_RESUME -> {
if(player == null) {
initializePlayer()
}
}
Lifecycle.Event.ON_STOP, Lifecycle.Event.ON_DESTROY -> {
releasePlayer()
}
else -> {}
}
}
fun playAudio(audio: Audio) {
mediaItem = MediaItem.fromUri(Uri.parse(audio.downloadUrl))
player?.setMediaItem(mediaItem!!)
}
}

View File

@ -0,0 +1,7 @@
package com.isolaatti.audio.player
import androidx.media3.common.Player
abstract class PlayerFactory {
abstract fun MakePlayer(): Player
}

View File

@ -1,6 +1,8 @@
package com.isolaatti.audio.recorder.ui package com.isolaatti.audio.recorder.ui
import android.media.MediaRecorder
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
class AudioRecorderMainFragment : Fragment() { class AudioRecorderMainFragment : Fragment() {
} }

View File

@ -14,7 +14,31 @@ data class Image(
val smallImageUrl : String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_SMALL) val smallImageUrl : String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_SMALL)
val reducedImageUrl: String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_REDUCED) val reducedImageUrl: String get() = UrlGen.imageUrl(id, UrlGen.IMAGE_MODE_REDUCED)
companion object { companion object {
fun fromDto(imageDto: ImageDto) = Image(imageDto.id, imageDto.userId, imageDto.name, imageDto.username) fun fromDto(imageDto: ImageDto) = Image(imageDto.id, imageDto.userId, imageDto.name, imageDto.username)
} }
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Image
if (id != other.id) return false
if (userId != other.userId) return false
if (name != other.name) return false
if (username != other.username) return false
return true
}
override fun hashCode(): Int {
var result = id.hashCode()
result = 31 * result + userId
result = 31 * result + name.hashCode()
result = 31 * result + username.hashCode()
return result
}
} }

View File

@ -13,20 +13,59 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
import kotlin.properties.Delegates
@HiltViewModel @HiltViewModel
class ImageListViewModel @Inject constructor(private val imagesRepository: ImagesRepository) : ViewModel() { class ImageListViewModel @Inject constructor(private val imagesRepository: ImagesRepository) : ViewModel() {
val list: MutableLiveData<Resource<List<Image>>> = MutableLiveData() val liveList: MutableLiveData<List<Image>> = MutableLiveData()
fun loadNext(userId: Int) { val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
val loading: MutableLiveData<Boolean> = MutableLiveData()
var noMoreContent = false
private var loadedFirstTime = false
var userId by Delegates.notNull<Int>()
private val list: List<Image> get() {
return liveList.value ?: listOf()
}
private val lastId: String? get() {
return list.lastOrNull()?.id
}
fun loadNext() {
viewModelScope.launch { viewModelScope.launch {
imagesRepository.getImagesOfUser(userId, null).onEach { imagesRepository.getImagesOfUser(userId, lastId).onEach { resource ->
list.postValue(it) when(resource) {
is Resource.Error -> {
error.postValue(resource.errorType)
}
is Resource.Loading -> {
if(!loadedFirstTime) {
loading.postValue(true)
}
}
is Resource.Success -> {
loading.postValue(false)
noMoreContent = resource.data?.isEmpty() == true
loadedFirstTime = true
if(noMoreContent) {
return@onEach
}
liveList.postValue(list + (resource.data ?: listOf()))
}
}
}.flowOn(Dispatchers.IO).launchIn(this) }.flowOn(Dispatchers.IO).launchIn(this)
} }
} }
fun refresh() {
liveList.value = listOf()
loadNext()
}
fun removeImages(images: List<Image>) { fun removeImages(images: List<Image>) {
} }

View File

@ -1,9 +1,12 @@
package com.isolaatti.images.image_list.presentation package com.isolaatti.images.image_list.presentation
import android.util.Log
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 android.widget.LinearLayout import android.widget.LinearLayout
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.Adapter
import coil.load import coil.load
@ -15,9 +18,22 @@ class ImagesAdapter(
private val imageOnClick: ((images: List<Image>, position: Int) -> Unit), private val imageOnClick: ((images: List<Image>, position: Int) -> Unit),
private val itemWidth: Int, private val itemWidth: Int,
private val onImageSelectedCountUpdate: ((count: Int) -> Unit)? = null, private val onImageSelectedCountUpdate: ((count: Int) -> Unit)? = null,
private val onDeleteMode: ((enabled: Boolean) -> Unit)? = null) : Adapter<ImagesAdapter.ImageViewHolder>(){ private val onDeleteMode: ((enabled: Boolean) -> Unit)? = null,
private val onContentRequested: (() -> Unit)? = null) : ListAdapter<Image, ImagesAdapter.ImageViewHolder>(diffCallback){
companion object {
val diffCallback = object: DiffUtil.ItemCallback<Image>() {
override fun areItemsTheSame(oldItem: Image, newItem: Image): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Image, newItem: Image): Boolean {
return oldItem == newItem
}
}
}
private var data: List<Image> = listOf()
private var selectionState: Array<Boolean> = arrayOf() private var selectionState: Array<Boolean> = arrayOf()
var deleteMode: Boolean = false var deleteMode: Boolean = false
@ -31,14 +47,17 @@ class ImagesAdapter(
inner class ImageViewHolder(val imageItemBinding: ImageItemBinding) : RecyclerView.ViewHolder(imageItemBinding.root) inner class ImageViewHolder(val imageItemBinding: ImageItemBinding) : RecyclerView.ViewHolder(imageItemBinding.root)
fun setData(data: List<Image>) { fun getSelectedImages(): List<Image> {
this.data = data return currentList.filterIndexed { index, _ -> selectionState[index] }
selectionState = Array(data.size) { false }
notifyDataSetChanged()
} }
fun getSelectedImages(): List<Image> { override fun onCurrentListChanged(
return data.filterIndexed { index, _ -> selectionState[index] } previousList: MutableList<Image>,
currentList: MutableList<Image>
) {
super.onCurrentListChanged(previousList, currentList)
noMoreContent = (currentList.size - previousList.size) == 0
selectionState = Array(currentList.size) { false }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
@ -49,12 +68,18 @@ class ImagesAdapter(
return ImageViewHolder(binding) return ImageViewHolder(binding)
} }
override fun getItemCount(): Int { private var requestedNewContent = false
return data.size private var noMoreContent = false
/**
* Call this method when new content has been added on onLoadMore() callback
*/
fun newContentRequestFinished() {
requestedNewContent = false
} }
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) { override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val image = data[position] val image = getItem(position)
holder.imageItemBinding.image.load(image.reducedImageUrl, imageLoader) holder.imageItemBinding.image.load(image.reducedImageUrl, imageLoader)
@ -76,7 +101,7 @@ class ImagesAdapter(
holder.imageItemBinding.imageCheckbox.isChecked = false holder.imageItemBinding.imageCheckbox.isChecked = false
holder.imageItemBinding.imageOverlay.visibility = View.GONE holder.imageItemBinding.imageOverlay.visibility = View.GONE
holder.imageItemBinding.root.setOnClickListener { holder.imageItemBinding.root.setOnClickListener {
imageOnClick(data, position) imageOnClick(currentList, position)
} }
holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener(null) holder.imageItemBinding.imageCheckbox.setOnCheckedChangeListener(null)
holder.imageItemBinding.root.setOnLongClickListener { holder.imageItemBinding.root.setOnLongClickListener {
@ -86,5 +111,16 @@ class ImagesAdapter(
true true
} }
} }
val totalItems = currentList.size
if(totalItems > 0 && !requestedNewContent && !noMoreContent) {
Log.d("ImagesAdapter", "Total items: $totalItems")
if(position == totalItems - 1) {
requestedNewContent = true
onContentRequested?.invoke()
} }
}
}
} }

View File

@ -15,6 +15,7 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.content.FileProvider import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs import androidx.navigation.fragment.navArgs
@ -22,6 +23,7 @@ import androidx.recyclerview.widget.GridLayoutManager
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.MyApplication import com.isolaatti.MyApplication
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.common.ErrorMessageViewModel
import com.isolaatti.databinding.FragmentImagesBinding import com.isolaatti.databinding.FragmentImagesBinding
import com.isolaatti.images.common.domain.entity.Image import com.isolaatti.images.common.domain.entity.Image
import com.isolaatti.images.image_list.presentation.ImageListViewModel import com.isolaatti.images.image_list.presentation.ImageListViewModel
@ -38,6 +40,7 @@ class ImagesFragment : Fragment() {
lateinit var viewBinding: FragmentImagesBinding lateinit var viewBinding: FragmentImagesBinding
lateinit var adapter: ImagesAdapter lateinit var adapter: ImagesAdapter
private val viewModel: ImageListViewModel by viewModels() private val viewModel: ImageListViewModel by viewModels()
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
private val arguments: ImagesFragmentArgs by navArgs() private val arguments: ImagesFragmentArgs by navArgs()
private var cameraPhotoUri: Uri? = null private var cameraPhotoUri: Uri? = null
@ -84,7 +87,8 @@ class ImagesFragment : Fragment() {
when(arguments.source) { when(arguments.source) {
SOURCE_SQUAD -> {} SOURCE_SQUAD -> {}
SOURCE_PROFILE -> { SOURCE_PROFILE -> {
viewModel.loadNext(arguments.sourceId.toInt()) viewModel.userId = arguments.sourceId.toInt()
viewModel.loadNext()
} }
} }
@ -141,11 +145,7 @@ class ImagesFragment : Fragment() {
} }
viewBinding.topAppBar.setOnMenuItemClickListener { viewBinding.topAppBar.setOnMenuItemClickListener {
when(it.itemId) { when(it.itemId) {
R.id.delete_mode_item -> {
adapter.deleteMode = true
actionMode = requireActivity().startActionMode(contextBarCallback)
true
}
else -> false else -> false
} }
} }
@ -170,6 +170,9 @@ class ImagesFragment : Fragment() {
popup.show() popup.show()
} }
viewBinding.swipeToRefresh.setOnRefreshListener {
viewModel.refresh()
}
} }
private fun setupAdapter() { private fun setupAdapter() {
@ -183,6 +186,10 @@ class ImagesFragment : Fragment() {
onDeleteMode = { onDeleteMode = {
adapter.deleteMode = it adapter.deleteMode = it
actionMode = requireActivity().startActionMode(contextBarCallback) actionMode = requireActivity().startActionMode(contextBarCallback)
},
onContentRequested = {
adapter.newContentRequestFinished()
viewModel.loadNext()
}) })
viewBinding.recyclerView.layoutManager = viewBinding.recyclerView.layoutManager =
GridLayoutManager(requireContext(), 3, GridLayoutManager.VERTICAL, false) GridLayoutManager(requireContext(), 3, GridLayoutManager.VERTICAL, false)
@ -191,19 +198,23 @@ class ImagesFragment : Fragment() {
private fun setupObservers() { private fun setupObservers() {
viewModel.list.observe(viewLifecycleOwner) { resource -> viewModel.liveList.observe(viewLifecycleOwner) { list ->
when(resource) { adapter.submitList(list)
is Resource.Error -> {}
is Resource.Loading -> {
viewBinding.progressBarLoading.visibility = View.VISIBLE
}
is Resource.Success -> {
resource.data?.let {
viewBinding.progressBarLoading.visibility = View.GONE
adapter.setData(it)
} }
viewModel.loading.observe(viewLifecycleOwner) {
viewBinding.progressBarLoading.visibility = if(it) {
View.VISIBLE
} else {
View.GONE
}
if(!it) {
viewBinding.swipeToRefresh.isRefreshing = false
} }
} }
viewModel.error.observe(viewLifecycleOwner) {
errorViewModel.error.value = it
} }
} }

View File

@ -1,12 +1,13 @@
package com.isolaatti.profile.data.remote package com.isolaatti.profile.data.remote
import retrofit2.Call import retrofit2.Call
import retrofit2.http.GET
import retrofit2.http.POST import retrofit2.http.POST
import retrofit2.http.Path import retrofit2.http.Path
interface ProfileApi { interface ProfileApi {
@POST("Fetch/UserProfile/{userId}") @GET("Fetch/UserProfile/{userId}")
fun userProfile(@Path("userId") userId: Int): Call<UserProfileDto> fun userProfile(@Path("userId") userId: Int): Call<UserProfileDto>
} }

View File

@ -1,5 +1,7 @@
package com.isolaatti.profile.data.remote package com.isolaatti.profile.data.remote
import com.isolaatti.audio.common.data.AudioDto
data class UserProfileDto( data class UserProfileDto(
val id: Int, val id: Int,
val name: String, val name: String,
@ -13,5 +15,6 @@ data class UserProfileDto(
val thisUserIsFollowingMe: Boolean, val thisUserIsFollowingMe: Boolean,
val profileImageId: String?, val profileImageId: String?,
val descriptionText: String?, val descriptionText: String?,
val descriptionAudioId: String? val descriptionAudioId: String?,
val audio: AudioDto?
) )

View File

@ -1,5 +1,6 @@
package com.isolaatti.profile.domain.entity package com.isolaatti.profile.domain.entity
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.common.Ownable import com.isolaatti.common.Ownable
import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.data.remote.UserProfileDto
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
@ -17,7 +18,8 @@ data class UserProfile(
val thisUserIsFollowingMe: Boolean, val thisUserIsFollowingMe: Boolean,
val profileImageId: String?, val profileImageId: String?,
val descriptionText: String?, val descriptionText: String?,
val descriptionAudioId: String? val descriptionAudioId: String?,
val descriptionAudio: Audio?
) : Ownable { ) : Ownable {
val profileAvatarPictureUrl: String get() = UrlGen.userProfileImage(userId) val profileAvatarPictureUrl: String get() = UrlGen.userProfileImage(userId)
@ -37,7 +39,8 @@ data class UserProfile(
thisUserIsFollowingMe = userProfileDto.thisUserIsFollowingMe, thisUserIsFollowingMe = userProfileDto.thisUserIsFollowingMe,
profileImageId = userProfileDto.profileImageId, profileImageId = userProfileDto.profileImageId,
descriptionText = userProfileDto.descriptionText, descriptionText = userProfileDto.descriptionText,
descriptionAudioId = userProfileDto.descriptionAudioId descriptionAudioId = userProfileDto.descriptionAudioId,
descriptionAudio = userProfileDto.audio?.let { Audio.fromDto(it) }
) )
} }
} }

View File

@ -0,0 +1,4 @@
package com.isolaatti.profile.presentation
class EditProfileContract {
}

View File

@ -0,0 +1,9 @@
package com.isolaatti.profile.presentation
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@HiltViewModel
class EditProfileViewModel @Inject constructor(): ViewModel() {
}

View File

@ -0,0 +1,19 @@
package com.isolaatti.profile.ui
import android.os.Bundle
import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.databinding.ActivityEditProfileBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class EditProfileActivity : IsolaattiBaseActivity() {
private lateinit var binding: ActivityEditProfileBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEditProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
}
}

View File

@ -20,6 +20,8 @@ import coil.load
import com.isolaatti.BuildConfig import com.isolaatti.BuildConfig
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.audio.audios_list.ui.AudiosFragment import com.isolaatti.audio.audios_list.ui.AudiosFragment
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.player.AudioPlayerConnector
import com.isolaatti.common.CoilImageLoader.imageLoader import com.isolaatti.common.CoilImageLoader.imageLoader
import com.isolaatti.common.Dialogs import com.isolaatti.common.Dialogs
import com.isolaatti.common.ErrorMessageViewModel import com.isolaatti.common.ErrorMessageViewModel
@ -61,6 +63,10 @@ class ProfileMainFragment : Fragment() {
lateinit var postsAdapter: PostsRecyclerViewAdapter lateinit var postsAdapter: PostsRecyclerViewAdapter
private var audioDescriptionAudio: Audio? = null
private lateinit var audioPlayerConnector: AudioPlayerConnector
// collapsing bar // collapsing bar
private var title = "" private var title = ""
private var scrollRange = -1 private var scrollRange = -1
@ -103,6 +109,9 @@ class ProfileMainFragment : Fragment() {
fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG) fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG)
} }
audioDescriptionAudio = profile.descriptionAudio
viewBinding.playButton.visibility = if(profile.descriptionAudio != null) View.VISIBLE else View.GONE
setupUiForUserType(profile.isUserItself) setupUiForUserType(profile.isUserItself)
} }
@ -247,6 +256,11 @@ class ProfileMainFragment : Fragment() {
viewBinding.swipeToRefresh.setOnRefreshListener { viewBinding.swipeToRefresh.setOnRefreshListener {
viewModel.getFeed(true) viewModel.getFeed(true)
} }
viewBinding.playButton.setOnClickListener {
audioDescriptionAudio?.let { audio ->
audioPlayerConnector.playAudio(audio)
}
}
} }
private fun setObservers() { private fun setObservers() {
@ -309,6 +323,7 @@ class ProfileMainFragment : Fragment() {
override fun onAttach(context: Context) { override fun onAttach(context: Context) {
super.onAttach(context) super.onAttach(context)
postListingRecyclerViewAdapterWiring = object: PostListingRecyclerViewAdapterWiring(viewModel) { postListingRecyclerViewAdapterWiring = object: PostListingRecyclerViewAdapterWiring(viewModel) {
override fun onComment(postId: Long) { override fun onComment(postId: Long) {
val modalBottomSheet = BottomSheetPostComments.getInstance(postId) val modalBottomSheet = BottomSheetPostComments.getInstance(postId)
@ -353,6 +368,9 @@ class ProfileMainFragment : Fragment() {
setObservers() setObservers()
setupCollapsingBar() setupCollapsingBar()
audioPlayerConnector = AudioPlayerConnector(requireContext())
viewLifecycleOwner.lifecycle.addObserver(audioPlayerConnector)
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -12,5 +12,4 @@
android:layout_height="match_parent" android:layout_height="match_parent"
app:defaultNavHost="true" app:defaultNavHost="true"
app:navGraph="@navigation/profile_navigation" /> app:navGraph="@navigation/profile_navigation" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -67,16 +67,38 @@
app:layout_constraintTop_toBottomOf="@id/text_view_username" app:layout_constraintTop_toBottomOf="@id/text_view_username"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"> app:layout_constraintEnd_toEndOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="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"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginStart="8dp"
app:icon="@drawable/baseline_play_circle_24"
android:visibility="gone"
tools:visibility="visible"
style="?attr/materialIconButtonStyle"/>
<TextView <TextView
android:id="@+id/text_view_description" android:id="@+id/text_view_description"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_margin="16dp" android:layout_margin="16dp"
android:layout_marginHorizontal="20dp" android:layout_marginHorizontal="20dp"
android:maxLines="4" android:maxLines="4"
android:textAlignment="center" android:textAlignment="center"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/play_button"
app:layout_constraintTop_toTopOf="parent"
tools:text="Hi there, I am software developer!" /> tools:text="Hi there, I am software developer!" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.google.android.material.card.MaterialCardView> </com.google.android.material.card.MaterialCardView>
<com.google.android.material.button.MaterialButton <com.google.android.material.button.MaterialButton
@ -102,8 +124,6 @@
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/go_to_followers_btn" /> app:layout_constraintTop_toBottomOf="@id/go_to_followers_btn" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
@ -122,7 +142,6 @@
android:id="@+id/swipe_to_refresh" android:id="@+id/swipe_to_refresh"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_marginBottom="80dp"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"> app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/feed_recycler_view" android:id="@+id/feed_recycler_view"
@ -150,5 +169,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:icon="@drawable/baseline_add_24" app:icon="@drawable/baseline_add_24"
app:layout_anchorGravity="bottom"
app:layout_anchor="@id/bottomAppBar" /> app:layout_anchor="@id/bottomAppBar" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -17,12 +17,16 @@
app:navigationIconTint="@color/on_surface" app:navigationIconTint="@color/on_surface"
app:titleCentered="true"/> app:titleCentered="true"/>
</com.google.android.material.appbar.AppBarLayout> </com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_to_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view" android:id="@+id/recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" /> </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<ProgressBar <ProgressBar
android:id="@+id/progress_bar_loading" android:id="@+id/progress_bar_loading"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -1,12 +1,3 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" <menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto"/>
<item
android:id="@+id/delete_mode_item"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:icon="@drawable/baseline_delete_24"
app:showAsAction="always"
android:title="@string/delete" />
</menu>