diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ExtensionOpenerActivity.kt b/app/src/main/java/dev/brahmkshatriya/echo/ExtensionOpenerActivity.kt index ec0c5a6b..03e99b22 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ExtensionOpenerActivity.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ExtensionOpenerActivity.kt @@ -17,15 +17,15 @@ import dev.brahmkshatriya.echo.ui.extension.ExtensionInstallerBottomSheet import dev.brahmkshatriya.echo.viewmodels.ExtensionViewModel import kotlinx.coroutines.launch import java.io.File +import kotlin.coroutines.resume +import kotlin.coroutines.suspendCoroutine class ExtensionOpenerActivity : Activity() { override fun onStart() { super.onStart() val uri = intent.data - println("uri : $uri") - println("mime : ${contentResolver.getType(uri!!)}") - val file = when (uri.scheme) { + val file = when (uri?.scheme) { "content" -> getTempFile(uri) "file" -> getTempFile(uri.toFile()) else -> null @@ -42,7 +42,7 @@ class ExtensionOpenerActivity : Activity() { startActivity(startIntent) } - private fun getTempFile(bytes:ByteArray) : File { + private fun getTempFile(bytes: ByteArray): File { val tempFile = File.createTempFile("temp", ".apk", getTempApkDir()) tempFile.writeBytes(bytes) return tempFile @@ -54,7 +54,7 @@ class ExtensionOpenerActivity : Activity() { return getTempFile(bytes) } - private fun getTempFile(file:File) : File { + private fun getTempFile(file: File): File { val bytes = file.readBytes() return getTempFile(bytes) } @@ -69,8 +69,13 @@ class ExtensionOpenerActivity : Activity() { } fun FragmentActivity.openExtensionInstaller(uri: Uri) { + lifecycleScope.launch { + installExtension(uri.toString()) + } + } - ExtensionInstallerBottomSheet.newInstance(uri.toString()) + suspend fun FragmentActivity.installExtension(fileString: String) = suspendCoroutine { + ExtensionInstallerBottomSheet.newInstance(fileString) .show(supportFragmentManager, null) supportFragmentManager.setFragmentResultListener(EXTENSION_INSTALLER, this) { _, b -> @@ -86,27 +91,34 @@ class ExtensionOpenerActivity : Activity() { if (result && installAsApk) { context.createLinksDialog(file, links) } + it.resume(result) } } } + } - private fun Context.createLinksDialog( + private suspend fun Context.createLinksDialog( file: File, links: List - ) = MaterialAlertDialogBuilder(this) - .setTitle(getString(R.string.allow_opening_links)) - .setMessage( - links.joinToString("\n") + "\n" + - getString(R.string.open_links_instruction) - ) - .setPositiveButton(getString(R.string.ok)) { dialog, _ -> - val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) - val packageName = getPackageName(file.path) - intent.setData(Uri.parse("package:$packageName")) - startActivity(intent) - dialog.dismiss() - } - .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() } - .show() + ) = suspendCoroutine { cont -> + MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.allow_opening_links)) + .setMessage( + links.joinToString("\n") + "\n" + + getString(R.string.open_links_instruction) + ) + .setPositiveButton(getString(R.string.ok)) { dialog, _ -> + val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS) + val packageName = getPackageName(file.path) + intent.setData(Uri.parse("package:$packageName")) + startActivity(intent) + dialog.dismiss() + } + .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.dismiss() } + .setOnDismissListener { + cont.resume(Unit) + } + .show() + } } } \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/PlayerService.kt b/app/src/main/java/dev/brahmkshatriya/echo/PlayerService.kt index 138878ec..718907d2 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/PlayerService.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/PlayerService.kt @@ -137,7 +137,6 @@ class PlayerService : MediaLibraryService() { } } - //TODO: extension updater //TODO: Spotify //TODO: EQ, Pitch, Tempo, Reverb & Sleep Timer(5m, 10m, 15m, 30m, 45m, 1hr, End of track) // val equalizer = Equalizer(1, exoPlayer.audioSessionId) diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extensions/ExtensionRepo.kt b/app/src/main/java/dev/brahmkshatriya/echo/extensions/ExtensionRepo.kt index 0726bf09..a134690a 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/extensions/ExtensionRepo.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/extensions/ExtensionRepo.kt @@ -43,7 +43,7 @@ sealed class ExtensionRepo( ApkManifestParser(ImportType.App), loader ) - LazyRepoComposer(appPluginRepo, apkFilePluginRepo, *repo) + LazyRepoComposer(*repo, appPluginRepo, apkFilePluginRepo) } private fun injected() = composed.getAllPlugins().mapState { list -> diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extensions/InstallationUtils.kt b/app/src/main/java/dev/brahmkshatriya/echo/extensions/InstallationUtils.kt index 13767b55..7be8de04 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/extensions/InstallationUtils.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/extensions/InstallationUtils.kt @@ -98,7 +98,7 @@ fun Context.getPackageName(path: String) = packageManager.getPackageArchiveInfo( path, ApkPluginSource.PACKAGE_FLAGS )!!.packageName -private suspend fun FragmentActivity.waitForResult(intent: Intent) = suspendCoroutine { cont -> +suspend fun FragmentActivity.waitForResult(intent: Intent) = suspendCoroutine { cont -> val contract = ActivityResultContracts.StartActivityForResult() val activityResultLauncher = registerActivityResultLauncher(contract) { cont.resume(it) diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extensions/InvalidExtensionListException.kt b/app/src/main/java/dev/brahmkshatriya/echo/extensions/InvalidExtensionListException.kt new file mode 100644 index 00000000..f68560b8 --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/extensions/InvalidExtensionListException.kt @@ -0,0 +1,3 @@ +package dev.brahmkshatriya.echo.extensions + +class InvalidExtensionListException(override val cause: Throwable) : Exception(cause) diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extensions/UpdateChecker.kt b/app/src/main/java/dev/brahmkshatriya/echo/extensions/UpdateChecker.kt index f69d738d..90ec8c62 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/extensions/UpdateChecker.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/extensions/UpdateChecker.kt @@ -87,4 +87,25 @@ data class Asset( val name: String, @SerialName("browser_download_url") val browserDownloadUrl: String +) + +suspend fun getExtensionList( + link: String, + client: OkHttpClient +) = runIOCatching { + val request = Request.Builder() + .addHeader("Cookie", "preview=1") + .url(link).build() + client.newCall(request).await().body.string().toData>() +}.getOrElse { + throw InvalidExtensionListException(it) +} + +@Serializable +data class ExtensionAssetResponse( + val id: String, + val name: String, + val subtitle: String? = null, + val iconUrl: String? = null, + val updateUrl: String ) \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/ApkManifestParser.kt b/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/ApkManifestParser.kt index d82bef98..94c401a9 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/ApkManifestParser.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/ApkManifestParser.kt @@ -15,7 +15,7 @@ class ApkManifestParser( path = data.path, className = get("class"), importType = importType, - id = get("id") + importType.name, + id = get("id"), name = get("name"), version = get("version"), description = get("description"), diff --git a/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/LazyPluginRepo.kt b/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/LazyPluginRepo.kt index 87a016a7..71c6d6de 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/LazyPluginRepo.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/extensions/plugger/LazyPluginRepo.kt @@ -1,15 +1,24 @@ package dev.brahmkshatriya.echo.extensions.plugger +import dev.brahmkshatriya.echo.common.models.Metadata import tel.jeelpa.plugger.PluginRepo import tel.jeelpa.plugger.utils.combineStates +import tel.jeelpa.plugger.utils.mapState interface LazyPluginRepo : PluginRepo>> -class LazyRepoComposer( - private vararg val repos: LazyPluginRepo -) : LazyPluginRepo { +class LazyRepoComposer( + private vararg val repos: LazyPluginRepo +) : LazyPluginRepo { override fun getAllPlugins() = repos.map { it.getAllPlugins() } .reduce { a, b -> combineStates(a, b) { x, y -> x + y } } + .mapState { list -> + list.groupBy { it.getOrNull()?.first?.id }.map { entry -> + entry.value.minBy { + it.getOrNull()?.first?.importType?.ordinal ?: Int.MAX_VALUE + } + } + } } fun catchLazy(function: () -> T) = lazy { runCatching { function() } } \ No newline at end of file diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/exception/ExceptionFragment.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/exception/ExceptionFragment.kt index 8d06711a..39d3e271 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/exception/ExceptionFragment.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/exception/ExceptionFragment.kt @@ -13,6 +13,7 @@ import dev.brahmkshatriya.echo.EchoApplication.Companion.appVersion import dev.brahmkshatriya.echo.R import dev.brahmkshatriya.echo.databinding.FragmentExceptionBinding import dev.brahmkshatriya.echo.extensions.ExtensionLoadingException +import dev.brahmkshatriya.echo.extensions.InvalidExtensionListException import dev.brahmkshatriya.echo.extensions.RequiredExtensionsException import dev.brahmkshatriya.echo.playback.MediaItemUtils.audioIndex import dev.brahmkshatriya.echo.playback.MediaItemUtils.clientId @@ -98,6 +99,7 @@ class ExceptionFragment : Fragment() { throwable.name, throwable.requiredExtensions.joinToString(", ") ) + is InvalidExtensionListException -> getString(R.string.invalid_extension_list) is AppException -> throwable.run { when (this) { is AppException.Unauthorized -> diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsAddListAdapter.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsAddListAdapter.kt new file mode 100644 index 00000000..cfdad244 --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsAddListAdapter.kt @@ -0,0 +1,110 @@ +package dev.brahmkshatriya.echo.ui.extension + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import dev.brahmkshatriya.echo.R +import dev.brahmkshatriya.echo.common.models.ImageHolder.Companion.toImageHolder +import dev.brahmkshatriya.echo.databinding.ItemExtensionAddBinding +import dev.brahmkshatriya.echo.databinding.ItemExtensionAddFooterBinding +import dev.brahmkshatriya.echo.databinding.ItemExtensionAddHeaderBinding +import dev.brahmkshatriya.echo.extensions.ExtensionAssetResponse +import dev.brahmkshatriya.echo.utils.loadAsCircle + +class ExtensionsAddListAdapter( + val map: List, + val listener: Listener +) : RecyclerView.Adapter() { + + data class Item( + val item: ExtensionAssetResponse, + val isChecked: Boolean, + val isInstalled: Boolean + ) + + fun interface Listener { + fun onChecked(item: ExtensionAssetResponse, isChecked: Boolean) + } + + inner class ViewHolder(val binding: ItemExtensionAddBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ItemExtensionAddBinding.inflate(inflater, parent, false) + return ViewHolder(binding) + } + + override fun getItemCount() = map.size + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + val (item, isChecked, isInstalled) = map[position] + val binding = holder.binding + binding.extensionName.text = if (!isInstalled) item.name + else binding.root.context.getString(R.string.extension_installed, item.name) + binding.extensionSubtitle.text = item.subtitle ?: item.id + binding.itemExtension.apply { + item.iconUrl?.toImageHolder().loadAsCircle(this, R.drawable.ic_extension) { + setImageDrawable(it) + } + } + binding.extensionSwitch.isChecked = isChecked + binding.extensionSwitch.setOnCheckedChangeListener { _, checked -> + listener.onChecked(item, checked) + } + binding.root.setOnClickListener { + binding.extensionSwitch.isChecked = !binding.extensionSwitch.isChecked + } + binding.extensionSwitch.isClickable = false + } + + class Header( + val listener: Listener + ) : RecyclerView.Adapter() { + + fun interface Listener { + fun onClose() + } + + class ViewHolder(val binding: ItemExtensionAddHeaderBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ItemExtensionAddHeaderBinding.inflate(inflater, parent, false) + return ViewHolder(binding) + } + + override fun getItemCount() = 1 + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.binding.root.setOnClickListener { listener.onClose() } + } + + } + + class Footer( + val listener: Listener + ) : RecyclerView.Adapter() { + + fun interface Listener { + fun onAdd() + } + + class ViewHolder(val binding: ItemExtensionAddFooterBinding) : + RecyclerView.ViewHolder(binding.root) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { + val inflater = LayoutInflater.from(parent.context) + val binding = ItemExtensionAddFooterBinding.inflate(inflater, parent, false) + return ViewHolder(binding) + } + + override fun getItemCount() = 1 + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.binding.root.setOnClickListener { listener.onAdd() } + } + + } +} diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsAddListBottomSheet.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsAddListBottomSheet.kt new file mode 100644 index 00000000..777b99ce --- /dev/null +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsAddListBottomSheet.kt @@ -0,0 +1,118 @@ +package dev.brahmkshatriya.echo.ui.extension + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.view.isVisible +import androidx.fragment.app.activityViewModels +import androidx.fragment.app.viewModels +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.lifecycleScope +import androidx.recyclerview.widget.ConcatAdapter +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dev.brahmkshatriya.echo.R +import dev.brahmkshatriya.echo.databinding.DialogExtensionAddBinding +import dev.brahmkshatriya.echo.databinding.DialogExtensionsAddListBinding +import dev.brahmkshatriya.echo.extensions.ExtensionAssetResponse +import dev.brahmkshatriya.echo.utils.autoCleared +import dev.brahmkshatriya.echo.utils.getSerialized +import dev.brahmkshatriya.echo.utils.putSerialized +import dev.brahmkshatriya.echo.viewmodels.ExtensionViewModel +import kotlinx.coroutines.launch + +class ExtensionsAddListBottomSheet : BottomSheetDialogFragment() { + + companion object { + fun newInstance(list: List) = ExtensionsAddListBottomSheet().apply { + arguments = Bundle().apply { + putSerialized("list", list) + } + } + } + + var binding by autoCleared() + val args by lazy { requireArguments() } + val list by lazy { args.getSerialized>("list")!! } + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = DialogExtensionsAddListBinding.inflate(inflater, container, false) + return binding.root + } + + class AddViewModel( + val list: List, + private val installed: List, + ) : ViewModel() { + val selectedExtensions = list.mapNotNull { + if (it.id !in installed) it else null + }.toMutableList() + + class Factory( + private val list: List, + private val installed: List + ) : ViewModelProvider.Factory { + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class): T { + return AddViewModel(list, installed) as T + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val extensionViewModel by activityViewModels() + lifecycleScope.launch { + val installed = extensionViewModel.allExtensions().map { it.id } + val viewModel by viewModels { AddViewModel.Factory(list, installed) } + val extensionListAdapter = ExtensionsAddListAdapter(list.map { + val isInstalled = it.id in installed + val isChecked = it in viewModel.selectedExtensions + ExtensionsAddListAdapter.Item(it, isChecked, isInstalled) + }) { item, isChecked -> + if (isChecked) viewModel.selectedExtensions.add(item) + else viewModel.selectedExtensions.remove(item) + } + val headerAdapter = ExtensionsAddListAdapter.Header { dismiss() } + val footerAdapter = ExtensionsAddListAdapter.Footer { + extensionViewModel.addExtensions(requireActivity(), viewModel.selectedExtensions) + dismiss() + } + binding.root.adapter = + ConcatAdapter(headerAdapter, extensionListAdapter, footerAdapter) + } + } + + class LinkFile : BottomSheetDialogFragment() { + + var binding by autoCleared() + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = DialogExtensionAddBinding.inflate(inflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val context = requireActivity() + binding.topAppBar.setNavigationOnClickListener { dismiss() } + binding.installationTypeGroup.addOnButtonCheckedListener { group, _, _ -> + val isLink = group.checkedButtonId == R.id.linkAdd + binding.textInputLayout.isVisible = isLink + } + binding.installButton.setOnClickListener { + val isLink = binding.installationTypeGroup.checkedButtonId == R.id.linkAdd + val viewModel by activityViewModels() + if (!isLink) viewModel.addFromFile(context) else { + val link = binding.editText.text.toString() + if (link.isEmpty()) return@setOnClickListener + viewModel.addFromLinkOrCode(context, link) + } + dismiss() + } + } + } +} diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsListBottomSheet.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsListBottomSheet.kt index bdd885fc..abf0f49d 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsListBottomSheet.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ExtensionsListBottomSheet.kt @@ -43,7 +43,10 @@ class ExtensionsListBottomSheet : BottomSheetDialogFragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { binding.topAppBar.setNavigationOnClickListener { dismiss() } - binding.addExtension.isEnabled = false + binding.addExtension.setOnClickListener { + dismiss() + ExtensionsAddListBottomSheet.LinkFile().show(parentFragmentManager, null) + } binding.manageExtensions.setOnClickListener { dismiss() requireActivity().openFragment(ManageExtensionsFragment()) diff --git a/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ManageExtensionsFragment.kt b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ManageExtensionsFragment.kt index ed9d1f9e..48162c90 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ManageExtensionsFragment.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/ui/extension/ManageExtensionsFragment.kt @@ -23,6 +23,7 @@ import dev.brahmkshatriya.echo.utils.onAppBarChangeListener import dev.brahmkshatriya.echo.utils.setupTransition import dev.brahmkshatriya.echo.viewmodels.ExtensionViewModel import dev.brahmkshatriya.echo.viewmodels.UiViewModel.Companion.applyBackPressCallback +import dev.brahmkshatriya.echo.viewmodels.UiViewModel.Companion.applyContentInsets import dev.brahmkshatriya.echo.viewmodels.UiViewModel.Companion.applyInsetsMain import kotlinx.coroutines.Job @@ -39,7 +40,9 @@ class ManageExtensionsFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { setupTransition(view) - applyInsetsMain(binding.appBarLayout, binding.recyclerView) + applyInsetsMain(binding.appBarLayout, binding.recyclerView) { + binding.fabContainer.applyContentInsets(it) + } applyBackPressCallback() binding.appBarLayout.onAppBarChangeListener { offset -> binding.appBarOutline.alpha = offset @@ -54,6 +57,10 @@ class ManageExtensionsFragment : Fragment() { refresh.setOnClickListener { viewModel.refresh() } binding.swipeRefresh.configure { viewModel.refresh() } + binding.fabAddExtensions.setOnClickListener { + ExtensionsAddListBottomSheet.LinkFile().show(parentFragmentManager, null) + } + var type = ExtensionType.entries[binding.tabLayout.selectedTabPosition] val callback = object : ItemTouchHelper.SimpleCallback( ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0 diff --git a/app/src/main/java/dev/brahmkshatriya/echo/viewmodels/ExtensionViewModel.kt b/app/src/main/java/dev/brahmkshatriya/echo/viewmodels/ExtensionViewModel.kt index 8b5c0c2d..fcdd1b3e 100644 --- a/app/src/main/java/dev/brahmkshatriya/echo/viewmodels/ExtensionViewModel.kt +++ b/app/src/main/java/dev/brahmkshatriya/echo/viewmodels/ExtensionViewModel.kt @@ -2,13 +2,17 @@ package dev.brahmkshatriya.echo.viewmodels import android.app.Application import android.content.Context +import android.content.Intent import android.content.SharedPreferences import androidx.core.content.edit +import androidx.core.net.toUri import androidx.fragment.app.FragmentActivity import androidx.lifecycle.viewModelScope import androidx.recyclerview.widget.RecyclerView import dagger.hilt.android.lifecycle.HiltViewModel import dev.brahmkshatriya.echo.EchoDatabase +import dev.brahmkshatriya.echo.ExtensionOpenerActivity +import dev.brahmkshatriya.echo.ExtensionOpenerActivity.Companion.installExtension import dev.brahmkshatriya.echo.R import dev.brahmkshatriya.echo.common.Extension import dev.brahmkshatriya.echo.common.LyricsExtension @@ -21,18 +25,22 @@ import dev.brahmkshatriya.echo.common.models.Metadata import dev.brahmkshatriya.echo.common.settings.Settings import dev.brahmkshatriya.echo.db.models.ExtensionEntity import dev.brahmkshatriya.echo.db.models.UserEntity +import dev.brahmkshatriya.echo.extensions.ExtensionAssetResponse import dev.brahmkshatriya.echo.extensions.ExtensionLoader import dev.brahmkshatriya.echo.extensions.ExtensionLoader.Companion.priorityKey import dev.brahmkshatriya.echo.extensions.ExtensionLoader.Companion.setupMusicExtension import dev.brahmkshatriya.echo.extensions.downloadUpdate import dev.brahmkshatriya.echo.extensions.get import dev.brahmkshatriya.echo.extensions.getExtension +import dev.brahmkshatriya.echo.extensions.getExtensionList import dev.brahmkshatriya.echo.extensions.getUpdateFileUrl import dev.brahmkshatriya.echo.extensions.installExtension import dev.brahmkshatriya.echo.extensions.uninstallExtension +import dev.brahmkshatriya.echo.extensions.waitForResult import dev.brahmkshatriya.echo.ui.common.ClientLoadingAdapter import dev.brahmkshatriya.echo.ui.common.ClientNotSupportedAdapter import dev.brahmkshatriya.echo.ui.extension.ClientSelectionViewModel +import dev.brahmkshatriya.echo.ui.extension.ExtensionsAddListBottomSheet import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableStateFlow @@ -117,6 +125,12 @@ class ExtensionViewModel @Inject constructor( function(result) } + fun getExtensionListFlow(type: ExtensionType) = when (type) { + ExtensionType.MUSIC -> extensionListFlow + ExtensionType.TRACKER -> trackerListFlow + ExtensionType.LYRICS -> lyricsListFlow + } + fun moveExtensionItem(type: ExtensionType, toPos: Int, fromPos: Int) { val flow = extensionLoader.priorityMap[type]!! val list = getExtensionListFlow(type).value.orEmpty().map { it.id }.toMutableList() @@ -127,22 +141,23 @@ class ExtensionViewModel @Inject constructor( } } + suspend fun allExtensions() = ExtensionType.entries.map { type -> + val flow = getExtensionListFlow(type) + flow.first { it != null }!! + }.flatten() + private var checkedForUpdates = false fun updateExtensions(context: FragmentActivity) { if (checkedForUpdates) return checkedForUpdates = true val check = settings.getBoolean("check_for_extension_updates", true) if (check) viewModelScope.launch { - ExtensionType.entries.map { type -> - val flow = getExtensionListFlow(type) - flow.first { it != null }!! - }.flatten().forEach { + allExtensions().forEach { updateExtension(context, it) } } } - val client = OkHttpClient() private suspend fun updateExtension( context: FragmentActivity, @@ -177,12 +192,6 @@ class ExtensionViewModel @Inject constructor( ) } - fun getExtensionListFlow(type: ExtensionType) = when (type) { - ExtensionType.MUSIC -> extensionListFlow - ExtensionType.TRACKER -> trackerListFlow - ExtensionType.LYRICS -> lyricsListFlow - } - companion object { fun Context.noClient() = SnackBar.Message( @@ -220,4 +229,61 @@ class ExtensionViewModel @Inject constructor( ) } } + + fun addFromLinkOrCode(context: FragmentActivity, link: String) { + viewModelScope.launch { + val actualLink = when { + link.startsWith("http://") or link.startsWith("https://") -> link + else -> "https://v.gd/$link" + } + + val list = runCatching { getExtensionList(actualLink, client) }.getOrElse { + throwableFlow.emit(it) + return@launch + } + if (list.isEmpty()) { + messageFlow.emit(SnackBar.Message(app.getString(R.string.list_is_empty))) + return@launch + } + ExtensionsAddListBottomSheet.newInstance(list) + .show(context.supportFragmentManager, null) + } + } + + fun addFromFile(context: FragmentActivity) { + viewModelScope.launch { + val intent = Intent(Intent.ACTION_GET_CONTENT).apply { + type = "application/octet-stream" + addCategory(Intent.CATEGORY_OPENABLE) + } + val result = context.waitForResult(intent) + val file = result.data?.data ?: return@launch + val newIntent = Intent(context, ExtensionOpenerActivity::class.java).apply { + setData(file) + } + context.startActivity(newIntent) + } + } + + fun addExtensions(context: FragmentActivity, extensions: List) { + viewModelScope.launch { + extensions.forEach { extension -> + val url = getUpdateFileUrl("", extension.updateUrl, client).getOrElse { + throwableFlow.emit(it) + null + } ?: return@forEach + messageFlow.emit( + SnackBar.Message( + app.getString(R.string.downloading_update_for_extension, extension.name) + ) + ) + val file = downloadUpdate(context, url, client).getOrElse { + throwableFlow.emit(it) + return@forEach + } + context.installExtension(file.toUri().toString()) + } + } + } + } \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_extension_add.xml b/app/src/main/res/layout/dialog_extension_add.xml new file mode 100644 index 00000000..b08a9408 --- /dev/null +++ b/app/src/main/res/layout/dialog_extension_add.xml @@ -0,0 +1,78 @@ + + + + + + + + + +