busqueda casi completa, refactorizacion, iconos nuevos en resultados, posts de hashtag
This commit is contained in:
parent
000937ded4
commit
3ef8961729
@ -26,15 +26,15 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".home.HomeActivity" android:theme="@style/Theme.Isolaatti" />
|
<activity android:name=".home.ui.HomeActivity" android:theme="@style/Theme.Isolaatti" />
|
||||||
<activity android:name=".login.LogInActivity" android:theme="@style/Theme.Isolaatti" />
|
<activity android:name=".login.LogInActivity" android:theme="@style/Theme.Isolaatti" />
|
||||||
<activity android:name=".profile.ui.ProfileActivity"
|
<activity android:name=".profile.ui.ProfileActivity"
|
||||||
android:theme="@style/Theme.Isolaatti"
|
android:theme="@style/Theme.Isolaatti"
|
||||||
android:parentActivityName=".home.HomeActivity"/>
|
android:parentActivityName=".home.ui.HomeActivity"/>
|
||||||
<activity android:name=".settings.ui.SettingsActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".settings.ui.SettingsActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
<activity android:name=".posting.posts.ui.CreatePostActivity" android:theme="@style/Theme.Isolaatti" android:windowSoftInputMode="adjustResize"/>
|
<activity android:name=".posting.posts.ui.CreatePostActivity" android:theme="@style/Theme.Isolaatti" android:windowSoftInputMode="adjustResize"/>
|
||||||
<activity android:name=".posting.posts.viewer.ui.PostViewerActivity" android:theme="@style/Theme.Isolaatti"
|
<activity android:name=".posting.posts.viewer.ui.PostViewerActivity" android:theme="@style/Theme.Isolaatti"
|
||||||
android:parentActivityName=".home.HomeActivity"/>
|
android:parentActivityName=".home.ui.HomeActivity"/>
|
||||||
<activity android:name=".drafts.ui.DraftsActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".drafts.ui.DraftsActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
<activity android:name=".about.AboutActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".about.AboutActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
<activity android:name=".images.picture_viewer.ui.PictureViewerActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".images.picture_viewer.ui.PictureViewerActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import androidx.activity.ComponentActivity
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import com.isolaatti.auth.data.AuthRepositoryImpl
|
import com.isolaatti.auth.data.AuthRepositoryImpl
|
||||||
import com.isolaatti.home.HomeActivity
|
import com.isolaatti.home.ui.HomeActivity
|
||||||
import com.isolaatti.login.LogInActivity
|
import com.isolaatti.login.LogInActivity
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
@ -6,7 +6,6 @@ import android.content.Intent
|
|||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@ -14,9 +13,7 @@ import androidx.lifecycle.Observer
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.connectivity.ConnectivityCallbackImpl
|
|
||||||
import com.isolaatti.connectivity.NetworkStatus
|
import com.isolaatti.connectivity.NetworkStatus
|
||||||
import com.isolaatti.home.HomeActivity
|
|
||||||
import com.isolaatti.login.LogInActivity
|
import com.isolaatti.login.LogInActivity
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|||||||
12
app/src/main/java/com/isolaatti/hashtags/data/HashtagsApi.kt
Normal file
12
app/src/main/java/com/isolaatti/hashtags/data/HashtagsApi.kt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.isolaatti.hashtags.data
|
||||||
|
|
||||||
|
import com.isolaatti.common.ResultDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
import retrofit2.http.Query
|
||||||
|
|
||||||
|
interface HashtagsApi {
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,11 +0,0 @@
|
|||||||
package com.isolaatti.hashtags.presentation
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
|
|
||||||
class HashtagsViewModel : ViewModel() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
class HashtagPostsViewModel : ViewModel() {
|
|
||||||
|
|
||||||
}
|
|
||||||
@ -5,9 +5,12 @@ import android.view.LayoutInflater
|
|||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import com.isolaatti.databinding.FragmentPostsHashtagBinding
|
import com.isolaatti.databinding.FragmentPostsHashtagBinding
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.posting.posts.ui.PostListingFragment
|
||||||
|
|
||||||
class HashtagPostsFragment : Fragment() {
|
class HashtagPostsFragment : Fragment() {
|
||||||
|
|
||||||
@ -29,6 +32,10 @@ class HashtagPostsFragment : Fragment() {
|
|||||||
binding.toolbar.title = "#${navArgs.hashtag}"
|
binding.toolbar.title = "#${navArgs.hashtag}"
|
||||||
|
|
||||||
setupListeners()
|
setupListeners()
|
||||||
|
|
||||||
|
// show feed
|
||||||
|
(childFragmentManager.findFragmentById(R.id.post_list_fragment_container) as NavHostFragment)
|
||||||
|
.navController.setGraph(R.navigation.post_listing_navigation, Bundle().apply { putString(PostListingFragment.ARG_HASHTAG, navArgs.hashtag) })
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupListeners() {
|
private fun setupListeners() {
|
||||||
|
|||||||
@ -2,12 +2,12 @@ package com.isolaatti.home.presentation
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.isolaatti.auth.domain.AuthRepository
|
import com.isolaatti.auth.domain.AuthRepository
|
||||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
|
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
|
||||||
import com.isolaatti.posting.posts.presentation.UpdateEvent
|
import com.isolaatti.posting.posts.presentation.UpdateEvent
|
||||||
import com.isolaatti.profile.data.remote.UserProfileDto
|
|
||||||
import com.isolaatti.profile.domain.entity.UserProfile
|
import com.isolaatti.profile.domain.entity.UserProfile
|
||||||
import com.isolaatti.profile.domain.use_case.GetProfile
|
import com.isolaatti.profile.domain.use_case.GetProfile
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
@ -22,9 +22,8 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class FeedViewModel @Inject constructor(
|
class FeedViewModel @Inject constructor(
|
||||||
private val getProfileUseCase: GetProfile,
|
private val getProfileUseCase: GetProfile,
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository
|
||||||
private val postsRepository: PostsRepository
|
) : ViewModel() {
|
||||||
) : PostListingViewModelBase() {
|
|
||||||
|
|
||||||
private val toRetry: MutableList<Runnable> = mutableListOf()
|
private val toRetry: MutableList<Runnable> = mutableListOf()
|
||||||
|
|
||||||
@ -39,41 +38,7 @@ class FeedViewModel @Inject constructor(
|
|||||||
toRetry.clear()
|
toRetry.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFeed(refresh: Boolean) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
if (refresh) {
|
|
||||||
posts.value = null
|
|
||||||
}
|
|
||||||
postsRepository.getFeed(getLastId()).onEach { listResource ->
|
|
||||||
when (listResource) {
|
|
||||||
is Resource.Success -> {
|
|
||||||
val eventType = if((postsList?.size ?: 0) > 0) UpdateEvent.UpdateType.PAGE_ADDED else UpdateEvent.UpdateType.REFRESH
|
|
||||||
loadingPosts.postValue(false)
|
|
||||||
posts.postValue(Pair(postsList?.apply {
|
|
||||||
addAll(listResource.data ?: listOf())
|
|
||||||
} ?: listResource.data,
|
|
||||||
UpdateEvent(eventType, null)))
|
|
||||||
|
|
||||||
noMoreContent.postValue(listResource.data?.size == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Resource.Loading -> {
|
|
||||||
if(!refresh)
|
|
||||||
loadingPosts.postValue(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Resource.Error -> {
|
|
||||||
errorLoading.postValue(listResource.errorType)
|
|
||||||
toRetry.add {
|
|
||||||
getFeed(refresh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
isLoadingFromScrolling = false
|
|
||||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User profile
|
// User profile
|
||||||
private val _userProfile: MutableLiveData<UserProfile> = MutableLiveData()
|
private val _userProfile: MutableLiveData<UserProfile> = MutableLiveData()
|
||||||
@ -88,7 +53,6 @@ class FeedViewModel @Inject constructor(
|
|||||||
|
|
||||||
when(profile) {
|
when(profile) {
|
||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
errorLoading.postValue(profile.errorType)
|
|
||||||
toRetry.add {
|
toRetry.add {
|
||||||
getProfile()
|
getProfile()
|
||||||
}
|
}
|
||||||
|
|||||||
140
app/src/main/java/com/isolaatti/home/ui/FeedFragment.kt
Normal file
140
app/src/main/java/com/isolaatti/home/ui/FeedFragment.kt
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package com.isolaatti.home.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import coil.load
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.about.AboutActivity
|
||||||
|
import com.isolaatti.common.CoilImageLoader.imageLoader
|
||||||
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
|
import com.isolaatti.databinding.FragmentFeedBinding
|
||||||
|
import com.isolaatti.home.presentation.FeedViewModel
|
||||||
|
import com.isolaatti.posting.posts.presentation.CreatePostContract
|
||||||
|
import com.isolaatti.posting.posts.ui.PostListingFragment
|
||||||
|
import com.isolaatti.profile.ui.ProfileActivity
|
||||||
|
import com.isolaatti.settings.ui.SettingsActivity
|
||||||
|
import com.isolaatti.utils.UrlGen
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class FeedFragment : Fragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance() = FeedFragment()
|
||||||
|
const val CALLER_ID = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||||
|
private val viewModel: FeedViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private var currentUserId = 0
|
||||||
|
|
||||||
|
private lateinit var viewBinding: FragmentFeedBinding
|
||||||
|
|
||||||
|
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
|
||||||
|
if(it != null) {
|
||||||
|
Toast.makeText(requireContext(), R.string.posted_successfully, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
viewBinding = FragmentFeedBinding.inflate(inflater)
|
||||||
|
|
||||||
|
return viewBinding.root
|
||||||
|
}
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
viewBinding.topAppBar.setNavigationOnClickListener {
|
||||||
|
viewBinding.drawerLayout?.openDrawer(viewBinding.homeDrawer)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
viewBinding.homeDrawer.setNavigationItemSelectedListener {
|
||||||
|
when(it.itemId) {
|
||||||
|
R.id.settings_menu_item -> {
|
||||||
|
startActivity(Intent(requireActivity(), SettingsActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.about_menu_item -> {
|
||||||
|
startActivity(Intent(requireActivity(), AboutActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> {true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
viewBinding.topAppBar.setOnMenuItemClickListener {
|
||||||
|
when(it.itemId) {
|
||||||
|
R.id.menu_item_new_discussion -> {
|
||||||
|
createDiscussion.launch(Unit)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show feed
|
||||||
|
|
||||||
|
(childFragmentManager.findFragmentById(R.id.post_list_fragment_container) as NavHostFragment).navController.setGraph(R.navigation.post_listing_navigation)
|
||||||
|
|
||||||
|
viewModel.userProfile.observe(viewLifecycleOwner) {
|
||||||
|
val header = viewBinding.homeDrawer.getHeaderView(0) as? ConstraintLayout
|
||||||
|
|
||||||
|
val image: ImageView? = header?.findViewById(R.id.profileImageView)
|
||||||
|
val textViewName: TextView? = header?.findViewById(R.id.textViewName)
|
||||||
|
val textViewEmail: TextView? = header?.findViewById(R.id.textViewEmail)
|
||||||
|
val textViewUsername: TextView? = header?.findViewById(R.id.textViewUsername)
|
||||||
|
|
||||||
|
image?.load(UrlGen.userProfileImage(it.userId), imageLoader)
|
||||||
|
|
||||||
|
val card: MaterialCardView? = header?.findViewById(R.id.drawer_header_card)
|
||||||
|
card?.setOnClickListener {
|
||||||
|
ProfileActivity.startActivity(requireContext(), currentUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
textViewName?.text = it.name
|
||||||
|
textViewEmail?.text = it.email
|
||||||
|
textViewUsername?.text = "@${it.uniqueUsername}"
|
||||||
|
currentUserId = it.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
viewModel.getProfile()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
errorViewModel.retry.collect {
|
||||||
|
viewModel.retry()
|
||||||
|
errorViewModel.handleRetry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.isolaatti.home
|
package com.isolaatti.home.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
@ -45,10 +45,6 @@ class HomeActivity : IsolaattiBaseActivity() {
|
|||||||
|
|
||||||
askNotificationPermission()
|
askNotificationPermission()
|
||||||
|
|
||||||
if(savedInstanceState == null) {
|
|
||||||
feedViewModel.getFeed(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.posting.posts.data.remote
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
import com.isolaatti.common.ResultDto
|
||||||
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
@ -22,4 +23,7 @@ interface FeedsApi {
|
|||||||
|
|
||||||
@GET("Feed")
|
@GET("Feed")
|
||||||
fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto>
|
fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto>
|
||||||
|
|
||||||
|
@GET("hashtags/hashtag/{hashtag}")
|
||||||
|
fun getHashtagPosts(@Path("hashtag") hashtag: String, @Query("after") afterPost: Long? = null): Call<FeedDto>
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.posting.posts.data.repository
|
package com.isolaatti.posting.posts.data.repository
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||||
import com.isolaatti.posting.posts.data.remote.DeletePostDto
|
import com.isolaatti.posting.posts.data.remote.DeletePostDto
|
||||||
@ -18,10 +19,18 @@ import retrofit2.awaitResponse
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
|
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
|
||||||
override fun getFeed(lastId: Long): Flow<Resource<MutableList<Post>>> = flow {
|
companion object {
|
||||||
|
const val LOG_TAG = "PostsRepositoryImpl"
|
||||||
|
}
|
||||||
|
override fun getFeed(lastId: Long, hashtag: String?): Flow<Resource<MutableList<Post>>> = flow {
|
||||||
emit(Resource.Loading())
|
emit(Resource.Loading())
|
||||||
try {
|
try {
|
||||||
val result = feedsApi.getChronology(lastId, 20).execute()
|
|
||||||
|
val result = if(hashtag == null) {
|
||||||
|
feedsApi.getChronology(lastId, 20).awaitResponse()
|
||||||
|
} else {
|
||||||
|
feedsApi.getHashtagPosts(hashtag, lastId).awaitResponse()
|
||||||
|
}
|
||||||
if(result.isSuccessful) {
|
if(result.isSuccessful) {
|
||||||
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
|
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
|
||||||
return@flow
|
return@flow
|
||||||
@ -32,7 +41,8 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
|||||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
}
|
}
|
||||||
} catch(_: Exception) {
|
} catch(e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "Error $e")
|
||||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
|
|
||||||
interface PostsRepository {
|
interface PostsRepository {
|
||||||
|
|
||||||
fun getFeed(lastId: Long): Flow<Resource<MutableList<Post>>>
|
fun getFeed(lastId: Long, hashtag: String?): Flow<Resource<MutableList<Post>>>
|
||||||
|
|
||||||
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>>
|
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.isolaatti.posting.posts.presentation
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class PostListingViewModel @Inject constructor(private val postsRepository: PostsRepository) : PostListingViewModelBase() {
|
||||||
|
override fun getFeed(refresh: Boolean, hashtag: String?) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (refresh) {
|
||||||
|
posts.value = null
|
||||||
|
}
|
||||||
|
postsRepository.getFeed(getLastId(), hashtag).onEach { listResource ->
|
||||||
|
when (listResource) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
val eventType = if((postsList?.size ?: 0) > 0) UpdateEvent.UpdateType.PAGE_ADDED else UpdateEvent.UpdateType.REFRESH
|
||||||
|
loadingPosts.postValue(false)
|
||||||
|
posts.postValue(Pair(postsList?.apply {
|
||||||
|
addAll(listResource.data ?: listOf())
|
||||||
|
} ?: listResource.data,
|
||||||
|
UpdateEvent(eventType, null)))
|
||||||
|
|
||||||
|
noMoreContent.postValue(listResource.data?.size == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Loading -> {
|
||||||
|
if(!refresh)
|
||||||
|
loadingPosts.postValue(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Error -> {
|
||||||
|
errorLoading.postValue(listResource.errorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
isLoadingFromScrolling = false
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,7 +44,7 @@ abstract class PostListingViewModelBase : ViewModel() {
|
|||||||
fun getLastId(): Long = try { posts.value?.first?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
|
fun getLastId(): Long = try { posts.value?.first?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
|
||||||
|
|
||||||
|
|
||||||
abstract fun getFeed(refresh: Boolean)
|
abstract fun getFeed(refresh: Boolean, hashtag: String?)
|
||||||
|
|
||||||
fun likePost(postId: Long) {
|
fun likePost(postId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|||||||
@ -1,52 +1,38 @@
|
|||||||
package com.isolaatti.home
|
package com.isolaatti.posting.posts.ui
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
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.ImageView
|
import androidx.fragment.app.Fragment
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import coil.load
|
|
||||||
import com.google.android.material.button.MaterialButton
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
|
||||||
import com.isolaatti.BuildConfig
|
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.Audio
|
||||||
import com.isolaatti.audio.common.domain.Playable
|
import com.isolaatti.audio.common.domain.Playable
|
||||||
import com.isolaatti.audio.player.AudioPlayerConnector
|
import com.isolaatti.audio.player.AudioPlayerConnector
|
||||||
import com.isolaatti.common.CoilImageLoader.imageLoader
|
import com.isolaatti.common.CoilImageLoader
|
||||||
import com.isolaatti.common.Dialogs
|
import com.isolaatti.common.Dialogs
|
||||||
import com.isolaatti.common.ErrorMessageViewModel
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
import com.isolaatti.databinding.FragmentFeedBinding
|
|
||||||
import com.isolaatti.drafts.ui.DraftsActivity
|
|
||||||
import com.isolaatti.home.presentation.FeedViewModel
|
|
||||||
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
|
|
||||||
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
|
|
||||||
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
|
||||||
import com.isolaatti.common.OnUserInteractedWithPostCallback
|
import com.isolaatti.common.OnUserInteractedWithPostCallback
|
||||||
import com.isolaatti.common.Ownable
|
import com.isolaatti.common.Ownable
|
||||||
import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
|
import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
|
||||||
import com.isolaatti.common.options_bottom_sheet.domain.Options
|
import com.isolaatti.common.options_bottom_sheet.domain.Options
|
||||||
import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
|
import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
|
||||||
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
||||||
|
import com.isolaatti.databinding.FragmentPostListingBinding
|
||||||
|
import com.isolaatti.home.presentation.FeedViewModel
|
||||||
|
import com.isolaatti.home.ui.FeedFragment
|
||||||
|
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
||||||
import com.isolaatti.posting.posts.domain.entity.Post
|
import com.isolaatti.posting.posts.domain.entity.Post
|
||||||
import com.isolaatti.posting.posts.presentation.CreatePostContract
|
|
||||||
import com.isolaatti.posting.posts.presentation.EditPostContract
|
import com.isolaatti.posting.posts.presentation.EditPostContract
|
||||||
|
import com.isolaatti.posting.posts.presentation.PostListingViewModel
|
||||||
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
||||||
|
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
|
||||||
import com.isolaatti.profile.ui.ProfileActivity
|
import com.isolaatti.profile.ui.ProfileActivity
|
||||||
import com.isolaatti.settings.ui.SettingsActivity
|
|
||||||
import com.isolaatti.utils.UrlGen
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.noties.markwon.AbstractMarkwonPlugin
|
import io.noties.markwon.AbstractMarkwonPlugin
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
@ -54,84 +40,23 @@ import io.noties.markwon.MarkwonConfiguration
|
|||||||
import io.noties.markwon.image.coil.CoilImagesPlugin
|
import io.noties.markwon.image.coil.CoilImagesPlugin
|
||||||
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
||||||
import io.noties.markwon.linkify.LinkifyPlugin
|
import io.noties.markwon.linkify.LinkifyPlugin
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.w3c.dom.Text
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = FeedFragment()
|
const val ARG_HASHTAG = "hashtag"
|
||||||
const val CALLER_ID = 20
|
const val LOG_TAG = "PostListingFragment"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hashtag: String? = null
|
||||||
|
|
||||||
|
private lateinit var viewBinding: FragmentPostListingBinding
|
||||||
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||||
private val viewModel: FeedViewModel by activityViewModels()
|
private val viewModel: PostListingViewModel by viewModels()
|
||||||
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||||
private var currentUserId = 0
|
|
||||||
|
|
||||||
private lateinit var viewBinding: FragmentFeedBinding
|
|
||||||
private lateinit var adapter: PostsRecyclerViewAdapter
|
private lateinit var adapter: PostsRecyclerViewAdapter
|
||||||
|
|
||||||
private lateinit var audioPlayerConnector: AudioPlayerConnector
|
private lateinit var audioPlayerConnector: AudioPlayerConnector
|
||||||
|
|
||||||
// region launchers
|
|
||||||
|
|
||||||
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
|
|
||||||
if(it != null) {
|
|
||||||
Toast.makeText(requireContext(), R.string.posted_successfully, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val editDiscussion = registerForActivityResult(EditPostContract()) {
|
|
||||||
if(it != null) {
|
|
||||||
viewModel.onPostUpdate(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region observers
|
|
||||||
|
|
||||||
private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
|
|
||||||
if(optionClicked?.callerId == CALLER_ID) {
|
|
||||||
// post id should come as payload
|
|
||||||
val post = optionClicked.payload as? Post ?: return@Observer
|
|
||||||
when(optionClicked.optionId) {
|
|
||||||
Options.Option.OPTION_DELETE -> {
|
|
||||||
Dialogs.buildDeletePostDialog(requireContext()) { delete ->
|
|
||||||
optionsViewModel.handle()
|
|
||||||
if(delete) {
|
|
||||||
viewModel.deletePost(post.id)
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
|
|
||||||
}
|
|
||||||
Options.Option.OPTION_EDIT -> {
|
|
||||||
optionsViewModel.handle()
|
|
||||||
editDiscussion.launch(post.id)
|
|
||||||
}
|
|
||||||
Options.Option.OPTION_REPORT -> {
|
|
||||||
optionsViewModel.handle()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region lifecycle
|
|
||||||
override fun onCreateView(
|
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
|
||||||
savedInstanceState: Bundle?
|
|
||||||
): View? {
|
|
||||||
viewBinding = FragmentFeedBinding.inflate(inflater)
|
|
||||||
|
|
||||||
return viewBinding.root
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
|
private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
|
||||||
override fun onPlaying(isPlaying: Boolean, audio: Playable) {
|
override fun onPlaying(isPlaying: Boolean, audio: Playable) {
|
||||||
if(audio is Audio)
|
if(audio is Audio)
|
||||||
@ -160,27 +85,24 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// region events
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
Log.d(LOG_TAG, "onCreate ; hashtag: $hashtag")
|
||||||
|
hashtag = arguments?.getString(ARG_HASHTAG)
|
||||||
|
viewModel.getFeed(false, hashtag)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
viewBinding = FragmentPostListingBinding.inflate(inflater, container, false)
|
||||||
|
return viewBinding.root
|
||||||
|
}
|
||||||
|
|
||||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
viewBinding.topAppBar.setNavigationOnClickListener {
|
|
||||||
viewBinding.drawerLayout?.openDrawer(viewBinding.homeDrawer)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
viewBinding.homeDrawer.setNavigationItemSelectedListener {
|
|
||||||
when(it.itemId) {
|
|
||||||
R.id.settings_menu_item -> {
|
|
||||||
startActivity(Intent(requireActivity(), SettingsActivity::class.java))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.about_menu_item -> {
|
|
||||||
startActivity(Intent(requireActivity(), AboutActivity::class.java))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> {true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audioPlayerConnector = AudioPlayerConnector(requireContext())
|
audioPlayerConnector = AudioPlayerConnector(requireContext())
|
||||||
audioPlayerConnector.addListener(audioPlayerConnectorListener)
|
audioPlayerConnector.addListener(audioPlayerConnectorListener)
|
||||||
@ -190,11 +112,12 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
.usePlugin(object: AbstractMarkwonPlugin() {
|
.usePlugin(object: AbstractMarkwonPlugin() {
|
||||||
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||||
builder
|
builder
|
||||||
.imageDestinationProcessor(ImageDestinationProcessorRelativeToAbsolute
|
.imageDestinationProcessor(
|
||||||
|
ImageDestinationProcessorRelativeToAbsolute
|
||||||
.create(BuildConfig.backend))
|
.create(BuildConfig.backend))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
|
.usePlugin(CoilImagesPlugin.create(requireContext(), CoilImageLoader.imageLoader))
|
||||||
.usePlugin(LinkifyPlugin.create())
|
.usePlugin(LinkifyPlugin.create())
|
||||||
.build()
|
.build()
|
||||||
adapter = PostsRecyclerViewAdapter(markwon, this)
|
adapter = PostsRecyclerViewAdapter(markwon, this)
|
||||||
@ -204,44 +127,9 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
|
|
||||||
|
|
||||||
viewBinding.swipeToRefresh.setOnRefreshListener {
|
viewBinding.swipeToRefresh.setOnRefreshListener {
|
||||||
viewModel.getFeed(refresh = true)
|
viewModel.getFeed(refresh = true, hashtag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
viewBinding.topAppBar.setOnMenuItemClickListener {
|
|
||||||
when(it.itemId) {
|
|
||||||
R.id.menu_item_new_discussion -> {
|
|
||||||
createDiscussion.launch(Unit)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.userProfile.observe(viewLifecycleOwner) {
|
|
||||||
val header = viewBinding.homeDrawer.getHeaderView(0) as? ConstraintLayout
|
|
||||||
|
|
||||||
val image: ImageView? = header?.findViewById(R.id.profileImageView)
|
|
||||||
val textViewName: TextView? = header?.findViewById(R.id.textViewName)
|
|
||||||
val textViewEmail: TextView? = header?.findViewById(R.id.textViewEmail)
|
|
||||||
val textViewUsername: TextView? = header?.findViewById(R.id.textViewUsername)
|
|
||||||
|
|
||||||
image?.load(UrlGen.userProfileImage(it.userId), imageLoader)
|
|
||||||
|
|
||||||
val card: MaterialCardView? = header?.findViewById(R.id.drawer_header_card)
|
|
||||||
card?.setOnClickListener {
|
|
||||||
ProfileActivity.startActivity(requireContext(), currentUserId)
|
|
||||||
}
|
|
||||||
|
|
||||||
textViewName?.text = it.name
|
|
||||||
textViewEmail?.text = it.email
|
|
||||||
textViewUsername?.text = "@${it.uniqueUsername}"
|
|
||||||
currentUserId = it.userId
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
viewModel.posts.observe(viewLifecycleOwner){
|
viewModel.posts.observe(viewLifecycleOwner){
|
||||||
if (it?.first != null) {
|
if (it?.first != null) {
|
||||||
adapter.updateList(it.first!!, it.second)
|
adapter.updateList(it.first!!, it.second)
|
||||||
@ -261,18 +149,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
errorViewModel.error.postValue(it)
|
errorViewModel.error.postValue(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.getProfile()
|
|
||||||
|
|
||||||
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorViewModel.retry.collect {
|
|
||||||
viewModel.retry()
|
|
||||||
errorViewModel.handleRetry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
||||||
@ -280,7 +157,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
|
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
|
||||||
|
|
||||||
override fun onOptions(post: Ownable) {
|
override fun onOptions(post: Ownable) {
|
||||||
optionsViewModel.setOptions(Options.POST_OPTIONS, CALLER_ID, post)
|
optionsViewModel.setOptions(Options.POST_OPTIONS, FeedFragment.CALLER_ID, post)
|
||||||
val modalBottomSheet = BottomSheetPostOptionsFragment()
|
val modalBottomSheet = BottomSheetPostOptionsFragment()
|
||||||
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
|
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
|
||||||
}
|
}
|
||||||
@ -303,8 +180,39 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadMore() {
|
override fun onLoadMore() {
|
||||||
viewModel.getFeed(false)
|
viewModel.getFeed(false, hashtag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
private val editDiscussion = registerForActivityResult(EditPostContract()) {
|
||||||
|
if(it != null) {
|
||||||
|
viewModel.onPostUpdate(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
|
||||||
|
if(optionClicked?.callerId == FeedFragment.CALLER_ID) {
|
||||||
|
// post id should come as payload
|
||||||
|
val post = optionClicked.payload as? Post ?: return@Observer
|
||||||
|
when(optionClicked.optionId) {
|
||||||
|
Options.Option.OPTION_DELETE -> {
|
||||||
|
Dialogs.buildDeletePostDialog(requireContext()) { delete ->
|
||||||
|
optionsViewModel.handle()
|
||||||
|
if(delete) {
|
||||||
|
viewModel.deletePost(post.id)
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
|
||||||
|
}
|
||||||
|
Options.Option.OPTION_EDIT -> {
|
||||||
|
optionsViewModel.handle()
|
||||||
|
editDiscussion.launch(post.id)
|
||||||
|
}
|
||||||
|
Options.Option.OPTION_REPORT -> {
|
||||||
|
optionsViewModel.handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ class ProfileViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFeed(refresh: Boolean) {
|
override fun getFeed(refresh: Boolean, hashtag: String?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if(refresh) {
|
if(refresh) {
|
||||||
posts.value = Pair(null, UpdateEvent(UpdateEvent.UpdateType.REFRESH, null))
|
posts.value = Pair(null, UpdateEvent(UpdateEvent.UpdateType.REFRESH, null))
|
||||||
@ -132,7 +132,7 @@ class ProfileViewModel @Inject constructor(
|
|||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
errorLoading.postValue(feedDtoResource.errorType)
|
errorLoading.postValue(feedDtoResource.errorType)
|
||||||
toRetry.add {
|
toRetry.add {
|
||||||
getFeed(refresh)
|
getFeed(refresh, hashtag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -358,7 +358,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||||
|
|
||||||
viewBinding.swipeToRefresh.setOnRefreshListener {
|
viewBinding.swipeToRefresh.setOnRefreshListener {
|
||||||
viewModel.getFeed(true)
|
viewModel.getFeed(true, null)
|
||||||
}
|
}
|
||||||
viewBinding.playButton.setOnClickListener {
|
viewBinding.playButton.setOnClickListener {
|
||||||
audioDescriptionAudio?.let { audio ->
|
audioDescriptionAudio?.let { audio ->
|
||||||
@ -396,7 +396,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
userId?.let { profileId ->
|
userId?.let { profileId ->
|
||||||
viewModel.profileId = profileId
|
viewModel.profileId = profileId
|
||||||
viewModel.getProfile()
|
viewModel.getProfile()
|
||||||
viewModel.getFeed(true)
|
viewModel.getFeed(true, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -466,7 +466,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadMore() {
|
override fun onLoadMore() {
|
||||||
viewModel.getFeed(false)
|
viewModel.getFeed(false, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,8 @@ class SearchResultsAdapter(
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
SearchResultType.Hashtag -> R.drawable.hashtag_solid
|
||||||
|
SearchResultType.Post -> R.drawable.baseline_search_24
|
||||||
else -> R.drawable.baseline_search_24
|
else -> R.drawable.baseline_search_24
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.databinding.FragmentMakeAccountBinding
|
import com.isolaatti.databinding.FragmentMakeAccountBinding
|
||||||
import com.isolaatti.home.HomeActivity
|
import com.isolaatti.home.ui.HomeActivity
|
||||||
import com.isolaatti.sign_up.domain.entity.SignUpResultCode
|
import com.isolaatti.sign_up.domain.entity.SignUpResultCode
|
||||||
import com.isolaatti.sign_up.presentation.MakeAccountViewModel
|
import com.isolaatti.sign_up.presentation.MakeAccountViewModel
|
||||||
import com.isolaatti.sign_up.presentation.SignUpViewModel
|
import com.isolaatti.sign_up.presentation.SignUpViewModel
|
||||||
|
|||||||
5
app/src/main/res/drawable/baseline_article_24.xml
Normal file
5
app/src/main/res/drawable/baseline_article_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/hashtag_solid.xml
Normal file
9
app/src/main/res/drawable/hashtag_solid.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="448dp"
|
||||||
|
android:height="512dp"
|
||||||
|
android:viewportWidth="448"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M181.3,32.4c17.4,2.9 29.2,19.4 26.3,36.8L197.8,128h95.1l11.5,-69.3c2.9,-17.4 19.4,-29.2 36.8,-26.3s29.2,19.4 26.3,36.8L357.8,128H416c17.7,0 32,14.3 32,32s-14.3,32 -32,32H347.1L325.8,320H384c17.7,0 32,14.3 32,32s-14.3,32 -32,32H315.1l-11.5,69.3c-2.9,17.4 -19.4,29.2 -36.8,26.3s-29.2,-19.4 -26.3,-36.8l9.8,-58.7H155.1l-11.5,69.3c-2.9,17.4 -19.4,29.2 -36.8,26.3s-29.2,-19.4 -26.3,-36.8L90.2,384H32c-17.7,0 -32,-14.3 -32,-32s14.3,-32 32,-32h68.9l21.3,-128H64c-17.7,0 -32,-14.3 -32,-32s14.3,-32 32,-32h68.9l11.5,-69.3c2.9,-17.4 19.4,-29.2 36.8,-26.3zM187.1,192L165.8,320h95.1l21.3,-128H187.1z"/>
|
||||||
|
</vector>
|
||||||
@ -17,11 +17,11 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/home_drawer"
|
app:layout_constraintStart_toEndOf="@+id/home_drawer"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:context=".home.FeedFragment">
|
tools:context=".home.ui.FeedFragment">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/topAppBar_layout"
|
android:id="@+id/topAppBar_layout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@ -46,26 +46,11 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/swipe_to_refresh"
|
android:id="@+id/post_list_fragment_container"
|
||||||
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"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/topAppBar_layout">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/feed_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:layout_gravity="bottom"/>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView
|
<com.google.android.material.navigation.NavigationView
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".home.FeedFragment">
|
tools:context=".home.ui.FeedFragment">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/topAppBar_layout"
|
android:id="@+id/topAppBar_layout"
|
||||||
@ -36,23 +36,14 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
android:id="@+id/swipe_to_refresh"
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/post_list_fragment_container"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
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">
|
app:defaultNavHost="true"
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
android:id="@+id/feed_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:layout_gravity="bottom"/>
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
|||||||
24
app/src/main/res/layout/fragment_post_listing.xml
Normal file
24
app/src/main/res/layout/fragment_post_listing.xml
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
<?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"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
|
android:id="@+id/swipe_to_refresh"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/feed_recycler_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent" />
|
||||||
|
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||||
|
android:id="@+id/loading_indicator"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:indeterminate="true"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@ -15,4 +15,12 @@
|
|||||||
app:titleCentered="true"/>
|
app:titleCentered="true"/>
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/post_list_fragment_container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
|
app:defaultNavHost="true"
|
||||||
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/feedFragment"
|
android:id="@+id/feedFragment"
|
||||||
android:name="com.isolaatti.home.FeedFragment"
|
android:name="com.isolaatti.home.ui.FeedFragment"
|
||||||
android:label="fragment_feed"
|
android:label="fragment_feed"
|
||||||
tools:layout="@layout/fragment_feed" >
|
tools:layout="@layout/fragment_feed" >
|
||||||
<action
|
<action
|
||||||
|
|||||||
16
app/src/main/res/navigation/post_listing_navigation.xml
Normal file
16
app/src/main/res/navigation/post_listing_navigation.xml
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:id="@+id/post_listing_navigation"
|
||||||
|
app:startDestination="@id/postListingFragment">
|
||||||
|
|
||||||
|
<fragment
|
||||||
|
android:id="@+id/postListingFragment"
|
||||||
|
android:name="com.isolaatti.posting.posts.ui.PostListingFragment"
|
||||||
|
android:label="PostListingFragment" >
|
||||||
|
<argument
|
||||||
|
android:name="hashtag"
|
||||||
|
app:argType="string"
|
||||||
|
app:nullable="true" />
|
||||||
|
</fragment>
|
||||||
|
</navigation>
|
||||||
Loading…
x
Reference in New Issue
Block a user