WIP
This commit is contained in:
parent
c00bd001ea
commit
a2c9091598
28
.idea/assetWizardSettings.xml
generated
28
.idea/assetWizardSettings.xml
generated
@ -79,7 +79,7 @@
|
||||
<option name="values">
|
||||
<map>
|
||||
<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="trimmed" value="true" />
|
||||
</map>
|
||||
@ -181,6 +181,19 @@
|
||||
</PersistentState>
|
||||
</value>
|
||||
</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">
|
||||
<value>
|
||||
<PersistentState>
|
||||
@ -205,6 +218,13 @@
|
||||
</entry>
|
||||
</map>
|
||||
</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>
|
||||
</value>
|
||||
</entry>
|
||||
@ -308,7 +328,7 @@
|
||||
<PersistentState>
|
||||
<option name="values">
|
||||
<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>
|
||||
</option>
|
||||
</PersistentState>
|
||||
@ -318,8 +338,8 @@
|
||||
</option>
|
||||
<option name="values">
|
||||
<map>
|
||||
<entry key="outputName" value="baseline_play_arrow_24" />
|
||||
<entry key="sourceFile" value="C:\Users\erike\Downloads\face-kiss-wink-heart-solid.svg" />
|
||||
<entry key="outputName" value="baseline_article_24" />
|
||||
<entry key="sourceFile" value="C:\Users\erike\Downloads\hashtag-solid.svg" />
|
||||
</map>
|
||||
</option>
|
||||
</PersistentState>
|
||||
|
||||
70
.idea/navEditor.xml
generated
70
.idea/navEditor.xml
generated
@ -25,6 +25,37 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</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">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
@ -206,6 +237,18 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</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">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
@ -217,11 +260,6 @@
|
||||
</option>
|
||||
<option name="myPositions">
|
||||
<map>
|
||||
<entry key="action_searchFragment_to_browseProfilesFragment">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
</value>
|
||||
</entry>
|
||||
<entry key="action_searchFragment_to_hashtagPostsFragment">
|
||||
<value>
|
||||
<LayoutPositions />
|
||||
@ -363,6 +401,28 @@
|
||||
</LayoutPositions>
|
||||
</value>
|
||||
</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">
|
||||
<value>
|
||||
<LayoutPositions>
|
||||
|
||||
@ -22,8 +22,8 @@ android {
|
||||
applicationId "com.isolaatti"
|
||||
minSdk 24
|
||||
targetSdk 34
|
||||
versionCode 6
|
||||
versionName "0.6-vc6"
|
||||
versionCode 7
|
||||
versionName "0.7-vc7"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
@ -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 -> {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,17 @@
|
||||
package com.isolaatti.audio.audio_selector.ui
|
||||
|
||||
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.SortingEnum
|
||||
import com.isolaatti.databinding.ActivityAudioSelectorBinding
|
||||
|
||||
class AudioSelectorActivity : IsolaattiBaseActivity() {
|
||||
@ -12,13 +22,69 @@ class AudioSelectorActivity : IsolaattiBaseActivity() {
|
||||
}
|
||||
|
||||
private lateinit var binding: ActivityAudioSelectorBinding
|
||||
private val viewModel: AudioSelectorViewModel by viewModels()
|
||||
|
||||
private val audioRecorderLauncher = registerForActivityResult(AudioRecorderContract()) {
|
||||
|
||||
}
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
||||
binding = ActivityAudioSelectorBinding.inflate(layoutInflater)
|
||||
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@ -14,6 +14,10 @@ import java.io.Serializable
|
||||
*/
|
||||
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
|
||||
|
||||
override fun createIntent(context: Context, input: SelectorConfig): Intent {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -70,6 +70,7 @@ class AudiosRepositoryImpl @Inject constructor(private val audiosApi: AudiosApi,
|
||||
val audioDto = response.body()
|
||||
if(audioDto != null) {
|
||||
Log.d(LOG_TAG, "emit audio dto")
|
||||
audiosDraftsDao.deleteDrafts(arrayOf(audioDraftEntity))
|
||||
emit(Resource.Success(Audio.fromDto(audioDto)))
|
||||
}
|
||||
|
||||
|
||||
@ -6,8 +6,9 @@ import com.isolaatti.MyApplication
|
||||
import com.isolaatti.audio.common.domain.Playable
|
||||
import com.isolaatti.audio.drafts.data.AudioDraftEntity
|
||||
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?
|
||||
get() = null
|
||||
|
||||
|
||||
@ -49,7 +49,7 @@ class AudioRecorderActivity : AppCompatActivity() {
|
||||
const val LOG_TAG = "AudioRecorderActivity"
|
||||
|
||||
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
|
||||
@ -204,6 +204,10 @@ class AudioRecorderActivity : AppCompatActivity() {
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
binding.toolbar.setNavigationOnClickListener {
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupObservers() {
|
||||
@ -214,7 +218,7 @@ class AudioRecorderActivity : AppCompatActivity() {
|
||||
// audio draft is saved!
|
||||
viewModel.audioDraft.observe(this) {
|
||||
val result = Intent().apply {
|
||||
putExtra(OUT_EXTRA_DRAFT_ID, it.id)
|
||||
putExtra(OUT_EXTRA_DRAFT, it)
|
||||
}
|
||||
setResult(RESULT_OK, result)
|
||||
finish()
|
||||
|
||||
@ -4,8 +4,9 @@ import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
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 {
|
||||
val intent = Intent(context, AudioRecorderActivity::class.java).apply {
|
||||
@ -17,10 +18,10 @@ class AudioRecorderContract : ActivityResultContract<Long?, Long?>() {
|
||||
return intent
|
||||
}
|
||||
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): Long? {
|
||||
override fun parseResult(resultCode: Int, intent: Intent?): AudioDraft? {
|
||||
return when(resultCode){
|
||||
Activity.RESULT_OK -> {
|
||||
intent?.getLongExtra(AudioRecorderActivity.OUT_EXTRA_DRAFT_ID, 0)?.takeUnless { it == 0L }
|
||||
intent?.getSerializableExtra(AudioRecorderActivity.OUT_EXTRA_DRAFT) as? AudioDraft
|
||||
}
|
||||
else -> null
|
||||
}
|
||||
|
||||
8
app/src/main/java/com/isolaatti/common/SortingEnum.kt
Normal file
8
app/src/main/java/com/isolaatti/common/SortingEnum.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package com.isolaatti.common
|
||||
|
||||
enum class SortingEnum {
|
||||
AscendingByName,
|
||||
DescendingByName,
|
||||
AscendingByCreationDate,
|
||||
DescendingByCreationDate
|
||||
}
|
||||
@ -23,6 +23,7 @@ data class Options(
|
||||
const val OPTION_PROFILE_PHOTO_VIEW_PHOTO = 6
|
||||
const val OPTION_PROFILE_PHOTO_CHANGE_PHOTO = 7
|
||||
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 COMMENT_OPTIONS = 2
|
||||
const val PROFILE_PHOTO_OPTIONS = 3
|
||||
const val PROFILE_DESCRIPTION_OPTIONS = 4
|
||||
|
||||
val noOptions = Options(0, 0, listOf())
|
||||
|
||||
@ -93,5 +95,10 @@ data class Options(
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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.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.PROFILE_DESCRIPTION_OPTIONS
|
||||
import com.isolaatti.common.options_bottom_sheet.domain.Options.Companion.PROFILE_PHOTO_OPTIONS
|
||||
import com.isolaatti.settings.domain.UserIdSetting
|
||||
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) {
|
||||
viewModelScope.launch {
|
||||
CoroutineScope(Dispatchers.IO).launch {
|
||||
_payload = payload
|
||||
_callerId = callerId
|
||||
when(options) {
|
||||
POST_OPTIONS -> {
|
||||
userIdSetting.getUserId().onEach { userId ->
|
||||
@ -49,8 +52,6 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
|
||||
savable = false,
|
||||
snapshotAble = false)
|
||||
)
|
||||
_callerId = callerId
|
||||
_payload = payload
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
COMMENT_OPTIONS -> {
|
||||
@ -61,17 +62,16 @@ class BottomSheetPostOptionsViewModel @Inject constructor(private val userIdSett
|
||||
savable = false,
|
||||
snapshotAble = false)
|
||||
)
|
||||
_callerId = callerId
|
||||
_payload = payload
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
PROFILE_PHOTO_OPTIONS -> {
|
||||
userIdSetting.getUserId().onEach { userId ->
|
||||
_options.postValue(Options.getProfilePhotoOptions(userOwned = userId == payload?.userId,))
|
||||
_callerId = callerId
|
||||
_payload = payload
|
||||
}.flowOn(Dispatchers.IO).launchIn(this)
|
||||
}
|
||||
PROFILE_DESCRIPTION_OPTIONS -> {
|
||||
_options.postValue(Options.getProfileDescriptionOptions())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -12,7 +12,12 @@ import com.isolaatti.common.OnUserInteractedCallback
|
||||
import com.isolaatti.utils.UrlGen
|
||||
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
|
||||
var blockInfiniteScroll = false
|
||||
|
||||
@ -230,7 +230,9 @@ class BottomSheetPostComments() : BottomSheetDialogFragment(), OnUserInteractedC
|
||||
.usePlugin(LinkifyPlugin.create())
|
||||
.build()
|
||||
|
||||
adapter = CommentsRecyclerViewAdapter(listOf(), markwon, this)
|
||||
adapter = CommentsRecyclerViewAdapter(listOf(), markwon, this, onCommentClick = {
|
||||
|
||||
})
|
||||
viewBinding.recyclerComments.adapter = adapter
|
||||
viewBinding.recyclerComments.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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"
|
||||
}
|
||||
}
|
||||
@ -7,6 +7,7 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.isolaatti.audio.common.domain.Audio
|
||||
import com.isolaatti.audio.common.domain.Playable
|
||||
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.posting.posts.data.remote.CreatePostDto
|
||||
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
|
||||
fun putAudioDraft(draftId: Long) {
|
||||
fun putAudioDraft(draft: AudioDraft) {
|
||||
viewModelScope.launch {
|
||||
audioDraftsRepository.getAudioDraftById(draftId).onEach { draft ->
|
||||
audioDraftsRepository.getAudioDraftById(draft.id).onEach { draft ->
|
||||
when(draft) {
|
||||
is Resource.Error -> {}
|
||||
is Resource.Loading -> {}
|
||||
is Resource.Success -> {
|
||||
audioAttachment.postValue(draft.data)
|
||||
this@CreatePostViewModel.audioDraft = draftId
|
||||
this@CreatePostViewModel.audioDraft = draft.data!!.id
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -40,9 +40,9 @@ class MarkdownEditingFragment : Fragment(){
|
||||
|
||||
private var audioPlayerConnector: AudioPlayerConnector? = null
|
||||
|
||||
private val audioRecorderLauncher = registerForActivityResult(AudioRecorderContract()) { draftId ->
|
||||
if(draftId != null) {
|
||||
viewModel.putAudioDraft(draftId)
|
||||
private val audioRecorderLauncher = registerForActivityResult(AudioRecorderContract()) { audioDraft ->
|
||||
if(audioDraft != null) {
|
||||
viewModel.putAudioDraft(audioDraft)
|
||||
binding.viewAnimator.displayedChild = 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -22,6 +22,7 @@ import coil.load
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import com.isolaatti.BuildConfig
|
||||
import com.isolaatti.R
|
||||
import com.isolaatti.audio.audio_selector.ui.AudioSelectorContract
|
||||
import com.isolaatti.audio.audios_list.ui.AudiosFragment
|
||||
import com.isolaatti.audio.common.domain.Audio
|
||||
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 {
|
||||
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.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 {
|
||||
viewBinding.createPostButton.visibility = View.GONE
|
||||
viewBinding.topAppBar.menu.removeItem(R.id.edit_profile)
|
||||
|
||||
@ -4,65 +4,64 @@
|
||||
android:layout_height="match_parent"
|
||||
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
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
app:title="@string/select_audio"
|
||||
app:navigationIcon="@drawable/baseline_close_24"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
/>
|
||||
|
||||
<HorizontalScrollView
|
||||
android:id="@+id/filter_container"
|
||||
android:id="@+id/options_container"
|
||||
android:layout_width="match_parent"
|
||||
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">
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/filter"
|
||||
android:id="@+id/options"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:singleLine="true">
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/chip_drafts"
|
||||
android:id="@+id/new_audio"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/drafts"
|
||||
android:checkable="true"
|
||||
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"/>
|
||||
android:text="@string/record_new_audio"
|
||||
app:chipIcon="@drawable/baseline_mic_24"/>
|
||||
|
||||
<com.google.android.material.chip.Chip
|
||||
android:id="@+id/sort"
|
||||
android:layout_width="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>
|
||||
</HorizontalScrollView>
|
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
android:id="@+id/swipe_to_refresh"
|
||||
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"
|
||||
<com.google.android.material.tabs.TabLayout
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
|
||||
android:layout_height="wrap_content"
|
||||
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"/>
|
||||
|
||||
|
||||
|
||||
|
||||
25
app/src/main/res/layout/activity_comment_thread.xml
Normal file
25
app/src/main/res/layout/activity_comment_thread.xml
Normal 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>
|
||||
16
app/src/main/res/layout/fragment_audios_list_selector.xml
Normal file
16
app/src/main/res/layout/fragment_audios_list_selector.xml
Normal 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>
|
||||
19
app/src/main/res/layout/fragment_comment_thread.xml
Normal file
19
app/src/main/res/layout/fragment_comment_thread.xml
Normal 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>
|
||||
14
app/src/main/res/menu/audios_sort_menu.xml
Normal file
14
app/src/main/res/menu/audios_sort_menu.xml
Normal 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>
|
||||
18
app/src/main/res/navigation/comment_thread_navigation.xml
Normal file
18
app/src/main/res/navigation/comment_thread_navigation.xml
Normal 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>
|
||||
@ -212,6 +212,12 @@
|
||||
<string name="edited_at">Edited at %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="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">
|
||||
<item>Spam</item>
|
||||
<item>Explicit content</item>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user