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>
|
</LayoutPositions>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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">
|
<entry key="home_navigation.xml">
|
||||||
<value>
|
<value>
|
||||||
<LayoutPositions>
|
<LayoutPositions>
|
||||||
<option name="myPositions">
|
<option name="myPositions">
|
||||||
<map>
|
<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">
|
<entry key="feedFragment">
|
||||||
<value>
|
<value>
|
||||||
<LayoutPositions>
|
<LayoutPositions>
|
||||||
@ -85,6 +140,39 @@
|
|||||||
</LayoutPositions>
|
</LayoutPositions>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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">
|
<entry key="notificationsFragment">
|
||||||
<value>
|
<value>
|
||||||
<LayoutPositions>
|
<LayoutPositions>
|
||||||
@ -106,18 +194,6 @@
|
|||||||
</LayoutPositions>
|
</LayoutPositions>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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">
|
<entry key="profileActivity">
|
||||||
<value>
|
<value>
|
||||||
<LayoutPositions>
|
<LayoutPositions>
|
||||||
@ -135,10 +211,29 @@
|
|||||||
<LayoutPositions>
|
<LayoutPositions>
|
||||||
<option name="myPosition">
|
<option name="myPosition">
|
||||||
<Point>
|
<Point>
|
||||||
<option name="x" value="12" />
|
<option name="x" value="-278" />
|
||||||
<option name="y" value="12" />
|
<option name="y" value="216" />
|
||||||
</Point>
|
</Point>
|
||||||
</option>
|
</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>
|
</LayoutPositions>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</entry>
|
||||||
@ -246,6 +341,28 @@
|
|||||||
</LayoutPositions>
|
</LayoutPositions>
|
||||||
</value>
|
</value>
|
||||||
</entry>
|
</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">
|
<entry key="profile_navigation.xml">
|
||||||
<value>
|
<value>
|
||||||
<LayoutPositions>
|
<LayoutPositions>
|
||||||
|
|||||||
@ -22,8 +22,8 @@ android {
|
|||||||
applicationId "com.isolaatti"
|
applicationId "com.isolaatti"
|
||||||
minSdk 24
|
minSdk 24
|
||||||
targetSdk 34
|
targetSdk 34
|
||||||
versionCode 2
|
versionCode 3
|
||||||
versionName "0.2"
|
versionName "0.3-vc3"
|
||||||
|
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -26,15 +26,15 @@
|
|||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name=".home.HomeActivity" android:theme="@style/Theme.Isolaatti" />
|
<activity android:name=".home.ui.HomeActivity" android:theme="@style/Theme.Isolaatti" />
|
||||||
<activity android:name=".login.LogInActivity" android:theme="@style/Theme.Isolaatti" />
|
<activity android:name=".login.LogInActivity" android:theme="@style/Theme.Isolaatti" />
|
||||||
<activity android:name=".profile.ui.ProfileActivity"
|
<activity android:name=".profile.ui.ProfileActivity"
|
||||||
android:theme="@style/Theme.Isolaatti"
|
android:theme="@style/Theme.Isolaatti"
|
||||||
android:parentActivityName=".home.HomeActivity"/>
|
android:parentActivityName=".home.ui.HomeActivity"/>
|
||||||
<activity android:name=".settings.ui.SettingsActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".settings.ui.SettingsActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
<activity android:name=".posting.posts.ui.CreatePostActivity" android:theme="@style/Theme.Isolaatti" android:windowSoftInputMode="adjustResize"/>
|
<activity android:name=".posting.posts.ui.CreatePostActivity" android:theme="@style/Theme.Isolaatti" android:windowSoftInputMode="adjustResize"/>
|
||||||
<activity android:name=".posting.posts.viewer.ui.PostViewerActivity" android:theme="@style/Theme.Isolaatti"
|
<activity android:name=".posting.posts.viewer.ui.PostViewerActivity" android:theme="@style/Theme.Isolaatti"
|
||||||
android:parentActivityName=".home.HomeActivity"/>
|
android:parentActivityName=".home.ui.HomeActivity"/>
|
||||||
<activity android:name=".drafts.ui.DraftsActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".drafts.ui.DraftsActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
<activity android:name=".about.AboutActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".about.AboutActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
<activity android:name=".images.picture_viewer.ui.PictureViewerActivity" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".images.picture_viewer.ui.PictureViewerActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import androidx.activity.ComponentActivity
|
|||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
|
||||||
import com.isolaatti.auth.data.AuthRepositoryImpl
|
import com.isolaatti.auth.data.AuthRepositoryImpl
|
||||||
import com.isolaatti.home.HomeActivity
|
import com.isolaatti.home.ui.HomeActivity
|
||||||
import com.isolaatti.login.LogInActivity
|
import com.isolaatti.login.LogInActivity
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|||||||
@ -5,6 +5,7 @@ import androidx.appcompat.app.AppCompatActivity
|
|||||||
import androidx.browser.customtabs.CustomTabsIntent
|
import androidx.browser.customtabs.CustomTabsIntent
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
|
import com.isolaatti.R
|
||||||
import com.isolaatti.databinding.ActivityAboutBinding
|
import com.isolaatti.databinding.ActivityAboutBinding
|
||||||
|
|
||||||
class AboutActivity : AppCompatActivity() {
|
class AboutActivity : AppCompatActivity() {
|
||||||
@ -51,5 +52,7 @@ class AboutActivity : AppCompatActivity() {
|
|||||||
.build()
|
.build()
|
||||||
.launchUrl(this, BuildConfig.terms.toUri())
|
.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.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.activity.viewModels
|
import androidx.activity.viewModels
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
@ -14,9 +13,7 @@ import androidx.lifecycle.Observer
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.google.android.material.snackbar.Snackbar
|
import com.google.android.material.snackbar.Snackbar
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.connectivity.ConnectivityCallbackImpl
|
|
||||||
import com.isolaatti.connectivity.NetworkStatus
|
import com.isolaatti.connectivity.NetworkStatus
|
||||||
import com.isolaatti.home.HomeActivity
|
|
||||||
import com.isolaatti.login.LogInActivity
|
import com.isolaatti.login.LogInActivity
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|||||||
@ -26,6 +26,12 @@ class RetrofitClient @Inject constructor(private val authenticationInterceptor:
|
|||||||
|
|
||||||
private val okHttpClient get() = OkHttpClient.Builder()
|
private val okHttpClient get() = OkHttpClient.Builder()
|
||||||
.addInterceptor(authenticationInterceptor)
|
.addInterceptor(authenticationInterceptor)
|
||||||
|
.addInterceptor { chain ->
|
||||||
|
chain.proceed(chain.request()
|
||||||
|
.newBuilder()
|
||||||
|
.header("User-Agent", "Isolaatti Android ${BuildConfig.VERSION_NAME}")
|
||||||
|
.build())
|
||||||
|
}
|
||||||
.connectTimeout(5, TimeUnit.MINUTES)
|
.connectTimeout(5, TimeUnit.MINUTES)
|
||||||
.writeTimeout(5, TimeUnit.MINUTES)
|
.writeTimeout(5, TimeUnit.MINUTES)
|
||||||
.readTimeout(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.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.isolaatti.auth.domain.AuthRepository
|
import com.isolaatti.auth.domain.AuthRepository
|
||||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
|
import com.isolaatti.posting.posts.presentation.PostListingViewModelBase
|
||||||
import com.isolaatti.posting.posts.presentation.UpdateEvent
|
import com.isolaatti.posting.posts.presentation.UpdateEvent
|
||||||
import com.isolaatti.profile.data.remote.UserProfileDto
|
|
||||||
import com.isolaatti.profile.domain.entity.UserProfile
|
import com.isolaatti.profile.domain.entity.UserProfile
|
||||||
import com.isolaatti.profile.domain.use_case.GetProfile
|
import com.isolaatti.profile.domain.use_case.GetProfile
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
@ -22,9 +22,8 @@ import javax.inject.Inject
|
|||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class FeedViewModel @Inject constructor(
|
class FeedViewModel @Inject constructor(
|
||||||
private val getProfileUseCase: GetProfile,
|
private val getProfileUseCase: GetProfile,
|
||||||
private val authRepository: AuthRepository,
|
private val authRepository: AuthRepository
|
||||||
private val postsRepository: PostsRepository
|
) : ViewModel() {
|
||||||
) : PostListingViewModelBase() {
|
|
||||||
|
|
||||||
private val toRetry: MutableList<Runnable> = mutableListOf()
|
private val toRetry: MutableList<Runnable> = mutableListOf()
|
||||||
|
|
||||||
@ -39,41 +38,7 @@ class FeedViewModel @Inject constructor(
|
|||||||
toRetry.clear()
|
toRetry.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFeed(refresh: Boolean) {
|
|
||||||
viewModelScope.launch {
|
|
||||||
if (refresh) {
|
|
||||||
posts.value = null
|
|
||||||
}
|
|
||||||
postsRepository.getFeed(getLastId()).onEach { listResource ->
|
|
||||||
when (listResource) {
|
|
||||||
is Resource.Success -> {
|
|
||||||
val eventType = if((postsList?.size ?: 0) > 0) UpdateEvent.UpdateType.PAGE_ADDED else UpdateEvent.UpdateType.REFRESH
|
|
||||||
loadingPosts.postValue(false)
|
|
||||||
posts.postValue(Pair(postsList?.apply {
|
|
||||||
addAll(listResource.data ?: listOf())
|
|
||||||
} ?: listResource.data,
|
|
||||||
UpdateEvent(eventType, null)))
|
|
||||||
|
|
||||||
noMoreContent.postValue(listResource.data?.size == 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Resource.Loading -> {
|
|
||||||
if(!refresh)
|
|
||||||
loadingPosts.postValue(true)
|
|
||||||
}
|
|
||||||
|
|
||||||
is Resource.Error -> {
|
|
||||||
errorLoading.postValue(listResource.errorType)
|
|
||||||
toRetry.add {
|
|
||||||
getFeed(refresh)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
isLoadingFromScrolling = false
|
|
||||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// User profile
|
// User profile
|
||||||
private val _userProfile: MutableLiveData<UserProfile> = MutableLiveData()
|
private val _userProfile: MutableLiveData<UserProfile> = MutableLiveData()
|
||||||
@ -88,7 +53,6 @@ class FeedViewModel @Inject constructor(
|
|||||||
|
|
||||||
when(profile) {
|
when(profile) {
|
||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
errorLoading.postValue(profile.errorType)
|
|
||||||
toRetry.add {
|
toRetry.add {
|
||||||
getProfile()
|
getProfile()
|
||||||
}
|
}
|
||||||
|
|||||||
140
app/src/main/java/com/isolaatti/home/ui/FeedFragment.kt
Normal file
140
app/src/main/java/com/isolaatti/home/ui/FeedFragment.kt
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
package com.isolaatti.home.ui
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import androidx.lifecycle.repeatOnLifecycle
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.navigation.fragment.NavHostFragment
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
|
import coil.load
|
||||||
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.about.AboutActivity
|
||||||
|
import com.isolaatti.common.CoilImageLoader.imageLoader
|
||||||
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
|
import com.isolaatti.databinding.FragmentFeedBinding
|
||||||
|
import com.isolaatti.home.presentation.FeedViewModel
|
||||||
|
import com.isolaatti.posting.posts.presentation.CreatePostContract
|
||||||
|
import com.isolaatti.posting.posts.ui.PostListingFragment
|
||||||
|
import com.isolaatti.profile.ui.ProfileActivity
|
||||||
|
import com.isolaatti.settings.ui.SettingsActivity
|
||||||
|
import com.isolaatti.utils.UrlGen
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class FeedFragment : Fragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun newInstance() = FeedFragment()
|
||||||
|
const val CALLER_ID = 20
|
||||||
|
}
|
||||||
|
|
||||||
|
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||||
|
private val viewModel: FeedViewModel by activityViewModels()
|
||||||
|
|
||||||
|
private var currentUserId = 0
|
||||||
|
|
||||||
|
private lateinit var viewBinding: FragmentFeedBinding
|
||||||
|
|
||||||
|
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
|
||||||
|
if(it != null) {
|
||||||
|
Toast.makeText(requireContext(), R.string.posted_successfully, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
viewBinding = FragmentFeedBinding.inflate(inflater)
|
||||||
|
|
||||||
|
return viewBinding.root
|
||||||
|
}
|
||||||
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
|
super.onViewCreated(view, savedInstanceState)
|
||||||
|
viewBinding.topAppBar.setNavigationOnClickListener {
|
||||||
|
viewBinding.drawerLayout?.openDrawer(viewBinding.homeDrawer)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
viewBinding.homeDrawer.setNavigationItemSelectedListener {
|
||||||
|
when(it.itemId) {
|
||||||
|
R.id.settings_menu_item -> {
|
||||||
|
startActivity(Intent(requireActivity(), SettingsActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
R.id.about_menu_item -> {
|
||||||
|
startActivity(Intent(requireActivity(), AboutActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> {true}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
viewBinding.topAppBar.setOnMenuItemClickListener {
|
||||||
|
when(it.itemId) {
|
||||||
|
R.id.menu_item_new_discussion -> {
|
||||||
|
createDiscussion.launch(Unit)
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// show feed
|
||||||
|
|
||||||
|
(childFragmentManager.findFragmentById(R.id.post_list_fragment_container) as NavHostFragment).navController.setGraph(R.navigation.post_listing_navigation)
|
||||||
|
|
||||||
|
viewModel.userProfile.observe(viewLifecycleOwner) {
|
||||||
|
val header = viewBinding.homeDrawer.getHeaderView(0) as? ConstraintLayout
|
||||||
|
|
||||||
|
val image: ImageView? = header?.findViewById(R.id.profileImageView)
|
||||||
|
val textViewName: TextView? = header?.findViewById(R.id.textViewName)
|
||||||
|
val textViewEmail: TextView? = header?.findViewById(R.id.textViewEmail)
|
||||||
|
val textViewUsername: TextView? = header?.findViewById(R.id.textViewUsername)
|
||||||
|
|
||||||
|
image?.load(UrlGen.userProfileImage(it.userId), imageLoader)
|
||||||
|
|
||||||
|
val card: MaterialCardView? = header?.findViewById(R.id.drawer_header_card)
|
||||||
|
card?.setOnClickListener {
|
||||||
|
ProfileActivity.startActivity(requireContext(), currentUserId)
|
||||||
|
}
|
||||||
|
|
||||||
|
textViewName?.text = it.name
|
||||||
|
textViewEmail?.text = it.email
|
||||||
|
textViewUsername?.text = "@${it.uniqueUsername}"
|
||||||
|
currentUserId = it.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
viewModel.getProfile()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
viewLifecycleOwner.lifecycleScope.launch {
|
||||||
|
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||||
|
errorViewModel.retry.collect {
|
||||||
|
viewModel.retry()
|
||||||
|
errorViewModel.handleRetry()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,4 @@
|
|||||||
package com.isolaatti.home
|
package com.isolaatti.home.ui
|
||||||
|
|
||||||
import android.Manifest
|
import android.Manifest
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
@ -45,10 +45,6 @@ class HomeActivity : IsolaattiBaseActivity() {
|
|||||||
|
|
||||||
askNotificationPermission()
|
askNotificationPermission()
|
||||||
|
|
||||||
if(savedInstanceState == null) {
|
|
||||||
feedViewModel.getFeed(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.posting.posts.data.remote
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
import com.isolaatti.common.ResultDto
|
||||||
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
@ -22,4 +23,7 @@ interface FeedsApi {
|
|||||||
|
|
||||||
@GET("Feed")
|
@GET("Feed")
|
||||||
fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto>
|
fun getChronology(@Query("lastId") lastId: Long, @Query("length") length: Int): Call<FeedDto>
|
||||||
|
|
||||||
|
@GET("hashtags/hashtag/{hashtag}")
|
||||||
|
fun getHashtagPosts(@Path("hashtag") hashtag: String, @Query("after") afterPost: Long? = null): Call<FeedDto>
|
||||||
}
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.posting.posts.data.repository
|
package com.isolaatti.posting.posts.data.repository
|
||||||
|
|
||||||
|
import android.util.Log
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||||
import com.isolaatti.posting.posts.data.remote.DeletePostDto
|
import com.isolaatti.posting.posts.data.remote.DeletePostDto
|
||||||
@ -18,10 +19,18 @@ import retrofit2.awaitResponse
|
|||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
|
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
|
||||||
override fun getFeed(lastId: Long): Flow<Resource<MutableList<Post>>> = flow {
|
companion object {
|
||||||
|
const val LOG_TAG = "PostsRepositoryImpl"
|
||||||
|
}
|
||||||
|
override fun getFeed(lastId: Long, hashtag: String?): Flow<Resource<MutableList<Post>>> = flow {
|
||||||
emit(Resource.Loading())
|
emit(Resource.Loading())
|
||||||
try {
|
try {
|
||||||
val result = feedsApi.getChronology(lastId, 20).execute()
|
|
||||||
|
val result = if(hashtag == null) {
|
||||||
|
feedsApi.getChronology(lastId, 20).awaitResponse()
|
||||||
|
} else {
|
||||||
|
feedsApi.getHashtagPosts(hashtag, lastId).awaitResponse()
|
||||||
|
}
|
||||||
if(result.isSuccessful) {
|
if(result.isSuccessful) {
|
||||||
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
|
emit(Resource.Success(result.body()?.let { Post.fromFeedDto(it) }))
|
||||||
return@flow
|
return@flow
|
||||||
@ -32,7 +41,8 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, pr
|
|||||||
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
}
|
}
|
||||||
} catch(_: Exception) {
|
} catch(e: Exception) {
|
||||||
|
Log.e(LOG_TAG, "Error $e")
|
||||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,7 @@ import kotlinx.coroutines.flow.Flow
|
|||||||
|
|
||||||
interface PostsRepository {
|
interface PostsRepository {
|
||||||
|
|
||||||
fun getFeed(lastId: Long): Flow<Resource<MutableList<Post>>>
|
fun getFeed(lastId: Long, hashtag: String?): Flow<Resource<MutableList<Post>>>
|
||||||
|
|
||||||
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>>
|
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto?): Flow<Resource<MutableList<Post>>>
|
||||||
|
|
||||||
|
|||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.isolaatti.posting.posts.presentation
|
||||||
|
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class PostListingViewModel @Inject constructor(private val postsRepository: PostsRepository) : PostListingViewModelBase() {
|
||||||
|
override fun getFeed(refresh: Boolean, hashtag: String?) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
if (refresh) {
|
||||||
|
posts.value = null
|
||||||
|
}
|
||||||
|
postsRepository.getFeed(getLastId(), hashtag).onEach { listResource ->
|
||||||
|
when (listResource) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
val eventType = if((postsList?.size ?: 0) > 0) UpdateEvent.UpdateType.PAGE_ADDED else UpdateEvent.UpdateType.REFRESH
|
||||||
|
loadingPosts.postValue(false)
|
||||||
|
posts.postValue(Pair(postsList?.apply {
|
||||||
|
addAll(listResource.data ?: listOf())
|
||||||
|
} ?: listResource.data,
|
||||||
|
UpdateEvent(eventType, null)))
|
||||||
|
|
||||||
|
noMoreContent.postValue(listResource.data?.size == 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Loading -> {
|
||||||
|
if(!refresh)
|
||||||
|
loadingPosts.postValue(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
is Resource.Error -> {
|
||||||
|
errorLoading.postValue(listResource.errorType)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
isLoadingFromScrolling = false
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -44,7 +44,7 @@ abstract class PostListingViewModelBase : ViewModel() {
|
|||||||
fun getLastId(): Long = try { posts.value?.first?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
|
fun getLastId(): Long = try { posts.value?.first?.last()?.id ?: 0 } catch (e: NoSuchElementException) { 0 }
|
||||||
|
|
||||||
|
|
||||||
abstract fun getFeed(refresh: Boolean)
|
abstract fun getFeed(refresh: Boolean, hashtag: String?)
|
||||||
|
|
||||||
fun likePost(postId: Long) {
|
fun likePost(postId: Long) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
|
|||||||
@ -361,7 +361,9 @@ class PostsRecyclerViewAdapter (
|
|||||||
if(totalItems != null && totalItems > 0 && !requestedNewContent) {
|
if(totalItems != null && totalItems > 0 && !requestedNewContent) {
|
||||||
if(position == totalItems - 1) {
|
if(position == totalItems - 1) {
|
||||||
requestedNewContent = true
|
requestedNewContent = true
|
||||||
callback.onLoadMore()
|
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 android.os.Bundle
|
||||||
import androidx.fragment.app.Fragment
|
import android.util.Log
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ImageView
|
import androidx.fragment.app.Fragment
|
||||||
import android.widget.TextView
|
|
||||||
import android.widget.Toast
|
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout
|
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import coil.load
|
|
||||||
import com.google.android.material.button.MaterialButton
|
|
||||||
import com.google.android.material.card.MaterialCardView
|
|
||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
import com.isolaatti.R
|
|
||||||
import com.isolaatti.about.AboutActivity
|
|
||||||
import com.isolaatti.audio.common.domain.Audio
|
import com.isolaatti.audio.common.domain.Audio
|
||||||
import com.isolaatti.audio.common.domain.Playable
|
import com.isolaatti.audio.common.domain.Playable
|
||||||
import com.isolaatti.audio.player.AudioPlayerConnector
|
import com.isolaatti.audio.player.AudioPlayerConnector
|
||||||
import com.isolaatti.common.CoilImageLoader.imageLoader
|
import com.isolaatti.common.CoilImageLoader
|
||||||
import com.isolaatti.common.Dialogs
|
import com.isolaatti.common.Dialogs
|
||||||
import com.isolaatti.common.ErrorMessageViewModel
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
import com.isolaatti.databinding.FragmentFeedBinding
|
|
||||||
import com.isolaatti.drafts.ui.DraftsActivity
|
|
||||||
import com.isolaatti.home.presentation.FeedViewModel
|
|
||||||
import com.isolaatti.images.picture_viewer.ui.PictureViewerActivity
|
|
||||||
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
|
|
||||||
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
|
||||||
import com.isolaatti.common.OnUserInteractedWithPostCallback
|
import com.isolaatti.common.OnUserInteractedWithPostCallback
|
||||||
import com.isolaatti.common.Ownable
|
import com.isolaatti.common.Ownable
|
||||||
import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
|
import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
|
||||||
import com.isolaatti.common.options_bottom_sheet.domain.Options
|
import com.isolaatti.common.options_bottom_sheet.domain.Options
|
||||||
import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
|
import com.isolaatti.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
|
||||||
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
import com.isolaatti.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
||||||
|
import com.isolaatti.databinding.FragmentPostListingBinding
|
||||||
|
import com.isolaatti.home.presentation.FeedViewModel
|
||||||
|
import com.isolaatti.home.ui.FeedFragment
|
||||||
|
import com.isolaatti.posting.comments.ui.BottomSheetPostComments
|
||||||
import com.isolaatti.posting.posts.domain.entity.Post
|
import com.isolaatti.posting.posts.domain.entity.Post
|
||||||
import com.isolaatti.posting.posts.presentation.CreatePostContract
|
|
||||||
import com.isolaatti.posting.posts.presentation.EditPostContract
|
import com.isolaatti.posting.posts.presentation.EditPostContract
|
||||||
|
import com.isolaatti.posting.posts.presentation.PostListingViewModel
|
||||||
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
||||||
|
import com.isolaatti.posting.posts.viewer.ui.PostViewerActivity
|
||||||
import com.isolaatti.profile.ui.ProfileActivity
|
import com.isolaatti.profile.ui.ProfileActivity
|
||||||
import com.isolaatti.reports.ui.NewReportBottomSheetDialogFragment
|
|
||||||
import com.isolaatti.settings.ui.SettingsActivity
|
|
||||||
import com.isolaatti.utils.UrlGen
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import io.noties.markwon.AbstractMarkwonPlugin
|
import io.noties.markwon.AbstractMarkwonPlugin
|
||||||
import io.noties.markwon.Markwon
|
import io.noties.markwon.Markwon
|
||||||
@ -55,85 +40,23 @@ import io.noties.markwon.MarkwonConfiguration
|
|||||||
import io.noties.markwon.image.coil.CoilImagesPlugin
|
import io.noties.markwon.image.coil.CoilImagesPlugin
|
||||||
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
||||||
import io.noties.markwon.linkify.LinkifyPlugin
|
import io.noties.markwon.linkify.LinkifyPlugin
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import org.w3c.dom.Text
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
@AndroidEntryPoint
|
||||||
class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
class PostListingFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance() = FeedFragment()
|
const val ARG_HASHTAG = "hashtag"
|
||||||
const val CALLER_ID = 20
|
const val LOG_TAG = "PostListingFragment"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hashtag: String? = null
|
||||||
|
|
||||||
|
private lateinit var viewBinding: FragmentPostListingBinding
|
||||||
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||||
private val viewModel: FeedViewModel by activityViewModels()
|
private val viewModel: PostListingViewModel by viewModels()
|
||||||
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||||
private var currentUserId = 0
|
|
||||||
|
|
||||||
private lateinit var viewBinding: FragmentFeedBinding
|
|
||||||
private lateinit var adapter: PostsRecyclerViewAdapter
|
private lateinit var adapter: PostsRecyclerViewAdapter
|
||||||
|
|
||||||
private lateinit var audioPlayerConnector: AudioPlayerConnector
|
private lateinit var audioPlayerConnector: AudioPlayerConnector
|
||||||
|
|
||||||
// region launchers
|
|
||||||
|
|
||||||
private val createDiscussion = registerForActivityResult(CreatePostContract()) {
|
|
||||||
if(it != null) {
|
|
||||||
Toast.makeText(requireContext(), R.string.posted_successfully, Toast.LENGTH_SHORT).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val editDiscussion = registerForActivityResult(EditPostContract()) {
|
|
||||||
if(it != null) {
|
|
||||||
viewModel.onPostUpdate(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// endregion
|
|
||||||
|
|
||||||
// region observers
|
|
||||||
|
|
||||||
private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
|
|
||||||
if(optionClicked?.callerId == CALLER_ID) {
|
|
||||||
// post id should come as payload
|
|
||||||
val post = optionClicked.payload as? Post ?: return@Observer
|
|
||||||
when(optionClicked.optionId) {
|
|
||||||
Options.Option.OPTION_DELETE -> {
|
|
||||||
Dialogs.buildDeletePostDialog(requireContext()) { delete ->
|
|
||||||
optionsViewModel.handle()
|
|
||||||
if(delete) {
|
|
||||||
viewModel.deletePost(post.id)
|
|
||||||
}
|
|
||||||
}.show()
|
|
||||||
|
|
||||||
}
|
|
||||||
Options.Option.OPTION_EDIT -> {
|
|
||||||
optionsViewModel.handle()
|
|
||||||
editDiscussion.launch(post.id)
|
|
||||||
}
|
|
||||||
Options.Option.OPTION_REPORT -> {
|
|
||||||
optionsViewModel.handle()
|
|
||||||
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 {
|
private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
|
||||||
override fun onPlaying(isPlaying: Boolean, audio: Playable) {
|
override fun onPlaying(isPlaying: Boolean, audio: Playable) {
|
||||||
if(audio is Audio)
|
if(audio is Audio)
|
||||||
@ -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?) {
|
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||||
super.onViewCreated(view, savedInstanceState)
|
super.onViewCreated(view, savedInstanceState)
|
||||||
viewBinding.topAppBar.setNavigationOnClickListener {
|
|
||||||
viewBinding.drawerLayout?.openDrawer(viewBinding.homeDrawer)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
viewBinding.homeDrawer.setNavigationItemSelectedListener {
|
|
||||||
when(it.itemId) {
|
|
||||||
R.id.settings_menu_item -> {
|
|
||||||
startActivity(Intent(requireActivity(), SettingsActivity::class.java))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.about_menu_item -> {
|
|
||||||
startActivity(Intent(requireActivity(), AboutActivity::class.java))
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> {true}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
audioPlayerConnector = AudioPlayerConnector(requireContext())
|
audioPlayerConnector = AudioPlayerConnector(requireContext())
|
||||||
audioPlayerConnector.addListener(audioPlayerConnectorListener)
|
audioPlayerConnector.addListener(audioPlayerConnectorListener)
|
||||||
@ -192,11 +112,12 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
.usePlugin(object: AbstractMarkwonPlugin() {
|
.usePlugin(object: AbstractMarkwonPlugin() {
|
||||||
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||||
builder
|
builder
|
||||||
.imageDestinationProcessor(ImageDestinationProcessorRelativeToAbsolute
|
.imageDestinationProcessor(
|
||||||
|
ImageDestinationProcessorRelativeToAbsolute
|
||||||
.create(BuildConfig.backend))
|
.create(BuildConfig.backend))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.usePlugin(CoilImagesPlugin.create(requireContext(), imageLoader))
|
.usePlugin(CoilImagesPlugin.create(requireContext(), CoilImageLoader.imageLoader))
|
||||||
.usePlugin(LinkifyPlugin.create())
|
.usePlugin(LinkifyPlugin.create())
|
||||||
.build()
|
.build()
|
||||||
adapter = PostsRecyclerViewAdapter(markwon, this)
|
adapter = PostsRecyclerViewAdapter(markwon, this)
|
||||||
@ -206,44 +127,9 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
|
|
||||||
|
|
||||||
viewBinding.swipeToRefresh.setOnRefreshListener {
|
viewBinding.swipeToRefresh.setOnRefreshListener {
|
||||||
viewModel.getFeed(refresh = true)
|
viewModel.getFeed(refresh = true, hashtag)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
viewBinding.topAppBar.setOnMenuItemClickListener {
|
|
||||||
when(it.itemId) {
|
|
||||||
R.id.menu_item_new_discussion -> {
|
|
||||||
createDiscussion.launch(Unit)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
else -> {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
viewModel.userProfile.observe(viewLifecycleOwner) {
|
|
||||||
val header = viewBinding.homeDrawer.getHeaderView(0) as? ConstraintLayout
|
|
||||||
|
|
||||||
val image: ImageView? = header?.findViewById(R.id.profileImageView)
|
|
||||||
val textViewName: TextView? = header?.findViewById(R.id.textViewName)
|
|
||||||
val textViewEmail: TextView? = header?.findViewById(R.id.textViewEmail)
|
|
||||||
val textViewUsername: TextView? = header?.findViewById(R.id.textViewUsername)
|
|
||||||
|
|
||||||
image?.load(UrlGen.userProfileImage(it.userId), imageLoader)
|
|
||||||
|
|
||||||
val card: MaterialCardView? = header?.findViewById(R.id.drawer_header_card)
|
|
||||||
card?.setOnClickListener {
|
|
||||||
ProfileActivity.startActivity(requireContext(), currentUserId)
|
|
||||||
}
|
|
||||||
|
|
||||||
textViewName?.text = it.name
|
|
||||||
textViewEmail?.text = it.email
|
|
||||||
textViewUsername?.text = "@${it.uniqueUsername}"
|
|
||||||
currentUserId = it.userId
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
viewModel.posts.observe(viewLifecycleOwner){
|
viewModel.posts.observe(viewLifecycleOwner){
|
||||||
if (it?.first != null) {
|
if (it?.first != null) {
|
||||||
adapter.updateList(it.first!!, it.second)
|
adapter.updateList(it.first!!, it.second)
|
||||||
@ -263,18 +149,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
errorViewModel.error.postValue(it)
|
errorViewModel.error.postValue(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.getProfile()
|
|
||||||
|
|
||||||
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver)
|
||||||
|
|
||||||
viewLifecycleOwner.lifecycleScope.launch {
|
|
||||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
errorViewModel.retry.collect {
|
|
||||||
viewModel.retry()
|
|
||||||
errorViewModel.handleRetry()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
||||||
@ -282,7 +157,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
|
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
|
||||||
|
|
||||||
override fun onOptions(post: Ownable) {
|
override fun onOptions(post: Ownable) {
|
||||||
optionsViewModel.setOptions(Options.POST_OPTIONS, CALLER_ID, post)
|
optionsViewModel.setOptions(Options.POST_OPTIONS, FeedFragment.CALLER_ID, post)
|
||||||
val modalBottomSheet = BottomSheetPostOptionsFragment()
|
val modalBottomSheet = BottomSheetPostOptionsFragment()
|
||||||
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
|
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
|
||||||
}
|
}
|
||||||
@ -305,8 +180,39 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadMore() {
|
override fun onLoadMore() {
|
||||||
viewModel.getFeed(false)
|
viewModel.getFeed(false, hashtag)
|
||||||
}
|
}
|
||||||
|
|
||||||
// endregion
|
private val editDiscussion = registerForActivityResult(EditPostContract()) {
|
||||||
|
if(it != null) {
|
||||||
|
viewModel.onPostUpdate(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val optionsObserver: Observer<OptionClicked?> = Observer { optionClicked ->
|
||||||
|
if(optionClicked?.callerId == FeedFragment.CALLER_ID) {
|
||||||
|
// post id should come as payload
|
||||||
|
val post = optionClicked.payload as? Post ?: return@Observer
|
||||||
|
when(optionClicked.optionId) {
|
||||||
|
Options.Option.OPTION_DELETE -> {
|
||||||
|
Dialogs.buildDeletePostDialog(requireContext()) { delete ->
|
||||||
|
optionsViewModel.handle()
|
||||||
|
if(delete) {
|
||||||
|
viewModel.deletePost(post.id)
|
||||||
|
}
|
||||||
|
}.show()
|
||||||
|
|
||||||
|
}
|
||||||
|
Options.Option.OPTION_EDIT -> {
|
||||||
|
optionsViewModel.handle()
|
||||||
|
editDiscussion.launch(post.id)
|
||||||
|
}
|
||||||
|
Options.Option.OPTION_REPORT -> {
|
||||||
|
optionsViewModel.handle()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -111,7 +111,7 @@ class ProfileViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getFeed(refresh: Boolean) {
|
override fun getFeed(refresh: Boolean, hashtag: String?) {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
if(refresh) {
|
if(refresh) {
|
||||||
posts.value = Pair(null, UpdateEvent(UpdateEvent.UpdateType.REFRESH, null))
|
posts.value = Pair(null, UpdateEvent(UpdateEvent.UpdateType.REFRESH, null))
|
||||||
@ -132,7 +132,7 @@ class ProfileViewModel @Inject constructor(
|
|||||||
is Resource.Error -> {
|
is Resource.Error -> {
|
||||||
errorLoading.postValue(feedDtoResource.errorType)
|
errorLoading.postValue(feedDtoResource.errorType)
|
||||||
toRetry.add {
|
toRetry.add {
|
||||||
getFeed(refresh)
|
getFeed(refresh, hashtag)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||||
|
|
||||||
viewBinding.swipeToRefresh.setOnRefreshListener {
|
viewBinding.swipeToRefresh.setOnRefreshListener {
|
||||||
viewModel.getFeed(true)
|
viewModel.getFeed(true, null)
|
||||||
}
|
}
|
||||||
viewBinding.playButton.setOnClickListener {
|
viewBinding.playButton.setOnClickListener {
|
||||||
audioDescriptionAudio?.let { audio ->
|
audioDescriptionAudio?.let { audio ->
|
||||||
@ -398,7 +398,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
userId?.let { profileId ->
|
userId?.let { profileId ->
|
||||||
viewModel.profileId = profileId
|
viewModel.profileId = profileId
|
||||||
viewModel.getProfile()
|
viewModel.getProfile()
|
||||||
viewModel.getFeed(true)
|
viewModel.getFeed(true, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -468,7 +468,7 @@ class ProfileMainFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onLoadMore() {
|
override fun onLoadMore() {
|
||||||
viewModel.getFeed(false)
|
viewModel.getFeed(false, null)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,8 +5,8 @@ import retrofit2.http.GET
|
|||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
|
||||||
interface SearchApi {
|
interface SearchApi {
|
||||||
@GET("Search/Quick")
|
@GET("Search/v2")
|
||||||
fun quickSearch(@Query("q") query: String): Call<SearchDto>
|
fun quickSearch(@Query("query") query: String): Call<SearchDto>
|
||||||
|
|
||||||
@GET("hashtags/trending")
|
@GET("hashtags/trending")
|
||||||
fun getTrendingHashtags(): Call<HashtagsDto>
|
fun getTrendingHashtags(): Call<HashtagsDto>
|
||||||
|
|||||||
@ -1,14 +1,33 @@
|
|||||||
package com.isolaatti.search.data
|
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 ProfileSearchDto(val id: Int, val name: String, val imageId: String?, val following: Boolean)
|
||||||
data class SearchDto(
|
data class SearchDto(val result: List<SearchResultDto>)
|
||||||
val profiles: List<ProfileSearchDto>,
|
|
||||||
val posts: List<FeedDto.PostDto>
|
data class SearchResultDto(
|
||||||
// TODO add the other types
|
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>)
|
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.Fragment
|
||||||
import androidx.fragment.app.viewModels
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.lifecycle.Observer
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.navigation.findNavController
|
||||||
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.google.android.material.carousel.CarouselLayoutManager
|
import com.google.android.material.carousel.CarouselLayoutManager
|
||||||
import com.google.android.material.carousel.UncontainedCarouselStrategy
|
import com.google.android.material.carousel.UncontainedCarouselStrategy
|
||||||
import com.google.android.material.chip.Chip
|
import com.google.android.material.chip.Chip
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.databinding.FragmentSearchBinding
|
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.profile.ui.ProfileActivity
|
||||||
import com.isolaatti.search.data.HashtagsDto
|
import com.isolaatti.search.data.HashtagsDto
|
||||||
import com.isolaatti.search.data.NewestUsersDto
|
import com.isolaatti.search.data.NewestUsersDto
|
||||||
import com.isolaatti.search.data.SearchDto
|
import com.isolaatti.search.data.SearchDto
|
||||||
import com.isolaatti.search.data.SearchHistoryEntity
|
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.SearchSuggestionsAdapter
|
||||||
import com.isolaatti.search.presentation.SearchViewModel
|
import com.isolaatti.search.presentation.SearchViewModel
|
||||||
import com.isolaatti.search.presentation.UserCarouselAdapter
|
import com.isolaatti.search.presentation.UserCarouselAdapter
|
||||||
@ -38,6 +42,7 @@ class SearchFragment : Fragment() {
|
|||||||
private val viewModel: SearchViewModel by viewModels()
|
private val viewModel: SearchViewModel by viewModels()
|
||||||
private var searchSuggestionsAdapter: SearchSuggestionsAdapter? = null
|
private var searchSuggestionsAdapter: SearchSuggestionsAdapter? = null
|
||||||
private var newestUsersAdapter: UserCarouselAdapter? = null
|
private var newestUsersAdapter: UserCarouselAdapter? = null
|
||||||
|
private var searchResultsAdapter: SearchResultsAdapter? = null
|
||||||
|
|
||||||
private val searchSuggestionsObserver: Observer<List<SearchHistoryEntity>> = Observer {
|
private val searchSuggestionsObserver: Observer<List<SearchHistoryEntity>> = Observer {
|
||||||
searchSuggestionsAdapter?.submitList(it)
|
searchSuggestionsAdapter?.submitList(it)
|
||||||
@ -45,6 +50,7 @@ class SearchFragment : Fragment() {
|
|||||||
|
|
||||||
private val searchResultsObserver: Observer<SearchDto> = Observer {
|
private val searchResultsObserver: Observer<SearchDto> = Observer {
|
||||||
Log.d(LOG_TAG, it.toString())
|
Log.d(LOG_TAG, it.toString())
|
||||||
|
searchResultsAdapter?.submitList(it.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val trendingHashtagsObserver: Observer<HashtagsDto> = Observer {
|
private val trendingHashtagsObserver: Observer<HashtagsDto> = Observer {
|
||||||
@ -55,7 +61,7 @@ class SearchFragment : Fragment() {
|
|||||||
viewBinding.chipGroup.addView(Chip(requireContext()).apply {
|
viewBinding.chipGroup.addView(Chip(requireContext()).apply {
|
||||||
text = "#$hashtag"
|
text = "#$hashtag"
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
requireContext().startActivity(Intent(requireContext(), HashtagsActivity::class.java))
|
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagPostsFragment(hashtag))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -123,7 +129,24 @@ class SearchFragment : Fragment() {
|
|||||||
layoutManager = CarouselLayoutManager(UncontainedCarouselStrategy())
|
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() {
|
private fun setupListeners() {
|
||||||
@ -147,11 +170,19 @@ class SearchFragment : Fragment() {
|
|||||||
if(it.itemId == R.id.close_button) {
|
if(it.itemId == R.id.close_button) {
|
||||||
showResults(false)
|
showResults(false)
|
||||||
true
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
}
|
}
|
||||||
false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
viewBinding.searchView.addTransitionListener { searchView, transitionState, transitionState2 -> }
|
viewBinding.openHashtagsButton.setOnClickListener {
|
||||||
|
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToHashtagsFragment())
|
||||||
|
}
|
||||||
|
|
||||||
|
viewBinding.browseProfilesButton.setOnClickListener {
|
||||||
|
findNavController().navigate(SearchFragmentDirections.actionSearchFragmentToBrowseProfilesFragment())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupObservers() {
|
private fun setupObservers() {
|
||||||
|
|||||||
@ -17,7 +17,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.databinding.FragmentMakeAccountBinding
|
import com.isolaatti.databinding.FragmentMakeAccountBinding
|
||||||
import com.isolaatti.home.HomeActivity
|
import com.isolaatti.home.ui.HomeActivity
|
||||||
import com.isolaatti.sign_up.domain.entity.SignUpResultCode
|
import com.isolaatti.sign_up.domain.entity.SignUpResultCode
|
||||||
import com.isolaatti.sign_up.presentation.MakeAccountViewModel
|
import com.isolaatti.sign_up.presentation.MakeAccountViewModel
|
||||||
import com.isolaatti.sign_up.presentation.SignUpViewModel
|
import com.isolaatti.sign_up.presentation.SignUpViewModel
|
||||||
|
|||||||
5
app/src/main/res/drawable/baseline_article_24.xml
Normal file
5
app/src/main/res/drawable/baseline_article_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:autoMirrored="true" android:height="24dp" android:tint="#000000" android:viewportHeight="24" android:viewportWidth="24" android:width="24dp">
|
||||||
|
|
||||||
|
<path android:fillColor="@android:color/white" android:pathData="M19,3L5,3c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2L21,5c0,-1.1 -0.9,-2 -2,-2zM14,17L7,17v-2h7v2zM17,13L7,13v-2h10v2zM17,9L7,9L7,7h10v2z"/>
|
||||||
|
|
||||||
|
</vector>
|
||||||
9
app/src/main/res/drawable/hashtag_solid.xml
Normal file
9
app/src/main/res/drawable/hashtag_solid.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="448dp"
|
||||||
|
android:height="512dp"
|
||||||
|
android:viewportWidth="448"
|
||||||
|
android:viewportHeight="512">
|
||||||
|
<path
|
||||||
|
android:fillColor="#FF000000"
|
||||||
|
android:pathData="M181.3,32.4c17.4,2.9 29.2,19.4 26.3,36.8L197.8,128h95.1l11.5,-69.3c2.9,-17.4 19.4,-29.2 36.8,-26.3s29.2,19.4 26.3,36.8L357.8,128H416c17.7,0 32,14.3 32,32s-14.3,32 -32,32H347.1L325.8,320H384c17.7,0 32,14.3 32,32s-14.3,32 -32,32H315.1l-11.5,69.3c-2.9,17.4 -19.4,29.2 -36.8,26.3s-29.2,-19.4 -26.3,-36.8l9.8,-58.7H155.1l-11.5,69.3c-2.9,17.4 -19.4,29.2 -36.8,26.3s-29.2,-19.4 -26.3,-36.8L90.2,384H32c-17.7,0 -32,-14.3 -32,-32s14.3,-32 32,-32h68.9l21.3,-128H64c-17.7,0 -32,-14.3 -32,-32s14.3,-32 32,-32h68.9l11.5,-69.3c2.9,-17.4 19.4,-29.2 36.8,-26.3zM187.1,192L165.8,320h95.1l21.3,-128H187.1z"/>
|
||||||
|
</vector>
|
||||||
@ -17,11 +17,11 @@
|
|||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toEndOf="@+id/home_drawer"
|
app:layout_constraintStart_toEndOf="@+id/home_drawer"
|
||||||
app:layout_constraintTop_toTopOf="parent"
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
tools:context=".home.FeedFragment">
|
tools:context=".home.ui.FeedFragment">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/topAppBar_layout"
|
android:id="@+id/topAppBar_layout"
|
||||||
android:layout_width="0dp"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
@ -46,26 +46,11 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
<androidx.fragment.app.FragmentContainerView
|
||||||
android:id="@+id/swipe_to_refresh"
|
android:id="@+id/post_list_fragment_container"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
|
||||||
app:layout_constraintTop_toBottomOf="@+id/topAppBar_layout">
|
|
||||||
|
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
|
||||||
android:id="@+id/feed_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:layout_gravity="bottom"/>
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
<com.google.android.material.navigation.NavigationView
|
<com.google.android.material.navigation.NavigationView
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/appBarLayout"
|
android:id="@+id/appBarLayout"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -48,6 +49,13 @@
|
|||||||
android:text="@string/app_description"
|
android:text="@string/app_description"
|
||||||
android:layout_marginHorizontal="24dp"
|
android:layout_marginHorizontal="24dp"
|
||||||
android:textAlignment="center"/>
|
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
|
<com.google.android.material.card.MaterialCardView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
|||||||
@ -13,7 +13,7 @@
|
|||||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context=".home.FeedFragment">
|
tools:context=".home.ui.FeedFragment">
|
||||||
|
|
||||||
<com.google.android.material.appbar.AppBarLayout
|
<com.google.android.material.appbar.AppBarLayout
|
||||||
android:id="@+id/topAppBar_layout"
|
android:id="@+id/topAppBar_layout"
|
||||||
@ -36,23 +36,14 @@
|
|||||||
|
|
||||||
</com.google.android.material.appbar.AppBarLayout>
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
|
||||||
android:id="@+id/swipe_to_refresh"
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
android:id="@+id/post_list_fragment_container"
|
||||||
|
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
|
app:defaultNavHost="true"
|
||||||
<androidx.recyclerview.widget.RecyclerView
|
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior" />
|
||||||
android:id="@+id/feed_recycler_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent" />
|
|
||||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
|
||||||
|
|
||||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
|
||||||
android:id="@+id/loading_indicator"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:indeterminate="true"
|
|
||||||
android:layout_gravity="bottom"/>
|
|
||||||
|
|
||||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
|
|
||||||
|
|||||||
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.chip.ChipGroup>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/open_hashtags_button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/chip_group"
|
app:layout_constraintTop_toBottomOf="@id/chip_group"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="@string/see_all"/>
|
android:text="@string/go_to_hashtags"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
<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"/>
|
app:layout_constraintTop_toBottomOf="@id/new_users_card_title"/>
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/browse_profiles_button"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_constraintTop_toBottomOf="@id/new_users_recycler_view"
|
app:layout_constraintTop_toBottomOf="@id/new_users_recycler_view"
|
||||||
android:layout_marginTop="8dp"
|
android:layout_marginTop="8dp"
|
||||||
android:text="@string/see_all"/>
|
android:text="@string/browse_profiles"/>
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
</com.google.android.material.card.MaterialCardView>
|
</com.google.android.material.card.MaterialCardView>
|
||||||
</LinearLayout>
|
</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:id="@+id/feedFragment"
|
||||||
android:icon="@drawable/baseline_home_24"
|
android:icon="@drawable/baseline_home_24"
|
||||||
android:title="@string/home" />
|
android:title="@string/home" />
|
||||||
<item
|
|
||||||
android:id="@+id/notificationsFragment"
|
|
||||||
android:icon="@drawable/baseline_notifications_24"
|
|
||||||
android:title="@string/notifications" />
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/searchFragment"
|
android:id="@+id/searchFragment"
|
||||||
android:icon="@drawable/baseline_search_24"
|
android:icon="@drawable/baseline_search_24"
|
||||||
android:title="@string/search" />
|
android:title="@string/search" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/notificationsFragment"
|
||||||
|
android:icon="@drawable/baseline_notifications_24"
|
||||||
|
android:title="@string/notifications" />
|
||||||
</menu>
|
</menu>
|
||||||
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/feedFragment"
|
android:id="@+id/feedFragment"
|
||||||
android:name="com.isolaatti.home.FeedFragment"
|
android:name="com.isolaatti.home.ui.FeedFragment"
|
||||||
android:label="fragment_feed"
|
android:label="fragment_feed"
|
||||||
tools:layout="@layout/fragment_feed" >
|
tools:layout="@layout/fragment_feed" >
|
||||||
<action
|
<action
|
||||||
@ -30,5 +30,35 @@
|
|||||||
<fragment
|
<fragment
|
||||||
android:id="@+id/searchFragment"
|
android:id="@+id/searchFragment"
|
||||||
android:name="com.isolaatti.search.ui.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>
|
</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="hashtags">Hashtags</string>
|
||||||
<string name="newest_profiles">Newest profiles</string>
|
<string name="newest_profiles">Newest profiles</string>
|
||||||
<string name="see_all">See all</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>
|
<string name="new_report">New report</string>
|
||||||
</resources>
|
</resources>
|
||||||
@ -13,8 +13,8 @@ buildscript {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
plugins {
|
plugins {
|
||||||
id 'com.android.application' version '8.3.0' apply false
|
id 'com.android.application' version '8.3.1' apply false
|
||||||
id 'com.android.library' version '8.3.0' apply false
|
id 'com.android.library' version '8.3.1' apply false
|
||||||
id 'org.jetbrains.kotlin.android' version '1.9.0' 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.dagger.hilt.android' version '2.47' apply false
|
||||||
id 'com.google.gms.google-services' version '4.4.0' apply false
|
id 'com.google.gms.google-services' version '4.4.0' apply false
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user