Merge remote-tracking branch 'origin/dev' into dev
# Conflicts: # app/src/main/java/com/isolaatti/posting/posts/ui/PostListingFragment.kt # app/src/main/res/values/strings.xml
This commit is contained in:
commit
ce8ec23100
145
.idea/navEditor.xml
generated
145
.idea/navEditor.xml
generated
@ -59,11 +59,66 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="hashtags_navigation.xml">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="hashtagPostsFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="-3" />
|
||||
<option name="y" value="36" />
|
||||
</Point>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="hashtagsFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="-219" />
|
||||
<option name="y" value="36" />
|
||||
</Point>
|
||||
</option>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="action_hashtagsFragment_to_hashtagPostsFragment">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="home_navigation.xml">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="browseProfilesFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="45" />
|
||||
<option name="y" value="385" />
|
||||
</Point>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="feedFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
@ -85,6 +140,39 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="hashtagPostsFragment2">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="267" />
|
||||
<option name="y" value="107" />
|
||||
</Point>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="hashtagsFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="36" />
|
||||
<option name="y" value="-76" />
|
||||
</Point>
|
||||
</option>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="action_hashtagsFragment2_to_hashtagPostsFragment2">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="notificationsFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
@ -106,18 +194,6 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="postViewerActivity">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="-520" />
|
||||
<option name="y" value="-373" />
|
||||
</Point>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="profileActivity">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
@ -135,10 +211,29 @@
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="12" />
|
||||
<option name="y" value="12" />
|
||||
<option name="x" value="-278" />
|
||||
<option name="y" value="216" />
|
||||
</Point>
|
||||
</option>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="action_searchFragment_to_browseProfilesFragment">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="action_searchFragment_to_hashtagPostsFragment">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="action_searchFragment_to_hashtagsFragment">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
@ -246,6 +341,28 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="post_listing_navigation.xml">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="postListingFragment">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
<option name="myPosition">
|
||||
<Point>
|
||||
<option name="x" value="40" />
|
||||
<option name="y" value="40" />
|
||||
</Point>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="profile_navigation.xml">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
|
||||
@ -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"
|
||||
}
|
||||
|
||||
@ -26,15 +26,15 @@
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</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=".profile.ui.ProfileActivity"
|
||||
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=".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"
|
||||
android:parentActivityName=".home.HomeActivity"/>
|
||||
android:parentActivityName=".home.ui.HomeActivity"/>
|
||||
<activity android:name=".drafts.ui.DraftsActivity" 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"/>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
|
||||
@ -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)
|
||||
|
||||
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 {
|
||||
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<Runnable> = 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<UserProfile> = MutableLiveData()
|
||||
@ -88,7 +53,6 @@ class FeedViewModel @Inject constructor(
|
||||
|
||||
when(profile) {
|
||||
is Resource.Error -> {
|
||||
errorLoading.postValue(profile.errorType)
|
||||
toRetry.add {
|
||||
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.content.SharedPreferences
|
||||
@ -45,10 +45,6 @@ class HomeActivity : IsolaattiBaseActivity() {
|
||||
|
||||
askNotificationPermission()
|
||||
|
||||
if(savedInstanceState == null) {
|
||||
feedViewModel.getFeed(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
@ -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<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
|
||||
|
||||
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<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())
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow
|
||||
|
||||
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>>>
|
||||
|
||||
|
||||
@ -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 }
|
||||
|
||||
|
||||
abstract fun getFeed(refresh: Boolean)
|
||||
abstract fun getFeed(refresh: Boolean, hashtag: String?)
|
||||
|
||||
fun likePost(postId: Long) {
|
||||
viewModelScope.launch {
|
||||
|
||||
@ -361,9 +361,11 @@ class PostsRecyclerViewAdapter (
|
||||
if(totalItems != null && totalItems > 0 && !requestedNewContent) {
|
||||
if(position == totalItems - 1) {
|
||||
requestedNewContent = true
|
||||
if(payloads.isEmpty()) {
|
||||
callback.onLoadMore()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -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<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()
|
||||
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<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 {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,6 @@
|
||||
package com.isolaatti.profile.ui
|
||||
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
class BrowseProfilesFragment : Fragment() {
|
||||
}
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,8 +5,8 @@ import retrofit2.http.GET
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface SearchApi {
|
||||
@GET("Search/Quick")
|
||||
fun quickSearch(@Query("q") query: String): Call<SearchDto>
|
||||
@GET("Search/v2")
|
||||
fun quickSearch(@Query("query") query: String): Call<SearchDto>
|
||||
|
||||
@GET("hashtags/trending")
|
||||
fun getTrendingHashtags(): Call<HashtagsDto>
|
||||
|
||||
@ -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<ProfileSearchDto>,
|
||||
val posts: List<FeedDto.PostDto>
|
||||
// TODO add the other types
|
||||
)
|
||||
data class SearchDto(val result: List<SearchResultDto>)
|
||||
|
||||
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<String>)
|
||||
|
||||
|
||||
@ -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<SearchResultDto, SearchResultsAdapter.SearchResultViewHolder>(itemCallback) {
|
||||
companion object {
|
||||
val itemCallback = object: DiffUtil.ItemCallback<SearchResultDto>() {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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<List<SearchHistoryEntity>> = Observer {
|
||||
searchSuggestionsAdapter?.submitList(it)
|
||||
@ -45,6 +50,7 @@ class SearchFragment : Fragment() {
|
||||
|
||||
private val searchResultsObserver: Observer<SearchDto> = Observer {
|
||||
Log.d(LOG_TAG, it.toString())
|
||||
searchResultsAdapter?.submitList(it.result)
|
||||
}
|
||||
|
||||
private val trendingHashtagsObserver: Observer<HashtagsDto> = 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
|
||||
}
|
||||
|
||||
viewBinding.searchView.addTransitionListener { searchView, transitionState, transitionState2 -> }
|
||||
}
|
||||
|
||||
viewBinding.openHashtagsButton.setOnClickListener {
|
||||
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagsFragment())
|
||||
}
|
||||
|
||||
viewBinding.browseProfilesButton.setOnClickListener {
|
||||
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToBrowseProfilesFragment())
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
|
||||
@ -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
|
||||
|
||||
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_constraintStart_toEndOf="@+id/home_drawer"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:context=".home.FeedFragment">
|
||||
tools:context=".home.ui.FeedFragment">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/topAppBar_layout"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
@ -46,26 +46,11 @@
|
||||
|
||||
</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:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
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"/>
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
|
||||
@ -2,7 +2,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">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools">
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/appBarLayout"
|
||||
android:layout_width="match_parent"
|
||||
@ -48,6 +49,13 @@
|
||||
android:text="@string/app_description"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:textAlignment="center"/>
|
||||
<TextView
|
||||
android:id="@+id/app_version"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
tools:text="v 1.0"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:textAlignment="center"/>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
@ -13,7 +13,7 @@
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".home.FeedFragment">
|
||||
tools:context=".home.ui.FeedFragment">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/topAppBar_layout"
|
||||
@ -36,23 +36,14 @@
|
||||
|
||||
</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_height="match_parent"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
||||
<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"/>
|
||||
app:defaultNavHost="true"
|
||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
||||
17
app/src/main/res/layout/fragment_hashtags.xml
Normal file
17
app/src/main/res/layout/fragment_hashtags.xml
Normal file
@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/baseline_close_24"
|
||||
app:title="@string/hashtags"/>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
</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>
|
||||
26
app/src/main/res/layout/fragment_posts_hashtag.xml
Normal file
26
app/src/main/res/layout/fragment_posts_hashtag.xml
Normal file
@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:navigationIcon="@drawable/baseline_arrow_back_24"
|
||||
app:titleCentered="true"/>
|
||||
</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>
|
||||
@ -52,11 +52,12 @@
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/open_hashtags_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/chip_group"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/see_all"/>
|
||||
android:text="@string/go_to_hashtags"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
@ -90,11 +91,12 @@
|
||||
app:layout_constraintTop_toBottomOf="@id/new_users_card_title"/>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/browse_profiles_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/new_users_recycler_view"
|
||||
android:layout_marginTop="8dp"
|
||||
android:text="@string/see_all"/>
|
||||
android:text="@string/browse_profiles"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
|
||||
57
app/src/main/res/layout/search_result_item.xml
Normal file
57
app/src/main/res/layout/search_result_item.xml
Normal file
@ -0,0 +1,57 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="70dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginVertical="4dp"
|
||||
style="?attr/materialCardViewFilledStyle">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_result_image"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginStart="16dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:src="@drawable/baseline_search_24"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_result_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintBottom_toTopOf="@+id/search_result_description"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/search_result_image"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:maxLines="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
tools:text="Title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_result_description"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="16sp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/search_result_image"
|
||||
app:layout_constraintTop_toBottomOf="@+id/search_result_title"
|
||||
android:maxLines="1"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
tools:text="Description" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
@ -5,12 +5,12 @@
|
||||
android:id="@+id/feedFragment"
|
||||
android:icon="@drawable/baseline_home_24"
|
||||
android:title="@string/home" />
|
||||
<item
|
||||
android:id="@+id/notificationsFragment"
|
||||
android:icon="@drawable/baseline_notifications_24"
|
||||
android:title="@string/notifications" />
|
||||
<item
|
||||
android:id="@+id/searchFragment"
|
||||
android:icon="@drawable/baseline_search_24"
|
||||
android:title="@string/search" />
|
||||
<item
|
||||
android:id="@+id/notificationsFragment"
|
||||
android:icon="@drawable/baseline_notifications_24"
|
||||
android:title="@string/notifications" />
|
||||
</menu>
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
<fragment
|
||||
android:id="@+id/feedFragment"
|
||||
android:name="com.isolaatti.home.FeedFragment"
|
||||
android:name="com.isolaatti.home.ui.FeedFragment"
|
||||
android:label="fragment_feed"
|
||||
tools:layout="@layout/fragment_feed" >
|
||||
<action
|
||||
@ -30,5 +30,35 @@
|
||||
<fragment
|
||||
android:id="@+id/searchFragment"
|
||||
android:name="com.isolaatti.search.ui.SearchFragment"
|
||||
android:label="@string/search" />
|
||||
android:label="@string/search" >
|
||||
<action
|
||||
android:id="@+id/action_searchFragment_to_hashtagsFragment"
|
||||
app:destination="@id/hashtagsFragment" />
|
||||
<action
|
||||
android:id="@+id/action_searchFragment_to_hashtagPostsFragment"
|
||||
app:destination="@id/hashtagPostsFragment2" />
|
||||
<action
|
||||
android:id="@+id/action_searchFragment_to_browseProfilesFragment"
|
||||
app:destination="@id/browseProfilesFragment" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/hashtagsFragment"
|
||||
android:name="com.isolaatti.hashtags.ui.HashtagsFragment"
|
||||
android:label="HashtagsFragment" >
|
||||
<action
|
||||
android:id="@+id/action_hashtagsFragment2_to_hashtagPostsFragment2"
|
||||
app:destination="@id/hashtagPostsFragment2" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/hashtagPostsFragment2"
|
||||
android:name="com.isolaatti.hashtags.ui.HashtagPostsFragment"
|
||||
android:label="HashtagPostsFragment">
|
||||
<argument
|
||||
android:name="hashtag"
|
||||
app:argType="string" />
|
||||
</fragment>
|
||||
<fragment
|
||||
android:id="@+id/browseProfilesFragment"
|
||||
android:name="com.isolaatti.profile.ui.BrowseProfilesFragment"
|
||||
android:label="BrowseProfilesFragment" />
|
||||
</navigation>
|
||||
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>
|
||||
@ -201,5 +201,8 @@
|
||||
<string name="hashtags">Hashtags</string>
|
||||
<string name="newest_profiles">Newest profiles</string>
|
||||
<string name="see_all">See all</string>
|
||||
<string name="go_to_hashtags">Go to hashtags</string>
|
||||
<string name="browse_profiles">Browse profiles</string>
|
||||
<string name="app_version">v%s</string>
|
||||
<string name="new_report">New report</string>
|
||||
</resources>
|
||||
@ -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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user