WIP
This commit is contained in:
parent
a51105c334
commit
86dc367837
46
.idea/assetWizardSettings.xml
generated
Normal file
46
.idea/assetWizardSettings.xml
generated
Normal file
@ -0,0 +1,46 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="WizardSettings">
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorWizard">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="vectorAssetStep">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="children">
|
||||
<map>
|
||||
<entry key="clipartAsset">
|
||||
<value>
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/report/baseline_report_24.xml" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="outputName" value="baseline_report_24" />
|
||||
<entry key="sourceFile" value="C:\Users\erike\Downloads\comments-solid.svg" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
</value>
|
||||
</entry>
|
||||
</map>
|
||||
</option>
|
||||
</component>
|
||||
</project>
|
||||
2
.idea/compiler.xml
generated
2
.idea/compiler.xml
generated
@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="11" />
|
||||
<bytecodeTargetLevel target="17" />
|
||||
</component>
|
||||
</project>
|
||||
3
.idea/misc.xml
generated
3
.idea/misc.xml
generated
@ -1,7 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ExternalStorageConfigurationManager" enabled="true" />
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="11" project-jdk-type="JavaSDK">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
|
||||
6
.idea/render.experimental.xml
generated
Normal file
6
.idea/render.experimental.xml
generated
Normal file
@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RenderSettings">
|
||||
<option name="showDecorations" value="true" />
|
||||
</component>
|
||||
</project>
|
||||
@ -40,8 +40,9 @@ android {
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.core:core-ktx:1.10.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.6.0'
|
||||
implementation "androidx.recyclerview:recyclerview:1.3.0"
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
||||
implementation 'androidx.core:core-ktx:1.9.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
@ -66,7 +67,7 @@ dependencies {
|
||||
implementation "com.google.android.material:material:1.8.0"
|
||||
|
||||
// Navigation
|
||||
def nav_version = "2.5.3"
|
||||
def nav_version = "2.6.0"
|
||||
implementation "androidx.navigation:navigation-fragment-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-ui-ktx:$nav_version"
|
||||
implementation "androidx.navigation:navigation-dynamic-features-fragment:$nav_version"
|
||||
@ -81,6 +82,9 @@ dependencies {
|
||||
// Markwon
|
||||
final def markwon_version = '4.6.2'
|
||||
|
||||
// Customtabs
|
||||
implementation 'androidx.browser:browser:1.5.0'
|
||||
|
||||
implementation "io.noties.markwon:core:$markwon_version"
|
||||
implementation "io.noties.markwon:editor:$markwon_version"
|
||||
implementation "io.noties.markwon:image-picasso:$markwon_version"
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
package com.isolaatti.comments.data.remote
|
||||
|
||||
import com.isolaatti.comments.domain.model.Comment
|
||||
|
||||
data class CommentDto(
|
||||
val comment: Comment,
|
||||
val username: String
|
||||
)
|
||||
@ -1,10 +0,0 @@
|
||||
package com.isolaatti.comments.data.remote
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface CommentsApi {
|
||||
@POST("Posting/Post/{postId}/Comment")
|
||||
fun postComment(@Body commentToPost: CommentToPostDto): Call<Nothing>
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
package com.isolaatti.comments.data.repository
|
||||
|
||||
import com.isolaatti.comments.data.remote.CommentsApi
|
||||
import com.isolaatti.comments.domain.CommentsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class CommentsRepositoryImpl @Inject constructor(private val commentsApi: CommentsApi) : CommentsRepository {
|
||||
}
|
||||
@ -1,5 +0,0 @@
|
||||
package com.isolaatti.comments.domain
|
||||
|
||||
interface CommentsRepository {
|
||||
|
||||
}
|
||||
@ -3,7 +3,6 @@ package com.isolaatti.connectivity
|
||||
import com.isolaatti.auth.domain.AuthRepository
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
|
||||
class AuthenticationInterceptor(private val authRepository: dagger.Lazy<AuthRepository>) : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
@ -16,7 +15,10 @@ class AuthenticationInterceptor(private val authRepository: dagger.Lazy<AuthRepo
|
||||
// Add auth header here
|
||||
val tokenDto = authRepository.get().getCurrentToken()
|
||||
tokenDto?.token?.let {
|
||||
val request = chain.request().newBuilder().addHeader("sessionToken", it) .build()
|
||||
val request = chain.request().newBuilder()
|
||||
.addHeader("Authorization", it)
|
||||
.addHeader("client-id", ClientId.guid.toString())
|
||||
.build()
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
|
||||
7
app/src/main/java/com/isolaatti/connectivity/ClientId.kt
Normal file
7
app/src/main/java/com/isolaatti/connectivity/ClientId.kt
Normal file
@ -0,0 +1,7 @@
|
||||
package com.isolaatti.connectivity
|
||||
|
||||
import java.util.UUID
|
||||
|
||||
object ClientId {
|
||||
val guid: UUID get() = UUID.randomUUID()
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
package com.isolaatti.connectivity
|
||||
|
||||
object NetworkStatus {
|
||||
var networkIsAvailable: Boolean = true
|
||||
}
|
||||
@ -3,6 +3,7 @@ package com.isolaatti.connectivity
|
||||
import okhttp3.OkHttpClient
|
||||
import retrofit2.Retrofit
|
||||
import retrofit2.converter.gson.GsonConverterFactory
|
||||
import java.io.IOException
|
||||
import javax.inject.Inject
|
||||
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
package com.isolaatti.home.feed.data.remote
|
||||
package com.isolaatti.feed.data.remote
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
@ -0,0 +1,6 @@
|
||||
package com.isolaatti.feed.data.remote
|
||||
|
||||
data class FeedDto(
|
||||
val data: MutableList<PostDto>,
|
||||
val moreContent: Boolean
|
||||
)
|
||||
12
app/src/main/java/com/isolaatti/feed/data/remote/PostDto.kt
Normal file
12
app/src/main/java/com/isolaatti/feed/data/remote/PostDto.kt
Normal file
@ -0,0 +1,12 @@
|
||||
package com.isolaatti.feed.data.remote
|
||||
|
||||
import com.isolaatti.feed.domain.model.Post
|
||||
|
||||
data class PostDto(
|
||||
val post: Post,
|
||||
var numberOfLikes: Int,
|
||||
var numberOfComments: Int,
|
||||
val userName: String,
|
||||
val squadName: String?,
|
||||
var liked: Boolean
|
||||
)
|
||||
@ -1,8 +1,8 @@
|
||||
package com.isolaatti.home.feed.data.repository
|
||||
package com.isolaatti.feed.data.repository
|
||||
|
||||
import com.isolaatti.home.feed.data.remote.FeedApi
|
||||
import com.isolaatti.home.feed.data.remote.FeedDto
|
||||
import com.isolaatti.home.feed.domain.repository.FeedRepository
|
||||
import com.isolaatti.feed.data.remote.FeedApi
|
||||
import com.isolaatti.feed.data.remote.FeedDto
|
||||
import com.isolaatti.feed.domain.repository.FeedRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
@ -1,12 +1,12 @@
|
||||
package com.isolaatti.home.feed.domain.model
|
||||
package com.isolaatti.feed.domain.model
|
||||
|
||||
data class Post(
|
||||
val id: Long,
|
||||
val textContent: String,
|
||||
var textContent: String,
|
||||
val userId: Int,
|
||||
val privacy: Int,
|
||||
val date: String,
|
||||
val audioId: String,
|
||||
var audioId: String,
|
||||
val squadId: String,
|
||||
val linkedDiscussionId: Long,
|
||||
val linkedCommentId: Long
|
||||
@ -1,6 +1,6 @@
|
||||
package com.isolaatti.home.feed.domain.repository
|
||||
package com.isolaatti.feed.domain.repository
|
||||
|
||||
import com.isolaatti.home.feed.data.remote.FeedDto
|
||||
import com.isolaatti.feed.data.remote.FeedDto
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FeedRepository {
|
||||
@ -1,8 +1,8 @@
|
||||
package com.isolaatti.home.feed.ui
|
||||
package com.isolaatti.feed.ui
|
||||
|
||||
import android.content.Intent
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import androidx.fragment.app.Fragment
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
@ -11,7 +11,11 @@ import androidx.fragment.app.activityViewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.databinding.FragmentFeedBinding
|
||||
import com.isolaatti.home.feed.presentation.FeedViewModel
|
||||
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
||||
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
||||
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
||||
import com.isolaatti.profile.ui.ProfileActivity
|
||||
import com.isolaatti.settings.ui.SettingsActivity
|
||||
import com.isolaatti.utils.PicassoImagesPluginDef
|
||||
@ -23,15 +27,15 @@ import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAb
|
||||
import io.noties.markwon.linkify.LinkifyPlugin
|
||||
|
||||
@AndroidEntryPoint
|
||||
class FeedFragment : Fragment() {
|
||||
class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
||||
|
||||
companion object {
|
||||
fun newInstance() = FeedFragment()
|
||||
}
|
||||
|
||||
private val viewModel: FeedViewModel by activityViewModels()
|
||||
private val viewModel: PostsViewModel by activityViewModels()
|
||||
private lateinit var viewBinding: FragmentFeedBinding
|
||||
private lateinit var adapter: FeedRecyclerViewAdapter
|
||||
private lateinit var adapter: PostsRecyclerViewAdapter
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater, container: ViewGroup?,
|
||||
@ -45,9 +49,10 @@ class FeedFragment : Fragment() {
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
viewBinding.topAppBar.setNavigationOnClickListener {
|
||||
viewBinding.drawerLayout.openDrawer(viewBinding.homeDrawer)
|
||||
viewBinding.drawerLayout?.openDrawer(viewBinding.homeDrawer)
|
||||
}
|
||||
|
||||
|
||||
viewBinding.homeDrawer.setNavigationItemSelectedListener {
|
||||
when(it.itemId) {
|
||||
R.id.my_profile_menu_item -> {
|
||||
@ -73,12 +78,37 @@ class FeedFragment : Fragment() {
|
||||
.usePlugin(PicassoImagesPluginDef.picassoImagePlugin)
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.build()
|
||||
adapter = FeedRecyclerViewAdapter(markwon)
|
||||
adapter = PostsRecyclerViewAdapter(markwon, this, listOf())
|
||||
viewBinding.feedRecyclerView.adapter = adapter
|
||||
viewBinding.feedRecyclerView.layoutManager = LinearLayoutManager(requireContext())
|
||||
viewModel.feed.observe(viewLifecycleOwner){
|
||||
adapter.submitList(it.data)
|
||||
|
||||
|
||||
viewModel.posts.observe(viewLifecycleOwner){
|
||||
Log.d("recycler", it.data.toString())
|
||||
adapter.updateList(it.data.toList(),null)
|
||||
}
|
||||
viewModel.postLiked.observe(viewLifecycleOwner) {
|
||||
adapter.updateList(viewModel.posts.value?.data, PostsRecyclerViewAdapter.UpdateEvent(
|
||||
PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLiked(postId: Long) = viewModel.likePost(postId)
|
||||
|
||||
override fun onUnLiked(postId: Long) = viewModel.unLikePost(postId)
|
||||
|
||||
override fun onOptions(postId: Long) {
|
||||
val modalBottomSheet = BottomSheetPostOptionsFragment()
|
||||
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostOptionsFragment.TAG)
|
||||
}
|
||||
|
||||
override fun onComment(postId: Long) {
|
||||
val modalBottomSheet = BottomSheetPostComments.getInstance(postId)
|
||||
modalBottomSheet.show(requireActivity().supportFragmentManager, BottomSheetPostComments.TAG)
|
||||
}
|
||||
|
||||
override fun onProfileClick(userId: Int) {
|
||||
TODO("Not yet implemented")
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
package com.isolaatti.followers.data
|
||||
|
||||
import com.isolaatti.followers.data.remote.FollowersApi
|
||||
import com.isolaatti.followers.domain.FollowersRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class FollowersRepositoryImpl @Inject constructor(followersApi: FollowersApi) : FollowersRepository {
|
||||
}
|
||||
@ -0,0 +1,33 @@
|
||||
package com.isolaatti.followers.data
|
||||
|
||||
import com.isolaatti.followers.data.remote.FollowersApi
|
||||
import com.isolaatti.followers.domain.FollowersRepository
|
||||
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
class FollowersRepositoryImpl @Inject constructor(private val followersApi: FollowersApi) : FollowersRepository {
|
||||
override fun getFollowersOfUser(userId: Int): Flow<List<ProfileListItemDto>> = flow {
|
||||
val response = followersApi.getFollowersOfUser(userId).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { emit(response.body()!!) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getFollowingsOfUser(userId: Int): Flow<List<ProfileListItemDto>> = flow {
|
||||
val response = followersApi.getFollowingsOfUser(userId).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
override fun followUser(userId: Int): Flow<Boolean> = flow {
|
||||
|
||||
}
|
||||
|
||||
override fun unfollowUser(userId: Int): Flow<Boolean> = flow {
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,4 +1,11 @@
|
||||
package com.isolaatti.followers.domain
|
||||
|
||||
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface FollowersRepository {
|
||||
fun getFollowersOfUser(userId: Int): Flow<List<ProfileListItemDto>>
|
||||
fun getFollowingsOfUser(userId: Int): Flow<List<ProfileListItemDto>>
|
||||
fun followUser(userId: Int): Flow<Boolean>
|
||||
fun unfollowUser(userId: Int): Flow<Boolean>
|
||||
}
|
||||
@ -4,22 +4,17 @@ import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LifecycleObserver
|
||||
import androidx.navigation.fragment.NavHostFragment
|
||||
import androidx.navigation.ui.setupWithNavController
|
||||
import com.google.android.material.search.SearchBar
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.databinding.ActivityHomeBinding
|
||||
import com.isolaatti.home.feed.presentation.FeedViewModel
|
||||
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
|
||||
@AndroidEntryPoint
|
||||
class HomeActivity : AppCompatActivity() {
|
||||
lateinit var viewBinding: ActivityHomeBinding
|
||||
val feedViewModel: FeedViewModel by viewModels()
|
||||
val postsViewModel: PostsViewModel by viewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -28,7 +23,10 @@ class HomeActivity : AppCompatActivity() {
|
||||
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
|
||||
viewBinding.bottomNavigation.setupWithNavController(navHostFragment.navController)
|
||||
|
||||
feedViewModel.getFeed()
|
||||
if(savedInstanceState == null) {
|
||||
postsViewModel.getFeed()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
|
||||
|
||||
@ -1,9 +1,9 @@
|
||||
package com.isolaatti.home
|
||||
|
||||
import com.isolaatti.connectivity.RetrofitClient
|
||||
import com.isolaatti.home.feed.data.remote.FeedApi
|
||||
import com.isolaatti.home.feed.data.repository.FeedRepositoryImpl
|
||||
import com.isolaatti.home.feed.domain.repository.FeedRepository
|
||||
import com.isolaatti.feed.data.remote.FeedApi
|
||||
import com.isolaatti.feed.data.repository.FeedRepositoryImpl
|
||||
import com.isolaatti.feed.domain.repository.FeedRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
||||
@ -1,6 +0,0 @@
|
||||
package com.isolaatti.home.feed.data.remote
|
||||
|
||||
data class FeedDto(
|
||||
val data: List<PostDto>,
|
||||
val moreContent: Boolean
|
||||
)
|
||||
@ -1,12 +0,0 @@
|
||||
package com.isolaatti.home.feed.data.remote
|
||||
|
||||
import com.isolaatti.home.feed.domain.model.Post
|
||||
|
||||
data class PostDto(
|
||||
val post: Post,
|
||||
val numberOfLikes: Int,
|
||||
val numberOfComments: Int,
|
||||
val userName: String,
|
||||
val squadName: String?,
|
||||
val liked: Boolean
|
||||
)
|
||||
@ -1,29 +0,0 @@
|
||||
package com.isolaatti.home.feed.presentation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.home.feed.data.remote.FeedDto
|
||||
import com.isolaatti.home.feed.domain.repository.FeedRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class FeedViewModel @Inject constructor(private val feedRepository: FeedRepository) : ViewModel() {
|
||||
|
||||
private val _feed: MutableLiveData<FeedDto> = MutableLiveData()
|
||||
val feed: LiveData<FeedDto> get() = _feed
|
||||
|
||||
private fun getLastId(): Long = try {_feed.value?.data?.last()?.post?.id ?: 0} catch (e: NoSuchElementException) { 0 }
|
||||
|
||||
fun getFeed() {
|
||||
viewModelScope.launch {
|
||||
feedRepository.getNextPage(0, 20).collect {
|
||||
_feed.postValue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,62 +0,0 @@
|
||||
package com.isolaatti.home.feed.ui
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListAdapter
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.home.feed.data.remote.PostDto
|
||||
import com.isolaatti.utils.UrlGen.userProfileImage
|
||||
import com.squareup.picasso.Picasso
|
||||
import io.noties.markwon.Markwon
|
||||
import java.text.DateFormat
|
||||
import java.util.Date
|
||||
|
||||
class FeedRecyclerViewAdapter(private val markwon: Markwon) : ListAdapter<PostDto, FeedRecyclerViewAdapter.FeedViewHolder>(DIFF_CALLBACK) {
|
||||
inner class FeedViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||
fun bindView(postDto: PostDto) {
|
||||
val username: TextView = itemView.findViewById(R.id.text_view_username)
|
||||
username.text = postDto.userName
|
||||
|
||||
val profileImageView: ImageView = itemView.findViewById(R.id.avatar_picture)
|
||||
Picasso.get().load(userProfileImage(postDto.post.userId)).into(profileImageView)
|
||||
|
||||
val dateTextView: TextView = itemView.findViewById(R.id.text_view_date)
|
||||
dateTextView.text = postDto.post.date
|
||||
|
||||
val content: TextView = itemView.findViewById(R.id.post_content)
|
||||
markwon.setMarkdown(content, postDto.post.textContent)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.post_layout, parent, false)
|
||||
|
||||
return FeedViewHolder(view)
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FeedViewHolder, position: Int) {
|
||||
holder.bindView(getItem(position))
|
||||
}
|
||||
|
||||
companion object {
|
||||
val DIFF_CALLBACK = object: DiffUtil.ItemCallback<PostDto>() {
|
||||
override fun areItemsTheSame(oldItem: PostDto, newItem: PostDto): Boolean {
|
||||
return oldItem.post.id == newItem.post.id
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItem: PostDto, newItem: PostDto): Boolean {
|
||||
return oldItem.post.id == newItem.post.id && oldItem.post.textContent == newItem.post.textContent
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,9 +1,9 @@
|
||||
package com.isolaatti.posts
|
||||
package com.isolaatti.posting
|
||||
|
||||
import com.isolaatti.connectivity.RetrofitClient
|
||||
import com.isolaatti.posts.data.remote.PostsApi
|
||||
import com.isolaatti.posts.data.repository.PostsRepositoryImpl
|
||||
import com.isolaatti.posts.domain.PostsRepository
|
||||
import com.isolaatti.posting.posts.data.remote.PostsApi
|
||||
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
|
||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
@ -1,8 +1,8 @@
|
||||
package com.isolaatti.comments
|
||||
package com.isolaatti.posting.comments
|
||||
|
||||
import com.isolaatti.comments.data.remote.CommentsApi
|
||||
import com.isolaatti.comments.data.repository.CommentsRepositoryImpl
|
||||
import com.isolaatti.comments.domain.CommentsRepository
|
||||
import com.isolaatti.posting.comments.data.remote.CommentsApi
|
||||
import com.isolaatti.posting.comments.data.repository.CommentsRepositoryImpl
|
||||
import com.isolaatti.posting.comments.domain.CommentsRepository
|
||||
import com.isolaatti.connectivity.RetrofitClient
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
@ -0,0 +1,8 @@
|
||||
package com.isolaatti.posting.comments.data.remote
|
||||
|
||||
import com.isolaatti.posting.comments.domain.model.Comment
|
||||
|
||||
data class CommentDto(
|
||||
val comment: Comment,
|
||||
val username: String
|
||||
)
|
||||
@ -1,4 +1,4 @@
|
||||
package com.isolaatti.comments.data.remote
|
||||
package com.isolaatti.posting.comments.data.remote
|
||||
|
||||
data class CommentToPostDto(
|
||||
val content: String,
|
||||
@ -0,0 +1,19 @@
|
||||
package com.isolaatti.posting.comments.data.remote
|
||||
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.POST
|
||||
import retrofit2.http.Path
|
||||
import retrofit2.http.Query
|
||||
|
||||
interface CommentsApi {
|
||||
@POST("Posting/Post/{postId}/Comment")
|
||||
fun postComment(@Body commentToPost: CommentToPostDto): Call<Nothing>
|
||||
|
||||
@GET("Fetch/Post/{postId}/Comments")
|
||||
fun getCommentsOfPosts(@Path("postId") postId: Long, @Query("lastId") lastId: Long, @Query("take") count: Int): Call<FeedCommentsDto>
|
||||
|
||||
@GET("Fetch/Comments/{commentId}")
|
||||
fun getComment(@Path("commentId") commentId: Long): Call<CommentDto>
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
package com.isolaatti.posting.comments.data.remote
|
||||
|
||||
data class FeedCommentsDto(val data: List<CommentDto>, val moreContent: Boolean)
|
||||
@ -0,0 +1,33 @@
|
||||
package com.isolaatti.posting.comments.data.repository
|
||||
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.comments.data.remote.CommentToPostDto
|
||||
import com.isolaatti.posting.comments.data.remote.CommentsApi
|
||||
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
|
||||
import com.isolaatti.posting.comments.domain.CommentsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
import javax.inject.Inject
|
||||
|
||||
class CommentsRepositoryImpl @Inject constructor(private val commentsApi: CommentsApi) :
|
||||
CommentsRepository {
|
||||
override fun getComments(postId: Long, lastId: Long): Flow<FeedCommentsDto> = flow {
|
||||
val response = commentsApi.getCommentsOfPosts(postId, lastId, 15).awaitResponse()
|
||||
if(response.isSuccessful){
|
||||
response.body()?.let { emit(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun getComment(commentId: Long): Flow<CommentDto> = flow {
|
||||
val response = commentsApi.getComment(commentId).awaitResponse()
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { emit(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun postComment(commentToPostDto: CommentToPostDto, postId: Long): Flow<Boolean> = flow {
|
||||
val response = commentsApi.postComment(commentToPostDto).awaitResponse()
|
||||
emit(response.isSuccessful)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package com.isolaatti.posting.comments.domain
|
||||
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.comments.data.remote.CommentToPostDto
|
||||
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface CommentsRepository {
|
||||
fun getComments(postId: Long, lastId: Long): Flow<FeedCommentsDto>
|
||||
fun getComment(commentId: Long): Flow<CommentDto>
|
||||
fun postComment(commentToPostDto: CommentToPostDto, postId: Long): Flow<Boolean>
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
package com.isolaatti.comments.domain.model
|
||||
package com.isolaatti.posting.comments.domain.model
|
||||
|
||||
data class Comment(
|
||||
val id: Long,
|
||||
@ -0,0 +1,12 @@
|
||||
package com.isolaatti.posting.comments.domain.use_case
|
||||
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
|
||||
import com.isolaatti.posting.comments.domain.CommentsRepository
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import javax.inject.Inject
|
||||
|
||||
class GetComments @Inject constructor(private val commentsRepository: CommentsRepository) {
|
||||
operator fun invoke(postId: Long, lastId: Long? = null): Flow<FeedCommentsDto> =
|
||||
commentsRepository.getComments(postId, lastId ?: 0)
|
||||
}
|
||||
@ -0,0 +1,100 @@
|
||||
package com.isolaatti.posting.comments.presentation
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.isolaatti.databinding.BottomSheetPostCommentsBinding
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
||||
import com.isolaatti.utils.PicassoImagesPluginDef
|
||||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import io.noties.markwon.AbstractMarkwonPlugin
|
||||
import io.noties.markwon.Markwon
|
||||
import io.noties.markwon.MarkwonConfiguration
|
||||
import io.noties.markwon.image.destination.ImageDestinationProcessorRelativeToAbsolute
|
||||
import io.noties.markwon.linkify.LinkifyPlugin
|
||||
|
||||
@AndroidEntryPoint
|
||||
class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedCallback {
|
||||
private lateinit var viewBinding: BottomSheetPostCommentsBinding
|
||||
val viewModel: CommentsViewModel by viewModels()
|
||||
|
||||
val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val postId = arguments?.getLong(ARG_POST_ID)
|
||||
if (postId != null) {
|
||||
viewModel.postId = postId
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
viewBinding = BottomSheetPostCommentsBinding.inflate(inflater)
|
||||
|
||||
return viewBinding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewBinding.recyclerComments.isNestedScrollingEnabled = true
|
||||
|
||||
val markwon = Markwon.builder(requireContext())
|
||||
.usePlugin(object: AbstractMarkwonPlugin() {
|
||||
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
|
||||
builder
|
||||
.imageDestinationProcessor(
|
||||
ImageDestinationProcessorRelativeToAbsolute
|
||||
.create("https://isolaatti.com/"))
|
||||
}
|
||||
})
|
||||
.usePlugin(PicassoImagesPluginDef.picassoImagePlugin)
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.build()
|
||||
|
||||
val adapter = CommentsRecyclerViewAdapter(listOf(), markwon, this)
|
||||
viewBinding.recyclerComments.adapter = adapter
|
||||
viewBinding.recyclerComments.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||
|
||||
viewModel.comments.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(it)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "BottomSheetPostComments"
|
||||
|
||||
const val ARG_POST_ID = "postId"
|
||||
|
||||
fun getInstance(postId: Long): BottomSheetPostComments {
|
||||
return BottomSheetPostComments().apply {
|
||||
arguments = Bundle().apply {
|
||||
putLong(ARG_POST_ID, postId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onOptions(postId: Long) {
|
||||
val fragment = BottomSheetPostOptionsFragment()
|
||||
fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG)
|
||||
optionsViewModel.setOptions(Options.commentOptions)
|
||||
}
|
||||
|
||||
override fun onProfileClick(userId: Int) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,45 @@
|
||||
package com.isolaatti.posting.comments.presentation
|
||||
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.isolaatti.databinding.CommentLayoutBinding
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedCallback
|
||||
import com.isolaatti.utils.UrlGen
|
||||
import com.squareup.picasso.Picasso
|
||||
import io.noties.markwon.Markwon
|
||||
|
||||
class CommentsRecyclerViewAdapter(private var list: List<CommentDto>, private val markwon: Markwon, private val callback: OnUserInteractedCallback) : RecyclerView.Adapter<CommentsRecyclerViewAdapter.CommentViewHolder>() {
|
||||
|
||||
inner class CommentViewHolder(val viewBinding: CommentLayoutBinding) : RecyclerView.ViewHolder(viewBinding.root)
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommentViewHolder {
|
||||
return CommentViewHolder(CommentLayoutBinding.inflate(LayoutInflater.from(parent.context)))
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = list.count()
|
||||
|
||||
override fun onBindViewHolder(holder: CommentViewHolder, position: Int) {
|
||||
val comment = list[position]
|
||||
|
||||
holder.viewBinding.textViewDate.text = comment.comment.date
|
||||
markwon.setMarkdown(holder.viewBinding.postContent, comment.comment.textContent)
|
||||
holder.viewBinding.textViewUsername.text = comment.username
|
||||
holder.viewBinding.textViewUsername.setOnClickListener {
|
||||
callback.onProfileClick(comment.comment.userId)
|
||||
}
|
||||
holder.viewBinding.moreButton.setOnClickListener {
|
||||
callback.onOptions(comment.comment.id)
|
||||
}
|
||||
Picasso.get()
|
||||
.load(UrlGen.userProfileImage(comment.comment.userId))
|
||||
.into(holder.viewBinding.avatarPicture)
|
||||
}
|
||||
|
||||
fun submitList(commentDtoList: List<CommentDto>) {
|
||||
val lastIndex = if(list.count() - 1 < 1) 0 else list.count() - 1
|
||||
list = commentDtoList
|
||||
notifyItemRangeChanged(lastIndex, commentDtoList.count())
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,58 @@
|
||||
package com.isolaatti.posting.comments.presentation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.posting.comments.data.remote.CommentDto
|
||||
import com.isolaatti.posting.comments.domain.use_case.GetComments
|
||||
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 CommentsViewModel @Inject constructor(private val getComments: GetComments) : ViewModel() {
|
||||
private val _comments: MutableLiveData<List<CommentDto>> = MutableLiveData()
|
||||
|
||||
val comments: LiveData<List<CommentDto>> get() = _comments
|
||||
|
||||
/**
|
||||
* postId to query comments for. First page will be fetched when set.
|
||||
* Call getContent() to get more content if available
|
||||
*/
|
||||
var postId: Long = 0
|
||||
set(value) {
|
||||
field = value
|
||||
getContent()
|
||||
}
|
||||
|
||||
private var lastId: Long = 0L
|
||||
|
||||
fun getContent() {
|
||||
viewModelScope.launch {
|
||||
getComments(postId, lastId).onEach {
|
||||
val newList = _comments.value?.toMutableList() ?: mutableListOf()
|
||||
newList.addAll(it.data)
|
||||
_comments.postValue(newList)
|
||||
if(it.data.isNotEmpty()){
|
||||
lastId = it.data.last().comment.id
|
||||
}
|
||||
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Use when new comment has been posted
|
||||
*/
|
||||
fun putCommentAtTheBeginning(commentDto: CommentDto) {
|
||||
val newList: MutableList<CommentDto> = mutableListOf(commentDto)
|
||||
newList.addAll(_comments.value ?: mutableListOf())
|
||||
_comments.postValue(newList)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
package com.isolaatti.posting.common.domain
|
||||
|
||||
interface OnUserInteractedCallback {
|
||||
fun onOptions(postId: Long)
|
||||
fun onProfileClick(userId: Int)
|
||||
}
|
||||
@ -0,0 +1,7 @@
|
||||
package com.isolaatti.posting.common.domain
|
||||
|
||||
interface OnUserInteractedWithPostCallback : OnUserInteractedCallback {
|
||||
fun onLiked(postId: Long)
|
||||
fun onUnLiked(postId: Long)
|
||||
fun onComment(postId: Long)
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
package com.isolaatti.posting.common.options_bottom_sheet.domain
|
||||
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.annotation.StringRes
|
||||
import com.isolaatti.R
|
||||
|
||||
data class Options(
|
||||
@StringRes val title: Int,
|
||||
val items: List<Option>
|
||||
) {
|
||||
data class Option(
|
||||
@StringRes val stringRes: Int,
|
||||
@DrawableRes val icon: Int
|
||||
)
|
||||
|
||||
companion object {
|
||||
val postOptions = Options(R.string.post_options_title, listOf(
|
||||
Option(R.string.delete, R.drawable.baseline_delete_24),
|
||||
Option(R.string.edit, R.drawable.baseline_edit_24),
|
||||
Option(R.string.report, R.drawable.baseline_report_24)
|
||||
))
|
||||
|
||||
val commentOptions = Options(R.string.post_options_title, listOf(
|
||||
Option(R.string.delete, R.drawable.baseline_delete_24),
|
||||
Option(R.string.edit, R.drawable.baseline_edit_24),
|
||||
Option(R.string.report, R.drawable.baseline_report_24)
|
||||
))
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,15 @@
|
||||
package com.isolaatti.posting.common.options_bottom_sheet.presentation
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
|
||||
|
||||
class BottomSheetPostOptionsViewModel : ViewModel() {
|
||||
private val _options: MutableLiveData<Options> = MutableLiveData()
|
||||
val options: LiveData<Options> get() = _options
|
||||
|
||||
fun setOptions(options: Options) {
|
||||
_options.postValue(options)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,59 @@
|
||||
package com.isolaatti.posting.common.options_bottom_sheet.ui
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ListAdapter
|
||||
import android.widget.ListView
|
||||
import androidx.appcompat.content.res.AppCompatResources
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.databinding.BottomSheetPostOptionsBinding
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.domain.Options
|
||||
import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel
|
||||
|
||||
class BottomSheetPostOptionsFragment : BottomSheetDialogFragment() {
|
||||
private lateinit var viewBinding: BottomSheetPostOptionsBinding
|
||||
|
||||
private val viewModel: BottomSheetPostOptionsViewModel by activityViewModels()
|
||||
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
viewBinding = BottomSheetPostOptionsBinding.inflate(inflater)
|
||||
|
||||
|
||||
return viewBinding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel.options.observe(viewLifecycleOwner) {
|
||||
renderOptions(it)
|
||||
}
|
||||
}
|
||||
|
||||
private fun renderOptions(options: Options) {
|
||||
viewBinding.optionsContainer.removeAllViews()
|
||||
for(option in options.items) {
|
||||
val button = MaterialButton(requireContext(), null, com.google.android.material.R.style.Widget_Material3_Button_TextButton)
|
||||
button.icon = AppCompatResources.getDrawable(requireContext(), option.icon)
|
||||
button.text = requireContext().getText(option.stringRes)
|
||||
button.textAlignment = MaterialButton.TEXT_ALIGNMENT_TEXT_START
|
||||
viewBinding.optionsContainer.addView(button)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val TAG = "BottomSheetPostOptions"
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
25
app/src/main/java/com/isolaatti/posting/likes/Module.kt
Normal file
25
app/src/main/java/com/isolaatti/posting/likes/Module.kt
Normal file
@ -0,0 +1,25 @@
|
||||
package com.isolaatti.posting.likes
|
||||
|
||||
import com.isolaatti.connectivity.RetrofitClient
|
||||
import com.isolaatti.posting.likes.data.LikesRepositoryImpl
|
||||
import com.isolaatti.posting.likes.data.remote.LikesApi
|
||||
import com.isolaatti.posting.likes.domain.repository.LikesRepository
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
class Module {
|
||||
@Provides
|
||||
fun provideLikesApi(retrofitClient: RetrofitClient): LikesApi {
|
||||
return retrofitClient.client.create(LikesApi::class.java)
|
||||
}
|
||||
|
||||
@Provides
|
||||
fun provideLikesRepository(likesApi: LikesApi): LikesRepository {
|
||||
return LikesRepositoryImpl(likesApi)
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package com.isolaatti.posting.likes.data
|
||||
|
||||
import android.util.Log
|
||||
import com.isolaatti.posting.likes.data.remote.LikeDto
|
||||
import com.isolaatti.posting.likes.data.remote.LikesApi
|
||||
import com.isolaatti.posting.likes.domain.repository.LikesRepository
|
||||
import com.isolaatti.utils.LongIdentificationWrapper
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
import retrofit2.awaitResponse
|
||||
|
||||
class LikesRepositoryImpl(private val likesApi: LikesApi) : LikesRepository {
|
||||
override fun likePost(postId: Long): Flow<LikeDto> = flow {
|
||||
val response = likesApi.likePost(LongIdentificationWrapper(postId)).awaitResponse()
|
||||
Log.d("likes_repo", response.toString())
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { emit(it) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun unLikePost(postId: Long): Flow<LikeDto> = flow {
|
||||
val response = likesApi.unLikePost(LongIdentificationWrapper(postId)).awaitResponse()
|
||||
Log.d("likes_repo", response.toString())
|
||||
if(response.isSuccessful) {
|
||||
response.body()?.let { emit(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,3 @@
|
||||
package com.isolaatti.posting.likes.data.remote
|
||||
|
||||
data class LikeDto(val likesCount: Int, val postId: Long)
|
||||
@ -0,0 +1,14 @@
|
||||
package com.isolaatti.posting.likes.data.remote
|
||||
|
||||
import com.isolaatti.utils.LongIdentificationWrapper
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.Body
|
||||
import retrofit2.http.POST
|
||||
|
||||
interface LikesApi {
|
||||
@POST("Likes/LikePost")
|
||||
fun likePost(@Body postId: LongIdentificationWrapper): Call<LikeDto>
|
||||
|
||||
@POST("Likes/UnLikePost")
|
||||
fun unLikePost(@Body postId: LongIdentificationWrapper): Call<LikeDto>
|
||||
}
|
||||
@ -0,0 +1,9 @@
|
||||
package com.isolaatti.posting.likes.domain.repository
|
||||
|
||||
import com.isolaatti.posting.likes.data.remote.LikeDto
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
|
||||
interface LikesRepository {
|
||||
fun likePost(postId: Long): Flow<LikeDto>
|
||||
fun unLikePost(postId: Long): Flow<LikeDto>
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
package com.isolaatti.posts.data.remote
|
||||
package com.isolaatti.posting.posts.data.remote
|
||||
|
||||
import com.isolaatti.home.feed.data.remote.FeedDto
|
||||
import com.isolaatti.home.feed.data.remote.PostDto
|
||||
import com.isolaatti.feed.data.remote.FeedDto
|
||||
import com.isolaatti.feed.data.remote.PostDto
|
||||
import com.isolaatti.profile.data.remote.ProfileListItemDto
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
@ -0,0 +1,8 @@
|
||||
package com.isolaatti.posting.posts.data.repository
|
||||
|
||||
import com.isolaatti.posting.posts.data.remote.PostsApi
|
||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class PostsRepositoryImpl @Inject constructor(private val postsApi: PostsApi) : PostsRepository {
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package com.isolaatti.posting.posts.domain
|
||||
|
||||
interface PostsRepository {
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package com.isolaatti.posting.posts.domain.use_case
|
||||
|
||||
class MakePost {
|
||||
}
|
||||
@ -0,0 +1,168 @@
|
||||
package com.isolaatti.posting.posts.presentation
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
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 androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.google.android.material.button.MaterialButton
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.feed.data.remote.PostDto
|
||||
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
||||
import com.isolaatti.utils.UrlGen.userProfileImage
|
||||
import com.squareup.picasso.Picasso
|
||||
import io.noties.markwon.Markwon
|
||||
|
||||
class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callback: OnUserInteractedWithPostCallback, private var list: List<PostDto>) : RecyclerView.Adapter<PostsRecyclerViewAdapter.FeedViewHolder>(){
|
||||
inner class FeedViewHolder(itemView: View) : ViewHolder(itemView) {
|
||||
fun bindView(postDto: PostDto, payloads: List<Any>) {
|
||||
|
||||
Log.d("payloads", payloads.count().toString())
|
||||
val likeButton: MaterialButton = itemView.findViewById(R.id.like_button)
|
||||
val commentsButton: MaterialButton = itemView.findViewById(R.id.comment_button)
|
||||
|
||||
if(payloads.isNotEmpty()) {
|
||||
for(payload in payloads) {
|
||||
when(payload) {
|
||||
is LikeCountUpdatePayload -> {
|
||||
|
||||
|
||||
likeButton.isEnabled = true
|
||||
|
||||
if(postDto.liked) {
|
||||
likeButton.setIconTintResource(R.color.purple_700)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.purple_700))
|
||||
} else {
|
||||
likeButton.setIconTintResource(R.color.black)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.black))
|
||||
}
|
||||
|
||||
likeButton.text = postDto.numberOfLikes.toString()
|
||||
|
||||
|
||||
}
|
||||
|
||||
is CommentsCountUpdatePayload -> {
|
||||
|
||||
commentsButton.text = postDto.numberOfComments.toString()
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
val username: TextView = itemView.findViewById(R.id.text_view_username)
|
||||
username.text = postDto.userName
|
||||
|
||||
val profileImageView: ImageView = itemView.findViewById(R.id.avatar_picture)
|
||||
Picasso.get().load(userProfileImage(postDto.post.userId)).into(profileImageView)
|
||||
|
||||
val dateTextView: TextView = itemView.findViewById(R.id.text_view_date)
|
||||
dateTextView.text = postDto.post.date
|
||||
|
||||
val content: TextView = itemView.findViewById(R.id.post_content)
|
||||
markwon.setMarkdown(content, postDto.post.textContent)
|
||||
|
||||
likeButton.isEnabled = true
|
||||
|
||||
if(postDto.liked) {
|
||||
likeButton.setIconTintResource(R.color.purple_700)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.purple_700))
|
||||
} else {
|
||||
likeButton.setIconTintResource(R.color.black)
|
||||
likeButton.setTextColor(itemView.context.getColor(R.color.black))
|
||||
}
|
||||
|
||||
likeButton.text = postDto.numberOfLikes.toString()
|
||||
|
||||
commentsButton.text = postDto.numberOfComments.toString()
|
||||
|
||||
val moreButton: MaterialButton = itemView.findViewById(R.id.more_button)
|
||||
moreButton.setOnClickListener {
|
||||
callback.onOptions(postDto.post.id)
|
||||
}
|
||||
|
||||
likeButton.setOnClickListener {
|
||||
likeButton.isEnabled = false
|
||||
if(postDto.liked){
|
||||
callback.onUnLiked(postDto.post.id)
|
||||
} else {
|
||||
callback.onLiked(postDto.post.id)
|
||||
}
|
||||
}
|
||||
commentsButton.setOnClickListener {
|
||||
callback.onComment(postDto.post.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
data class LikeCountUpdatePayload(val likeCount: Int)
|
||||
data class CommentsCountUpdatePayload(val commentsCount: Int)
|
||||
|
||||
data class UpdateEvent(val updateType: UpdateType, val affectedId: Long) {
|
||||
enum class UpdateType {
|
||||
POST_LIKED,
|
||||
POST_COMMENTED,
|
||||
POST_REMOVED,
|
||||
POST_ADDED
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedViewHolder {
|
||||
val view = LayoutInflater.from(parent.context).inflate(R.layout.post_layout, parent, false)
|
||||
|
||||
return FeedViewHolder(view)
|
||||
}
|
||||
override fun getItemCount(): Int = list.size
|
||||
|
||||
override fun getItemId(position: Int): Long = list[position].post.id
|
||||
|
||||
override fun setHasStableIds(hasStableIds: Boolean) {
|
||||
super.setHasStableIds(true)
|
||||
}
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun updateList(newList: List<PostDto>?, updateEvent: UpdateEvent? = null) {
|
||||
if(updateEvent == null) {
|
||||
if(newList != null) {
|
||||
list = newList
|
||||
}
|
||||
|
||||
notifyDataSetChanged()
|
||||
return
|
||||
}
|
||||
val postUpdated = list.find { p -> p.post.id == updateEvent.affectedId } ?: return
|
||||
val position = list.indexOf(postUpdated)
|
||||
|
||||
if(newList != null) {
|
||||
list = newList
|
||||
}
|
||||
|
||||
when(updateEvent.updateType) {
|
||||
UpdateEvent.UpdateType.POST_LIKED -> {
|
||||
notifyItemChanged(position, LikeCountUpdatePayload(postUpdated.numberOfLikes))
|
||||
}
|
||||
UpdateEvent.UpdateType.POST_COMMENTED -> {
|
||||
notifyItemChanged(position, CommentsCountUpdatePayload(postUpdated.numberOfComments))
|
||||
}
|
||||
UpdateEvent.UpdateType.POST_REMOVED -> {
|
||||
notifyItemRemoved(position)
|
||||
}
|
||||
UpdateEvent.UpdateType.POST_ADDED -> {
|
||||
notifyItemInserted(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: FeedViewHolder, position: Int) {}
|
||||
|
||||
override fun onBindViewHolder(holder: FeedViewHolder, position: Int, payloads: List<Any>) {
|
||||
holder.bindView(list[position], payloads)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,89 @@
|
||||
package com.isolaatti.posting.posts.presentation
|
||||
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.posting.comments.data.remote.FeedCommentsDto
|
||||
import com.isolaatti.feed.data.remote.FeedDto
|
||||
import com.isolaatti.feed.domain.repository.FeedRepository
|
||||
import com.isolaatti.posting.likes.data.remote.LikeDto
|
||||
import com.isolaatti.posting.likes.domain.repository.LikesRepository
|
||||
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 PostsViewModel @Inject constructor(private val feedRepository: FeedRepository, private val likesRepository: LikesRepository) : ViewModel() {
|
||||
|
||||
private val _posts: MutableLiveData<FeedDto> = MutableLiveData()
|
||||
val posts: LiveData<FeedDto> get() = _posts
|
||||
|
||||
private val _comments: MutableLiveData<FeedCommentsDto> = MutableLiveData()
|
||||
val comments: LiveData<FeedCommentsDto> get() = _comments
|
||||
|
||||
private fun getLastId(): Long = try {_posts.value?.data?.last()?.post?.id ?: 0} catch (e: NoSuchElementException) { 0 }
|
||||
|
||||
private val _postLiked: MutableLiveData<LikeDto> = MutableLiveData()
|
||||
val postLiked: LiveData<LikeDto> get() = _postLiked
|
||||
|
||||
fun getFeed() {
|
||||
viewModelScope.launch {
|
||||
feedRepository.getNextPage(getLastId(), 20).onEach {feedDto ->
|
||||
val temp = _posts.value
|
||||
if(temp != null) {
|
||||
feedDto?.data?.let { it ->
|
||||
temp.data.addAll(it)
|
||||
}
|
||||
temp.let {
|
||||
_posts.postValue(it)
|
||||
}
|
||||
} else {
|
||||
_posts.postValue(feedDto)
|
||||
}
|
||||
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun likePost(postId: Long) {
|
||||
viewModelScope.launch {
|
||||
likesRepository.likePost(postId).onEach {likeDto ->
|
||||
val likedPost = _posts.value?.data?.find { post -> post.post.id == likeDto.postId }
|
||||
val index = _posts.value?.data?.indexOf(likedPost)
|
||||
Log.d("***", index.toString())
|
||||
if(index != null){
|
||||
val temp = _posts.value
|
||||
Log.d("***", temp.toString())
|
||||
temp?.data?.set(index, likedPost!!.apply {
|
||||
liked = true
|
||||
numberOfLikes = likeDto.likesCount
|
||||
|
||||
})
|
||||
}
|
||||
_postLiked.postValue(likeDto)
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun unLikePost(postId: Long) {
|
||||
viewModelScope.launch {
|
||||
likesRepository.unLikePost(postId).onEach {likeDto ->
|
||||
val likedPost = _posts.value?.data?.find { post -> post.post.id == likeDto.postId }
|
||||
val index = _posts.value?.data?.indexOf(likedPost)
|
||||
if(index != null){
|
||||
_posts.value?.data?.set(index, likedPost!!.apply {
|
||||
liked = false
|
||||
numberOfLikes = likeDto.likesCount
|
||||
})
|
||||
}
|
||||
_postLiked.postValue(likeDto)
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package com.isolaatti.posting.posts.ui
|
||||
|
||||
class CreatePostDialogFragment {
|
||||
}
|
||||
@ -1,8 +0,0 @@
|
||||
package com.isolaatti.posts.data.repository
|
||||
|
||||
import com.isolaatti.posts.data.remote.PostsApi
|
||||
import com.isolaatti.posts.domain.PostsRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class PostsRepositoryImpl @Inject constructor(private val postsApi: PostsApi) : PostsRepository {
|
||||
}
|
||||
@ -1,4 +0,0 @@
|
||||
package com.isolaatti.posts.domain
|
||||
|
||||
interface PostsRepository {
|
||||
}
|
||||
@ -1,6 +1,6 @@
|
||||
package com.isolaatti.profile.data.remote
|
||||
|
||||
import com.isolaatti.home.feed.data.remote.FeedDto
|
||||
import com.isolaatti.feed.data.remote.FeedDto
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
@ -10,5 +10,4 @@ interface ProfileApi {
|
||||
@GET("Fetch/UserProfile/{userId}")
|
||||
fun userProfile(@Path("userId") userId: Int): Call<UserProfileDto>
|
||||
|
||||
|
||||
}
|
||||
@ -0,0 +1,4 @@
|
||||
package com.isolaatti.profile.domain.use_case
|
||||
|
||||
class GetProfile {
|
||||
}
|
||||
@ -17,6 +17,10 @@ class SettingsFragment : Fragment() {
|
||||
): View? {
|
||||
viewBinding = FragmentSettingsBinding.inflate(inflater)
|
||||
|
||||
viewBinding.topAppBar.setNavigationOnClickListener {
|
||||
requireActivity().onBackPressed()
|
||||
}
|
||||
|
||||
return viewBinding.root
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
package com.isolaatti.utils
|
||||
|
||||
data class IntIdentificationWrapper(val id: Int)
|
||||
data class LongIdentificationWrapper(val id: Long)
|
||||
9
app/src/main/java/com/isolaatti/utils/Resource.kt
Normal file
9
app/src/main/java/com/isolaatti/utils/Resource.kt
Normal file
@ -0,0 +1,9 @@
|
||||
package com.isolaatti.utils
|
||||
|
||||
class Resource<T> {
|
||||
inner class Success(data: T)
|
||||
|
||||
inner class Loading()
|
||||
|
||||
inner class Error
|
||||
}
|
||||
@ -3,5 +3,5 @@ package com.isolaatti.utils
|
||||
import com.isolaatti.connectivity.RetrofitClient.Companion.BASE_URL
|
||||
|
||||
object UrlGen {
|
||||
fun userProfileImage(userId: Int) = "${BASE_URL}images/profile_image/of_user/1?mode=small"
|
||||
fun userProfileImage(userId: Int) = "${BASE_URL}images/profile_image/of_user/$userId?mode=small"
|
||||
}
|
||||
5
app/src/main/res/drawable/baseline_close_24.xml
Normal file
5
app/src/main/res/drawable/baseline_close_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_delete_24.xml
Normal file
5
app/src/main/res/drawable/baseline_delete_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_edit_24.xml
Normal file
5
app/src/main/res/drawable/baseline_edit_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_more_horiz_24.xml
Normal file
5
app/src/main/res/drawable/baseline_more_horiz_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_open_in_full_24.xml
Normal file
5
app/src/main/res/drawable/baseline_open_in_full_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M21,11l0,-8l-8,0l3.29,3.29l-10,10l-3.29,-3.29l0,8l8,0l-3.29,-3.29l10,-10z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_report_24.xml
Normal file
5
app/src/main/res/drawable/baseline_report_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M15.73,3L8.27,3L3,8.27v7.46L8.27,21h7.46L21,15.73L21,8.27L15.73,3zM12,17.3c-0.72,0 -1.3,-0.58 -1.3,-1.3 0,-0.72 0.58,-1.3 1.3,-1.3 0.72,0 1.3,0.58 1.3,1.3 0,0.72 -0.58,1.3 -1.3,1.3zM13,13h-2L11,7h2v6z"/>
|
||||
</vector>
|
||||
5
app/src/main/res/drawable/baseline_send_24.xml
Normal file
5
app/src/main/res/drawable/baseline_send_24.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:autoMirrored="true" android:height="24dp"
|
||||
android:tint="#000000" android:viewportHeight="24"
|
||||
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
|
||||
</vector>
|
||||
98
app/src/main/res/layout-land/fragment_feed.xml
Normal file
98
app/src/main/res/layout-land/fragment_feed.xml
Normal file
@ -0,0 +1,98 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout
|
||||
android:id="@+id/main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent">
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/home_drawer"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:context=".feed.ui.FeedFragment">
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:hint="@string/searchbar_hint" />
|
||||
|
||||
<com.google.android.material.search.SearchView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/searchbar_hint"
|
||||
app:layout_anchor="@id/search_bar">
|
||||
<!-- Search suggestions/results go here (ScrollView, RecyclerView, etc.). -->
|
||||
|
||||
</com.google.android.material.search.SearchView>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="80dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/topAppBar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/topAppBar"
|
||||
style="@style/Theme.Isolaatti"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:navigationIcon="@drawable/baseline_menu_24"
|
||||
app:title="@string/app_name"
|
||||
app:titleCentered="true" />
|
||||
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/feed_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/floating_action_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_margin="16dp"
|
||||
android:contentDescription=""
|
||||
app:srcCompat="@drawable/baseline_add_24" />
|
||||
</FrameLayout>
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
<com.google.android.material.navigation.NavigationView
|
||||
android:id="@+id/home_drawer"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="start"
|
||||
app:headerLayout="@layout/header_drawer_home"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:menu="@menu/home_drawer_menu" />
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</FrameLayout>
|
||||
@ -6,6 +6,7 @@
|
||||
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/settings_fragment_container"
|
||||
android:name="androidx.navigation.fragment.NavHostFragment"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:defaultNavHost="true"
|
||||
|
||||
70
app/src/main/res/layout/bottom_sheet_post_comments.xml
Normal file
70
app/src/main/res/layout/bottom_sheet_post_comments.xml
Normal file
@ -0,0 +1,70 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/topAppBar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/recycler_comments">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/topAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:titleCentered="true"
|
||||
style="@style/Theme.Isolaatti"
|
||||
app:title="Comments"/>
|
||||
|
||||
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/recycler_comments"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="400dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/newCommentTextField"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/topAppBar_layout" />
|
||||
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/newCommentTextField"
|
||||
style="?attr/textInputOutlinedStyle"
|
||||
android:layout_margin="8dp"
|
||||
app:boxCornerRadiusBottomEnd="20dp"
|
||||
app:boxCornerRadiusBottomStart="20dp"
|
||||
app:boxCornerRadiusTopEnd="20dp"
|
||||
app:boxCornerRadiusTopStart="20dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toStartOf="@id/submitCommentButton"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/new_comment">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/submitCommentButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_margin="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/newCommentTextField"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:icon="@drawable/baseline_send_24"/>
|
||||
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
40
app/src/main/res/layout/bottom_sheet_post_options.xml
Normal file
40
app/src/main/res/layout/bottom_sheet_post_options.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:orientation="vertical"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<com.google.android.material.bottomsheet.BottomSheetDragHandleView
|
||||
android:id="@+id/drag_handle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/options_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
||||
android:text="@string/edit"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/delete"
|
||||
android:textAlignment="textStart" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/report"
|
||||
android:textAlignment="textStart" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
72
app/src/main/res/layout/comment_layout.xml
Normal file
72
app/src/main/res/layout/comment_layout.xml
Normal file
@ -0,0 +1,72 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<com.google.android.material.card.MaterialCardView
|
||||
android:id="@+id/card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/materialCardViewFilledStyle"
|
||||
android:layout_margin="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="4dp">
|
||||
|
||||
<RelativeLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/avatar_picture"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"/>
|
||||
<LinearLayout
|
||||
android:id="@+id/post_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_toEndOf="@id/avatar_picture"
|
||||
android:layout_toStartOf="@id/more_button">
|
||||
<TextView
|
||||
android:id="@+id/text_view_username"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textStyle="bold"/>
|
||||
<TextView
|
||||
android:id="@+id/text_view_date"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/more_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:layout_alignParentEnd="true"
|
||||
app:icon="@drawable/baseline_more_horiz_24"
|
||||
android:gravity="end"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/post_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"/>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
||||
</LinearLayout>
|
||||
@ -5,13 +5,11 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/textView"
|
||||
android:layout_width="wrap_content"
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/discussionRecycler"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/discussions"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintEnd_toEndOf="parent"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -1,5 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout
|
||||
android:id="@+id/main_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<androidx.drawerlayout.widget.DrawerLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/drawer_layout"
|
||||
@ -9,7 +14,7 @@
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".home.feed.ui.FeedFragment">
|
||||
tools:context=".feed.ui.FeedFragment">
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
@ -48,31 +53,15 @@
|
||||
app:titleCentered="true"
|
||||
style="@style/Theme.Isolaatti"
|
||||
app:navigationIcon="@drawable/baseline_menu_24"
|
||||
app:title="Feed"/>
|
||||
app:title="@string/app_name"/>
|
||||
|
||||
|
||||
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/feed_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toTopOf="@id/topAppBar_layout"
|
||||
app:layout_constraintBottom_toBottomOf="parent"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"/>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
@ -97,3 +86,4 @@
|
||||
|
||||
|
||||
</androidx.drawerlayout.widget.DrawerLayout>
|
||||
</FrameLayout>
|
||||
@ -1,16 +1,19 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout 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="match_parent"
|
||||
android:orientation="vertical">
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:id="@+id/topAppBar_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/topAppBar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"/>
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
app:title="@string/settings"
|
||||
app:navigationIcon="@drawable/baseline_arrow_back_24"/>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
@ -19,12 +22,12 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
<TextView
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/account"
|
||||
android:padding="10dp"
|
||||
style="?attr/textAppearanceBodyLarge"/>
|
||||
android:textAlignment="textStart"
|
||||
style="@style/Widget.MaterialComponents.Button.TextButton"/>
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
||||
</LinearLayout>
|
||||
@ -19,7 +19,7 @@
|
||||
android:orientation="vertical"
|
||||
android:padding="4dp">
|
||||
|
||||
<LinearLayout android:layout_width="match_parent"
|
||||
<RelativeLayout android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
@ -28,11 +28,13 @@
|
||||
android:layout_height="40dp"
|
||||
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"/>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/post_header"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_gravity="center_vertical">
|
||||
android:layout_toEndOf="@id/avatar_picture"
|
||||
android:layout_toStartOf="@id/more_button">
|
||||
<TextView
|
||||
android:id="@+id/text_view_username"
|
||||
android:layout_width="match_parent"
|
||||
@ -44,29 +46,42 @@
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/more_button"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:layout_alignParentEnd="true"
|
||||
app:icon="@drawable/baseline_more_horiz_24"
|
||||
android:gravity="end"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/post_content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="20dp"
|
||||
android:layout_marginHorizontal="8dp"/>
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="8dp"
|
||||
android:gravity="end">
|
||||
<Button
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/comment_button"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
app:icon="@drawable/comments_solid" />
|
||||
<Button
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/like_button"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?attr/materialIconButtonFilledTonalStyle"
|
||||
app:icon="@drawable/hands_clapping_solid" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
|
||||
<fragment
|
||||
android:id="@+id/feedFragment"
|
||||
android:name="com.isolaatti.home.feed.ui.FeedFragment"
|
||||
android:name="com.isolaatti.feed.ui.FeedFragment"
|
||||
android:label="fragment_feed"
|
||||
tools:layout="@layout/fragment_feed" />
|
||||
<fragment
|
||||
|
||||
@ -11,4 +11,12 @@
|
||||
<string name="audios">Audios</string>
|
||||
<string name="images">Images</string>
|
||||
<string name="account">Account</string>
|
||||
<string name="settings">Settings</string>
|
||||
<string name="edit">Edit</string>
|
||||
<string name="delete">Delete</string>
|
||||
<string name="new_comment">New comment</string>
|
||||
<string name="report">Report</string>
|
||||
|
||||
<!--Post options -->
|
||||
<string name="post_options_title">Discussion options</string>
|
||||
</resources>
|
||||
Loading…
x
Reference in New Issue
Block a user