diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e2c6abd..0176c48 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -26,15 +26,15 @@
-
+
+ android:parentActivityName=".home.ui.HomeActivity"/>
+ android:parentActivityName=".home.ui.HomeActivity"/>
diff --git a/app/src/main/java/com/isolaatti/MainActivity.kt b/app/src/main/java/com/isolaatti/MainActivity.kt
index 04a494f..213d5f8 100644
--- a/app/src/main/java/com/isolaatti/MainActivity.kt
+++ b/app/src/main/java/com/isolaatti/MainActivity.kt
@@ -7,7 +7,7 @@ import androidx.activity.ComponentActivity
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import com.isolaatti.auth.data.AuthRepositoryImpl
-import com.isolaatti.home.HomeActivity
+import com.isolaatti.home.ui.HomeActivity
import com.isolaatti.login.LogInActivity
import dagger.hilt.android.AndroidEntryPoint
import javax.inject.Inject
diff --git a/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt b/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt
index 4d2dad3..c087d50 100644
--- a/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt
+++ b/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt
@@ -6,7 +6,6 @@ import android.content.Intent
import android.os.Bundle
import android.util.Log
import android.view.View
-import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
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.snackbar.Snackbar
import com.isolaatti.R
-import com.isolaatti.connectivity.ConnectivityCallbackImpl
import com.isolaatti.connectivity.NetworkStatus
-import com.isolaatti.home.HomeActivity
import com.isolaatti.login.LogInActivity
import com.isolaatti.utils.Resource
import dagger.hilt.android.AndroidEntryPoint
diff --git a/app/src/main/java/com/isolaatti/hashtags/data/HashtagsApi.kt b/app/src/main/java/com/isolaatti/hashtags/data/HashtagsApi.kt
new file mode 100644
index 0000000..76960b5
--- /dev/null
+++ b/app/src/main/java/com/isolaatti/hashtags/data/HashtagsApi.kt
@@ -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 {
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/hashtags/presentation/HashtagsViewModels.kt b/app/src/main/java/com/isolaatti/hashtags/presentation/HashtagsViewModels.kt
deleted file mode 100644
index 33ad357..0000000
--- a/app/src/main/java/com/isolaatti/hashtags/presentation/HashtagsViewModels.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.isolaatti.hashtags.presentation
-
-import androidx.lifecycle.ViewModel
-
-class HashtagsViewModel : ViewModel() {
-
-}
-
-class HashtagPostsViewModel : ViewModel() {
-
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt
index 0763734..6194d7f 100644
--- a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt
+++ b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt
@@ -5,9 +5,12 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import com.isolaatti.databinding.FragmentPostsHashtagBinding
import androidx.navigation.fragment.navArgs
+import com.isolaatti.R
+import com.isolaatti.posting.posts.ui.PostListingFragment
class HashtagPostsFragment : Fragment() {
@@ -29,6 +32,10 @@ class HashtagPostsFragment : Fragment() {
binding.toolbar.title = "#${navArgs.hashtag}"
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() {
diff --git a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt
index bf5cece..fb9a27a 100644
--- a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt
+++ b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt
@@ -2,12 +2,12 @@ package com.isolaatti.home.presentation
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.auth.domain.AuthRepository
import com.isolaatti.posting.posts.domain.PostsRepository
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
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.use_case.GetProfile
import com.isolaatti.utils.Resource
@@ -22,9 +22,8 @@ import javax.inject.Inject
@HiltViewModel
class FeedViewModel @Inject constructor(
private val getProfileUseCase: GetProfile,
- private val authRepository: AuthRepository,
- private val postsRepository: PostsRepository
-) : PostListingViewModelBase() {
+ private val authRepository: AuthRepository
+) : ViewModel() {
private val toRetry: MutableList = mutableListOf()
@@ -39,41 +38,7 @@ class FeedViewModel @Inject constructor(
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
private val _userProfile: MutableLiveData = MutableLiveData()
@@ -88,7 +53,6 @@ class FeedViewModel @Inject constructor(
when(profile) {
is Resource.Error -> {
- errorLoading.postValue(profile.errorType)
toRetry.add {
getProfile()
}
diff --git a/app/src/main/java/com/isolaatti/home/ui/FeedFragment.kt b/app/src/main/java/com/isolaatti/home/ui/FeedFragment.kt
new file mode 100644
index 0000000..f22f9e6
--- /dev/null
+++ b/app/src/main/java/com/isolaatti/home/ui/FeedFragment.kt
@@ -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()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/home/HomeActivity.kt b/app/src/main/java/com/isolaatti/home/ui/HomeActivity.kt
similarity index 96%
rename from app/src/main/java/com/isolaatti/home/HomeActivity.kt
rename to app/src/main/java/com/isolaatti/home/ui/HomeActivity.kt
index dd593be..9fe8c59 100644
--- a/app/src/main/java/com/isolaatti/home/HomeActivity.kt
+++ b/app/src/main/java/com/isolaatti/home/ui/HomeActivity.kt
@@ -1,4 +1,4 @@
-package com.isolaatti.home
+package com.isolaatti.home.ui
import android.Manifest
import android.content.SharedPreferences
@@ -45,10 +45,6 @@ class HomeActivity : IsolaattiBaseActivity() {
askNotificationPermission()
- if(savedInstanceState == null) {
- feedViewModel.getFeed(false)
- }
-
}
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedsApi.kt b/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedsApi.kt
index 6ab7fae..3fd95c2 100644
--- a/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedsApi.kt
+++ b/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedsApi.kt
@@ -1,5 +1,6 @@
package com.isolaatti.posting.posts.data.remote
+import com.isolaatti.common.ResultDto
import com.isolaatti.profile.data.remote.ProfileListItemDto
import retrofit2.Call
import retrofit2.http.GET
@@ -22,4 +23,7 @@ interface FeedsApi {
@GET("Feed")
fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call
+
+ @GET("hashtags/hashtag/{hashtag}")
+ fun getHashtagPosts(@Path("hashtag") hashtag: String, @Query("after") afterPost: Long? = null): Call
}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt
index 236f014..5867bb9 100644
--- a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt
+++ b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt
@@ -1,5 +1,6 @@
package com.isolaatti.posting.posts.data.repository
+import android.util.Log
import com.google.gson.Gson
import com.isolaatti.posting.posts.data.remote.CreatePostDto
import com.isolaatti.posting.posts.data.remote.DeletePostDto
@@ -18,10 +19,18 @@ import retrofit2.awaitResponse
import javax.inject.Inject
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
- override fun getFeed(lastId: Long): Flow>> = flow {
+ companion object {
+ const val LOG_TAG = "PostsRepositoryImpl"
+ }
+ override fun getFeed(lastId: Long, hashtag: String?): Flow>> = flow {
emit(Resource.Loading())
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) {
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
return@flow
@@ -32,7 +41,8 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
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))
}
}
diff --git a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt
index 8ac02c7..a5900ab 100644
--- a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt
+++ b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt
@@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow
interface PostsRepository {
- fun getFeed(lastId: Long): Flow>>
+ fun getFeed(lastId: Long, hashtag: String?): Flow>>
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow>>
diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModel.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModel.kt
new file mode 100644
index 0000000..4fb6e28
--- /dev/null
+++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModel.kt
@@ -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)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt
index 59c1319..8303f1d 100644
--- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt
+++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostListingViewModelBase.kt
@@ -44,7 +44,7 @@ abstract class PostListingViewModelBase : ViewModel() {
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) {
viewModelScope.launch {
diff --git a/app/src/main/java/com/isolaatti/home/FeedFragment.kt b/app/src/main/java/com/isolaatti/posting/posts/ui/PostListingFragment.kt
similarity index 61%
rename from app/src/main/java/com/isolaatti/home/FeedFragment.kt
rename to app/src/main/java/com/isolaatti/posting/posts/ui/PostListingFragment.kt
index a281b72..805dd67 100644
--- a/app/src/main/java/com/isolaatti/home/FeedFragment.kt
+++ b/app/src/main/java/com/isolaatti/posting/posts/ui/PostListingFragment.kt
@@ -1,52 +1,38 @@
-package com.isolaatti.home
+package com.isolaatti.posting.posts.ui
-import android.content.Intent
import android.os.Bundle
-import androidx.fragment.app.Fragment
+import android.util.Log
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.Fragment
import androidx.fragment.app.activityViewModels
-import androidx.lifecycle.Lifecycle
+import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
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.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.CoilImageLoader
import com.isolaatti.common.Dialogs
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.Ownable
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.presentation.BottomSheetPostOptionsViewModel
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.presentation.CreatePostContract
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.viewer.ui.PostViewerActivity
import com.isolaatti.profile.ui.ProfileActivity
-import com.isolaatti.settings.ui.SettingsActivity
-import com.isolaatti.utils.UrlGen
import dagger.hilt.android.AndroidEntryPoint
import io.noties.markwon.AbstractMarkwonPlugin
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.destination.ImageDestinationProcessorRelativeToAbsolute
import io.noties.markwon.linkify.LinkifyPlugin
-import kotlinx.coroutines.launch
-import org.w3c.dom.Text
@AndroidEntryPoint
-class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
-
+class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
companion object {
- fun newInstance() = FeedFragment()
- const val CALLER_ID = 20
+ const val ARG_HASHTAG = "hashtag"
+ const val LOG_TAG = "PostListingFragment"
}
+ var hashtag: String? = null
+
+ private lateinit var viewBinding: FragmentPostListingBinding
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
- private val viewModel: FeedViewModel by activityViewModels()
+ private val viewModel: PostListingViewModel by viewModels()
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
- private var currentUserId = 0
-
- private lateinit var viewBinding: FragmentFeedBinding
private lateinit var adapter: PostsRecyclerViewAdapter
-
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 = 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 {
override fun onPlaying(isPlaying: Boolean, audio: Playable) {
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?) {
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.addListener(audioPlayerConnectorListener)
@@ -190,11 +112,12 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
.usePlugin(object: AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder
- .imageDestinationProcessor(ImageDestinationProcessorRelativeToAbsolute
+ .imageDestinationProcessor(
+ ImageDestinationProcessorRelativeToAbsolute
.create(BuildConfig.backend))
}
})
- .usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
+ .usePlugin(CoilImagesPlugin.create(requireContext(), CoilImageLoader.imageLoader))
.usePlugin(LinkifyPlugin.create())
.build()
adapter = PostsRecyclerViewAdapter(markwon, this)
@@ -204,44 +127,9 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
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){
if (it?.first != null) {
adapter.updateList(it.first!!, it.second)
@@ -261,18 +149,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
errorViewModel.error.postValue(it)
}
- viewModel.getProfile()
-
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)
@@ -280,7 +157,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
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()
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
}
@@ -303,8 +180,39 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
}
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 = 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()
+ }
+ }
+ }
+ }
+
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt
index 1fcedbd..9adc5f9 100644
--- a/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt
+++ b/app/src/main/java/com/isolaatti/profile/presentation/ProfileViewModel.kt
@@ -111,7 +111,7 @@ class ProfileViewModel @Inject constructor(
}
}
- override fun getFeed(refresh: Boolean) {
+ override fun getFeed(refresh: Boolean, hashtag: String?) {
viewModelScope.launch {
if(refresh) {
posts.value = Pair(null, UpdateEvent(UpdateEvent.UpdateType.REFRESH, null))
@@ -132,7 +132,7 @@ class ProfileViewModel @Inject constructor(
is Resource.Error -> {
errorLoading.postValue(feedDtoResource.errorType)
toRetry.add {
- getFeed(refresh)
+ getFeed(refresh, hashtag)
}
}
}
diff --git a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt
index a6a614d..c827eb9 100644
--- a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt
+++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt
@@ -358,7 +358,7 @@ class ProfileMainFragment : Fragment() {
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
viewBinding.swipeToRefresh.setOnRefreshListener {
- viewModel.getFeed(true)
+ viewModel.getFeed(true, null)
}
viewBinding.playButton.setOnClickListener {
audioDescriptionAudio?.let { audio ->
@@ -396,7 +396,7 @@ class ProfileMainFragment : Fragment() {
userId?.let { profileId ->
viewModel.profileId = profileId
viewModel.getProfile()
- viewModel.getFeed(true)
+ viewModel.getFeed(true, null)
}
}
@@ -466,7 +466,7 @@ class ProfileMainFragment : Fragment() {
}
override fun onLoadMore() {
- viewModel.getFeed(false)
+ viewModel.getFeed(false, null)
}
}
}
diff --git a/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt b/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt
index 669e458..37f8590 100644
--- a/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt
+++ b/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt
@@ -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
}
diff --git a/app/src/main/java/com/isolaatti/sign_up/ui/MakeAccountFragment.kt b/app/src/main/java/com/isolaatti/sign_up/ui/MakeAccountFragment.kt
index 91ec910..9e3109e 100644
--- a/app/src/main/java/com/isolaatti/sign_up/ui/MakeAccountFragment.kt
+++ b/app/src/main/java/com/isolaatti/sign_up/ui/MakeAccountFragment.kt
@@ -17,7 +17,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.BuildConfig
import com.isolaatti.R
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.presentation.MakeAccountViewModel
import com.isolaatti.sign_up.presentation.SignUpViewModel
diff --git a/app/src/main/res/drawable/baseline_article_24.xml b/app/src/main/res/drawable/baseline_article_24.xml
new file mode 100644
index 0000000..30d5d26
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_article_24.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/hashtag_solid.xml b/app/src/main/res/drawable/hashtag_solid.xml
new file mode 100644
index 0000000..9304bf8
--- /dev/null
+++ b/app/src/main/res/drawable/hashtag_solid.xml
@@ -0,0 +1,9 @@
+
+
+
diff --git a/app/src/main/res/layout-land/fragment_feed.xml b/app/src/main/res/layout-land/fragment_feed.xml
index 578bef4..4fc373d 100644
--- a/app/src/main/res/layout-land/fragment_feed.xml
+++ b/app/src/main/res/layout-land/fragment_feed.xml
@@ -17,11 +17,11 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/home_drawer"
app:layout_constraintTop_toTopOf="parent"
- tools:context=".home.FeedFragment">
+ tools:context=".home.ui.FeedFragment">
-
-
-
-
-
-
+ app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
+ tools:context=".home.ui.FeedFragment">
-
-
-
-
-
+ app:defaultNavHost="true"
+ app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
diff --git a/app/src/main/res/layout/fragment_post_listing.xml b/app/src/main/res/layout/fragment_post_listing.xml
new file mode 100644
index 0000000..ee1e7f8
--- /dev/null
+++ b/app/src/main/res/layout/fragment_post_listing.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_posts_hashtag.xml b/app/src/main/res/layout/fragment_posts_hashtag.xml
index 8748d07..f9c0982 100644
--- a/app/src/main/res/layout/fragment_posts_hashtag.xml
+++ b/app/src/main/res/layout/fragment_posts_hashtag.xml
@@ -15,4 +15,12 @@
app:titleCentered="true"/>
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/home_navigation.xml b/app/src/main/res/navigation/home_navigation.xml
index b6349d4..abda5dc 100644
--- a/app/src/main/res/navigation/home_navigation.xml
+++ b/app/src/main/res/navigation/home_navigation.xml
@@ -7,7 +7,7 @@
+ android:label="HashtagPostsFragment">
diff --git a/app/src/main/res/navigation/post_listing_navigation.xml b/app/src/main/res/navigation/post_listing_navigation.xml
new file mode 100644
index 0000000..703282c
--- /dev/null
+++ b/app/src/main/res/navigation/post_listing_navigation.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
\ No newline at end of file