diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index 03b92aa..d5a4cd7 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -18,7 +18,7 @@ @@ -28,7 +28,7 @@ diff --git a/app/build.gradle b/app/build.gradle index be6c315..0c043b8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -41,13 +41,13 @@ android { dependencies { implementation 'androidx.core:core-ktx:1.10.1' - implementation 'androidx.appcompat:appcompat:1.6.0' + implementation 'androidx.appcompat:appcompat:1.6.1' implementation "androidx.recyclerview:recyclerview:1.3.0" implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.core:core-ktx:1.9.0' + implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' - implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.5.1' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' + implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.6.1' + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1' implementation 'com.google.code.gson:gson:2.10.1' implementation 'com.squareup.retrofit2:converter-gson:2.3.0' testImplementation 'junit:junit:4.13.2' @@ -64,7 +64,7 @@ dependencies { // Material 3 - implementation "com.google.android.material:material:1.8.0" + implementation "com.google.android.material:material:1.9.0" // Navigation def nav_version = "2.6.0" @@ -74,7 +74,7 @@ dependencies { androidTestImplementation "androidx.navigation:navigation-testing:$nav_version" // Splash screen - implementation "androidx.core:core-splashscreen:1.0.0" + implementation "androidx.core:core-splashscreen:1.0.1" // Data security implementation "androidx.security:security-crypto:1.0.0" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9e40c5f..cf51f7f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -22,7 +22,7 @@ - + diff --git a/app/src/main/java/com/isolaatti/home/HomeActivity.kt b/app/src/main/java/com/isolaatti/home/HomeActivity.kt index fedb0fe..52d394a 100644 --- a/app/src/main/java/com/isolaatti/home/HomeActivity.kt +++ b/app/src/main/java/com/isolaatti/home/HomeActivity.kt @@ -21,7 +21,8 @@ class HomeActivity : AppCompatActivity() { viewBinding = ActivityHomeBinding.inflate(layoutInflater) setContentView(viewBinding.root) val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment - viewBinding.bottomNavigation.setupWithNavController(navHostFragment.navController) + viewBinding.bottomNavigation?.setupWithNavController(navHostFragment.navController) + viewBinding.navigationRail?.setupWithNavController(navHostFragment.navController) if(savedInstanceState == null) { postsViewModel.getFeed() diff --git a/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt b/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt index ec5a25b..e594b68 100644 --- a/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt +++ b/app/src/main/java/com/isolaatti/posting/comments/presentation/BottomSheetPostComments.kt @@ -4,12 +4,20 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import androidx.appcompat.app.AlertDialog import androidx.fragment.app.activityViewModels import androidx.fragment.app.viewModels +import androidx.lifecycle.Observer import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.google.android.material.dialog.MaterialDialogs +import com.isolaatti.R import com.isolaatti.databinding.BottomSheetPostCommentsBinding import com.isolaatti.posting.common.domain.OnUserInteractedCallback +import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel import com.isolaatti.posting.common.options_bottom_sheet.ui.BottomSheetPostOptionsFragment @@ -23,11 +31,20 @@ import io.noties.markwon.linkify.LinkifyPlugin @AndroidEntryPoint class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedCallback { + private lateinit var viewBinding: BottomSheetPostCommentsBinding val viewModel: CommentsViewModel by viewModels() + val optionsViewModel: BottomSheetPostOptionsViewModel by activityViewModels() + val optionsObserver: Observer = Observer { + if(it.callerId == CALLER_ID) { + optionsViewModel.optionClicked(-1) + } + + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val postId = arguments?.getLong(ARG_POST_ID) @@ -43,6 +60,7 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC ): View { viewBinding = BottomSheetPostCommentsBinding.inflate(inflater) + (dialog as BottomSheetDialog).behavior.state = BottomSheetBehavior.STATE_EXPANDED return viewBinding.root } @@ -71,12 +89,24 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC viewModel.comments.observe(viewLifecycleOwner) { adapter.submitList(it) } + + // New comment area + val textField = viewBinding.newCommentTextField + + textField.setStartIconOnClickListener { + AlertDialog.Builder(requireContext()).setView(R.layout.write_comment_multiline_dialog).show() + } + + optionsViewModel.optionClicked(-1) + optionsViewModel.optionClicked.observe(viewLifecycleOwner, optionsObserver) + } companion object { const val TAG = "BottomSheetPostComments" const val ARG_POST_ID = "postId" + const val CALLER_ID = 10 fun getInstance(postId: Long): BottomSheetPostComments { return BottomSheetPostComments().apply { @@ -89,9 +119,9 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC override fun onOptions(postId: Long) { + optionsViewModel.setOptions(Options.commentOptions, CALLER_ID) val fragment = BottomSheetPostOptionsFragment() fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG) - optionsViewModel.setOptions(Options.commentOptions) } override fun onProfileClick(userId: Int) { diff --git a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/OptionClicked.kt b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/OptionClicked.kt new file mode 100644 index 0000000..a37d8ae --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/OptionClicked.kt @@ -0,0 +1,3 @@ +package com.isolaatti.posting.common.options_bottom_sheet.domain + +data class OptionClicked(val optionId: Int, val callerId: Int) \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt index b0954fd..cc31233 100644 --- a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt +++ b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/domain/Options.kt @@ -10,20 +10,27 @@ data class Options( ) { data class Option( @StringRes val stringRes: Int, - @DrawableRes val icon: Int - ) + @DrawableRes val icon: Int, + val optionId: Int + ) { + companion object { + const val OPTION_DELETE = 1 + const val OPTION_EDIT = 2 + const val OPTION_REPORT = 3 + } + } companion object { val postOptions = Options(R.string.post_options_title, listOf( - Option(R.string.delete, R.drawable.baseline_delete_24), - Option(R.string.edit, R.drawable.baseline_edit_24), - Option(R.string.report, R.drawable.baseline_report_24) + Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE), + Option(R.string.edit, R.drawable.baseline_edit_24, Option.OPTION_EDIT), + Option(R.string.report, R.drawable.baseline_report_24, Option.OPTION_REPORT) )) val commentOptions = Options(R.string.post_options_title, listOf( - Option(R.string.delete, R.drawable.baseline_delete_24), - Option(R.string.edit, R.drawable.baseline_edit_24), - Option(R.string.report, R.drawable.baseline_report_24) + Option(R.string.delete, R.drawable.baseline_delete_24, Option.OPTION_DELETE), + Option(R.string.edit, R.drawable.baseline_edit_24, Option.OPTION_EDIT), + Option(R.string.report, R.drawable.baseline_report_24, Option.OPTION_REPORT) )) } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt index ee73897..65daddf 100644 --- a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt +++ b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/BottomSheetPostOptionsViewModel.kt @@ -3,13 +3,25 @@ package com.isolaatti.posting.common.options_bottom_sheet.presentation import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import com.isolaatti.posting.common.options_bottom_sheet.domain.OptionClicked import com.isolaatti.posting.common.options_bottom_sheet.domain.Options class BottomSheetPostOptionsViewModel : ViewModel() { private val _options: MutableLiveData = MutableLiveData() val options: LiveData get() = _options - fun setOptions(options: Options) { + private var _callerId: Int = 0 + + private val _optionClicked: MutableLiveData = MutableLiveData() + val optionClicked: LiveData get() = _optionClicked + + fun setOptions(options: Options, callerId: Int) { _options.postValue(options) + _callerId = callerId } + + fun optionClicked(optionId: Int) { + _optionClicked.postValue(OptionClicked(optionId, _callerId)) + } + } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/OptionsRecyclerAdapter.kt b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/OptionsRecyclerAdapter.kt new file mode 100644 index 0000000..1d54f07 --- /dev/null +++ b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/presentation/OptionsRecyclerAdapter.kt @@ -0,0 +1,41 @@ +package com.isolaatti.posting.common.options_bottom_sheet.presentation + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.appcompat.content.res.AppCompatResources +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.recyclerview.widget.RecyclerView +import com.isolaatti.databinding.OptionItemBinding +import com.isolaatti.posting.common.options_bottom_sheet.domain.Options + +class OptionsRecyclerAdapter(val options: List, private val optionCallback: OptionsCallback) : RecyclerView.Adapter() { + + inner class OptionViewHolder(val viewBinding: OptionItemBinding) : RecyclerView.ViewHolder(viewBinding.root) + + fun interface OptionsCallback { + fun optionClicked(optionId: Int) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OptionViewHolder { + return OptionViewHolder( + OptionItemBinding.inflate(LayoutInflater.from(parent.context)).apply { + root.layoutParams = ConstraintLayout.LayoutParams( + ConstraintLayout.LayoutParams.MATCH_PARENT, + ConstraintLayout.LayoutParams.WRAP_CONTENT + ) + } + ) + } + + override fun getItemCount(): Int = options.count() + + override fun onBindViewHolder(holder: OptionViewHolder, position: Int) { + holder.viewBinding.optionButton.apply { + text = context.getText(options[position].stringRes) + icon = AppCompatResources.getDrawable(context, options[position].icon) + setOnClickListener { + optionCallback.optionClicked(options[position].optionId) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/ui/BottomSheetPostOptionsFragment.kt b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/ui/BottomSheetPostOptionsFragment.kt index 6a7befe..5bb624e 100644 --- a/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/ui/BottomSheetPostOptionsFragment.kt +++ b/app/src/main/java/com/isolaatti/posting/common/options_bottom_sheet/ui/BottomSheetPostOptionsFragment.kt @@ -8,14 +8,17 @@ import android.widget.ListAdapter import android.widget.ListView import androidx.appcompat.content.res.AppCompatResources import androidx.fragment.app.activityViewModels +import androidx.recyclerview.widget.LinearLayoutManager +import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.bottomsheet.BottomSheetDialogFragment import com.google.android.material.button.MaterialButton import com.isolaatti.R import com.isolaatti.databinding.BottomSheetPostOptionsBinding import com.isolaatti.posting.common.options_bottom_sheet.domain.Options import com.isolaatti.posting.common.options_bottom_sheet.presentation.BottomSheetPostOptionsViewModel +import com.isolaatti.posting.common.options_bottom_sheet.presentation.OptionsRecyclerAdapter -class BottomSheetPostOptionsFragment : BottomSheetDialogFragment() { +class BottomSheetPostOptionsFragment : BottomSheetDialogFragment(), OptionsRecyclerAdapter.OptionsCallback { private lateinit var viewBinding: BottomSheetPostOptionsBinding private val viewModel: BottomSheetPostOptionsViewModel by activityViewModels() @@ -28,27 +31,29 @@ class BottomSheetPostOptionsFragment : BottomSheetDialogFragment() { ): View { viewBinding = BottomSheetPostOptionsBinding.inflate(inflater) - return viewBinding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + viewModel.options.observe(viewLifecycleOwner) { renderOptions(it) } + + viewModel.optionClicked.observe(viewLifecycleOwner) { + if(it.optionId > -1) { + (dialog as BottomSheetDialog).dismiss() + } + + } } + private fun renderOptions(options: Options) { - viewBinding.optionsContainer.removeAllViews() - for(option in options.items) { - val button = MaterialButton(requireContext(), null, com.google.android.material.R.style.Widget_Material3_Button_TextButton) - button.icon = AppCompatResources.getDrawable(requireContext(), option.icon) - button.text = requireContext().getText(option.stringRes) - button.textAlignment = MaterialButton.TEXT_ALIGNMENT_TEXT_START - viewBinding.optionsContainer.addView(button) - } + viewBinding.recyclerOptions.adapter = OptionsRecyclerAdapter(options.items, this) + viewBinding.recyclerOptions.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) } companion object { @@ -56,4 +61,8 @@ class BottomSheetPostOptionsFragment : BottomSheetDialogFragment() { } + + override fun optionClicked(optionId: Int) { + viewModel.optionClicked(optionId) + } } \ No newline at end of file diff --git a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt index 792a151..c5c656b 100644 --- a/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt +++ b/app/src/main/java/com/isolaatti/posting/posts/presentation/PostsRecyclerViewAdapter.kt @@ -34,11 +34,11 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba likeButton.isEnabled = true if(postDto.liked) { - likeButton.setIconTintResource(R.color.purple_700) - likeButton.setTextColor(itemView.context.getColor(R.color.purple_700)) + likeButton.setIconTintResource(R.color.purple_lighter) + likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter)) } else { - likeButton.setIconTintResource(R.color.black) - likeButton.setTextColor(itemView.context.getColor(R.color.black)) + likeButton.setIconTintResource(R.color.on_surface) + likeButton.setTextColor(itemView.context.getColor(R.color.on_surface)) } likeButton.text = postDto.numberOfLikes.toString() @@ -70,11 +70,11 @@ class PostsRecyclerViewAdapter (private val markwon: Markwon, private val callba likeButton.isEnabled = true if(postDto.liked) { - likeButton.setIconTintResource(R.color.purple_700) - likeButton.setTextColor(itemView.context.getColor(R.color.purple_700)) + likeButton.setIconTintResource(R.color.purple_lighter) + likeButton.setTextColor(itemView.context.getColor(R.color.purple_lighter)) } else { - likeButton.setIconTintResource(R.color.black) - likeButton.setTextColor(itemView.context.getColor(R.color.black)) + likeButton.setIconTintResource(R.color.on_surface) + likeButton.setTextColor(itemView.context.getColor(R.color.on_surface)) } likeButton.text = postDto.numberOfLikes.toString() diff --git a/app/src/main/res/drawable/baseline_search_24.xml b/app/src/main/res/drawable/baseline_search_24.xml new file mode 100644 index 0000000..a5687c6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_search_24.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout-land/activity_home.xml b/app/src/main/res/layout-land/activity_home.xml new file mode 100644 index 0000000..958836f --- /dev/null +++ b/app/src/main/res/layout-land/activity_home.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-land/fragment_feed.xml b/app/src/main/res/layout-land/fragment_feed.xml index 1846231..d4032bb 100644 --- a/app/src/main/res/layout-land/fragment_feed.xml +++ b/app/src/main/res/layout-land/fragment_feed.xml @@ -1,13 +1,12 @@ - - @@ -20,63 +19,55 @@ app:layout_constraintTop_toTopOf="parent" tools:context=".feed.ui.FeedFragment"> - - - - - + android:layout_height="match_parent"> - + android:orientation="vertical" + android:id="@+id/linearLayout"> + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:layout_conversion_absoluteHeight="64dp" + tools:layout_conversion_absoluteWidth="531dp"> + app:titleCentered="true" + tools:layout_conversion_absoluteHeight="64dp" + tools:layout_conversion_absoluteWidth="531dp" + tools:layout_editor_absoluteX="360dp" + tools:layout_editor_absoluteY="0dp" /> - + android:layout_width="0dp" + android:layout_height="0dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@+id/topAppBar_layout" + tools:layout_conversion_absoluteHeight="242dp" + tools:layout_conversion_absoluteWidth="531dp" /> + - diff --git a/app/src/main/res/layout/bottom_sheet_post_comments.xml b/app/src/main/res/layout/bottom_sheet_post_comments.xml index f92e218..dd4d844 100644 --- a/app/src/main/res/layout/bottom_sheet_post_comments.xml +++ b/app/src/main/res/layout/bottom_sheet_post_comments.xml @@ -30,41 +30,61 @@ android:id="@+id/recycler_comments" android:layout_width="match_parent" android:layout_height="400dp" - app:layout_constraintBottom_toTopOf="@id/newCommentTextField" + app:layout_constraintBottom_toTopOf="@id/new_comment_area" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/topAppBar_layout" /> - - - - - - - + app:layout_constraintStart_toStartOf="parent"> + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_post_options.xml b/app/src/main/res/layout/bottom_sheet_post_options.xml index bbc837b..3337252 100644 --- a/app/src/main/res/layout/bottom_sheet_post_options.xml +++ b/app/src/main/res/layout/bottom_sheet_post_options.xml @@ -9,32 +9,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content"/> - - - - - - - - + android:layout_height="wrap_content"/> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index 2525143..db6bebc 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -1,12 +1,11 @@ - @@ -16,31 +15,17 @@ android:layout_height="match_parent" tools:context=".feed.ui.FeedFragment"> - - - - - - + android:layout_marginBottom="0dp"> + - + app:navigationIconTint="@color/on_surface" + app:title="@string/app_name" + app:titleCentered="true" /> + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/write_comment_multiline_dialog.xml b/app/src/main/res/layout/write_comment_multiline_dialog.xml new file mode 100644 index 0000000..7d718c8 --- /dev/null +++ b/app/src/main/res/layout/write_comment_multiline_dialog.xml @@ -0,0 +1,40 @@ + + + + + + + + +