Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -59,4 +59,5 @@ object IntentData {
const val segments = "segments"
const val alreadyStarted = "alreadyStarted"
const val showUpcoming = "showUpcoming"
const val customInstance = "customInstance"
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ object PreferenceKeys {
const val AUTH_INSTANCE = "selectAuthInstance"
const val AUTH_INSTANCE_TOGGLE = "auth_instance_toggle"
const val CUSTOM_INSTANCE = "customInstance"
const val CLEAR_CUSTOM_INSTANCES = "clearCustomInstances"
const val LOGIN_REGISTER = "login_register"
const val LOGOUT = "logout"
const val DELETE_ACCOUNT = "delete_account"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,33 @@
package com.github.libretube.db.dao

import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import com.github.libretube.db.obj.CustomInstance
import kotlinx.coroutines.flow.Flow

@Dao
interface CustomInstanceDao {
@Query("SELECT * FROM customInstance")
@Query("SELECT * FROM customInstance ORDER BY name")
suspend fun getAll(): List<CustomInstance>

@Query("SELECT * FROM customInstance ORDER BY name")
fun getAllFlow(): Flow<List<CustomInstance>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(customInstance: CustomInstance)

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(customInstances: List<CustomInstance>)

@Query("SELECT * FROM customInstance WHERE apiUrl = :apiUrl")
suspend fun getByApiUrl(apiUrl: String): CustomInstance?

@Delete
suspend fun deleteCustomInstance(customInstance: CustomInstance)

@Query("DELETE FROM customInstance")
suspend fun deleteAll()
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
package com.github.libretube.db.obj

import android.os.Parcelable
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.PrimaryKey
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable

@Serializable
@Entity(tableName = "customInstance")
@Parcelize
class CustomInstance(
@PrimaryKey var name: String = "",
@ColumnInfo var apiUrl: String = "",
@ColumnInfo var frontendUrl: String = ""
)
) : Parcelable
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.github.libretube.ui.adapters

import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.ListAdapter
import com.github.libretube.databinding.CustomInstanceRowBinding
import com.github.libretube.db.obj.CustomInstance
import com.github.libretube.ui.adapters.callbacks.DiffUtilItemCallback
import com.github.libretube.ui.viewholders.CustomInstancesViewHolder

class CustomInstancesAdapter(
private val onClickInstance: (CustomInstance) -> Unit,
private val onDeleteInstance: (CustomInstance) -> Unit
) : ListAdapter<CustomInstance, CustomInstancesViewHolder>(
DiffUtilItemCallback()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomInstancesViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = CustomInstanceRowBinding.inflate(layoutInflater, parent, false)
return CustomInstancesViewHolder(binding)
}

override fun onBindViewHolder(holder: CustomInstancesViewHolder, position: Int) {
val instance = getItem(position)!!

with (holder.binding) {
instanceName.text = instance.name

root.setOnClickListener {
onClickInstance(instance)
}

deleteInstance.setOnClickListener {
onDeleteInstance.invoke(instance)
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.github.libretube.ui.dialogs

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.DialogCustomInstanceBinding
import com.github.libretube.db.obj.CustomInstance
import com.github.libretube.extensions.parcelable
import com.github.libretube.extensions.toastFromMainThread
import com.github.libretube.ui.models.CustomInstancesModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.net.MalformedURLException

class CreateCustomInstanceDialog : DialogFragment() {
val viewModel: CustomInstancesModel by activityViewModels()

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogCustomInstanceBinding.inflate(layoutInflater)
arguments?.parcelable<CustomInstance>(IntentData.customInstance)?.let { initialInstance ->
binding.instanceName.setText(initialInstance.name)
binding.instanceApiUrl.setText(initialInstance.apiUrl)
binding.instanceFrontendUrl.setText(initialInstance.frontendUrl)
}

binding.instanceApiUrl.setOnFocusChangeListener { _, hasFocus ->
if (hasFocus || !binding.instanceName.text.isNullOrEmpty()) return@setOnFocusChangeListener

// automatically set the api name
val apiUrl = binding.instanceApiUrl.text.toString().toHttpUrlOrNull()
if (apiUrl != null) {
binding.instanceName.setText(apiUrl.host)
}
}

return MaterialAlertDialogBuilder(requireContext())
.setTitle(R.string.customInstance)
.setView(binding.root)
.setPositiveButton(R.string.addInstance, null)
.setNegativeButton(R.string.cancel, null)
.show()
.apply {
getButton(DialogInterface.BUTTON_POSITIVE).setOnClickListener {
val instanceName = binding.instanceName.text.toString()
val apiUrl = binding.instanceApiUrl.text.toString()
val frontendUrl = binding.instanceFrontendUrl.text.toString()

try {
viewModel.addCustomInstance(apiUrl, instanceName, frontendUrl)
requireDialog().dismiss()
} catch (e: IllegalArgumentException) {
context.toastFromMainThread(R.string.empty_instance)
} catch (e: MalformedURLException) {
context.toastFromMainThread(R.string.invalid_url)
}
}
}
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package com.github.libretube.ui.dialogs

import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import androidx.core.os.bundleOf
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.lifecycleScope
import com.github.libretube.R
import com.github.libretube.constants.IntentData
import com.github.libretube.databinding.DialogCustomIntancesListBinding
import com.github.libretube.ui.adapters.CustomInstancesAdapter
import com.github.libretube.ui.models.CustomInstancesModel
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch

class CustomInstancesListDialog: DialogFragment() {
val viewModel: CustomInstancesModel by activityViewModels()

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val binding = DialogCustomIntancesListBinding.inflate(layoutInflater)
val adapter = CustomInstancesAdapter(
onClickInstance = {
CreateCustomInstanceDialog()
.apply {
arguments = bundleOf(IntentData.customInstance to it)
}
.show(childFragmentManager, null)
},
onDeleteInstance = {
viewModel.deleteCustomInstance(it)
}
)
binding.customInstancesRecycler.adapter = adapter

lifecycleScope.launch {
viewModel.instances.collectLatest {
adapter.submitList(it)
}
}

return MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.customInstance))
.setView(binding.root)
.setPositiveButton(getString(R.string.okay), null)
.setNegativeButton(getString(R.string.addInstance), null)
.show()
.apply {
getButton(DialogInterface.BUTTON_NEGATIVE).setOnClickListener {
CreateCustomInstanceDialog()
.show(childFragmentManager, null)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -118,12 +118,12 @@ class ShareDialog : DialogFragment() {
)

// get the api urls of the other custom instances
val customInstances = runBlocking(Dispatchers.IO) {
Database.customInstanceDao().getAll()
val customInstance = runBlocking(Dispatchers.IO) {
Database.customInstanceDao().getByApiUrl(instancePref)
}

// return the custom instance frontend url if available
return customInstances.firstOrNull { it.apiUrl == instancePref }?.frontendUrl.orEmpty()
return customInstance?.frontendUrl.orEmpty()
}

private fun generateLinkText(binding: DialogShareBinding, customInstanceUrl: HttpUrl?): String {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.github.libretube.ui.models

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.libretube.db.DatabaseHolder.Database
import com.github.libretube.db.obj.CustomInstance
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import java.net.MalformedURLException

class CustomInstancesModel: ViewModel() {
val instances = Database.customInstanceDao().getAllFlow()
.flowOn(Dispatchers.IO)

fun addCustomInstance(apiUrlInput: String, instanceNameInput: String?, frontendUrlInput: String?) {
if (apiUrlInput.isEmpty()) throw IllegalArgumentException()

val apiUrl = apiUrlInput.toHttpUrlOrNull() ?: throw MalformedURLException()
val frontendUrl = if (!frontendUrlInput.isNullOrBlank()) {
frontendUrlInput.toHttpUrlOrNull() ?: throw MalformedURLException()
} else {
null
}

viewModelScope.launch(Dispatchers.IO) {
val instanceName = instanceNameInput ?: apiUrl.host

Database.customInstanceDao()
.insert(CustomInstance(instanceName, apiUrl.toString(), frontendUrl?.toString().orEmpty()))
}
}

fun deleteCustomInstance(customInstance: CustomInstance) = viewModelScope.launch(Dispatchers.IO) {
Database.customInstanceDao().deleteCustomInstance(customInstance)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ import com.github.libretube.extensions.toastFromMainDispatcher
import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.ui.adapters.InstancesAdapter
import com.github.libretube.ui.base.BasePreferenceFragment
import com.github.libretube.ui.dialogs.CustomInstanceDialog
import com.github.libretube.ui.dialogs.CreateCustomInstanceDialog
import com.github.libretube.ui.dialogs.CustomInstancesListDialog
import com.github.libretube.ui.dialogs.DeleteAccountDialog
import com.github.libretube.ui.dialogs.LoginDialog
import com.github.libretube.ui.dialogs.LogoutDialog
Expand Down Expand Up @@ -82,17 +83,8 @@ class InstanceSettings : BasePreferenceFragment() {

val customInstance = findPreference<Preference>(PreferenceKeys.CUSTOM_INSTANCE)
customInstance?.setOnPreferenceClickListener {
CustomInstanceDialog()
.show(childFragmentManager, CustomInstanceDialog::class.java.name)
true
}

val clearCustomInstances = findPreference<Preference>(PreferenceKeys.CLEAR_CUSTOM_INSTANCES)
clearCustomInstances?.setOnPreferenceClickListener {
lifecycleScope.launch {
Database.customInstanceDao().deleteAll()
ActivityCompat.recreate(requireActivity())
}
CustomInstancesListDialog()
.show(childFragmentManager, CreateCustomInstanceDialog::class.java.name)
true
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.github.libretube.ui.viewholders

import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.databinding.CustomInstanceRowBinding

class CustomInstancesViewHolder(
val binding: CustomInstanceRowBinding
): RecyclerView.ViewHolder(binding.root)
Loading
Loading