From 72290eb50ff03d003dbaceee677150a65ae79732 Mon Sep 17 00:00:00 2001 From: Erik Cavazos Date: Thu, 27 Jul 2023 22:49:55 -0600 Subject: [PATCH] creacion de post, se ve informacion de perfil en drawer y otros cambios --- app/src/main/AndroidManifest.xml | 1 + app/src/main/java/com/isolaatti/MainModule.kt | 1 - .../main/java/com/isolaatti/auth/Module.kt | 13 +- .../isolaatti/auth/data/AuthRepositoryImpl.kt | 15 ++- .../isolaatti/auth/data/local/KeyValueDao.kt | 15 +++ .../auth/data/local/KeyValueEntity.kt | 10 ++ .../auth/data/remote/AuthTokenDto.kt | 2 +- .../isolaatti/auth/domain/AuthRepository.kt | 2 + .../isolaatti/common/IsolaattiBaseActivity.kt | 3 +- .../com/isolaatti/database/AppDatabase.kt | 12 ++ .../java/com/isolaatti/database/Module.kt | 20 +++ .../java/com/isolaatti/home/FeedFragment.kt | 46 +++++++ .../home/presentation/FeedViewModel.kt | 42 +++++++ .../main/java/com/isolaatti/posting/Module.kt | 11 +- .../posts/data/remote/CreatePostDto.kt | 8 ++ .../posts/data/remote/DeletePostDto.kt | 3 + .../posting/posts/data/remote/EditPostDto.kt | 15 +++ .../posting/posts/data/remote/FeedDto.kt | 84 ++++++++++++- .../posting/posts/data/remote/PostApi.kt | 17 +++ .../data/repository/PostsRepositoryImpl.kt | 43 ++++++- .../posting/posts/domain/PostsRepository.kt | 5 + .../posting/posts/domain/use_case/EditPost.kt | 20 +++ .../posting/posts/domain/use_case/MakePost.kt | 17 ++- .../posts/presentation/CreatePostViewModel.kt | 67 ++++++++++ .../posting/posts/ui/CreatePostActivity.kt | 117 ++++++++++++++++++ .../posts/ui/CreatePostDialogFragment.kt | 4 - .../profile/data/remote/ProfileApi.kt | 4 +- .../data/repository/ProfileRepositoryImpl.kt | 18 ++- .../profile/domain/ProfileRepository.kt | 3 +- .../profile/domain/use_case/GetProfile.kt | 9 +- .../main/res/drawable/baseline_check_24.xml | 5 + .../main/res/drawable/baseline_image_24.xml | 5 + app/src/main/res/drawable/baseline_mic_24.xml | 4 +- .../res/drawable/baseline_post_add_24.xml | 12 +- .../main/res/layout/activity_create_post.xml | 70 +++++++++++ .../main/res/layout/header_drawer_home.xml | 53 ++++++-- .../main/res/menu/discussion_creator_menu.xml | 11 ++ app/src/main/res/menu/home_topbar_menu.xml | 9 -- app/src/main/res/values-night/colors.xml | 2 +- app/src/main/res/values/strings.xml | 4 + 40 files changed, 749 insertions(+), 53 deletions(-) create mode 100644 app/src/main/java/com/isolaatti/auth/data/local/KeyValueDao.kt create mode 100644 app/src/main/java/com/isolaatti/auth/data/local/KeyValueEntity.kt create mode 100644 app/src/main/java/com/isolaatti/database/AppDatabase.kt create mode 100644 app/src/main/java/com/isolaatti/database/Module.kt create mode 100644 app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt create mode 100644 app/src/main/java/com/isolaatti/posting/posts/data/remote/CreatePostDto.kt create mode 100644 app/src/main/java/com/isolaatti/posting/posts/data/remote/DeletePostDto.kt create mode 100644 app/src/main/java/com/isolaatti/posting/posts/data/remote/EditPostDto.kt create mode 100644 app/src/main/java/com/isolaatti/posting/posts/data/remote/PostApi.kt create mode 100644 app/src/main/java/com/isolaatti/posting/posts/domain/use_case/EditPost.kt create mode 100644 app/src/main/java/com/isolaatti/posting/posts/presentation/CreatePostViewModel.kt create mode 100644 app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt delete mode 100644 app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostDialogFragment.kt create mode 100644 app/src/main/res/drawable/baseline_check_24.xml create mode 100644 app/src/main/res/drawable/baseline_image_24.xml create mode 100644 app/src/main/res/layout/activity_create_post.xml create mode 100644 app/src/main/res/menu/discussion_creator_menu.xml delete mode 100644 app/src/main/res/menu/home_topbar_menu.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 398dcc5..d66df83 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ + \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/MainModule.kt b/app/src/main/java/com/isolaatti/MainModule.kt index 3dbf0e8..a06d544 100644 --- a/app/src/main/java/com/isolaatti/MainModule.kt +++ b/app/src/main/java/com/isolaatti/MainModule.kt @@ -25,5 +25,4 @@ class MainModule { return RetrofitClient(authenticationInterceptor) } - } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/Module.kt b/app/src/main/java/com/isolaatti/auth/Module.kt index d326293..e54f335 100644 --- a/app/src/main/java/com/isolaatti/auth/Module.kt +++ b/app/src/main/java/com/isolaatti/auth/Module.kt @@ -1,13 +1,17 @@ package com.isolaatti.auth +import android.content.Context 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.remote.AuthApi import com.isolaatti.auth.domain.AuthRepository import com.isolaatti.connectivity.RetrofitClient +import com.isolaatti.database.AppDatabase import dagger.Module import dagger.Provides import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.components.SingletonComponent @Module @@ -18,7 +22,12 @@ class Module { return retrofitClient.client.create(AuthApi::class.java) } @Provides - fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi): AuthRepository { - return AuthRepositoryImpl(tokenStorage, authApi) + fun provideKeyValueDao(database: AppDatabase): KeyValueDao { + return database.keyValueDao() + } + + @Provides + fun provideAuthRepository(tokenStorage: TokenStorage, authApi: AuthApi, keyValueDao: KeyValueDao): AuthRepository { + return AuthRepositoryImpl(tokenStorage, authApi, keyValueDao) } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt index 8994698..d851c59 100644 --- a/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/auth/data/AuthRepositoryImpl.kt @@ -1,5 +1,7 @@ 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.local.TokenStorage import com.isolaatti.auth.data.remote.AuthApi @@ -8,16 +10,18 @@ import com.isolaatti.auth.domain.AuthRepository import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -import retrofit2.await import retrofit2.awaitResponse -import java.io.IOException import javax.inject.Inject class AuthRepositoryImpl @Inject constructor( private val tokenStorage: TokenStorage, - private val authApi: AuthApi + private val authApi: AuthApi, + private val keyValueDao: KeyValueDao ) : AuthRepository { + companion object { + val KEY_USERID = "user_id" + } override fun authWithEmailAndPassword( email: String, password: String @@ -33,6 +37,7 @@ class AuthRepositoryImpl @Inject constructor( return@flow } tokenStorage.storeToken(dto) + keyValueDao.setValue(KeyValueEntity(KEY_USERID, dto.userId.toString())) emit(Resource.Success(true)) return@flow } @@ -60,4 +65,8 @@ class AuthRepositoryImpl @Inject constructor( return tokenStorage.token } + override fun getUserId(): Flow = flow { + emit(keyValueDao.getValue(KEY_USERID).toIntOrNull()) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/local/KeyValueDao.kt b/app/src/main/java/com/isolaatti/auth/data/local/KeyValueDao.kt new file mode 100644 index 0000000..c4a7c87 --- /dev/null +++ b/app/src/main/java/com/isolaatti/auth/data/local/KeyValueDao.kt @@ -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) +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/auth/data/local/KeyValueEntity.kt b/app/src/main/java/com/isolaatti/auth/data/local/KeyValueEntity.kt new file mode 100644 index 0000000..a7c734c --- /dev/null +++ b/app/src/main/java/com/isolaatti/auth/data/local/KeyValueEntity.kt @@ -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 +) diff --git a/app/src/main/java/com/isolaatti/auth/data/remote/AuthTokenDto.kt b/app/src/main/java/com/isolaatti/auth/data/remote/AuthTokenDto.kt index af44141..13ff700 100644 --- a/app/src/main/java/com/isolaatti/auth/data/remote/AuthTokenDto.kt +++ b/app/src/main/java/com/isolaatti/auth/data/remote/AuthTokenDto.kt @@ -1,6 +1,6 @@ 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 { return token } diff --git a/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt b/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt index 4edd198..c7a3e37 100644 --- a/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt +++ b/app/src/main/java/com/isolaatti/auth/domain/AuthRepository.kt @@ -8,4 +8,6 @@ interface AuthRepository { fun authWithEmailAndPassword(email: String, password: String): Flow> fun logout(): Flow fun getCurrentToken(): AuthTokenDto? + fun getUserId(): Flow + } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt b/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt index dc3d4a0..c046cef 100644 --- a/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt +++ b/app/src/main/java/com/isolaatti/common/IsolaattiBaseActivity.kt @@ -13,6 +13,7 @@ import androidx.lifecycle.Observer import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.isolaatti.R import com.isolaatti.home.HomeActivity +import com.isolaatti.login.LogInActivity import com.isolaatti.utils.Resource import dagger.hilt.android.AndroidEntryPoint @@ -46,7 +47,7 @@ abstract class IsolaattiBaseActivity : AppCompatActivity() { abstract fun onRetry() private val onAcceptReAuthClick = DialogInterface.OnClickListener { _, _ -> - + signInActivityResult.launch(Intent(this, LogInActivity::class.java)) } private fun showReAuthDialog() { diff --git a/app/src/main/java/com/isolaatti/database/AppDatabase.kt b/app/src/main/java/com/isolaatti/database/AppDatabase.kt new file mode 100644 index 0000000..efe4a95 --- /dev/null +++ b/app/src/main/java/com/isolaatti/database/AppDatabase.kt @@ -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 + +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/database/Module.kt b/app/src/main/java/com/isolaatti/database/Module.kt new file mode 100644 index 0000000..f16aa64 --- /dev/null +++ b/app/src/main/java/com/isolaatti/database/Module.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/home/FeedFragment.kt b/app/src/main/java/com/isolaatti/home/FeedFragment.kt index dc53e08..24dec8f 100644 --- a/app/src/main/java/com/isolaatti/home/FeedFragment.kt +++ b/app/src/main/java/com/isolaatti/home/FeedFragment.kt @@ -1,5 +1,6 @@ package com.isolaatti.home +import android.app.Activity import android.content.Intent import android.os.Bundle import android.util.Log @@ -7,20 +8,30 @@ import androidx.fragment.app.Fragment import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.activity.result.contract.ActivityResultContracts +import androidx.constraintlayout.widget.ConstraintLayout import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels import androidx.recyclerview.widget.LinearLayoutManager import com.isolaatti.BuildConfig import com.isolaatti.R import com.isolaatti.common.ErrorMessageViewModel import com.isolaatti.databinding.FragmentFeedBinding +import com.isolaatti.home.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.posting.posts.ui.CreatePostActivity import com.isolaatti.profile.ui.ProfileActivity import com.isolaatti.settings.ui.SettingsActivity import com.isolaatti.utils.PicassoImagesPluginDef +import com.isolaatti.utils.UrlGen +import com.squareup.picasso.Picasso import dagger.hilt.android.AndroidEntryPoint import io.noties.markwon.AbstractMarkwonPlugin import io.noties.markwon.Markwon @@ -37,9 +48,16 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { private val viewModel: PostsViewModel by activityViewModels() private val errorViewModel: ErrorMessageViewModel by activityViewModels() + private val screenViewModel: FeedViewModel by viewModels() private lateinit var viewBinding: FragmentFeedBinding 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( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? @@ -99,6 +117,33 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { 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){ if (it != null) { @@ -128,6 +173,7 @@ class FeedFragment : Fragment(), OnUserInteractedWithPostCallback { PostsRecyclerViewAdapter.UpdateEvent.UpdateType.POST_LIKED, it.postId)) } } + screenViewModel.getProfile() } fun onNewMenuItemClicked(v: View) { diff --git a/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt new file mode 100644 index 0000000..aff554c --- /dev/null +++ b/app/src/main/java/com/isolaatti/home/presentation/FeedViewModel.kt @@ -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 = MutableLiveData() + val userProfile: LiveData 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) + + } + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/Module.kt b/app/src/main/java/com/isolaatti/posting/Module.kt index a09a669..2f0cb0d 100644 --- a/app/src/main/java/com/isolaatti/posting/Module.kt +++ b/app/src/main/java/com/isolaatti/posting/Module.kt @@ -2,12 +2,14 @@ package com.isolaatti.posting import com.isolaatti.connectivity.RetrofitClient 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.domain.PostsRepository import dagger.Module import dagger.Provides import dagger.hilt.InstallIn import dagger.hilt.components.SingletonComponent +import retrofit2.create @Module @InstallIn(SingletonComponent::class) @@ -18,7 +20,12 @@ class Module { } @Provides - fun providePostsRepository(feedsApi: FeedsApi): PostsRepository { - return PostsRepositoryImpl(feedsApi) + fun providePostApi(retrofitClient: RetrofitClient): PostApi { + return retrofitClient.client.create(PostApi::class.java) + } + + @Provides + fun providePostsRepository(feedsApi: FeedsApi, postApi: PostApi): PostsRepository { + return PostsRepositoryImpl(feedsApi, postApi) } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/remote/CreatePostDto.kt b/app/src/main/java/com/isolaatti/posting/posts/data/remote/CreatePostDto.kt new file mode 100644 index 0000000..05fdf0c --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/data/remote/CreatePostDto.kt @@ -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? +) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/remote/DeletePostDto.kt b/app/src/main/java/com/isolaatti/posting/posts/data/remote/DeletePostDto.kt new file mode 100644 index 0000000..52d99f0 --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/data/remote/DeletePostDto.kt @@ -0,0 +1,3 @@ +package com.isolaatti.posting.posts.data.remote + +data class DeletePostDto(val id: Long) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/remote/EditPostDto.kt b/app/src/main/java/com/isolaatti/posting/posts/data/remote/EditPostDto.kt new file mode 100644 index 0000000..daf4832 --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/data/remote/EditPostDto.kt @@ -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 + } +} diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedDto.kt b/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedDto.kt index 59749e1..7a33375 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedDto.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/data/remote/FeedDto.kt @@ -1,5 +1,9 @@ package com.isolaatti.posting.posts.data.remote +import android.os.Parcel +import android.os.Parcelable +import java.io.Serializable + data class FeedDto( val data: MutableList, var moreContent: Boolean @@ -20,17 +24,89 @@ data class FeedDto( val userName: String, val squadName: String?, 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( val id: Long, var textContent: String, val userId: Int, val privacy: Int, val date: String, - var audioId: String, - val squadId: String, + var audioId: String?, + val squadId: String?, val linkedDiscussionId: 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 { + override fun createFromParcel(parcel: Parcel): Post { + return Post(parcel) + } + + override fun newArray(size: Int): Array { + 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 { + override fun createFromParcel(parcel: Parcel): PostDto { + return PostDto(parcel) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/remote/PostApi.kt b/app/src/main/java/com/isolaatti/posting/posts/data/remote/PostApi.kt new file mode 100644 index 0000000..739cea9 --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/data/remote/PostApi.kt @@ -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 + + @POST("Posting/Edit") + fun editPost(@Body editedPost: EditPostDto): Call + + @POST("Posting/Delete") + fun deletePost(@Body postToDelete: DeletePostDto): Call + +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt index 35eaf99..9f728ac 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/data/repository/PostsRepositoryImpl.kt @@ -1,9 +1,12 @@ package com.isolaatti.posting.posts.data.repository 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.FeedFilterDto 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.utils.Resource import kotlinx.coroutines.flow.Flow @@ -16,7 +19,7 @@ import javax.inject.Inject import kotlin.coroutines.resume 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> = flow { emit(Resource.Loading()) try { @@ -55,4 +58,42 @@ class PostsRepositoryImpl @Inject constructor(private val feedsApi: FeedsApi) : emit(Resource.Error(Resource.Error.ErrorType.NetworkError)) } } + + override fun makePost(createPostDto: CreatePostDto): Flow> = 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> = 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)) + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt index 68c92cf..69ca2b5 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/domain/PostsRepository.kt @@ -1,5 +1,7 @@ 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.FeedFilterDto import com.isolaatti.utils.Resource @@ -10,4 +12,7 @@ interface PostsRepository { fun getFeed(lastId: Long): Flow> fun getProfilePosts(userId: Int, lastId: Long, olderFirst: Boolean, filter: FeedFilterDto): Flow> + + fun makePost(createPostDto: CreatePostDto): Flow> + fun editPost(editPostDto: EditPostDto): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/domain/use_case/EditPost.kt b/app/src/main/java/com/isolaatti/posting/posts/domain/use_case/EditPost.kt new file mode 100644 index 0000000..0dc02f5 --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/domain/use_case/EditPost.kt @@ -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> { + return postsRepository.editPost(EditPostDto(privacy, content, audioId, squadId, postId)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/domain/use_case/MakePost.kt b/app/src/main/java/com/isolaatti/posting/posts/domain/use_case/MakePost.kt index 0a6ffa9..6c6c67a 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/domain/use_case/MakePost.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/domain/use_case/MakePost.kt @@ -1,4 +1,19 @@ 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> { + return postsRepository.makePost(CreatePostDto(privacy, content, audioId, squadId)) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/CreatePostViewModel.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/CreatePostViewModel.kt new file mode 100644 index 0000000..e48dccb --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/CreatePostViewModel.kt @@ -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 = MutableLiveData(false) + val posted: MutableLiveData = MutableLiveData() + val error: MutableLiveData = MutableLiveData() + val loading: MutableLiveData = 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) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt b/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt new file mode 100644 index 0000000..c744bc3 --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostActivity.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostDialogFragment.kt b/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostDialogFragment.kt deleted file mode 100644 index 4d4f1de..0000000 --- a/app/src/main/java/com/isolaatti/posting/posts/ui/CreatePostDialogFragment.kt +++ /dev/null @@ -1,4 +0,0 @@ -package com.isolaatti.posting.posts.ui - -class CreatePostDialogFragment { -} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/data/remote/ProfileApi.kt b/app/src/main/java/com/isolaatti/profile/data/remote/ProfileApi.kt index 155fed7..7ff4901 100644 --- a/app/src/main/java/com/isolaatti/profile/data/remote/ProfileApi.kt +++ b/app/src/main/java/com/isolaatti/profile/data/remote/ProfileApi.kt @@ -1,12 +1,12 @@ package com.isolaatti.profile.data.remote import retrofit2.Call -import retrofit2.http.GET +import retrofit2.http.POST import retrofit2.http.Path interface ProfileApi { - @GET("Fetch/UserProfile/{userId}") + @POST("Fetch/UserProfile/{userId}") fun userProfile(@Path("userId") userId: Int): Call } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt b/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt index 78997a6..5af4f67 100644 --- a/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt +++ b/app/src/main/java/com/isolaatti/profile/data/repository/ProfileRepositoryImpl.kt @@ -3,13 +3,29 @@ package com.isolaatti.profile.data.repository import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.data.remote.UserProfileDto import com.isolaatti.profile.domain.ProfileRepository +import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow import retrofit2.await import javax.inject.Inject class ProfileRepositoryImpl @Inject constructor(private val profileApi: ProfileApi) : ProfileRepository { - override fun getProfile(): Flow = flow { + override fun getProfile(userId: Int): Flow> = 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)) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/domain/ProfileRepository.kt b/app/src/main/java/com/isolaatti/profile/domain/ProfileRepository.kt index 8fd59fe..dd3cb20 100644 --- a/app/src/main/java/com/isolaatti/profile/domain/ProfileRepository.kt +++ b/app/src/main/java/com/isolaatti/profile/domain/ProfileRepository.kt @@ -2,8 +2,9 @@ package com.isolaatti.profile.domain import com.isolaatti.profile.data.remote.ProfileApi import com.isolaatti.profile.data.remote.UserProfileDto +import com.isolaatti.utils.Resource import kotlinx.coroutines.flow.Flow interface ProfileRepository { - fun getProfile(): Flow + fun getProfile(userId: Int): Flow> } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfile.kt b/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfile.kt index 12ef136..c135802 100644 --- a/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfile.kt +++ b/app/src/main/java/com/isolaatti/profile/domain/use_case/GetProfile.kt @@ -1,4 +1,11 @@ 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> = profileRepository.getProfile(userId) } \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_check_24.xml b/app/src/main/res/drawable/baseline_check_24.xml new file mode 100644 index 0000000..cf143d4 --- /dev/null +++ b/app/src/main/res/drawable/baseline_check_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_image_24.xml b/app/src/main/res/drawable/baseline_image_24.xml new file mode 100644 index 0000000..f757acc --- /dev/null +++ b/app/src/main/res/drawable/baseline_image_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/baseline_mic_24.xml b/app/src/main/res/drawable/baseline_mic_24.xml index 5eb92eb..81b0217 100644 --- a/app/src/main/res/drawable/baseline_mic_24.xml +++ b/app/src/main/res/drawable/baseline_mic_24.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/baseline_post_add_24.xml b/app/src/main/res/drawable/baseline_post_add_24.xml index 141a0a9..80a084e 100644 --- a/app/src/main/res/drawable/baseline_post_add_24.xml +++ b/app/src/main/res/drawable/baseline_post_add_24.xml @@ -1,9 +1,9 @@ - - - - - - + + + + + diff --git a/app/src/main/res/layout/activity_create_post.xml b/app/src/main/res/layout/activity_create_post.xml new file mode 100644 index 0000000..ecc640a --- /dev/null +++ b/app/src/main/res/layout/activity_create_post.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/header_drawer_home.xml b/app/src/main/res/layout/header_drawer_home.xml index b0b5e63..5412e19 100644 --- a/app/src/main/res/layout/header_drawer_home.xml +++ b/app/src/main/res/layout/header_drawer_home.xml @@ -1,15 +1,48 @@ - + android:layout_height="match_parent" + android:paddingTop="16dp" + android:paddingBottom="16dp" + android:background="@color/purple"> + + - \ No newline at end of file + android:layout_marginStart="8dp" + android:layout_marginTop="16dp" + android:text="TextView" + android:textAlignment="textStart" + android:textColor="@android:color/white" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="0.0" + app:layout_constraintStart_toEndOf="@+id/profileImageView" + app:layout_constraintTop_toTopOf="@+id/profileImageView" + tools:text="Name" /> + + + \ No newline at end of file diff --git a/app/src/main/res/menu/discussion_creator_menu.xml b/app/src/main/res/menu/discussion_creator_menu.xml new file mode 100644 index 0000000..1e686cc --- /dev/null +++ b/app/src/main/res/menu/discussion_creator_menu.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/home_topbar_menu.xml b/app/src/main/res/menu/home_topbar_menu.xml deleted file mode 100644 index 1bc9bae..0000000 --- a/app/src/main/res/menu/home_topbar_menu.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - \ No newline at end of file diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index b98fab3..76e2552 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -1,7 +1,7 @@ #4d3b68 - #7015ea + #3F0095 #1D1725 #FFFFFF \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ad05424..68f6eb4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -43,4 +43,8 @@ New audio Load more There is no more content to show + Post + Add image + What do you want to talk about? Markdown is compatible. You can record an audio if you want. + Posted! \ No newline at end of file