WIP reproduccion de audios en feeds y push notifications
This commit is contained in:
parent
c87e12caab
commit
0aa7b1001f
@ -23,7 +23,7 @@ android {
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "0.1"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@ -3,11 +3,21 @@ package com.isolaatti
|
||||
import android.app.Application
|
||||
import android.content.Context
|
||||
import android.net.ConnectivityManager
|
||||
import android.util.Log
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.datastore.preferences.core.Preferences
|
||||
import androidx.datastore.preferences.preferencesDataStore
|
||||
import com.google.firebase.Firebase
|
||||
import com.google.firebase.messaging.FirebaseMessaging
|
||||
import com.isolaatti.connectivity.ConnectivityCallbackImpl
|
||||
import com.isolaatti.push_notifications.FcmService
|
||||
import com.isolaatti.push_notifications.PushNotificationsApi
|
||||
import dagger.hilt.android.HiltAndroidApp
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.awaitResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
|
||||
|
||||
@ -16,8 +26,12 @@ class MyApplication : Application() {
|
||||
|
||||
companion object {
|
||||
lateinit var myApp: MyApplication
|
||||
const val LOG_TAG = "MyApplication"
|
||||
}
|
||||
|
||||
@Inject
|
||||
lateinit var pushNotificationsApi: PushNotificationsApi
|
||||
|
||||
private val activityLifecycleCallbacks = ActivityLifecycleCallbacks()
|
||||
lateinit var connectivityCallbackImpl: ConnectivityCallbackImpl
|
||||
|
||||
@ -27,6 +41,19 @@ class MyApplication : Application() {
|
||||
registerActivityLifecycleCallbacks(activityLifecycleCallbacks)
|
||||
connectivityCallbackImpl = ConnectivityCallbackImpl()
|
||||
getSystemService(ConnectivityManager::class.java).registerDefaultNetworkCallback(connectivityCallbackImpl)
|
||||
|
||||
FirebaseMessaging.getInstance().token.addOnCompleteListener {
|
||||
if(!it.isSuccessful) {
|
||||
Log.w(LOG_TAG, "Failed fetching fcm token")
|
||||
return@addOnCompleteListener
|
||||
}
|
||||
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = pushNotificationsApi.registerDevice(it.result).awaitResponse()
|
||||
Log.d(FcmService.LOG_TAG, "Device registered. FCM token: $it.result")
|
||||
Log.d(FcmService.LOG_TAG, "Response: isSuccessful: ${response.isSuccessful}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTerminate() {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
package com.isolaatti.audio.common.data
|
||||
|
||||
import java.io.Serializable
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
data class AudiosDto(val data: List<AudioDto>)
|
||||
@ -10,4 +11,4 @@ data class AudioDto(
|
||||
val userId: Int,
|
||||
val firestoreObjectPath: String,
|
||||
val userName: String
|
||||
)
|
||||
): Serializable
|
||||
@ -6,6 +6,8 @@ abstract class Playable {
|
||||
var isPlaying: Boolean = false
|
||||
abstract val uri: Uri
|
||||
var isLoading: Boolean = false
|
||||
var progress: Int = 0
|
||||
var duration: Int = 0
|
||||
|
||||
/**
|
||||
* Image url, null indicating no image should be shown
|
||||
|
||||
@ -1,8 +1,11 @@
|
||||
package com.isolaatti.common
|
||||
|
||||
import com.isolaatti.audio.common.domain.Audio
|
||||
|
||||
interface OnUserInteractedWithPostCallback : OnUserInteractedCallback {
|
||||
fun onLiked(postId: Long)
|
||||
fun onUnLiked(postId: Long)
|
||||
fun onComment(postId: Long)
|
||||
fun onOpenPost(postId: Long)
|
||||
fun onPlay(audio: Audio)
|
||||
}
|
||||
@ -22,6 +22,9 @@ import com.google.android.material.card.MaterialCardView
|
||||
import com.isolaatti.BuildConfig
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.about.AboutActivity
|
||||
import com.isolaatti.audio.common.domain.Audio
|
||||
import com.isolaatti.audio.common.domain.Playable
|
||||
import com.isolaatti.audio.player.AudioPlayerConnector
|
||||
import com.isolaatti.common.CoilImageLoader.imageLoader
|
||||
import com.isolaatti.common.Dialogs
|
||||
import com.isolaatti.common.ErrorMessageViewModel
|
||||
@ -70,6 +73,8 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
private lateinit var viewBinding: FragmentFeedBinding
|
||||
private lateinit var adapter: PostsRecyclerViewAdapter
|
||||
|
||||
private lateinit var audioPlayerConnector: AudioPlayerConnector
|
||||
|
||||
// region launchers
|
||||
|
||||
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
|
||||
@ -127,6 +132,34 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
|
||||
// endregion
|
||||
|
||||
private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
|
||||
override fun onPlaying(isPlaying: Boolean, audio: Playable) {
|
||||
if(audio is Audio)
|
||||
adapter.setIsPlaying(isPlaying, audio)
|
||||
}
|
||||
|
||||
override fun isLoading(isLoading: Boolean, audio: Playable) {
|
||||
if(audio is Audio)
|
||||
adapter.setIsLoading(isLoading, audio)
|
||||
}
|
||||
|
||||
override fun progressChanged(second: Int, audio: Playable) {
|
||||
if(audio is Audio)
|
||||
adapter.setProgress(second, audio)
|
||||
}
|
||||
|
||||
override fun durationChanged(duration: Int, audio: Playable) {
|
||||
if(audio is Audio)
|
||||
adapter.setDuration(duration, audio)
|
||||
}
|
||||
|
||||
override fun onEnded(audio: Playable) {
|
||||
if(audio is Audio)
|
||||
adapter.setEnded(audio)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// region events
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
@ -149,6 +182,9 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
}
|
||||
}
|
||||
|
||||
audioPlayerConnector = AudioPlayerConnector(requireContext())
|
||||
audioPlayerConnector.addListener(audioPlayerConnectorListener)
|
||||
viewLifecycleOwner.lifecycle.addObserver(audioPlayerConnector)
|
||||
|
||||
val markwon = Markwon.builder(requireContext())
|
||||
.usePlugin(object: AbstractMarkwonPlugin() {
|
||||
@ -258,6 +294,10 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
PostViewerActivity.startActivity(requireContext(), postId)
|
||||
}
|
||||
|
||||
override fun onPlay(audio: Audio) {
|
||||
audioPlayerConnector.playPauseAudio(audio)
|
||||
}
|
||||
|
||||
override fun onProfileClick(userId: Int) {
|
||||
ProfileActivity.startActivity(requireContext(), userId)
|
||||
}
|
||||
|
||||
@ -1,7 +0,0 @@
|
||||
package com.isolaatti.home.notifications.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
|
||||
class NotificationsViewModel : ViewModel() {
|
||||
// TODO: Implement the ViewModel
|
||||
}
|
||||
25
app/src/main/java/com/isolaatti/notifications/Module.kt
Normal file
25
app/src/main/java/com/isolaatti/notifications/Module.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package com.isolaatti.notifications
|
||||
|
||||
import com.isolaatti.connectivity.RetrofitClient
|
||||
import com.isolaatti.notifications.data.NotificationsApi
|
||||
import com.isolaatti.notifications.data.NotificationsRepositoryImpl
|
||||
import com.isolaatti.notifications.domain.NotificationsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class Module {
|
||||
|
||||
@Provides
|
||||
fun provideNotificationsApi(retrofitClient: RetrofitClient): NotificationsApi {
|
||||
return retrofitClient.client.create(NotificationsApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideNotificationsRepository(notificationsApi: NotificationsApi): NotificationsRepository {
|
||||
return NotificationsRepositoryImpl(notificationsApi)
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.isolaatti.home.notifications.data
|
||||
package com.isolaatti.notifications.data
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
@ -1,5 +1,6 @@
|
||||
package com.isolaatti.home.notifications.data
|
||||
package com.isolaatti.notifications.data
|
||||
|
||||
import com.google.gson.internal.LinkedTreeMap
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
data class NotificationsDto(
|
||||
@ -18,5 +19,6 @@ data class NotificationPayload(
|
||||
val type: String,
|
||||
val authorId: Int,
|
||||
val authorName: String?,
|
||||
val intentData: String?
|
||||
val intentData: String?,
|
||||
val data: Map<String, String>
|
||||
)
|
||||
@ -0,0 +1,30 @@
|
||||
package com.isolaatti.notifications.data
|
||||
|
||||
import android.util.Log
|
||||
import com.isolaatti.notifications.domain.Notification
|
||||
import com.isolaatti.notifications.domain.NotificationsRepository
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
|
||||
class NotificationsRepositoryImpl(private val notificationsApi: NotificationsApi) : NotificationsRepository {
|
||||
companion object {
|
||||
const val LOG_TAG = "NotificationsRepositoryImpl"
|
||||
}
|
||||
override fun getNotifications(after: Long?): Flow<Resource<List<Notification>>> = flow {
|
||||
try {
|
||||
val response = notificationsApi.getNotifications(after).awaitResponse()
|
||||
|
||||
if(response.isSuccessful) {
|
||||
|
||||
} else {
|
||||
Log.e(LOG_TAG, "getNotifications(): Request is not successful, response code is ${response.code()}")
|
||||
emit(Resource.Error(Resource.Error.mapErrorCode(response.code())))
|
||||
}
|
||||
} catch(e: Exception) {
|
||||
Log.e(LOG_TAG, e.message.toString())
|
||||
emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,106 @@
|
||||
package com.isolaatti.notifications.domain
|
||||
|
||||
import com.isolaatti.databinding.NotificationItemBinding
|
||||
import com.isolaatti.notifications.data.NotificationDto
|
||||
import com.isolaatti.notifications.data.NotificationPayload
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
|
||||
class GenericNotification(id: Long, date: ZonedDateTime, userId: Int, read: Boolean) : Notification(id, date, userId, read) {
|
||||
|
||||
var title: String? = null
|
||||
var message: String? = null
|
||||
|
||||
override fun ingestPayload(notificationPayload: NotificationPayload) {
|
||||
|
||||
}
|
||||
|
||||
override fun bind(notificationBinding: NotificationItemBinding) {
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TYPE = "generic"
|
||||
}
|
||||
}
|
||||
|
||||
class LikeNotification(id: Long, date: ZonedDateTime, userId: Int, read: Boolean) : Notification(id, date, userId, read) {
|
||||
companion object {
|
||||
const val TYPE = "like"
|
||||
}
|
||||
|
||||
override fun ingestPayload(notificationPayload: NotificationPayload) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun bind(notificationBinding: NotificationItemBinding) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
class FollowNotification(id: Long, date: ZonedDateTime, userId: Int, read: Boolean) : Notification(id, date, userId, read) {
|
||||
|
||||
companion object {
|
||||
const val TYPE = "follow"
|
||||
}
|
||||
|
||||
override fun ingestPayload(notificationPayload: NotificationPayload) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun bind(notificationBinding: NotificationItemBinding) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
abstract class Notification(
|
||||
val id: Long,
|
||||
val date: ZonedDateTime,
|
||||
val userId: Int,
|
||||
var read: Boolean
|
||||
) {
|
||||
|
||||
abstract fun ingestPayload(notificationPayload: NotificationPayload)
|
||||
|
||||
abstract fun bind(notificationBinding: NotificationItemBinding)
|
||||
|
||||
companion object {
|
||||
fun fromDto(notificationDto: NotificationDto): Notification? {
|
||||
return when(notificationDto.payload.type) {
|
||||
GenericNotification.TYPE -> {
|
||||
|
||||
GenericNotification(
|
||||
notificationDto.id,
|
||||
notificationDto.date,
|
||||
notificationDto.userId,
|
||||
notificationDto.read
|
||||
).apply {
|
||||
ingestPayload(notificationDto.payload)
|
||||
}
|
||||
}
|
||||
LikeNotification.TYPE -> {
|
||||
LikeNotification(
|
||||
notificationDto.id,
|
||||
notificationDto.date,
|
||||
notificationDto.userId,
|
||||
notificationDto.read
|
||||
).apply {
|
||||
ingestPayload(notificationDto.payload)
|
||||
}
|
||||
}
|
||||
FollowNotification.TYPE -> {
|
||||
FollowNotification(
|
||||
notificationDto.id,
|
||||
notificationDto.date,
|
||||
notificationDto.userId,
|
||||
notificationDto.read
|
||||
).apply {
|
||||
ingestPayload(notificationDto.payload)
|
||||
}
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package com.isolaatti.notifications.domain
|
||||
|
||||
import com.isolaatti.utils.Resource
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface NotificationsRepository {
|
||||
fun getNotifications(after: Long?): Flow<Resource<List<Notification>>>
|
||||
}
|
||||
@ -0,0 +1,36 @@
|
||||
package com.isolaatti.notifications.presentation
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.isolaatti.databinding.NotificationItemBinding
|
||||
import com.isolaatti.notifications.domain.Notification
|
||||
|
||||
class NotificationsAdapter : ListAdapter<Notification, NotificationsAdapter.NotificationViewHolder>(
|
||||
diffCallback
|
||||
) {
|
||||
inner class NotificationViewHolder(val notificationItemBinding: NotificationItemBinding) : ViewHolder(notificationItemBinding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationViewHolder {
|
||||
return NotificationViewHolder(NotificationItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: NotificationViewHolder, position: Int) {
|
||||
getItem(position).bind(holder.notificationItemBinding)
|
||||
}
|
||||
|
||||
companion object {
|
||||
val diffCallback = object: DiffUtil.ItemCallback<Notification>() {
|
||||
override fun areItemsTheSame(oldItem: Notification, newItem: Notification): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: Notification, newItem: Notification): Boolean {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,11 @@
|
||||
package com.isolaatti.notifications.presentation
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.isolaatti.notifications.domain.NotificationsRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class NotificationsViewModel @Inject constructor(private val notificationsRepository: NotificationsRepository) : ViewModel() {
|
||||
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.isolaatti.home.notifications.ui
|
||||
package com.isolaatti.notifications.ui
|
||||
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import android.os.Bundle
|
||||
@ -7,7 +7,7 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.home.notifications.presentation.NotificationsViewModel
|
||||
import com.isolaatti.notifications.presentation.NotificationsViewModel
|
||||
|
||||
class NotificationsFragment : Fragment() {
|
||||
|
||||
@ -2,6 +2,7 @@ package com.isolaatti.posting.posts.data.remote
|
||||
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import com.isolaatti.audio.common.data.AudioDto
|
||||
import java.io.Serializable
|
||||
|
||||
data class FeedDto(
|
||||
@ -23,7 +24,8 @@ data class FeedDto(
|
||||
var numberOfComments: Int,
|
||||
val userName: String,
|
||||
val squadName: String?,
|
||||
var liked: Boolean
|
||||
var liked: Boolean,
|
||||
var audio: AudioDto?
|
||||
): Parcelable {
|
||||
|
||||
constructor(parcel: Parcel) : this(
|
||||
@ -32,7 +34,8 @@ data class FeedDto(
|
||||
parcel.readInt(),
|
||||
parcel.readString()!!,
|
||||
parcel.readString(),
|
||||
parcel.readByte() != 0.toByte()
|
||||
parcel.readByte() != 0.toByte(),
|
||||
parcel.readParcelable(AudioDto::class.java.classLoader)
|
||||
)
|
||||
|
||||
data class Post(
|
||||
@ -93,6 +96,7 @@ data class FeedDto(
|
||||
parcel.writeString(userName)
|
||||
parcel.writeString(squadName)
|
||||
parcel.writeByte(if (liked) 1 else 0)
|
||||
parcel.writeSerializable(audio)
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
|
||||
@ -54,7 +54,8 @@ data class Post(
|
||||
numberOfLikes = it.numberOfLikes,
|
||||
userName = it.userName,
|
||||
squadName = it.squadName,
|
||||
liked = it.liked
|
||||
liked = it.liked,
|
||||
audio = it.audio?.let { audioDto -> Audio.fromDto(audioDto) }
|
||||
)
|
||||
}.toMutableList()
|
||||
}
|
||||
|
||||
@ -7,107 +7,149 @@ import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import coil.load
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.audio.common.domain.Audio
|
||||
import com.isolaatti.common.CoilImageLoader.imageLoader
|
||||
import com.isolaatti.common.OnUserInteractedWithPostCallback
|
||||
import com.isolaatti.databinding.PostLayoutBinding
|
||||
import com.isolaatti.posting.posts.domain.entity.Post
|
||||
import com.isolaatti.utils.UrlGen.userProfileImage
|
||||
import io.noties.markwon.Markwon
|
||||
|
||||
class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callback: OnUserInteractedWithPostCallback) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
|
||||
class PostsRecyclerViewAdapter (
|
||||
private val markwon: Markwon,
|
||||
private val callback: OnUserInteractedWithPostCallback
|
||||
) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
|
||||
private var postList: List<Post>? = null
|
||||
inner class FeedViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||
fun bindView(postDto: Post, payloads: List<Any>) {
|
||||
|
||||
Log.d("payloads", payloads.count().toString())
|
||||
val likeButton: MaterialButton = itemView.findViewById(R.id.like_button)
|
||||
val commentsButton: MaterialButton = itemView.findViewById(R.id.comment_button)
|
||||
inner class FeedViewHolder(val itemBinding: PostLayoutBinding) : ViewHolder(itemBinding.root) {
|
||||
fun bindView(post: Post, payloads: List<Any>) {
|
||||
|
||||
if(payloads.isNotEmpty()) {
|
||||
for(payload in payloads) {
|
||||
when(payload) {
|
||||
is LikeCountUpdatePayload -> {
|
||||
when {
|
||||
payload is LikeCountUpdatePayload -> {
|
||||
itemBinding.likeButton.isEnabled = true
|
||||
|
||||
|
||||
likeButton.isEnabled = true
|
||||
|
||||
if(postDto.liked) {
|
||||
likeButton.setIconTintResource(R.color.purple_lighter)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter))
|
||||
if(post.liked) {
|
||||
itemBinding.likeButton.setIconTintResource(R.color.purple_lighter)
|
||||
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter))
|
||||
} else {
|
||||
likeButton.setIconTintResource(R.color.on_surface)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.on_surface))
|
||||
itemBinding.likeButton.setIconTintResource(R.color.on_surface)
|
||||
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.on_surface))
|
||||
}
|
||||
|
||||
likeButton.text = postDto.numberOfLikes.toString()
|
||||
|
||||
|
||||
itemBinding.likeButton.text = post.numberOfLikes.toString()
|
||||
}
|
||||
payload is CommentsCountUpdatePayload -> {
|
||||
itemBinding.commentButton.text = post.numberOfComments.toString()
|
||||
}
|
||||
payload is AudioEventPayload && payload == AudioEventPayload.IsPLaying -> {
|
||||
val audio = post.audio
|
||||
if(audio != null){
|
||||
itemBinding.audio.playButton.icon =
|
||||
AppCompatResources.getDrawable(
|
||||
itemView.context,
|
||||
if(audio.isPlaying) R.drawable.baseline_pause_circle_24 else R.drawable.baseline_play_circle_24
|
||||
)
|
||||
}
|
||||
}
|
||||
payload is AudioEventPayload && payload == AudioEventPayload.ProgressChanged -> {
|
||||
val audio = post.audio
|
||||
if(audio != null){
|
||||
itemBinding.audio.audioProgress.progress = audio.progress
|
||||
}
|
||||
}
|
||||
payload is AudioEventPayload && payload == AudioEventPayload.IsLoading -> {
|
||||
val audio = post.audio
|
||||
if(audio != null){
|
||||
itemBinding.audio.audioProgress.isIndeterminate = audio.isLoading
|
||||
}
|
||||
}
|
||||
payload is AudioEventPayload && payload == AudioEventPayload.DurationChanged -> {
|
||||
val audio = post.audio
|
||||
if(audio != null){
|
||||
itemBinding.audio.audioProgress.max = audio.duration
|
||||
}
|
||||
}
|
||||
payload is AudioEventPayload && payload == AudioEventPayload.Ended -> {
|
||||
val audio = post.audio
|
||||
if(audio != null){
|
||||
itemBinding.audio.audioProgress.progress = 0
|
||||
itemBinding.audio.playButton.icon = AppCompatResources.getDrawable(itemView.context, R.drawable.baseline_play_circle_24)
|
||||
}
|
||||
|
||||
is CommentsCountUpdatePayload -> {
|
||||
|
||||
commentsButton.text = postDto.numberOfComments.toString()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
val username: TextView = itemView.findViewById(R.id.text_view_username)
|
||||
username.text = postDto.userName
|
||||
username.text = post.userName
|
||||
username.setOnClickListener {
|
||||
callback.onProfileClick(postDto.userId)
|
||||
callback.onProfileClick(post.userId)
|
||||
}
|
||||
|
||||
val profileImageView: ImageView = itemView.findViewById(R.id.avatar_picture)
|
||||
profileImageView.load(userProfileImage(postDto.userId), imageLoader)
|
||||
profileImageView.load(userProfileImage(post.userId), imageLoader)
|
||||
|
||||
val dateTextView: TextView = itemView.findViewById(R.id.text_view_date)
|
||||
dateTextView.text = postDto.date
|
||||
dateTextView.text = post.date
|
||||
|
||||
val content: TextView = itemView.findViewById(R.id.post_content)
|
||||
markwon.setMarkdown(content, postDto.textContent)
|
||||
markwon.setMarkdown(content, post.textContent)
|
||||
|
||||
likeButton.isEnabled = true
|
||||
itemBinding.likeButton.isEnabled = true
|
||||
|
||||
if(postDto.liked) {
|
||||
likeButton.setIconTintResource(R.color.purple_lighter)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter))
|
||||
if(post.liked) {
|
||||
itemBinding.likeButton.setIconTintResource(R.color.purple_lighter)
|
||||
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter))
|
||||
} else {
|
||||
likeButton.setIconTintResource(R.color.on_surface)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.on_surface))
|
||||
itemBinding.likeButton.setIconTintResource(R.color.on_surface)
|
||||
itemBinding.likeButton.setTextColor(itemView.context.getColor(R.color.on_surface))
|
||||
}
|
||||
|
||||
likeButton.text = postDto.numberOfLikes.toString()
|
||||
itemBinding.likeButton.text = post.numberOfLikes.toString()
|
||||
|
||||
commentsButton.text = postDto.numberOfComments.toString()
|
||||
itemBinding.commentButton.text = post.numberOfComments.toString()
|
||||
|
||||
val moreButton: MaterialButton = itemView.findViewById(R.id.more_button)
|
||||
moreButton.setOnClickListener {
|
||||
callback.onOptions(postDto)
|
||||
callback.onOptions(post)
|
||||
}
|
||||
|
||||
likeButton.setOnClickListener {
|
||||
likeButton.isEnabled = false
|
||||
if(postDto.liked){
|
||||
callback.onUnLiked(postDto.id)
|
||||
itemBinding.likeButton.setOnClickListener {
|
||||
itemBinding.likeButton.isEnabled = false
|
||||
if(post.liked){
|
||||
callback.onUnLiked(post.id)
|
||||
} else {
|
||||
callback.onLiked(postDto.id)
|
||||
callback.onLiked(post.id)
|
||||
}
|
||||
}
|
||||
commentsButton.setOnClickListener {
|
||||
callback.onComment(postDto.id)
|
||||
itemBinding.commentButton.setOnClickListener {
|
||||
callback.onComment(post.id)
|
||||
}
|
||||
|
||||
itemView.findViewById<MaterialCardView>(R.id.card).setOnClickListener {
|
||||
callback.onOpenPost(postDto.id)
|
||||
callback.onOpenPost(post.id)
|
||||
}
|
||||
if(post.audio != null){
|
||||
itemBinding.audio.apply {
|
||||
root.visibility = View.VISIBLE
|
||||
textViewDescription.text = post.audio.name
|
||||
}
|
||||
itemBinding.audio.playButton.setOnClickListener {
|
||||
callback.onPlay(post.audio)
|
||||
}
|
||||
} else {
|
||||
itemBinding.audio.root.visibility = View.GONE
|
||||
itemBinding.audio.playButton.setOnClickListener(null)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -116,11 +158,138 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
|
||||
data class LikeCountUpdatePayload(val likeCount: Int)
|
||||
data class CommentsCountUpdatePayload(val commentsCount: Int)
|
||||
|
||||
private var currentAudio: Audio? = null
|
||||
private var currentAudioPosition: Int = -1
|
||||
enum class AudioEventPayload {
|
||||
ProgressChanged, IsLoading, IsPLaying, DurationChanged, Ended
|
||||
}
|
||||
|
||||
fun setIsPlaying(isPlaying: Boolean, audio: Audio) {
|
||||
if(audio == currentAudio) {
|
||||
currentAudio?.isPlaying = isPlaying
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsPLaying)
|
||||
}
|
||||
return
|
||||
}
|
||||
currentAudio?.isPlaying = false
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsPLaying)
|
||||
} else {
|
||||
if(postList != null) {
|
||||
for((index, post) in postList!!.withIndex()){
|
||||
post.audio?.isPlaying = false
|
||||
post.audio?.progress = 0
|
||||
post.audio?.isLoading = false
|
||||
if(post.audio != null) {
|
||||
notifyItemChanged(index)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
|
||||
Log.d(LOG_TAG, "setIsPlaying currentAudioPosition: $currentAudioPosition")
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
currentAudio = postList?.get(currentAudioPosition)?.audio?.also { it.isPlaying = isPlaying }
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsPLaying)
|
||||
}
|
||||
}
|
||||
|
||||
fun setIsLoading(isLoading: Boolean, audio: Audio) {
|
||||
if(audio == currentAudio) {
|
||||
currentAudio?.isLoading = isLoading
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsLoading)
|
||||
}
|
||||
return
|
||||
}
|
||||
currentAudio?.isPlaying = false
|
||||
currentAudio?.isLoading = false
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsLoading)
|
||||
}
|
||||
|
||||
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
|
||||
|
||||
Log.d(LOG_TAG, "setIsLoading currentAudioPosition: $currentAudioPosition")
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
postList?.get(currentAudioPosition)?.audio?.isLoading = isLoading
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.IsLoading)
|
||||
}
|
||||
}
|
||||
|
||||
fun setProgress(progress: Int, audio: Audio){
|
||||
if(audio == currentAudio) {
|
||||
audio.progress = progress
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
return
|
||||
}
|
||||
currentAudio?.isPlaying = false
|
||||
currentAudio?.progress = 0
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
|
||||
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
|
||||
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
postList?.get(currentAudioPosition)?.audio?.progress = progress
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
}
|
||||
|
||||
fun setDuration(duration: Int, audio: Audio) {
|
||||
if(audio == currentAudio) {
|
||||
audio.duration = duration
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
return
|
||||
}
|
||||
currentAudio?.isPlaying = false
|
||||
|
||||
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
postList?.get(currentAudioPosition)?.audio?.duration = duration
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
}
|
||||
|
||||
fun setEnded(audio: Audio) {
|
||||
if(audio == currentAudio) {
|
||||
audio.isPlaying = false
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
return
|
||||
}
|
||||
currentAudio?.isPlaying = false
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
|
||||
currentAudioPosition = postList?.indexOf(postList?.find { it.audio == audio }) ?: -1
|
||||
|
||||
if(currentAudioPosition > -1) {
|
||||
postList?.get(currentAudioPosition)?.audio?.isPlaying = false
|
||||
notifyItemChanged(currentAudioPosition, AudioEventPayload.ProgressChanged)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.post_layout, parent, false)
|
||||
|
||||
return FeedViewHolder(view)
|
||||
return FeedViewHolder(PostLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false))
|
||||
}
|
||||
|
||||
var previousSize = 0
|
||||
@ -197,4 +366,8 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val LOG_TAG = "PostsRecyclerViewAdapter"
|
||||
}
|
||||
}
|
||||
@ -106,29 +106,59 @@ class ProfileMainFragment : Fragment() {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
|
||||
override fun onPlaying(isPlaying: Boolean, audio: Playable) {
|
||||
viewBinding.playButton.icon = AppCompatResources.getDrawable(requireContext(), if(isPlaying) R.drawable.baseline_pause_circle_24 else R.drawable.baseline_play_circle_24)
|
||||
|
||||
Log.d(LOG_TAG, "onPlaying() isPlaying: $isPlaying: audio $audio")
|
||||
if(audio == audioDescriptionAudio) {
|
||||
viewBinding.playButton.icon =
|
||||
AppCompatResources.getDrawable(
|
||||
requireContext(),
|
||||
if(isPlaying) R.drawable.baseline_pause_circle_24 else R.drawable.baseline_play_circle_24
|
||||
)
|
||||
}
|
||||
if(audio is Audio)
|
||||
postsAdapter.setIsPlaying(isPlaying, audio)
|
||||
}
|
||||
|
||||
override fun isLoading(isLoading: Boolean, audio: Playable) {
|
||||
if(audio == audioDescriptionAudio) {
|
||||
viewBinding.playButton.isEnabled = !isLoading
|
||||
viewBinding.audioProgress.isIndeterminate = isLoading
|
||||
}
|
||||
|
||||
if(audio is Audio)
|
||||
postsAdapter.setIsLoading(isLoading, audio)
|
||||
|
||||
}
|
||||
|
||||
override fun progressChanged(second: Int, audio: Playable) {
|
||||
if(audio == audioDescriptionAudio) {
|
||||
viewBinding.audioProgress.setProgress(second, true)
|
||||
}
|
||||
|
||||
if(audio is Audio)
|
||||
postsAdapter.setProgress(second, audio)
|
||||
}
|
||||
|
||||
override fun durationChanged(duration: Int, audio: Playable) {
|
||||
if(audio == audioDescriptionAudio) {
|
||||
viewBinding.audioProgress.max = duration
|
||||
}
|
||||
|
||||
if(audio is Audio)
|
||||
postsAdapter.setDuration(duration, audio)
|
||||
}
|
||||
|
||||
override fun onEnded(audio: Playable) {
|
||||
if(audio == audioDescriptionAudio) {
|
||||
viewBinding.audioProgress.progress = 0
|
||||
}
|
||||
|
||||
if(audio is Audio)
|
||||
postsAdapter.setEnded(audio)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private val profileObserver = Observer<UserProfile> { profile ->
|
||||
@ -431,6 +461,10 @@ class ProfileMainFragment : Fragment() {
|
||||
//ProfileActivity.startActivity(requireContext(), userId)
|
||||
}
|
||||
|
||||
override fun onPlay(audio: Audio) {
|
||||
audioPlayerConnector.playPauseAudio(audio)
|
||||
}
|
||||
|
||||
override fun onLoadMore() {
|
||||
viewModel.getFeed(false)
|
||||
}
|
||||
@ -474,5 +508,6 @@ class ProfileMainFragment : Fragment() {
|
||||
|
||||
companion object {
|
||||
const val CALLER_ID = 30
|
||||
const val LOG_TAG = "ProfileMainFragment"
|
||||
}
|
||||
}
|
||||
@ -4,8 +4,13 @@ import android.Manifest
|
||||
import android.util.Log
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.firebase.messaging.FirebaseMessagingService
|
||||
import com.google.firebase.messaging.RemoteMessage
|
||||
import dagger.hilt.EntryPoint
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import retrofit2.awaitResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
@AndroidEntryPoint
|
||||
@ -19,7 +24,20 @@ class FcmService : FirebaseMessagingService() {
|
||||
|
||||
override fun onNewToken(token: String) {
|
||||
|
||||
pushNotificationsApi.registerDevice(token)
|
||||
Log.d(LOG_TAG, token)
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
val response = pushNotificationsApi.registerDevice(token).awaitResponse()
|
||||
Log.d(LOG_TAG, "Device registered. FCM token: $token")
|
||||
Log.d(LOG_TAG, "Response: isSuccessful: ${response.isSuccessful}")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onMessageReceived(message: RemoteMessage) {
|
||||
super.onMessageReceived(message)
|
||||
|
||||
Log.d(LOG_TAG, "Message received")
|
||||
message.data.forEach { t, u ->
|
||||
Log.d(LOG_TAG, "$t $u")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,6 @@ interface PushNotificationsApi {
|
||||
|
||||
@PUT("/api/push_notifications/register_device")
|
||||
@Multipart
|
||||
fun registerDevice(@Part("token") token: String): Call<Any>
|
||||
fun registerDevice(@Part("token") token: String): Call<Unit>
|
||||
|
||||
}
|
||||
65
app/src/main/res/layout/notification_item.xml
Normal file
65
app/src/main/res/layout/notification_item.xml
Normal file
@ -0,0 +1,65 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginVertical="4dp">
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/notification_main_image"
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:layout_marginStart="20dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
tools:src="@tools:sample/avatars" />
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/notification_secondary_image"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/notification_main_image"
|
||||
app:layout_constraintEnd_toEndOf="@+id/notification_main_image"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||
tools:src="@drawable/baseline_image_24" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toTopOf="@+id/notification_message"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/notification_main_image"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintVertical_chainStyle="packed"
|
||||
android:layout_marginStart="16dp"
|
||||
android:textStyle="bold"
|
||||
android:textSize="18sp"
|
||||
android:maxLines="1"
|
||||
tools:text="Notification title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/notification_message"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.5"
|
||||
app:layout_constraintStart_toEndOf="@id/notification_main_image"
|
||||
app:layout_constraintTop_toBottomOf="@+id/notification_title"
|
||||
android:layout_marginStart="16dp"
|
||||
android:maxLines="1"
|
||||
tools:text="Notification message" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
|
||||
|
||||
@ -56,6 +56,11 @@
|
||||
android:gravity="end"/>
|
||||
|
||||
</RelativeLayout>
|
||||
<include android:id="@+id/audio"
|
||||
layout="@layout/audio_attachment"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/post_content"
|
||||
|
||||
@ -16,7 +16,7 @@
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/notificationsFragment"
|
||||
android:name="com.isolaatti.home.notifications.ui.NotificationsFragment"
|
||||
android:name="com.isolaatti.notifications.ui.NotificationsFragment"
|
||||
android:label="fragment_notifications"
|
||||
tools:layout="@layout/fragment_notifications" >
|
||||
<action
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user