diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml
index aab14b8..cb6a743 100644
--- a/.idea/navEditor.xml
+++ b/.idea/navEditor.xml
@@ -59,11 +59,66 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index f229ea9..02127e6 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -22,8 +22,8 @@ android {
applicationId "com.isolaatti"
minSdk 24
targetSdk 34
- versionCode 2
- versionName "0.2"
+ versionCode 3
+ versionName "0.3-vc3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
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/about/AboutActivity.kt b/app/src/main/java/com/isolaatti/about/AboutActivity.kt
index 3822453..c281e5a 100644
--- a/app/src/main/java/com/isolaatti/about/AboutActivity.kt
+++ b/app/src/main/java/com/isolaatti/about/AboutActivity.kt
@@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent
import androidx.core.net.toUri
import com.isolaatti.BuildConfig
+import com.isolaatti.R
import com.isolaatti.databinding.ActivityAboutBinding
class AboutActivity : AppCompatActivity() {
@@ -51,5 +52,7 @@ class AboutActivity : AppCompatActivity() {
.build()
.launchUrl(this, BuildConfig.terms.toUri())
}
+
+ binding.appVersion.text = getString(R.string.app_version, BuildConfig.VERSION_NAME)
}
}
\ No newline at end of file
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/connectivity/RetrofitClient.kt b/app/src/main/java/com/isolaatti/connectivity/RetrofitClient.kt
index 613418d..dd66093 100644
--- a/app/src/main/java/com/isolaatti/connectivity/RetrofitClient.kt
+++ b/app/src/main/java/com/isolaatti/connectivity/RetrofitClient.kt
@@ -26,6 +26,12 @@ class RetrofitClient @Inject constructor(private val authenticationInterceptor:
private val okHttpClient get() = OkHttpClient.Builder()
.addInterceptor(authenticationInterceptor)
+ .addInterceptor { chain ->
+ chain.proceed(chain.request()
+ .newBuilder()
+ .header("User-Agent", "Isolaatti Android ${BuildConfig.VERSION_NAME}")
+ .build())
+ }
.connectTimeout(5, TimeUnit.MINUTES)
.writeTimeout(5, TimeUnit.MINUTES)
.readTimeout(5, TimeUnit.MINUTES)
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/ui/HashtagPostsFragment.kt b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt
new file mode 100644
index 0000000..6194d7f
--- /dev/null
+++ b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagPostsFragment.kt
@@ -0,0 +1,46 @@
+package com.isolaatti.hashtags.ui
+
+import android.os.Bundle
+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() {
+
+ private lateinit var binding: FragmentPostsHashtagBinding
+ private val navArgs: HashtagPostsFragmentArgs by navArgs()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentPostsHashtagBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ 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() {
+ binding.toolbar.setNavigationOnClickListener {
+ findNavController().popBackStack()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsActivity.kt b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsActivity.kt
deleted file mode 100644
index 970447c..0000000
--- a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsActivity.kt
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.isolaatti.hashtags.ui
-
-import android.content.Context
-import com.isolaatti.common.IsolaattiBaseActivity
-
-class HashtagsActivity : IsolaattiBaseActivity() {
- companion object {
- fun startActivity(context: Context, hashtag: String? = null) {
-
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsFragment.kt b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsFragment.kt
new file mode 100644
index 0000000..346a1ca
--- /dev/null
+++ b/app/src/main/java/com/isolaatti/hashtags/ui/HashtagsFragment.kt
@@ -0,0 +1,35 @@
+package com.isolaatti.hashtags.ui
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.isolaatti.databinding.FragmentHashtagsBinding
+
+class HashtagsFragment : Fragment() {
+ private lateinit var binding: FragmentHashtagsBinding
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentHashtagsBinding.inflate(inflater, container, false)
+
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setupListeners()
+ }
+
+ private fun setupListeners() {
+ binding.toolbar.setNavigationOnClickListener {
+ findNavController().popBackStack()
+ }
+ }
+}
\ No newline at end of file
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/posting/posts/presentation/PostsRecyclerViewAdapter.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt
index 445dc82..1f82a6e 100644
--- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt
+++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt
@@ -361,7 +361,9 @@ class PostsRecyclerViewAdapter (
if(totalItems != null && totalItems > 0 && !requestedNewContent) {
if(position == totalItems - 1) {
requestedNewContent = true
- callback.onLoadMore()
+ if(payloads.isEmpty()) {
+ callback.onLoadMore()
+ }
}
}
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 60%
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 14d6b0e..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,53 +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.reports.ui.NewReportBottomSheetDialogFragment
-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
@@ -55,85 +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()
- NewReportBottomSheetDialogFragment.newInstance().show(childFragmentManager, NewReportBottomSheetDialogFragment.LOG_TAG)
- }
- }
- }
- }
-
- // 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)
@@ -162,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)
@@ -192,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)
@@ -206,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)
@@ -263,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)
@@ -282,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)
}
@@ -305,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/BrowseProfilesFragment.kt b/app/src/main/java/com/isolaatti/profile/ui/BrowseProfilesFragment.kt
new file mode 100644
index 0000000..44524e3
--- /dev/null
+++ b/app/src/main/java/com/isolaatti/profile/ui/BrowseProfilesFragment.kt
@@ -0,0 +1,6 @@
+package com.isolaatti.profile.ui
+
+import androidx.fragment.app.Fragment
+
+class BrowseProfilesFragment : Fragment() {
+}
\ No newline at end of file
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 cf344c4..8c4fc75 100644
--- a/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt
+++ b/app/src/main/java/com/isolaatti/profile/ui/ProfileMainFragment.kt
@@ -360,7 +360,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 ->
@@ -398,7 +398,7 @@ class ProfileMainFragment : Fragment() {
userId?.let { profileId ->
viewModel.profileId = profileId
viewModel.getProfile()
- viewModel.getFeed(true)
+ viewModel.getFeed(true, null)
}
}
@@ -468,7 +468,7 @@ class ProfileMainFragment : Fragment() {
}
override fun onLoadMore() {
- viewModel.getFeed(false)
+ viewModel.getFeed(false, null)
}
}
}
diff --git a/app/src/main/java/com/isolaatti/search/data/SearchApi.kt b/app/src/main/java/com/isolaatti/search/data/SearchApi.kt
index 86e237c..273da9d 100644
--- a/app/src/main/java/com/isolaatti/search/data/SearchApi.kt
+++ b/app/src/main/java/com/isolaatti/search/data/SearchApi.kt
@@ -5,8 +5,8 @@ import retrofit2.http.GET
import retrofit2.http.Query
interface SearchApi {
- @GET("Search/Quick")
- fun quickSearch(@Query("q") query: String): Call
+ @GET("Search/v2")
+ fun quickSearch(@Query("query") query: String): Call
@GET("hashtags/trending")
fun getTrendingHashtags(): Call
diff --git a/app/src/main/java/com/isolaatti/search/data/SearchDto.kt b/app/src/main/java/com/isolaatti/search/data/SearchDto.kt
index fbf44c6..d68f296 100644
--- a/app/src/main/java/com/isolaatti/search/data/SearchDto.kt
+++ b/app/src/main/java/com/isolaatti/search/data/SearchDto.kt
@@ -1,14 +1,33 @@
package com.isolaatti.search.data
-import com.isolaatti.posting.posts.data.remote.FeedDto
-
+enum class SearchResultType {
+ Profile, Post, Hashtag, Unknown
+}
data class ProfileSearchDto(val id: Int, val name: String, val imageId: String?, val following: Boolean)
-data class SearchDto(
- val profiles: List,
- val posts: List
- // TODO add the other types
-)
+data class SearchDto(val result: List)
+
+data class SearchResultDto(
+ val origin: String,
+ val resourceId: String,
+ val title: String,
+ val description: String
+) {
+
+ companion object {
+ const val ORIGIN_POSTS = "posts"
+ const val ORIGIN_HASHTAGS = "hashtags"
+ const val ORIGIN_USERS = "users"
+ }
+ val type: SearchResultType get() {
+ return when(origin) {
+ ORIGIN_POSTS -> SearchResultType.Post
+ ORIGIN_HASHTAGS -> SearchResultType.Hashtag
+ ORIGIN_USERS -> SearchResultType.Profile
+ else -> SearchResultType.Unknown
+ }
+ }
+}
data class HashtagsDto(val result: List)
diff --git a/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt b/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt
new file mode 100644
index 0000000..37f8590
--- /dev/null
+++ b/app/src/main/java/com/isolaatti/search/presentation/SearchResultsAdapter.kt
@@ -0,0 +1,61 @@
+package com.isolaatti.search.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 coil.load
+import com.isolaatti.R
+import com.isolaatti.databinding.SearchResultItemBinding
+import com.isolaatti.search.data.SearchResultDto
+import com.isolaatti.search.data.SearchResultType
+import com.isolaatti.utils.UrlGen
+
+class SearchResultsAdapter(
+ private val onItemClick: (item: SearchResultDto ) -> Unit = {}
+) : ListAdapter(itemCallback) {
+ companion object {
+ val itemCallback = object: DiffUtil.ItemCallback() {
+ override fun areItemsTheSame(oldItem: SearchResultDto, newItem: SearchResultDto): Boolean {
+ return oldItem.resourceId == newItem.resourceId && oldItem.type == newItem.type
+ }
+
+ override fun areContentsTheSame(oldItem: SearchResultDto, newItem: SearchResultDto): Boolean {
+ return oldItem == newItem
+ }
+ }
+ }
+ inner class SearchResultViewHolder(val searchResultItemBinding: SearchResultItemBinding) : ViewHolder(searchResultItemBinding.root)
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SearchResultViewHolder {
+ return SearchResultViewHolder(SearchResultItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
+ }
+
+ override fun onBindViewHolder(holder: SearchResultViewHolder, position: Int) {
+ val searchResult = getItem(position)
+ holder.searchResultItemBinding.apply {
+ searchResultTitle.text = searchResult.title
+ searchResultDescription.text = searchResult.description
+ root.setOnClickListener { onItemClick(searchResult) }
+
+ // TODO complete this
+ val image = when(searchResult.type) {
+ SearchResultType.Profile -> {
+ val userId = searchResult.resourceId.toIntOrNull()
+ if(userId != null) {
+ UrlGen.userProfileImage(userId)
+ } else {
+ R.drawable.baseline_search_24
+ }
+
+ }
+ SearchResultType.Hashtag -> R.drawable.hashtag_solid
+ SearchResultType.Post -> R.drawable.baseline_search_24
+ else -> R.drawable.baseline_search_24
+ }
+
+ searchResultImage.load(image)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt b/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt
index d67e85d..29b393e 100644
--- a/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt
+++ b/app/src/main/java/com/isolaatti/search/ui/SearchFragment.kt
@@ -10,18 +10,22 @@ import androidx.core.widget.doAfterTextChanged
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
+import androidx.navigation.findNavController
+import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.android.material.carousel.CarouselLayoutManager
import com.google.android.material.carousel.UncontainedCarouselStrategy
import com.google.android.material.chip.Chip
import com.isolaatti.R
import com.isolaatti.databinding.FragmentSearchBinding
-import com.isolaatti.hashtags.ui.HashtagsActivity
+import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
import com.isolaatti.profile.ui.ProfileActivity
import com.isolaatti.search.data.HashtagsDto
import com.isolaatti.search.data.NewestUsersDto
import com.isolaatti.search.data.SearchDto
import com.isolaatti.search.data.SearchHistoryEntity
+import com.isolaatti.search.data.SearchResultType
+import com.isolaatti.search.presentation.SearchResultsAdapter
import com.isolaatti.search.presentation.SearchSuggestionsAdapter
import com.isolaatti.search.presentation.SearchViewModel
import com.isolaatti.search.presentation.UserCarouselAdapter
@@ -38,6 +42,7 @@ class SearchFragment : Fragment() {
private val viewModel: SearchViewModel by viewModels()
private var searchSuggestionsAdapter: SearchSuggestionsAdapter? = null
private var newestUsersAdapter: UserCarouselAdapter? = null
+ private var searchResultsAdapter: SearchResultsAdapter? = null
private val searchSuggestionsObserver: Observer> = Observer {
searchSuggestionsAdapter?.submitList(it)
@@ -45,6 +50,7 @@ class SearchFragment : Fragment() {
private val searchResultsObserver: Observer = Observer {
Log.d(LOG_TAG, it.toString())
+ searchResultsAdapter?.submitList(it.result)
}
private val trendingHashtagsObserver: Observer = Observer {
@@ -55,7 +61,7 @@ class SearchFragment : Fragment() {
viewBinding.chipGroup.addView(Chip(requireContext()).apply {
text = "#$hashtag"
setOnClickListener {
- requireContext().startActivity(Intent(requireContext(), HashtagsActivity::class.java))
+ findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagPostsFragment(hashtag))
}
})
}
@@ -123,7 +129,24 @@ class SearchFragment : Fragment() {
layoutManager = CarouselLayoutManager(UncontainedCarouselStrategy())
}
-
+ searchResultsAdapter = SearchResultsAdapter(
+ onItemClick = {
+ when(it.type) {
+ SearchResultType.Profile -> {
+ it.resourceId.toIntOrNull()?.also { ProfileActivity.startActivity(requireContext(), it) }
+ }
+ SearchResultType.Post -> {
+ it.resourceId.toLongOrNull()?.also { PostViewerActivity.startActivity(requireContext(), it) }
+ }
+ SearchResultType.Hashtag -> {
+ findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagPostsFragment(it.resourceId))
+ }
+ SearchResultType.Unknown -> {}
+ }
+ }
+ )
+ viewBinding.recyclerViewSearchResults.adapter = searchResultsAdapter
+ viewBinding.recyclerViewSearchResults.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
}
private fun setupListeners() {
@@ -147,11 +170,19 @@ class SearchFragment : Fragment() {
if(it.itemId == R.id.close_button) {
showResults(false)
true
+ } else {
+ false
}
- false
+
+ }
+
+ viewBinding.openHashtagsButton.setOnClickListener {
+ findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagsFragment())
+ }
+
+ viewBinding.browseProfilesButton.setOnClickListener {
+ findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToBrowseProfilesFragment())
}
-
- viewBinding.searchView.addTransitionListener { searchView, transitionState, transitionState2 -> }
}
private fun setupObservers() {
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" />
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools">
+
+ 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_hashtags.xml b/app/src/main/res/layout/fragment_hashtags.xml
new file mode 100644
index 0000000..cb65f9e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_hashtags.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
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
new file mode 100644
index 0000000..f9c0982
--- /dev/null
+++ b/app/src/main/res/layout/fragment_posts_hashtag.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml
index c253351..22024ba 100644
--- a/app/src/main/res/layout/fragment_search.xml
+++ b/app/src/main/res/layout/fragment_search.xml
@@ -52,11 +52,12 @@
+ android:text="@string/go_to_hashtags"/>
+ android:text="@string/browse_profiles"/>
diff --git a/app/src/main/res/layout/search_result_item.xml b/app/src/main/res/layout/search_result_item.xml
new file mode 100644
index 0000000..014b567
--- /dev/null
+++ b/app/src/main/res/layout/search_result_item.xml
@@ -0,0 +1,57 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/home_menu.xml b/app/src/main/res/menu/home_menu.xml
index d01e7e1..2126d31 100644
--- a/app/src/main/res/menu/home_menu.xml
+++ b/app/src/main/res/menu/home_menu.xml
@@ -5,12 +5,12 @@
android:id="@+id/feedFragment"
android:icon="@drawable/baseline_home_24"
android:title="@string/home" />
-
+
\ 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 a233317..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="@string/search" >
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3debc60..910ea55 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -201,5 +201,8 @@
Hashtags
Newest profiles
See all
+ Go to hashtags
+ Browse profiles
+ v%s
New report
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 94c2a08..13ca289 100644
--- a/build.gradle
+++ b/build.gradle
@@ -13,8 +13,8 @@ buildscript {
}
}
plugins {
- id 'com.android.application' version '8.3.0' apply false
- id 'com.android.library' version '8.3.0' apply false
+ id 'com.android.application' version '8.3.1' apply false
+ id 'com.android.library' version '8.3.1' apply false
id 'org.jetbrains.kotlin.android' version '1.9.0' apply false
id 'com.google.dagger.hilt.android' version '2.47' apply false
id 'com.google.gms.google-services' version '4.4.0' apply false