This commit is contained in:
erik-everardo 2024-07-14 13:21:34 -06:00
parent c00bd001ea
commit a2c9091598
30 changed files with 593 additions and 170 deletions

View File

@ -79,7 +79,7 @@
<option name="values"> <option name="values">
<map> <map>
<entry key="color" value="000000" /> <entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\erike\Downloads\path1-3.svg" /> <entry key="imagePath" value="C:\Users\erike\isolaatti-icon.png" />
<entry key="scalingPercent" value="74" /> <entry key="scalingPercent" value="74" />
<entry key="trimmed" value="true" /> <entry key="trimmed" value="true" />
</map> </map>
@ -181,6 +181,19 @@
</PersistentState> </PersistentState>
</value> </value>
</entry> </entry>
<entry key="image">
<value>
<PersistentState>
<option name="values">
<map>
<entry key="color" value="000000" />
<entry key="imagePath" value="C:\Users\erike\isolaatti-notification-icon.png" />
<entry key="paddingPercent" value="-10" />
</map>
</option>
</PersistentState>
</value>
</entry>
<entry key="text"> <entry key="text">
<value> <value>
<PersistentState> <PersistentState>
@ -205,6 +218,13 @@
</entry> </entry>
</map> </map>
</option> </option>
<option name="values">
<map>
<entry key="assetType" value="IMAGE" />
<entry key="imageAsset" value="C:\Users\erike\isolaatti-notification-icon.png" />
<entry key="outputName" value="ic_notification" />
</map>
</option>
</PersistentState> </PersistentState>
</value> </value>
</entry> </entry>
@ -308,7 +328,7 @@
<PersistentState> <PersistentState>
<option name="values"> <option name="values">
<map> <map>
<entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/play_arrow/baseline_play_arrow_24.xml" /> <entry key="url" value="file:/$USER_HOME$/AppData/Local/Android/Sdk/icons/material/materialicons/article/baseline_article_24.xml" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>
@ -318,8 +338,8 @@
</option> </option>
<option name="values"> <option name="values">
<map> <map>
<entry key="outputName" value="baseline_play_arrow_24" /> <entry key="outputName" value="baseline_article_24" />
<entry key="sourceFile" value="C:\Users\erike\Downloads\face-kiss-wink-heart-solid.svg" /> <entry key="sourceFile" value="C:\Users\erike\Downloads\hashtag-solid.svg" />
</map> </map>
</option> </option>
</PersistentState> </PersistentState>

70
.idea/navEditor.xml generated
View File

@ -25,6 +25,37 @@
</LayoutPositions> </LayoutPositions>
</value> </value>
</entry> </entry>
<entry key="comment_thread_navigation.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="commentThreadFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-258" />
<option name="y" value="8" />
</Point>
</option>
<option name="myPositions">
<map>
<entry key="action_commentThreadFragment_self">
<value>
<LayoutPositions />
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="followers_navigation.xml"> <entry key="followers_navigation.xml">
<value> <value>
<LayoutPositions> <LayoutPositions>
@ -206,6 +237,18 @@
</LayoutPositions> </LayoutPositions>
</value> </value>
</entry> </entry>
<entry key="profileListingFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="-277" />
<option name="y" value="154" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="searchFragment"> <entry key="searchFragment">
<value> <value>
<LayoutPositions> <LayoutPositions>
@ -217,11 +260,6 @@
</option> </option>
<option name="myPositions"> <option name="myPositions">
<map> <map>
<entry key="action_searchFragment_to_browseProfilesFragment">
<value>
<LayoutPositions />
</value>
</entry>
<entry key="action_searchFragment_to_hashtagPostsFragment"> <entry key="action_searchFragment_to_hashtagPostsFragment">
<value> <value>
<LayoutPositions /> <LayoutPositions />
@ -363,6 +401,28 @@
</LayoutPositions> </LayoutPositions>
</value> </value>
</entry> </entry>
<entry key="profile_listing_navigation.xml">
<value>
<LayoutPositions>
<option name="myPositions">
<map>
<entry key="profileListingFragment">
<value>
<LayoutPositions>
<option name="myPosition">
<Point>
<option name="x" value="40" />
<option name="y" value="40" />
</Point>
</option>
</LayoutPositions>
</value>
</entry>
</map>
</option>
</LayoutPositions>
</value>
</entry>
<entry key="profile_navigation.xml"> <entry key="profile_navigation.xml">
<value> <value>
<LayoutPositions> <LayoutPositions>

View File

@ -22,8 +22,8 @@ android {
applicationId "com.isolaatti" applicationId "com.isolaatti"
minSdk 24 minSdk 24
targetSdk 34 targetSdk 34
versionCode 6 versionCode 7
versionName "0.6-vc6" versionName "0.7-vc7"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} }

View File

@ -1,85 +0,0 @@
package com.isolaatti.audio.audio_selector.presentation
import android.view.LayoutInflater
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.marginStart
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.divider.MaterialDivider
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.common.domain.Playable
import com.isolaatti.audio.drafts.domain.AudioDraft
import com.isolaatti.audio.drafts.presentation.AudioDraftsAdapter
import com.isolaatti.databinding.AudioListItemBinding
class AudioSelectorAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
companion object {
const val TYPE_TITLE = 1
const val TYPE_DIVIDER = 2
const val TYPE_AUDIO = 3
const val TYPE_AUDIO_DRAFT = 4
const val TYPE_UNKNOWN = -1
}
// viewholders
inner class AudioViewHolder(val audioListItemBinding: AudioListItemBinding) : RecyclerView.ViewHolder(audioListItemBinding.root)
inner class TitleViewHolder(val textView: TextView) : RecyclerView.ViewHolder(textView)
inner class DividerViewHolder(val divider: MaterialDivider) : RecyclerView.ViewHolder(divider)
data object Divider
data class TitleItem(val text: String)
private var list: List<Any> = listOf()
fun setList(item: List<Any>) {
}
override fun getItemViewType(position: Int): Int {
return when(list[position]) {
is Divider -> TYPE_DIVIDER
is TitleItem -> TYPE_TITLE
is Audio -> TYPE_AUDIO
is AudioDraft -> TYPE_AUDIO_DRAFT
else -> TYPE_UNKNOWN
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
TYPE_TITLE -> {
val textView = TextView(parent.context)
TitleViewHolder(textView)
}
TYPE_DIVIDER -> {
DividerViewHolder(MaterialDivider(parent.context))
}
TYPE_AUDIO, TYPE_AUDIO_DRAFT -> {
AudioViewHolder(AudioListItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
// this should not enter
else -> {
object: RecyclerView.ViewHolder(LinearLayout(parent.context)){}
}
}
}
override fun getItemCount(): Int {
return list.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when(holder) {
is TitleViewHolder -> {
holder.textView.text = (list[position] as TitleItem).text
}
is AudioViewHolder -> {
}
}
}
}

View File

@ -0,0 +1,18 @@
package com.isolaatti.audio.audio_selector.presentation
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.isolaatti.audio.audio_selector.ui.AudiosListSelectorFragment
class AudioSelectorFragmentAdapter(fragmentActivity: FragmentActivity) : FragmentStateAdapter(fragmentActivity) {
override fun getItemCount(): Int = 2
override fun createFragment(position: Int): Fragment {
return when(position) {
0 -> AudiosListSelectorFragment.getInstance()
1 -> AudiosListSelectorFragment.getInstanceForDrafts()
else -> Fragment()
}
}
}

View File

@ -0,0 +1,43 @@
package com.isolaatti.audio.audio_selector.presentation
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.common.domain.AudiosRepository
import com.isolaatti.audio.common.domain.Playable
import com.isolaatti.audio.drafts.domain.AudioDraft
import com.isolaatti.audio.drafts.domain.repository.AudioDraftsRepository
import com.isolaatti.common.SortingEnum
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import javax.inject.Inject
@HiltViewModel
class AudioSelectorViewModel @Inject constructor(
private val audiosRepository: AudiosRepository,
private val audioDraftsRepository: AudioDraftsRepository
) : ViewModel() {
val audios: MutableLiveData<List<Audio>> = MutableLiveData()
val drafts: MutableLiveData<List<AudioDraft>> = MutableLiveData()
val sorting: MutableLiveData<SortingEnum> = MutableLiveData()
fun setSorting(sort: SortingEnum) {
sorting.value = sort
}
fun getAudioDrafts() {
viewModelScope.launch {
audioDraftsRepository.getAudioDrafts().onEach {
drafts.postValue(it)
}
}
}
fun getAudios() {
viewModelScope.launch {
}
}
}

View File

@ -1,7 +1,17 @@
package com.isolaatti.audio.audio_selector.ui package com.isolaatti.audio.audio_selector.ui
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.widget.PopupMenu
import androidx.navigation.findNavController
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.tabs.TabLayoutMediator
import com.isolaatti.R
import com.isolaatti.audio.audio_selector.presentation.AudioSelectorFragmentAdapter
import com.isolaatti.audio.audio_selector.presentation.AudioSelectorViewModel
import com.isolaatti.audio.recorder.ui.AudioRecorderContract
import com.isolaatti.common.IsolaattiBaseActivity import com.isolaatti.common.IsolaattiBaseActivity
import com.isolaatti.common.SortingEnum
import com.isolaatti.databinding.ActivityAudioSelectorBinding import com.isolaatti.databinding.ActivityAudioSelectorBinding
class AudioSelectorActivity : IsolaattiBaseActivity() { class AudioSelectorActivity : IsolaattiBaseActivity() {
@ -12,13 +22,69 @@ class AudioSelectorActivity : IsolaattiBaseActivity() {
} }
private lateinit var binding: ActivityAudioSelectorBinding private lateinit var binding: ActivityAudioSelectorBinding
private val viewModel: AudioSelectorViewModel by viewModels()
private val audioRecorderLauncher = registerForActivityResult(AudioRecorderContract()) {
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding = ActivityAudioSelectorBinding.inflate(layoutInflater) binding = ActivityAudioSelectorBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
binding.viewpager.adapter = AudioSelectorFragmentAdapter(this)
TabLayoutMediator(binding.tabLayout, binding.viewpager) {tab, position ->
when(position) {
0 -> tab.text = getString(R.string.audios)
1 -> tab.text = getString(R.string.drafts)
}
}.attach()
binding.sort.setText(R.string.descending_by_name)
setupListeners()
}
private fun setupListeners() {
binding.toolbar.setNavigationOnClickListener {
finish()
}
binding.newAudio.setOnClickListener {
audioRecorderLauncher.launch(null)
}
binding.sort.setOnClickListener {
val popupMenu = PopupMenu(this, it)
popupMenu.inflate(R.menu.audios_sort_menu)
popupMenu.setOnMenuItemClickListener { menuItem ->
binding.sort.text = menuItem.title
when(menuItem.itemId) {
R.id.desc_by_name -> {
viewModel.setSorting(SortingEnum.DescendingByName)
true
}
R.id.asc_by_name -> {
viewModel.setSorting(SortingEnum.AscendingByName)
true
}
R.id.asc_by_creation_date -> {
viewModel.setSorting(SortingEnum.AscendingByCreationDate)
true
}
R.id.desc_by_creation_date -> {
viewModel.setSorting(SortingEnum.DescendingByCreationDate)
true
}
else -> false
}
}
popupMenu.show()
}
} }

View File

@ -14,6 +14,10 @@ import java.io.Serializable
*/ */
class AudioSelectorContract : ActivityResultContract<AudioSelectorContract.SelectorConfig, Playable?>() { class AudioSelectorContract : ActivityResultContract<AudioSelectorContract.SelectorConfig, Playable?>() {
/**
* @param forSquad audios source will be a specified squad in the id param
* @param id squad id, should be non null if forSquad is true
*/
data class SelectorConfig(val forSquad: Boolean, val id: String?): Serializable data class SelectorConfig(val forSquad: Boolean, val id: String?): Serializable
override fun createIntent(context: Context, input: SelectorConfig): Intent { override fun createIntent(context: Context, input: SelectorConfig): Intent {

View File

@ -0,0 +1,103 @@
package com.isolaatti.audio.audio_selector.ui
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import com.isolaatti.audio.audio_selector.presentation.AudioSelectorViewModel
import com.isolaatti.audio.audios_list.presentation.AudiosAdapter
import com.isolaatti.databinding.FragmentAudiosListSelectorBinding
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class AudiosListSelectorFragment : Fragment() {
private lateinit var binding: FragmentAudiosListSelectorBinding
private val viewModel: AudioSelectorViewModel by activityViewModels()
private var mode: Int = ARG_VAL_MODE_AUDIOS
private var squadId: String? = null
private lateinit var adapter: AudiosAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.getInt(ARG_MODE)?.let { mode = it }
arguments?.getString(ARG_SQUAD_ID)?.let { squadId = it }
when(mode) {
ARG_VAL_MODE_AUDIOS -> {}
ARG_VAL_MODE_DRAFTS -> {
viewModel.getAudioDrafts()
}
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = FragmentAudiosListSelectorBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = AudiosAdapter(
onPlayClick = {
},
onOptionsClick = {audio, button ->
false
}
)
binding.recycler.adapter = adapter
when(mode) {
ARG_VAL_MODE_AUDIOS -> {
viewModel.audios.observe(viewLifecycleOwner) {
}
}
ARG_VAL_MODE_DRAFTS -> {
viewModel.drafts.observe(viewLifecycleOwner) {
}
}
}
}
companion object {
private const val ARG_MODE = "mode"
private const val ARG_SQUAD_ID = "squadId"
private const val ARG_VAL_MODE_DRAFTS = 0
private const val ARG_VAL_MODE_AUDIOS = 1
fun getInstance(): AudiosListSelectorFragment {
return AudiosListSelectorFragment().apply {
arguments = Bundle().apply {
putInt(ARG_MODE, ARG_VAL_MODE_AUDIOS)
}
}
}
fun getInstanceForDrafts(): AudiosListSelectorFragment {
return AudiosListSelectorFragment().apply {
arguments = Bundle().apply {
putInt(ARG_MODE, ARG_VAL_MODE_DRAFTS)
}
}
}
fun getInstanceForSquad(squadId: String): AudiosListSelectorFragment {
return AudiosListSelectorFragment().apply {
arguments = Bundle().apply {
putString(ARG_SQUAD_ID, squadId)
}
}
}
}
}

View File

@ -70,6 +70,7 @@ class AudiosRepositoryImpl @Inject constructor(private val audiosApi: AudiosApi,
val audioDto = response.body() val audioDto = response.body()
if(audioDto != null) { if(audioDto != null) {
Log.d(LOG_TAG, "emit audio dto") Log.d(LOG_TAG, "emit audio dto")
audiosDraftsDao.deleteDrafts(arrayOf(audioDraftEntity))
emit(Resource.Success(Audio.fromDto(audioDto))) emit(Resource.Success(Audio.fromDto(audioDto)))
} }

View File

@ -6,8 +6,9 @@ import com.isolaatti.MyApplication
import com.isolaatti.audio.common.domain.Playable import com.isolaatti.audio.common.domain.Playable
import com.isolaatti.audio.drafts.data.AudioDraftEntity import com.isolaatti.audio.drafts.data.AudioDraftEntity
import java.io.File import java.io.File
import java.io.Serializable
data class AudioDraft(val id: Long, val name: String, val localStorageRelativePath: String, val size: Long) : Playable() { data class AudioDraft(val id: Long, val name: String, val localStorageRelativePath: String, val size: Long) : Playable(), Serializable {
override val thumbnail: String? override val thumbnail: String?
get() = null get() = null

View File

@ -49,7 +49,7 @@ class AudioRecorderActivity : AppCompatActivity() {
const val LOG_TAG = "AudioRecorderActivity" const val LOG_TAG = "AudioRecorderActivity"
const val IN_EXTRA_DRAFT_ID = "in_draft_id" const val IN_EXTRA_DRAFT_ID = "in_draft_id"
const val OUT_EXTRA_DRAFT_ID = "out_draft_id" const val OUT_EXTRA_DRAFT = "out_draft"
} }
private lateinit var binding: ActivityAudioRecorderBinding private lateinit var binding: ActivityAudioRecorderBinding
@ -204,6 +204,10 @@ class AudioRecorderActivity : AppCompatActivity() {
else -> false else -> false
} }
} }
binding.toolbar.setNavigationOnClickListener {
finish()
}
} }
private fun setupObservers() { private fun setupObservers() {
@ -214,7 +218,7 @@ class AudioRecorderActivity : AppCompatActivity() {
// audio draft is saved! // audio draft is saved!
viewModel.audioDraft.observe(this) { viewModel.audioDraft.observe(this) {
val result = Intent().apply { val result = Intent().apply {
putExtra(OUT_EXTRA_DRAFT_ID, it.id) putExtra(OUT_EXTRA_DRAFT, it)
} }
setResult(RESULT_OK, result) setResult(RESULT_OK, result)
finish() finish()

View File

@ -4,8 +4,9 @@ import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import androidx.activity.result.contract.ActivityResultContract import androidx.activity.result.contract.ActivityResultContract
import com.isolaatti.audio.drafts.domain.AudioDraft
class AudioRecorderContract : ActivityResultContract<Long?, Long?>() { class AudioRecorderContract : ActivityResultContract<Long?, AudioDraft?>() {
override fun createIntent(context: Context, input: Long?): Intent { override fun createIntent(context: Context, input: Long?): Intent {
val intent = Intent(context, AudioRecorderActivity::class.java).apply { val intent = Intent(context, AudioRecorderActivity::class.java).apply {
@ -17,10 +18,10 @@ class AudioRecorderContract : ActivityResultContract<Long?, Long?>() {
return intent return intent
} }
override fun parseResult(resultCode: Int, intent: Intent?): Long? { override fun parseResult(resultCode: Int, intent: Intent?): AudioDraft? {
return when(resultCode){ return when(resultCode){
Activity.RESULT_OK -> { Activity.RESULT_OK -> {
intent?.getLongExtra(AudioRecorderActivity.OUT_EXTRA_DRAFT_ID, 0)?.takeUnless { it == 0L } intent?.getSerializableExtra(AudioRecorderActivity.OUT_EXTRA_DRAFT) as? AudioDraft
} }
else -> null else -> null
} }

View File

@ -0,0 +1,8 @@
package com.isolaatti.common
enum class SortingEnum {
AscendingByName,
DescendingByName,
AscendingByCreationDate,
DescendingByCreationDate
}

View File

@ -23,6 +23,7 @@ data class Options(
const val OPTION_PROFILE_PHOTO_VIEW_PHOTO = 6 const val OPTION_PROFILE_PHOTO_VIEW_PHOTO = 6
const val OPTION_PROFILE_PHOTO_CHANGE_PHOTO = 7 const val OPTION_PROFILE_PHOTO_CHANGE_PHOTO = 7
const val OPTION_PROFILE_PHOTO_REMOVE_PHOTO = 8 const val OPTION_PROFILE_PHOTO_REMOVE_PHOTO = 8
const val OPTION_PROFILE_DESCRIPTION_SET_AUDIO = 9
} }
} }
@ -30,6 +31,7 @@ data class Options(
const val POST_OPTIONS = 1 const val POST_OPTIONS = 1
const val COMMENT_OPTIONS = 2 const val COMMENT_OPTIONS = 2
const val PROFILE_PHOTO_OPTIONS = 3 const val PROFILE_PHOTO_OPTIONS = 3
const val PROFILE_DESCRIPTION_OPTIONS = 4
val noOptions = Options(0, 0, listOf()) val noOptions = Options(0, 0, listOf())
@ -93,5 +95,10 @@ data class Options(
return Options(R.string.profile_photo,PROFILE_PHOTO_OPTIONS, list) return Options(R.string.profile_photo,PROFILE_PHOTO_OPTIONS, list)
} }
fun getProfileDescriptionOptions(): Options {
val list = listOf(Option(R.string.set_audio_description, R.drawable.baseline_audio_file_24, Option.OPTION_PROFILE_DESCRIPTION_SET_AUDIO))
return Options(R.string.description, PROFILE_DESCRIPTION_OPTIONS, list)
}
} }
} }

View File

@ -9,6 +9,7 @@ import com.isolaatti.common.options_bottom_sheet.domain.OptionClicked
import com.isolaatti.common.options_bottom_sheet.domain.Options import com.isolaatti.common.options_bottom_sheet.domain.Options
import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.COMMENT_OPTIONS import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.COMMENT_OPTIONS
import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.POST_OPTIONS import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.POST_OPTIONS
import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.PROFILE_DESCRIPTION_OPTIONS
import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.PROFILE_PHOTO_OPTIONS import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.PROFILE_PHOTO_OPTIONS
import com.isolaatti.settings.domain.UserIdSetting import com.isolaatti.settings.domain.UserIdSetting
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -40,6 +41,8 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
fun setOptions(options: Int, callerId: Int, payload: Ownable? = null) { fun setOptions(options: Int, callerId: Int, payload: Ownable? = null) {
viewModelScope.launch { viewModelScope.launch {
CoroutineScope(Dispatchers.IO).launch { CoroutineScope(Dispatchers.IO).launch {
_payload = payload
_callerId = callerId
when(options) { when(options) {
POST_OPTIONS -> { POST_OPTIONS -> {
userIdSetting.getUserId().onEach { userId -> userIdSetting.getUserId().onEach { userId ->
@ -49,8 +52,6 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
savable = false, savable = false,
snapshotAble = false) snapshotAble = false)
) )
_callerId = callerId
_payload = payload
}.flowOn(Dispatchers.IO).launchIn(this) }.flowOn(Dispatchers.IO).launchIn(this)
} }
COMMENT_OPTIONS -> { COMMENT_OPTIONS -> {
@ -61,17 +62,16 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
savable = false, savable = false,
snapshotAble = false) snapshotAble = false)
) )
_callerId = callerId
_payload = payload
}.flowOn(Dispatchers.IO).launchIn(this) }.flowOn(Dispatchers.IO).launchIn(this)
} }
PROFILE_PHOTO_OPTIONS -> { PROFILE_PHOTO_OPTIONS -> {
userIdSetting.getUserId().onEach { userId -> userIdSetting.getUserId().onEach { userId ->
_options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,)) _options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,))
_callerId = callerId
_payload = payload
}.flowOn(Dispatchers.IO).launchIn(this) }.flowOn(Dispatchers.IO).launchIn(this)
} }
PROFILE_DESCRIPTION_OPTIONS -> {
_options.postValue(Options.getProfileDescriptionOptions())
}
} }
} }

View File

@ -12,7 +12,12 @@ import com.isolaatti.common.OnUserInteractedCallback
import com.isolaatti.utils.UrlGen import com.isolaatti.utils.UrlGen
import io.noties.markwon.Markwon import io.noties.markwon.Markwon
class CommentsRecyclerViewAdapter(private var list: List<Comment>, private val markwon: Markwon, private val callback: OnUserInteractedCallback) : RecyclerView.Adapter<CommentsRecyclerViewAdapter.CommentViewHolder>() { class CommentsRecyclerViewAdapter(
private var list: List<Comment>,
private val markwon: Markwon,
private val callback: OnUserInteractedCallback,
private val onCommentClick: (Comment) -> Unit
) : RecyclerView.Adapter<CommentsRecyclerViewAdapter.CommentViewHolder>() {
private var previousSize = 0 private var previousSize = 0
var blockInfiniteScroll = false var blockInfiniteScroll = false

View File

@ -230,7 +230,9 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
.usePlugin(LinkifyPlugin.create()) .usePlugin(LinkifyPlugin.create())
.build() .build()
adapter = CommentsRecyclerViewAdapter(listOf(), markwon, this) adapter = CommentsRecyclerViewAdapter(listOf(), markwon, this, onCommentClick = {
})
viewBinding.recyclerComments.adapter = adapter viewBinding.recyclerComments.adapter = adapter
viewBinding.recyclerComments.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) viewBinding.recyclerComments.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)

View File

@ -0,0 +1,44 @@
package com.isolaatti.posting.comments.ui
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.navigation.fragment.NavHostFragment
import com.isolaatti.R
import com.isolaatti.databinding.ActivityCommentThreadBinding
class CommentThreadActivity : AppCompatActivity() {
private lateinit var binding: ActivityCommentThreadBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityCommentThreadBinding.inflate(layoutInflater)
setContentView(binding.root)
val commentId = intent.extras?.getLong(EXTRA_COMMENT_ID)
if(commentId == null || commentId == 0L) {
Toast.makeText(this, R.string.invalid_arg, Toast.LENGTH_SHORT).show()
finish()
} else {
(supportFragmentManager.findFragmentById(R.id.fragment_container) as NavHostFragment)
.navController.setGraph(R.navigation.comment_thread_navigation,
Bundle().apply {
putLong(CommentThreadFragment.ARG_COMMENT_ID, commentId)
})
}
}
companion object {
private const val EXTRA_COMMENT_ID = "commentId"
fun startActivity(context: Context, commentId: Long) {
val intent = Intent(context, CommentThreadActivity::class.java).apply {
putExtra(EXTRA_COMMENT_ID, commentId)
}
context.startActivity(intent)
}
}
}

View File

@ -0,0 +1,9 @@
package com.isolaatti.posting.comments.ui
import androidx.fragment.app.Fragment
class CommentThreadFragment : Fragment() {
companion object {
const val ARG_COMMENT_ID = "commentId"
}
}

View File

@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
import com.isolaatti.audio.common.domain.Audio import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.common.domain.Playable import com.isolaatti.audio.common.domain.Playable
import com.isolaatti.audio.common.domain.UploadAudioUC import com.isolaatti.audio.common.domain.UploadAudioUC
import com.isolaatti.audio.drafts.domain.AudioDraft
import com.isolaatti.audio.drafts.domain.repository.AudioDraftsRepository import com.isolaatti.audio.drafts.domain.repository.AudioDraftsRepository
import com.isolaatti.posting.posts.data.remote.CreatePostDto import com.isolaatti.posting.posts.data.remote.CreatePostDto
import com.isolaatti.posting.posts.data.remote.EditPostDto import com.isolaatti.posting.posts.data.remote.EditPostDto
@ -161,15 +162,15 @@ class CreatePostViewModel @Inject constructor(
} }
// call this when user has recorded or selected a draft // call this when user has recorded or selected a draft
fun putAudioDraft(draftId: Long) { fun putAudioDraft(draft: AudioDraft) {
viewModelScope.launch { viewModelScope.launch {
audioDraftsRepository.getAudioDraftById(draftId).onEach { draft -> audioDraftsRepository.getAudioDraftById(draft.id).onEach { draft ->
when(draft) { when(draft) {
is Resource.Error -> {} is Resource.Error -> {}
is Resource.Loading -> {} is Resource.Loading -> {}
is Resource.Success -> { is Resource.Success -> {
audioAttachment.postValue(draft.data) audioAttachment.postValue(draft.data)
this@CreatePostViewModel.audioDraft = draftId this@CreatePostViewModel.audioDraft = draft.data!!.id
} }
} }

View File

@ -40,9 +40,9 @@ class MarkdownEditingFragment : Fragment(){
private var audioPlayerConnector: AudioPlayerConnector? = null private var audioPlayerConnector: AudioPlayerConnector? = null
private val audioRecorderLauncher = registerForActivityResult(AudioRecorderContract()) { draftId -> private val audioRecorderLauncher = registerForActivityResult(AudioRecorderContract()) { audioDraft ->
if(draftId != null) { if(audioDraft != null) {
viewModel.putAudioDraft(draftId) viewModel.putAudioDraft(audioDraft)
binding.viewAnimator.displayedChild = 1 binding.viewAnimator.displayedChild = 1
} }
} }

View File

@ -22,6 +22,7 @@ import coil.load
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.isolaatti.BuildConfig import com.isolaatti.BuildConfig
import com.isolaatti.R import com.isolaatti.R
import com.isolaatti.audio.audio_selector.ui.AudioSelectorContract
import com.isolaatti.audio.audios_list.ui.AudiosFragment import com.isolaatti.audio.audios_list.ui.AudiosFragment
import com.isolaatti.audio.common.domain.Audio import com.isolaatti.audio.common.domain.Audio
import com.isolaatti.audio.common.domain.Playable import com.isolaatti.audio.common.domain.Playable
@ -111,6 +112,10 @@ class ProfileMainFragment : Fragment() {
} }
} }
private val audioSelectorLauncher = registerForActivityResult(AudioSelectorContract()) {
}
private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener { private val audioPlayerConnectorListener = object: AudioPlayerConnector.Listener {
override fun onPlaying(isPlaying: Boolean, audio: Playable) { override fun onPlaying(isPlaying: Boolean, audio: Playable) {
@ -280,6 +285,10 @@ class ProfileMainFragment : Fragment() {
} }
} }
} }
Options.PROFILE_DESCRIPTION_OPTIONS -> {
optionsViewModel.handle()
audioSelectorLauncher.launch(AudioSelectorContract.SelectorConfig(false, null))
}
} }
@ -458,6 +467,11 @@ class ProfileMainFragment : Fragment() {
removeItem(R.id.block_profile_menu_item) removeItem(R.id.block_profile_menu_item)
removeItem(R.id.report_profile_menu_item) removeItem(R.id.report_profile_menu_item)
} }
viewBinding.descriptionCard.setOnClickListener {
optionsViewModel.setOptions(Options.PROFILE_DESCRIPTION_OPTIONS, CALLER_ID, null)
val fragment = BottomSheetPostOptionsFragment()
fragment.show(parentFragmentManager, BottomSheetPostOptionsFragment.TAG)
}
} else { } else {
viewBinding.createPostButton.visibility = View.GONE viewBinding.createPostButton.visibility = View.GONE
viewBinding.topAppBar.menu.removeItem(R.id.edit_profile) viewBinding.topAppBar.menu.removeItem(R.id.edit_profile)

View File

@ -4,65 +4,64 @@
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.appbar.MaterialToolbar <com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar" android:id="@+id/toolbar"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:title="@string/select_audio" app:title="@string/select_audio"
app:navigationIcon="@drawable/baseline_close_24" app:navigationIcon="@drawable/baseline_close_24"
app:layout_constraintTop_toTopOf="parent"/> />
<HorizontalScrollView <HorizontalScrollView
android:id="@+id/filter_container" android:id="@+id/options_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/toolbar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginHorizontal="8dp"> android:layout_marginHorizontal="8dp">
<com.google.android.material.chip.ChipGroup <com.google.android.material.chip.ChipGroup
android:id="@+id/filter" android:id="@+id/options"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:singleLine="true"> app:singleLine="true">
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/chip_drafts" android:id="@+id/new_audio"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/drafts" android:text="@string/record_new_audio"
android:checkable="true" app:chipIcon="@drawable/baseline_mic_24"/>
app:chipIcon="@drawable/baseline_pending_24"/>
<com.google.android.material.chip.Chip
android:id="@+id/chip_audios"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/audios"
android:checkable="true"
app:chipIcon="@drawable/baseline_audio_file_24"/>
<com.google.android.material.chip.Chip <com.google.android.material.chip.Chip
android:id="@+id/sort" android:id="@+id/sort"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/sort" app:chipIcon="@drawable/baseline_sort_24"
app:chipIcon="@drawable/baseline_sort_24"/> android:enabled="false"/>
</com.google.android.material.chip.ChipGroup> </com.google.android.material.chip.ChipGroup>
</HorizontalScrollView> </HorizontalScrollView>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout <com.google.android.material.tabs.TabLayout
android:id="@+id/swipe_to_refresh" android:id="@+id/tab_layout"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:layout_constraintTop_toBottomOf="@id/filter_container"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="wrap_content"
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> app:layout_constraintTop_toBottomOf="@id/options_container"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@id/appbar"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.appbar.MaterialToolbar
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:navigationIcon="@drawable/baseline_arrow_back_24"
/>
</com.google.android.material.appbar.AppBarLayout>
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:name="androidx.navigation.fragment.NavHostFragment"
app:defaultNavHost="true"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior"/>
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_to_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipe_to_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/desc_by_name"
android:title="@string/descending_by_name"/>
<item android:id="@+id/asc_by_name"
android:title="@string/ascending_by_name"/>
<item android:id="@+id/asc_by_creation_date"
android:title="@string/ascending_by_creation_date"/>
<item android:id="@+id/desc_by_creation_date"
android:title="@string/descending_by_creation_date"/>
</menu>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/comment_thread_navigation"
app:startDestination="@id/commentThreadFragment">
<fragment
android:id="@+id/commentThreadFragment"
android:name="com.isolaatti.posting.comments.ui.CommentThreadFragment"
android:label="CommentThreadFragment" >
<action
android:id="@+id/action_commentThreadFragment_self"
app:destination="@id/commentThreadFragment" />
<argument
android:name="commentId"
app:argType="long" />
</fragment>
</navigation>

View File

@ -212,6 +212,12 @@
<string name="edited_at">Edited at %s</string> <string name="edited_at">Edited at %s</string>
<string name="block_profile_dialog_message">Do you really want to block %s?</string> <string name="block_profile_dialog_message">Do you really want to block %s?</string>
<string name="user_profile_picture">%s\'s profile picture</string> <string name="user_profile_picture">%s\'s profile picture</string>
<string name="set_audio_description">Set audio description</string>
<string name="ascending_by_name">Z-A</string>
<string name="descending_by_name">A-Z</string>
<string name="ascending_by_creation_date">older-newer</string>
<string name="descending_by_creation_date">newer-older</string>
<string name="invalid_arg">Invalid value passed</string>
<string-array name="report_reasons"> <string-array name="report_reasons">
<item>Spam</item> <item>Spam</item>
<item>Explicit content</item> <item>Explicit content</item>