creacion de post, se ve informacion de perfil en drawer y otros cambios
This commit is contained in:
parent
1e6be6db5b
commit
72290eb50f
@ -27,6 +27,7 @@
|
|||||||
<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" android:theme="@style/Theme.Isolaatti"/>
|
<activity android:name=".profile.ui.ProfileActivity" android:theme="@style/Theme.Isolaatti"/>
|
||||||
<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"/>
|
||||||
</application>
|
</application>
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
</manifest>
|
</manifest>
|
||||||
@ -25,5 +25,4 @@ class MainModule {
|
|||||||
return RetrofitClient(authenticationInterceptor)
|
return RetrofitClient(authenticationInterceptor)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1,13 +1,17 @@
|
|||||||
package com.isolaatti.auth
|
package com.isolaatti.auth
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import com.isolaatti.auth.data.AuthRepositoryImpl
|
import com.isolaatti.auth.data.AuthRepositoryImpl
|
||||||
|
import com.isolaatti.auth.data.local.KeyValueDao
|
||||||
import com.isolaatti.auth.data.local.TokenStorage
|
import com.isolaatti.auth.data.local.TokenStorage
|
||||||
import com.isolaatti.auth.data.remote.AuthApi
|
import com.isolaatti.auth.data.remote.AuthApi
|
||||||
import com.isolaatti.auth.domain.AuthRepository
|
import com.isolaatti.auth.domain.AuthRepository
|
||||||
import com.isolaatti.connectivity.RetrofitClient
|
import com.isolaatti.connectivity.RetrofitClient
|
||||||
|
import com.isolaatti.database.AppDatabase
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@ -18,7 +22,12 @@ class Module {
|
|||||||
return retrofitClient.client.create(AuthApi::class.java)
|
return retrofitClient.client.create(AuthApi::class.java)
|
||||||
}
|
}
|
||||||
@Provides
|
@Provides
|
||||||
fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi): AuthRepository {
|
fun provideKeyValueDao(database: AppDatabase): KeyValueDao {
|
||||||
return AuthRepositoryImpl(tokenStorage, authApi)
|
return database.keyValueDao()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, keyValueDao: KeyValueDao): AuthRepository {
|
||||||
|
return AuthRepositoryImpl(tokenStorage, authApi, keyValueDao)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package com.isolaatti.auth.data
|
package com.isolaatti.auth.data
|
||||||
|
|
||||||
|
import com.isolaatti.auth.data.local.KeyValueDao
|
||||||
|
import com.isolaatti.auth.data.local.KeyValueEntity
|
||||||
import com.isolaatti.auth.data.remote.AuthTokenDto
|
import com.isolaatti.auth.data.remote.AuthTokenDto
|
||||||
import com.isolaatti.auth.data.local.TokenStorage
|
import com.isolaatti.auth.data.local.TokenStorage
|
||||||
import com.isolaatti.auth.data.remote.AuthApi
|
import com.isolaatti.auth.data.remote.AuthApi
|
||||||
@ -8,16 +10,18 @@ import com.isolaatti.auth.domain.AuthRepository
|
|||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import retrofit2.await
|
|
||||||
import retrofit2.awaitResponse
|
import retrofit2.awaitResponse
|
||||||
import java.io.IOException
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
class AuthRepositoryImpl @Inject constructor(
|
class AuthRepositoryImpl @Inject constructor(
|
||||||
private val tokenStorage: TokenStorage,
|
private val tokenStorage: TokenStorage,
|
||||||
private val authApi: AuthApi
|
private val authApi: AuthApi,
|
||||||
|
private val keyValueDao: KeyValueDao
|
||||||
) : AuthRepository {
|
) : AuthRepository {
|
||||||
|
companion object {
|
||||||
|
val KEY_USERID = "user_id"
|
||||||
|
}
|
||||||
override fun authWithEmailAndPassword(
|
override fun authWithEmailAndPassword(
|
||||||
email: String,
|
email: String,
|
||||||
password: String
|
password: String
|
||||||
@ -33,6 +37,7 @@ class AuthRepositoryImpl @Inject constructor(
|
|||||||
return@flow
|
return@flow
|
||||||
}
|
}
|
||||||
tokenStorage.storeToken(dto)
|
tokenStorage.storeToken(dto)
|
||||||
|
keyValueDao.setValue(KeyValueEntity(KEY_USERID, dto.userId.toString()))
|
||||||
emit(Resource.Success(true))
|
emit(Resource.Success(true))
|
||||||
return@flow
|
return@flow
|
||||||
}
|
}
|
||||||
@ -60,4 +65,8 @@ class AuthRepositoryImpl @Inject constructor(
|
|||||||
return tokenStorage.token
|
return tokenStorage.token
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getUserId(): Flow<Int?> = flow {
|
||||||
|
emit(keyValueDao.getValue(KEY_USERID).toIntOrNull())
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.isolaatti.auth.data.local
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface KeyValueDao {
|
||||||
|
@Query("SELECT value FROM key_values WHERE id = :key")
|
||||||
|
fun getValue(key: String): String
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
fun setValue(entity: KeyValueEntity)
|
||||||
|
}
|
||||||
@ -0,0 +1,10 @@
|
|||||||
|
package com.isolaatti.auth.data.local
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
|
||||||
|
@Entity(tableName = "key_values")
|
||||||
|
data class KeyValueEntity(
|
||||||
|
@PrimaryKey val id: String,
|
||||||
|
val value: String
|
||||||
|
)
|
||||||
@ -1,6 +1,6 @@
|
|||||||
package com.isolaatti.auth.data.remote
|
package com.isolaatti.auth.data.remote
|
||||||
|
|
||||||
data class AuthTokenDto(val token: String) {
|
data class AuthTokenDto(val token: String, val userId: Int) {
|
||||||
override fun toString(): String {
|
override fun toString(): String {
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,4 +8,6 @@ interface AuthRepository {
|
|||||||
fun authWithEmailAndPassword(email: String, password: String): Flow<Resource<Boolean>>
|
fun authWithEmailAndPassword(email: String, password: String): Flow<Resource<Boolean>>
|
||||||
fun logout(): Flow<Boolean>
|
fun logout(): Flow<Boolean>
|
||||||
fun getCurrentToken(): AuthTokenDto?
|
fun getCurrentToken(): AuthTokenDto?
|
||||||
|
fun getUserId(): Flow<Int?>
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -13,6 +13,7 @@ import androidx.lifecycle.Observer
|
|||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.home.HomeActivity
|
import com.isolaatti.home.HomeActivity
|
||||||
|
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
|
||||||
|
|
||||||
@ -46,7 +47,7 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() {
|
|||||||
abstract fun onRetry()
|
abstract fun onRetry()
|
||||||
|
|
||||||
private val onAcceptReAuthClick = DialogInterface.OnClickListener { _, _ ->
|
private val onAcceptReAuthClick = DialogInterface.OnClickListener { _, _ ->
|
||||||
|
signInActivityResult.launch(Intent(this, LogInActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showReAuthDialog() {
|
private fun showReAuthDialog() {
|
||||||
|
|||||||
12
app/src/main/java/com/isolaatti/database/AppDatabase.kt
Normal file
12
app/src/main/java/com/isolaatti/database/AppDatabase.kt
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
package com.isolaatti.database
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import com.isolaatti.auth.data.local.KeyValueDao
|
||||||
|
import com.isolaatti.auth.data.local.KeyValueEntity
|
||||||
|
|
||||||
|
@Database(entities = [KeyValueEntity::class], version = 1)
|
||||||
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
|
abstract fun keyValueDao(): KeyValueDao
|
||||||
|
|
||||||
|
}
|
||||||
20
app/src/main/java/com/isolaatti/database/Module.kt
Normal file
20
app/src/main/java/com/isolaatti/database/Module.kt
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
package com.isolaatti.database
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.room.Room
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
class Module {
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideAppDatabase(@ApplicationContext applicationContext: Context): AppDatabase {
|
||||||
|
return Room.databaseBuilder(applicationContext, AppDatabase::class.java, "database.db").build()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
package com.isolaatti.home
|
package com.isolaatti.home
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
@ -7,20 +8,30 @@ import androidx.fragment.app.Fragment
|
|||||||
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 android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.constraintlayout.widget.ConstraintLayout
|
||||||
import androidx.fragment.app.activityViewModels
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import com.isolaatti.BuildConfig
|
import com.isolaatti.BuildConfig
|
||||||
import com.isolaatti.R
|
import com.isolaatti.R
|
||||||
import com.isolaatti.common.ErrorMessageViewModel
|
import com.isolaatti.common.ErrorMessageViewModel
|
||||||
import com.isolaatti.databinding.FragmentFeedBinding
|
import com.isolaatti.databinding.FragmentFeedBinding
|
||||||
|
import com.isolaatti.home.presentation.FeedViewModel
|
||||||
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
import com.isolaatti.posting.posts.presentation.PostsViewModel
|
||||||
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
|
import com.isolaatti.posting.comments.presentation.BottomSheetPostComments
|
||||||
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
import com.isolaatti.posting.common.domain.OnUserInteractedWithPostCallback
|
||||||
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment
|
||||||
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
import com.isolaatti.posting.posts.presentation.PostsRecyclerViewAdapter
|
||||||
|
import com.isolaatti.posting.posts.ui.CreatePostActivity
|
||||||
import com.isolaatti.profile.ui.ProfileActivity
|
import com.isolaatti.profile.ui.ProfileActivity
|
||||||
import com.isolaatti.settings.ui.SettingsActivity
|
import com.isolaatti.settings.ui.SettingsActivity
|
||||||
import com.isolaatti.utils.PicassoImagesPluginDef
|
import com.isolaatti.utils.PicassoImagesPluginDef
|
||||||
|
import com.isolaatti.utils.UrlGen
|
||||||
|
import com.squareup.picasso.Picasso
|
||||||
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
|
||||||
@ -37,9 +48,16 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
|
|
||||||
private val viewModel: PostsViewModel by activityViewModels()
|
private val viewModel: PostsViewModel by activityViewModels()
|
||||||
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
private val errorViewModel: ErrorMessageViewModel by activityViewModels()
|
||||||
|
private val screenViewModel: FeedViewModel by viewModels()
|
||||||
private lateinit var viewBinding: FragmentFeedBinding
|
private lateinit var viewBinding: FragmentFeedBinding
|
||||||
private lateinit var adapter: PostsRecyclerViewAdapter
|
private lateinit var adapter: PostsRecyclerViewAdapter
|
||||||
|
|
||||||
|
private val createDiscussion = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
|
||||||
|
if(it.resultCode == Activity.RESULT_OK) {
|
||||||
|
Toast.makeText(requireContext(), R.string.posted_successfully, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onCreateView(
|
override fun onCreateView(
|
||||||
inflater: LayoutInflater, container: ViewGroup?,
|
inflater: LayoutInflater, container: ViewGroup?,
|
||||||
savedInstanceState: Bundle?
|
savedInstanceState: Bundle?
|
||||||
@ -99,6 +117,33 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
viewModel.getFeed(refresh = false)
|
viewModel.getFeed(refresh = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
viewBinding.topAppBar.setOnMenuItemClickListener {
|
||||||
|
when(it.itemId) {
|
||||||
|
R.id.menu_item_new_discussion -> {
|
||||||
|
createDiscussion.launch(Intent(requireContext(),CreatePostActivity::class.java))
|
||||||
|
true
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
screenViewModel.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)
|
||||||
|
|
||||||
|
Picasso.get().load(UrlGen.userProfileImage(it.id)).into(image)
|
||||||
|
|
||||||
|
textViewName?.text = it.name
|
||||||
|
textViewEmail?.text = it.email
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
viewModel.posts.observe(viewLifecycleOwner){
|
viewModel.posts.observe(viewLifecycleOwner){
|
||||||
if (it != null) {
|
if (it != null) {
|
||||||
@ -128,6 +173,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback {
|
|||||||
PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId))
|
PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
screenViewModel.getProfile()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onNewMenuItemClicked(v: View) {
|
fun onNewMenuItemClicked(v: View) {
|
||||||
|
|||||||
@ -0,0 +1,42 @@
|
|||||||
|
package com.isolaatti.home.presentation
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.isolaatti.auth.domain.AuthRepository
|
||||||
|
import com.isolaatti.profile.data.remote.UserProfileDto
|
||||||
|
import com.isolaatti.profile.domain.use_case.GetProfile
|
||||||
|
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 FeedViewModel @Inject constructor(private val getProfileUseCase: GetProfile, private val authRepository: AuthRepository) : ViewModel() {
|
||||||
|
|
||||||
|
// User profile
|
||||||
|
private val _userProfile: MutableLiveData<UserProfileDto> = MutableLiveData()
|
||||||
|
val userProfile: LiveData<UserProfileDto> get() = _userProfile
|
||||||
|
|
||||||
|
fun getProfile() {
|
||||||
|
viewModelScope.launch {
|
||||||
|
authRepository.getUserId().onEach {userId ->
|
||||||
|
userId?.let {
|
||||||
|
getProfileUseCase(userId).onEach {profile ->
|
||||||
|
if(profile is Resource.Success) {
|
||||||
|
profile.data?.let { _userProfile.postValue(it) }
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
}
|
||||||
|
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -2,12 +2,14 @@ package com.isolaatti.posting
|
|||||||
|
|
||||||
import com.isolaatti.connectivity.RetrofitClient
|
import com.isolaatti.connectivity.RetrofitClient
|
||||||
import com.isolaatti.posting.posts.data.remote.FeedsApi
|
import com.isolaatti.posting.posts.data.remote.FeedsApi
|
||||||
|
import com.isolaatti.posting.posts.data.remote.PostApi
|
||||||
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
|
import com.isolaatti.posting.posts.data.repository.PostsRepositoryImpl
|
||||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
import dagger.hilt.components.SingletonComponent
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import retrofit2.create
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
@ -18,7 +20,12 @@ class Module {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
fun providePostsRepository(feedsApi: FeedsApi): PostsRepository {
|
fun providePostApi(retrofitClient: RetrofitClient): PostApi {
|
||||||
return PostsRepositoryImpl(feedsApi)
|
return retrofitClient.client.create(PostApi::class.java)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
fun providePostsRepository(feedsApi: FeedsApi, postApi: PostApi): PostsRepository {
|
||||||
|
return PostsRepositoryImpl(feedsApi, postApi)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,8 @@
|
|||||||
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
data class CreatePostDto(
|
||||||
|
val privacy: Int,
|
||||||
|
val content: String,
|
||||||
|
val audioId: String?,
|
||||||
|
val squadId: String?
|
||||||
|
)
|
||||||
@ -0,0 +1,3 @@
|
|||||||
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
data class DeletePostDto(val id: Long)
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
data class EditPostDto(
|
||||||
|
val privacy: Int,
|
||||||
|
val content: String,
|
||||||
|
val audioId: String?,
|
||||||
|
val squadId: String?,
|
||||||
|
val postId: Long
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
const val PRIVACY_PRIVATE = 1
|
||||||
|
const val PRIVACY_ISOLAATTI = 2
|
||||||
|
const val PRIVACY_PUBLIC = 3
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,9 @@
|
|||||||
package com.isolaatti.posting.posts.data.remote
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
import android.os.Parcel
|
||||||
|
import android.os.Parcelable
|
||||||
|
import java.io.Serializable
|
||||||
|
|
||||||
data class FeedDto(
|
data class FeedDto(
|
||||||
val data: MutableList<PostDto>,
|
val data: MutableList<PostDto>,
|
||||||
var moreContent: Boolean
|
var moreContent: Boolean
|
||||||
@ -20,17 +24,89 @@ data class FeedDto(
|
|||||||
val userName: String,
|
val userName: String,
|
||||||
val squadName: String?,
|
val squadName: String?,
|
||||||
var liked: Boolean
|
var liked: Boolean
|
||||||
) {
|
): Parcelable {
|
||||||
|
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readParcelable(Post::class.java.classLoader)!!,
|
||||||
|
parcel.readInt(),
|
||||||
|
parcel.readInt(),
|
||||||
|
parcel.readString()!!,
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readByte() != 0.toByte()
|
||||||
|
)
|
||||||
|
|
||||||
data class Post(
|
data class Post(
|
||||||
val id: Long,
|
val id: Long,
|
||||||
var textContent: String,
|
var textContent: String,
|
||||||
val userId: Int,
|
val userId: Int,
|
||||||
val privacy: Int,
|
val privacy: Int,
|
||||||
val date: String,
|
val date: String,
|
||||||
var audioId: String,
|
var audioId: String?,
|
||||||
val squadId: String,
|
val squadId: String?,
|
||||||
val linkedDiscussionId: Long,
|
val linkedDiscussionId: Long,
|
||||||
val linkedCommentId: Long
|
val linkedCommentId: Long
|
||||||
)
|
) : Parcelable {
|
||||||
|
constructor(parcel: Parcel) : this(
|
||||||
|
parcel.readLong(),
|
||||||
|
parcel.readString() ?: "",
|
||||||
|
parcel.readInt(),
|
||||||
|
parcel.readInt(),
|
||||||
|
parcel.readString() ?: "",
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readString(),
|
||||||
|
parcel.readLong(),
|
||||||
|
parcel.readLong()
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeLong(id)
|
||||||
|
parcel.writeString(textContent)
|
||||||
|
parcel.writeInt(userId)
|
||||||
|
parcel.writeInt(privacy)
|
||||||
|
parcel.writeString(date)
|
||||||
|
parcel.writeString(audioId)
|
||||||
|
parcel.writeString(squadId)
|
||||||
|
parcel.writeLong(linkedDiscussionId)
|
||||||
|
parcel.writeLong(linkedCommentId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<Post> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): Post {
|
||||||
|
return Post(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<Post?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun writeToParcel(parcel: Parcel, flags: Int) {
|
||||||
|
parcel.writeParcelable(post, flags)
|
||||||
|
parcel.writeInt(numberOfLikes)
|
||||||
|
parcel.writeInt(numberOfComments)
|
||||||
|
parcel.writeString(userName)
|
||||||
|
parcel.writeString(squadName)
|
||||||
|
parcel.writeByte(if (liked) 1 else 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun describeContents(): Int {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object CREATOR : Parcelable.Creator<PostDto> {
|
||||||
|
override fun createFromParcel(parcel: Parcel): PostDto {
|
||||||
|
return PostDto(parcel)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun newArray(size: Int): Array<PostDto?> {
|
||||||
|
return arrayOfNulls(size)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package com.isolaatti.posting.posts.data.remote
|
||||||
|
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.Body
|
||||||
|
import retrofit2.http.POST
|
||||||
|
|
||||||
|
interface PostApi {
|
||||||
|
@POST("Posting/Make")
|
||||||
|
fun makePost(@Body post: CreatePostDto): Call<FeedDto.PostDto>
|
||||||
|
|
||||||
|
@POST("Posting/Edit")
|
||||||
|
fun editPost(@Body editedPost: EditPostDto): Call<FeedDto.PostDto>
|
||||||
|
|
||||||
|
@POST("Posting/Delete")
|
||||||
|
fun deletePost(@Body postToDelete: DeletePostDto): Call<Any>
|
||||||
|
|
||||||
|
}
|
||||||
@ -1,9 +1,12 @@
|
|||||||
package com.isolaatti.posting.posts.data.repository
|
package com.isolaatti.posting.posts.data.repository
|
||||||
|
|
||||||
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.EditPostDto
|
||||||
import com.isolaatti.posting.posts.data.remote.FeedDto
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
|
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
|
||||||
import com.isolaatti.posting.posts.data.remote.FeedsApi
|
import com.isolaatti.posting.posts.data.remote.FeedsApi
|
||||||
|
import com.isolaatti.posting.posts.data.remote.PostApi
|
||||||
import com.isolaatti.posting.posts.domain.PostsRepository
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
@ -16,7 +19,7 @@ import javax.inject.Inject
|
|||||||
import kotlin.coroutines.resume
|
import kotlin.coroutines.resume
|
||||||
import kotlin.coroutines.suspendCoroutine
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi) : PostsRepository {
|
class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi, private val postApi: PostApi) : PostsRepository {
|
||||||
override fun getFeed(lastId: Long): Flow<Resource<FeedDto>> = flow {
|
override fun getFeed(lastId: Long): Flow<Resource<FeedDto>> = flow {
|
||||||
emit(Resource.Loading())
|
emit(Resource.Loading())
|
||||||
try {
|
try {
|
||||||
@ -55,4 +58,42 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi) :
|
|||||||
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>> = flow {
|
||||||
|
emit(Resource.Loading())
|
||||||
|
try {
|
||||||
|
val result = postApi.makePost(createPostDto).execute()
|
||||||
|
if(result.isSuccessful) {
|
||||||
|
emit(Resource.Success(result.body()))
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
when(result.code()) {
|
||||||
|
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||||
|
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||||
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
|
}
|
||||||
|
} catch(_: Exception) {
|
||||||
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>> = flow {
|
||||||
|
emit(Resource.Loading())
|
||||||
|
try {
|
||||||
|
val result = postApi.editPost(editPostDto).execute()
|
||||||
|
if(result.isSuccessful) {
|
||||||
|
emit(Resource.Success(result.body()))
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
when(result.code()) {
|
||||||
|
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||||
|
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||||
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
|
}
|
||||||
|
} catch(_: Exception) {
|
||||||
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
package com.isolaatti.posting.posts.domain
|
package com.isolaatti.posting.posts.domain
|
||||||
|
|
||||||
|
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.EditPostDto
|
||||||
import com.isolaatti.posting.posts.data.remote.FeedDto
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
|
import com.isolaatti.posting.posts.data.remote.FeedFilterDto
|
||||||
import com.isolaatti.utils.Resource
|
import com.isolaatti.utils.Resource
|
||||||
@ -10,4 +12,7 @@ interface PostsRepository {
|
|||||||
fun getFeed(lastId: Long): Flow<Resource<FeedDto>>
|
fun getFeed(lastId: Long): Flow<Resource<FeedDto>>
|
||||||
|
|
||||||
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow<Resource<FeedDto>>
|
fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow<Resource<FeedDto>>
|
||||||
|
|
||||||
|
fun makePost(createPostDto: CreatePostDto): Flow<Resource<FeedDto.PostDto>>
|
||||||
|
fun editPost(editPostDto: EditPostDto): Flow<Resource<FeedDto.PostDto>>
|
||||||
}
|
}
|
||||||
@ -0,0 +1,20 @@
|
|||||||
|
package com.isolaatti.posting.posts.domain.use_case
|
||||||
|
|
||||||
|
import com.isolaatti.posting.posts.data.remote.EditPostDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class EditPost @Inject constructor(private val postsRepository: PostsRepository) {
|
||||||
|
operator fun invoke(
|
||||||
|
postId: Long,
|
||||||
|
privacy: Int,
|
||||||
|
content: String,
|
||||||
|
audioId: String?,
|
||||||
|
squadId: String?
|
||||||
|
): Flow<Resource<FeedDto.PostDto>> {
|
||||||
|
return postsRepository.editPost(EditPostDto(privacy, content, audioId, squadId, postId))
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +1,19 @@
|
|||||||
package com.isolaatti.posting.posts.domain.use_case
|
package com.isolaatti.posting.posts.domain.use_case
|
||||||
|
|
||||||
class MakePost {
|
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
|
import com.isolaatti.posting.posts.domain.PostsRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class MakePost @Inject constructor(private val postsRepository: PostsRepository) {
|
||||||
|
operator fun invoke(
|
||||||
|
privacy: Int,
|
||||||
|
content: String,
|
||||||
|
audioId: String?,
|
||||||
|
squadId: String?
|
||||||
|
): Flow<Resource<FeedDto.PostDto>> {
|
||||||
|
return postsRepository.makePost(CreatePostDto(privacy, content, audioId, squadId))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -0,0 +1,67 @@
|
|||||||
|
package com.isolaatti.posting.posts.presentation
|
||||||
|
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.isolaatti.posting.posts.data.remote.CreatePostDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.EditPostDto
|
||||||
|
import com.isolaatti.posting.posts.data.remote.FeedDto
|
||||||
|
import com.isolaatti.posting.posts.domain.use_case.EditPost
|
||||||
|
import com.isolaatti.posting.posts.domain.use_case.MakePost
|
||||||
|
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 CreatePostViewModel @Inject constructor(private val makePost: MakePost, private val editPost: EditPost) : ViewModel() {
|
||||||
|
val validation: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
|
val posted: MutableLiveData<FeedDto.PostDto?> = MutableLiveData()
|
||||||
|
val error: MutableLiveData<Resource.Error.ErrorType?> = MutableLiveData()
|
||||||
|
val loading: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
|
|
||||||
|
fun postDiscussion(content: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
makePost(EditPostDto.PRIVACY_ISOLAATTI, content, null, null).onEach {
|
||||||
|
when(it) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
loading.postValue(false)
|
||||||
|
posted.postValue(it.data)
|
||||||
|
}
|
||||||
|
is Resource.Error -> {
|
||||||
|
loading.postValue(false)
|
||||||
|
error.postValue(it.errorType)
|
||||||
|
}
|
||||||
|
is Resource.Loading -> {
|
||||||
|
loading.postValue(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun editDiscussion(postId: Long, content: String) {
|
||||||
|
viewModelScope.launch {
|
||||||
|
editPost(postId, EditPostDto.PRIVACY_ISOLAATTI, content, null, null).onEach {
|
||||||
|
when(it) {
|
||||||
|
is Resource.Success -> {
|
||||||
|
loading.postValue(false)
|
||||||
|
posted.postValue(it.data)
|
||||||
|
}
|
||||||
|
is Resource.Error -> {
|
||||||
|
loading.postValue(false)
|
||||||
|
error.postValue(it.errorType)
|
||||||
|
}
|
||||||
|
is Resource.Loading -> {
|
||||||
|
loading.postValue(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,117 @@
|
|||||||
|
package com.isolaatti.posting.posts.ui
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.PersistableBundle
|
||||||
|
import android.view.View
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.content.res.ResourcesCompat
|
||||||
|
import androidx.core.widget.doOnTextChanged
|
||||||
|
import com.isolaatti.R
|
||||||
|
import com.isolaatti.common.IsolaattiBaseActivity
|
||||||
|
import com.isolaatti.databinding.ActivityCreatePostBinding
|
||||||
|
import com.isolaatti.posting.posts.presentation.CreatePostViewModel
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class CreatePostActivity : IsolaattiBaseActivity() {
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_MODE_CREATE = 0
|
||||||
|
/**
|
||||||
|
* Post activity in edit mode
|
||||||
|
*/
|
||||||
|
const val EXTRA_MODE_EDIT = 1
|
||||||
|
const val EXTRA_KEY_MODE = "mode"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* post id, pass long
|
||||||
|
*/
|
||||||
|
const val EXTRA_KEY_POST_ID = "postId"
|
||||||
|
|
||||||
|
const val EXTRA_KEY_POST_POSTED = "post"
|
||||||
|
}
|
||||||
|
|
||||||
|
lateinit var binding: ActivityCreatePostBinding
|
||||||
|
val viewModel: CreatePostViewModel by viewModels()
|
||||||
|
var mode: Int = EXTRA_MODE_CREATE
|
||||||
|
var postId: Long = 0L
|
||||||
|
override fun onRetry() {
|
||||||
|
if(mode == EXTRA_MODE_EDIT && postId != 0L) {
|
||||||
|
viewModel.editDiscussion(postId, binding.filledTextField.editText?.text.toString())
|
||||||
|
} else {
|
||||||
|
viewModel.postDiscussion(binding.filledTextField.editText?.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
intent.extras?.let {
|
||||||
|
mode = it.getInt(EXTRA_KEY_MODE, EXTRA_MODE_CREATE)
|
||||||
|
postId = it.getLong(EXTRA_KEY_POST_ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
binding = ActivityCreatePostBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
setupUI()
|
||||||
|
setListeners()
|
||||||
|
setObservers()
|
||||||
|
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setupUI() {
|
||||||
|
binding.toolbar.setTitle(if(mode == EXTRA_MODE_EDIT && postId != 0L) R.string.edit else R.string.new_post)
|
||||||
|
binding.filledTextField.requestFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setListeners() {
|
||||||
|
binding.toolbar.setNavigationOnClickListener {
|
||||||
|
exit()
|
||||||
|
}
|
||||||
|
binding.filledTextField.editText?.doOnTextChanged { text, _, _, _ ->
|
||||||
|
// make better validation :)
|
||||||
|
viewModel.validation.postValue(!text.isNullOrEmpty())
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.postButton.setOnClickListener {
|
||||||
|
if(mode == EXTRA_MODE_EDIT && postId != 0L) {
|
||||||
|
viewModel.editDiscussion(postId, binding.filledTextField.editText?.text.toString())
|
||||||
|
} else {
|
||||||
|
viewModel.postDiscussion(binding.filledTextField.editText?.text.toString())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setObservers() {
|
||||||
|
viewModel.validation.observe(this@CreatePostActivity) {
|
||||||
|
binding.postButton.isEnabled = it
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.error.observe(this@CreatePostActivity) {
|
||||||
|
errorViewModel.error.postValue(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.posted.observe(this@CreatePostActivity) {
|
||||||
|
setResult(Activity.RESULT_OK, Intent().apply{
|
||||||
|
putExtra(EXTRA_KEY_POST_POSTED, it)
|
||||||
|
})
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.loading.observe(this@CreatePostActivity) {
|
||||||
|
binding.progressBarLoading.visibility = if(it) View.VISIBLE else View.GONE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun exit() {
|
||||||
|
setResult(Activity.RESULT_CANCELED)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,4 +0,0 @@
|
|||||||
package com.isolaatti.posting.posts.ui
|
|
||||||
|
|
||||||
class CreatePostDialogFragment {
|
|
||||||
}
|
|
||||||
@ -1,12 +1,12 @@
|
|||||||
package com.isolaatti.profile.data.remote
|
package com.isolaatti.profile.data.remote
|
||||||
|
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.POST
|
||||||
import retrofit2.http.Path
|
import retrofit2.http.Path
|
||||||
|
|
||||||
interface ProfileApi {
|
interface ProfileApi {
|
||||||
|
|
||||||
@GET("Fetch/UserProfile/{userId}")
|
@POST("Fetch/UserProfile/{userId}")
|
||||||
fun userProfile(@Path("userId") userId: Int): Call<UserProfileDto>
|
fun userProfile(@Path("userId") userId: Int): Call<UserProfileDto>
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -3,13 +3,29 @@ package com.isolaatti.profile.data.repository
|
|||||||
import com.isolaatti.profile.data.remote.ProfileApi
|
import com.isolaatti.profile.data.remote.ProfileApi
|
||||||
import com.isolaatti.profile.data.remote.UserProfileDto
|
import com.isolaatti.profile.data.remote.UserProfileDto
|
||||||
import com.isolaatti.profile.domain.ProfileRepository
|
import com.isolaatti.profile.domain.ProfileRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import retrofit2.await
|
import retrofit2.await
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository {
|
class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository {
|
||||||
override fun getProfile(): Flow<UserProfileDto> = flow {
|
override fun getProfile(userId: Int): Flow<Resource<UserProfileDto>> = flow {
|
||||||
|
try {
|
||||||
|
val result = profileApi.userProfile(userId).execute()
|
||||||
|
if(result.isSuccessful) {
|
||||||
|
emit(Resource.Success(result.body()))
|
||||||
|
return@flow
|
||||||
|
}
|
||||||
|
|
||||||
|
when(result.code()) {
|
||||||
|
401 -> emit(Resource.Error(Resource.Error.ErrorType.AuthError))
|
||||||
|
404 -> emit(Resource.Error(Resource.Error.ErrorType.NotFoundError))
|
||||||
|
500 -> emit(Resource.Error(Resource.Error.ErrorType.ServerError))
|
||||||
|
else -> emit(Resource.Error(Resource.Error.ErrorType.OtherError))
|
||||||
|
}
|
||||||
|
} catch(_: Exception) {
|
||||||
|
emit(Resource.Error(Resource.Error.ErrorType.NetworkError))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2,8 +2,9 @@ package com.isolaatti.profile.domain
|
|||||||
|
|
||||||
import com.isolaatti.profile.data.remote.ProfileApi
|
import com.isolaatti.profile.data.remote.ProfileApi
|
||||||
import com.isolaatti.profile.data.remote.UserProfileDto
|
import com.isolaatti.profile.data.remote.UserProfileDto
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
interface ProfileRepository {
|
interface ProfileRepository {
|
||||||
fun getProfile(): Flow<UserProfileDto>
|
fun getProfile(userId: Int): Flow<Resource<UserProfileDto>>
|
||||||
}
|
}
|
||||||
@ -1,4 +1,11 @@
|
|||||||
package com.isolaatti.profile.domain.use_case
|
package com.isolaatti.profile.domain.use_case
|
||||||
|
|
||||||
class GetProfile {
|
import com.isolaatti.profile.data.remote.UserProfileDto
|
||||||
|
import com.isolaatti.profile.domain.ProfileRepository
|
||||||
|
import com.isolaatti.utils.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetProfile @Inject constructor(private val profileRepository: ProfileRepository) {
|
||||||
|
operator fun invoke(userId: Int): Flow<Resource<UserProfileDto>> = profileRepository.getProfile(userId)
|
||||||
}
|
}
|
||||||
5
app/src/main/res/drawable/baseline_check_24.xml
Normal file
5
app/src/main/res/drawable/baseline_check_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="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z"/>
|
||||||
|
</vector>
|
||||||
5
app/src/main/res/drawable/baseline_image_24.xml
Normal file
5
app/src/main/res/drawable/baseline_image_24.xml
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
<vector android:height="24dp"
|
||||||
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<path android:fillColor="@color/on_surface" android:pathData="M21,19V5c0,-1.1 -0.9,-2 -2,-2H5c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h14c1.1,0 2,-0.9 2,-2zM8.5,13.5l2.5,3.01L14.5,12l4.5,6H5l3.5,-4.5z"/>
|
||||||
|
</vector>
|
||||||
@ -1,5 +1,5 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
<vector android:height="24dp"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
|
<path android:fillColor="@color/on_surface" android:pathData="M12,14c1.66,0 2.99,-1.34 2.99,-3L15,5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6c0,1.66 1.34,3 3,3zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11L5,11c0,3.41 2.72,6.23 6,6.72L11,21h2v-3.28c3.28,-0.48 6,-3.3 6,-6.72h-1.7z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
@ -1,9 +1,9 @@
|
|||||||
<vector android:height="24dp" android:tint="#000000"
|
<vector android:height="24dp"
|
||||||
android:viewportHeight="24" android:viewportWidth="24"
|
android:viewportHeight="24" android:viewportWidth="24"
|
||||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M17,19.22H5V7h7V5H5C3.9,5 3,5.9 3,7v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-7h-2V19.22z"/>
|
<path android:fillColor="@color/on_surface" android:pathData="M17,19.22H5V7h7V5H5C3.9,5 3,5.9 3,7v12c0,1.1 0.9,2 2,2h12c1.1,0 2,-0.9 2,-2v-7h-2V19.22z"/>
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M19,2h-2v3h-3c0.01,0.01 0,2 0,2h3v2.99c0.01,0.01 2,0 2,0V7h3V5h-3V2z"/>
|
<path android:fillColor="@color/on_surface" android:pathData="M19,2h-2v3h-3c0.01,0.01 0,2 0,2h3v2.99c0.01,0.01 2,0 2,0V7h3V5h-3V2z"/>
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7,9h8v2h-8z"/>
|
<path android:fillColor="@color/on_surface" android:pathData="M7,9h8v2h-8z"/>
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7,12l0,2l8,0l0,-2l-3,0z"/>
|
<path android:fillColor="@color/on_surface" android:pathData="M7,12l0,2l8,0l0,-2l-3,0z"/>
|
||||||
<path android:fillColor="@android:color/white" android:pathData="M7,15h8v2h-8z"/>
|
<path android:fillColor="@color/on_surface" android:pathData="M7,15h8v2h-8z"/>
|
||||||
</vector>
|
</vector>
|
||||||
|
|||||||
70
app/src/main/res/layout/activity_create_post.xml
Normal file
70
app/src/main/res/layout/activity_create_post.xml
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
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:id="@+id/appBarLayout"
|
||||||
|
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">
|
||||||
|
<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:navigationIconTint="@color/on_surface"
|
||||||
|
app:title="@string/new_post"
|
||||||
|
app:titleCentered="true">
|
||||||
|
</com.google.android.material.appbar.MaterialToolbar>
|
||||||
|
|
||||||
|
|
||||||
|
</com.google.android.material.appbar.AppBarLayout>
|
||||||
|
|
||||||
|
<androidx.core.widget.NestedScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingBottom="100dp"
|
||||||
|
android:layout_marginTop="?attr/actionBarSize"
|
||||||
|
android:clipToPadding="false">
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
style="?attr/textInputFilledStyle"
|
||||||
|
android:id="@+id/filledTextField"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
app:boxBackgroundMode="none"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:hintEnabled="false">
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:hint="@string/what_do_you_want_to_talk_about_markdown_is_compatible_you_can_record_an_audio_if_you_want"/>
|
||||||
|
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progress_bar_loading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:visibility="gone"
|
||||||
|
tools:visibility="visible"/>
|
||||||
|
|
||||||
|
<com.google.android.material.bottomappbar.BottomAppBar
|
||||||
|
android:id="@+id/bottomAppBar"
|
||||||
|
style="@style/Widget.Material3.BottomAppBar"
|
||||||
|
app:menu="@menu/discussion_creator_menu"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="bottom" />
|
||||||
|
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||||
|
android:id="@+id/postButton"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:srcCompat="@drawable/baseline_send_24"
|
||||||
|
app:layout_anchor="@id/bottomAppBar" />
|
||||||
|
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||||
@ -1,15 +1,48 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout 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_width="match_parent"
|
||||||
android:layout_height="match_parent">
|
android:layout_height="match_parent"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:background="@color/purple">
|
||||||
|
|
||||||
|
<com.google.android.material.imageview.ShapeableImageView
|
||||||
|
android:id="@+id/profileImageView"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:shapeAppearance="@style/ShapeAppearanceOverlay.Avatar"
|
||||||
|
tools:srcCompat="@tools:sample/avatars" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="wrap_content"
|
android:id="@+id/textViewName"
|
||||||
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginStart="8dp"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginTop="16dp"
|
||||||
android:layout_marginEnd="24dp"
|
android:text="TextView"
|
||||||
android:textAppearance="?attr/textAppearanceHeadlineSmall"
|
android:textAlignment="textStart"
|
||||||
android:textColor="?attr/colorOnSurface"
|
android:textColor="@android:color/white"
|
||||||
android:text="@string/app_name" />
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
</LinearLayout>
|
app:layout_constraintHorizontal_bias="0.0"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/profileImageView"
|
||||||
|
app:layout_constraintTop_toTopOf="@+id/profileImageView"
|
||||||
|
tools:text="Name" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textViewEmail"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:text="TextView"
|
||||||
|
android:textAlignment="textStart"
|
||||||
|
android:textColor="@android:color/white"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/profileImageView"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/textViewName" />
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
11
app/src/main/res/menu/discussion_creator_menu.xml
Normal file
11
app/src/main/res/menu/discussion_creator_menu.xml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
<item android:title="@string/add_audio"
|
||||||
|
android:icon="@drawable/baseline_mic_24"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
<item android:title="@string/add_image"
|
||||||
|
android:icon="@drawable/baseline_image_24"
|
||||||
|
app:showAsAction="ifRoom"/>
|
||||||
|
|
||||||
|
</menu>
|
||||||
@ -1,9 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:icon="@drawable/baseline_add_24"
|
|
||||||
app:showAsAction="always"
|
|
||||||
android:title="@string/new_post" />
|
|
||||||
</menu>
|
|
||||||
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<color name="purple">#4d3b68</color>
|
<color name="purple">#4d3b68</color>
|
||||||
<color name="purple_lighter">#7015ea</color>
|
<color name="purple_lighter">#3F0095</color>
|
||||||
<color name="surface">#1D1725</color>
|
<color name="surface">#1D1725</color>
|
||||||
<color name="on_surface">#FFFFFF</color>
|
<color name="on_surface">#FFFFFF</color>
|
||||||
</resources>
|
</resources>
|
||||||
@ -43,4 +43,8 @@
|
|||||||
<string name="add_audio">New audio</string>
|
<string name="add_audio">New audio</string>
|
||||||
<string name="load_more">Load more</string>
|
<string name="load_more">Load more</string>
|
||||||
<string name="there_is_no_more_content_to_show">There is no more content to show</string>
|
<string name="there_is_no_more_content_to_show">There is no more content to show</string>
|
||||||
|
<string name="post">Post</string>
|
||||||
|
<string name="add_image">Add image</string>
|
||||||
|
<string name="what_do_you_want_to_talk_about_markdown_is_compatible_you_can_record_an_audio_if_you_want">What do you want to talk about? Markdown is compatible. You can record an audio if you want.</string>
|
||||||
|
<string name="posted_successfully">Posted!</string>
|
||||||
</resources>
|
</resources>
|
||||||
Loading…
x
Reference in New Issue
Block a user