diff --git a/app/app/src/main/AndroidManifest.xml b/app/app/src/main/AndroidManifest.xml
index 992298cc1..4518be726 100644
--- a/app/app/src/main/AndroidManifest.xml
+++ b/app/app/src/main/AndroidManifest.xml
@@ -20,6 +20,133 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
when (state.peekContent()) {
// go to login fragment
@@ -71,8 +89,113 @@ class MainActivity : AppCompatActivity() {
}
}
})
+
+ // check if the app has been opened by clicking on torrents/magnet on sharing links
+ getIntentData()
+
+ // observe for torrents downloaded
+ registerReceiver(getDownloadCompleteReceiver(), IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE))
+ }
+
+ private fun getDownloadCompleteReceiver(): BroadcastReceiver {
+ return object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ intent?.let {
+ viewModel.checkDownload(it.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1))
+ }
+ }
+ }
}
+ private fun getIntentData() {
+
+ when (intent?.action) {
+ Intent.ACTION_SEND -> {
+ if (intent.type == "text/plain")
+ intent.getStringExtra(Intent.EXTRA_TEXT)?.let { text ->
+ when {
+ text.isMagnet() -> {
+ // check auth state before loading it
+ viewModel.authenticationState.observeOnce(this, { auth ->
+ when (auth.peekContent()) {
+ AuthenticationState.AUTHENTICATED -> processLinkIntent(text)
+ AuthenticationState.AUTHENTICATED_NO_PREMIUM -> baseContext.showToast(
+ R.string.premium_needed_torrent
+ )
+ else -> showToast(R.string.please_login)
+ }
+ })
+ }
+ text.isTorrent() -> {
+ viewModel.authenticationState.observeOnce(this, { auth ->
+ when (auth.peekContent()) {
+ AuthenticationState.AUTHENTICATED -> processLinkIntent(text)
+ AuthenticationState.AUTHENTICATED_NO_PREMIUM -> baseContext.showToast(
+ R.string.premium_needed_torrent
+ )
+ else -> showToast(R.string.please_login)
+ }
+ })
+ }
+ else -> {
+ // we do not have other cases
+ }
+ }
+ }
+
+ }
+ Intent.ACTION_VIEW -> {
+ /* Implicit intent with path to torrent file or magnet link */
+
+ val data = intent.data
+ // check uri content
+ if (data != null) {
+
+ when (data.scheme) {
+ //clicked on a torrent file or a magnet link
+ SCHEME_MAGNET, SCHEME_CONTENT, SCHEME_FILE -> {
+ // check auth state before loading it
+ viewModel.authenticationState.observeOnce(this, { auth ->
+ when (auth.peekContent()) {
+ AuthenticationState.AUTHENTICATED -> processLinkIntent(data)
+ AuthenticationState.AUTHENTICATED_NO_PREMIUM -> baseContext.showToast(
+ R.string.premium_needed_torrent
+ )
+ else -> showToast(R.string.please_login)
+ }
+ })
+ }
+ SCHEME_HTTP, SCHEME_HTTPS -> {
+ showToast("You activated the http/s scheme somehow")
+ }
+ }
+ }
+ }
+ null -> { // app opened directly by the user. Do nothing.
+ }
+ else -> {
+
+ }
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ //todo: test if this works (probably not)
+ unregisterReceiver(getDownloadCompleteReceiver())
+ }
+
+ private fun processLinkIntent(uri: Uri) {
+ // simulate click on new download tab
+ val bottomNav = findViewById(R.id.bottom_nav_view)
+ if (bottomNav.selectedItemId != R.id.navigation_download) {
+ bottomNav.selectedItemId = R.id.navigation_download
+ }
+ viewModel.addLink(uri)
+ }
+
+ private fun processLinkIntent(text: String) = processLinkIntent(Uri.parse(text))
+
private fun openSettings() {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/downloaddetails/view/DownloadDetailsFragment.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/downloaddetails/view/DownloadDetailsFragment.kt
index 447afb115..df0af65a1 100644
--- a/app/app/src/main/java/com/github/livingwithhippos/unchained/downloaddetails/view/DownloadDetailsFragment.kt
+++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/downloaddetails/view/DownloadDetailsFragment.kt
@@ -50,7 +50,7 @@ class DownloadDetailsFragment : UnchainedFragment(), DownloadDetailsListener {
override fun onCopyClick(text: String) {
copyToClipboard("Real-Debrid Download Link", text)
- showToast(R.string.link_copied)
+ context?.showToast(R.string.link_copied)
}
override fun onOpenClick(url: String) {
@@ -62,7 +62,7 @@ class DownloadDetailsFragment : UnchainedFragment(), DownloadDetailsListener {
if (activityViewModel.isTokenPrivate()) {
viewModel.fetchStreamingInfo(id)
} else
- showToast(R.string.api_needs_private_token)
+ context?.showToast(R.string.api_needs_private_token)
}
}
diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/lists/view/ListsTabFragment.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/lists/view/ListsTabFragment.kt
index cfc5543a0..6eb746f96 100644
--- a/app/app/src/main/java/com/github/livingwithhippos/unchained/lists/view/ListsTabFragment.kt
+++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/lists/view/ListsTabFragment.kt
@@ -167,7 +167,7 @@ class ListsTabFragment : UnchainedFragment(), DownloadListListener, TorrentListL
val action = ListsTabFragmentDirections.actionListsTabToDownloadDetails(item)
findNavController().navigate(action)
} else
- showToast(R.string.premium_needed)
+ context?.showToast(R.string.premium_needed)
}
override fun onClick(item: TorrentItem) {
@@ -176,13 +176,13 @@ class ListsTabFragment : UnchainedFragment(), DownloadListListener, TorrentListL
if (item.status == "downloaded") {
// if the item has many links to download, show a toast
if (item.links.size>2)
- showToast(R.string.downloading_torrent)
+ context?.showToast(R.string.downloading_torrent)
viewModel.downloadTorrent(item)
}
else
- showToast(R.string.torrent_not_downloaded)
+ context?.showToast(R.string.torrent_not_downloaded)
} else
- showToast(R.string.premium_needed)
+ context?.showToast(R.string.premium_needed)
}
companion object {
diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/newdownload/view/NewDownloadFragment.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/newdownload/view/NewDownloadFragment.kt
index aab719b0a..8f591c02e 100644
--- a/app/app/src/main/java/com/github/livingwithhippos/unchained/newdownload/view/NewDownloadFragment.kt
+++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/newdownload/view/NewDownloadFragment.kt
@@ -1,8 +1,13 @@
package com.github.livingwithhippos.unchained.newdownload.view
+import android.app.DownloadManager
import android.content.ContentResolver
+import android.content.ContentResolver.SCHEME_CONTENT
+import android.content.ContentResolver.SCHEME_FILE
+import android.content.Context
import android.net.Uri
import android.os.Bundle
+import android.os.Environment
import android.os.ParcelFileDescriptor
import android.util.Log
import android.view.LayoutInflater
@@ -11,6 +16,7 @@ import android.view.ViewGroup
import android.widget.TextView
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.core.net.toUri
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController
@@ -21,16 +27,22 @@ import com.github.livingwithhippos.unchained.base.UnchainedFragment
import com.github.livingwithhippos.unchained.data.model.AuthenticationState
import com.github.livingwithhippos.unchained.databinding.NewDownloadFragmentBinding
import com.github.livingwithhippos.unchained.newdownload.viewmodel.NewDownloadViewModel
-import com.github.livingwithhippos.unchained.start.viewmodel.MainActivityViewModel
import com.github.livingwithhippos.unchained.utilities.REMOTE_TRAFFIC_ON
+import com.github.livingwithhippos.unchained.utilities.SCHEME_HTTP
+import com.github.livingwithhippos.unchained.utilities.SCHEME_HTTPS
+import com.github.livingwithhippos.unchained.utilities.SCHEME_MAGNET
import com.github.livingwithhippos.unchained.utilities.extension.getClipboardText
import com.github.livingwithhippos.unchained.utilities.extension.isMagnet
import com.github.livingwithhippos.unchained.utilities.extension.isWebUrl
import com.github.livingwithhippos.unchained.utilities.extension.runRippleAnimation
import com.github.livingwithhippos.unchained.utilities.extension.showToast
import dagger.hilt.android.AndroidEntryPoint
+import java.io.File
import java.io.FileDescriptor
import java.io.FileInputStream
+import java.nio.file.Path
+import java.util.regex.Matcher
+import java.util.regex.Pattern
/**
* A simple [UnchainedFragment] subclass.
@@ -73,53 +85,54 @@ class NewDownloadFragment : UnchainedFragment(), NewDownloadListener {
viewModel.apiErrorLiveData.observe(viewLifecycleOwner, Observer {
it.getContentIfNotHandled()?.let { error ->
+ //todo: move this code outside onCreateView
when (error.errorCode) {
- -1 -> showToast(R.string.internal_error)
- 1 -> showToast(R.string.missing_parameter)
- 2 -> showToast(R.string.bad_parameter_value)
- 3 -> showToast(R.string.unknown_method)
- 4 -> showToast(R.string.method_not_allowed)
+ -1 -> context?.showToast(R.string.internal_error)
+ 1 -> context?.showToast(R.string.missing_parameter)
+ 2 -> context?.showToast(R.string.bad_parameter_value)
+ 3 -> context?.showToast(R.string.unknown_method)
+ 4 -> context?.showToast(R.string.method_not_allowed)
// what is this error for?
- 5 -> showToast(R.string.slow_down)
- 6 -> showToast(R.string.resource_unreachable)
- 7 -> showToast(R.string.resource_not_found)
+ 5 -> context?.showToast(R.string.slow_down)
+ 6 -> context?.showToast(R.string.resource_unreachable)
+ 7 -> context?.showToast(R.string.resource_not_found)
//todo: check these
8 -> {
- showToast(R.string.bad_token)
+ context?.showToast(R.string.bad_token)
activityViewModel.setUnauthenticated()
}
- 9 -> showToast(R.string.permission_denied)
- 10 -> showToast(R.string.tfa_needed)
- 11 -> showToast(R.string.tfa_pending)
+ 9 -> context?.showToast(R.string.permission_denied)
+ 10 -> context?.showToast(R.string.tfa_needed)
+ 11 -> context?.showToast(R.string.tfa_pending)
12 -> {
- showToast(R.string.invalid_login)
+ context?.showToast(R.string.invalid_login)
activityViewModel.setUnauthenticated()
}
- 13 -> showToast(R.string.invalid_password)
+ 13 -> context?.showToast(R.string.invalid_password)
14 -> {
- showToast(R.string.account_locked)
+ context?.showToast(R.string.account_locked)
activityViewModel.setUnauthenticated()
}
- 15 -> showToast(R.string.account_not_activated)
- 16 -> showToast(R.string.unsupported_hoster)
- 17 -> showToast(R.string.hoster_in_maintenance)
- 18 -> showToast(R.string.hoster_limit_reached)
- 19 -> showToast(R.string.hoster_temporarily_unavailable)
- 20 -> showToast(R.string.hoster_not_available_for_free_users)
- 21 -> showToast(R.string.too_many_active_downloads)
- 22 -> showToast(R.string.ip_Address_not_allowed)
- 23 -> showToast(R.string.traffic_exhausted)
- 24 -> showToast(R.string.file_unavailable)
- 25 -> showToast(R.string.service_unavailable)
- 26 -> showToast(R.string.upload_too_big)
- 27 -> showToast(R.string.upload_error)
- 28 -> showToast(R.string.file_not_allowed)
- 29 -> showToast(R.string.torrent_too_big)
- 30 -> showToast(R.string.torrent_file_invalid)
- 31 -> showToast(R.string.action_already_done)
- 32 -> showToast(R.string.image_resolution_error)
- 33 -> showToast(R.string.torrent_already_active)
- else -> showToast(R.string.error_unrestricting_download)
+ 15 -> context?.showToast(R.string.account_not_activated)
+ 16 -> context?.showToast(R.string.unsupported_hoster)
+ 17 -> context?.showToast(R.string.hoster_in_maintenance)
+ 18 -> context?.showToast(R.string.hoster_limit_reached)
+ 19 -> context?.showToast(R.string.hoster_temporarily_unavailable)
+ 20 -> context?.showToast(R.string.hoster_not_available_for_free_users)
+ 21 -> context?.showToast(R.string.too_many_active_downloads)
+ 22 -> context?.showToast(R.string.ip_Address_not_allowed)
+ 23 -> context?.showToast(R.string.traffic_exhausted)
+ 24 -> context?.showToast(R.string.file_unavailable)
+ 25 -> context?.showToast(R.string.service_unavailable)
+ 26 -> context?.showToast(R.string.upload_too_big)
+ 27 -> context?.showToast(R.string.upload_error)
+ 28 -> context?.showToast(R.string.file_not_allowed)
+ 29 -> context?.showToast(R.string.torrent_too_big)
+ 30 -> context?.showToast(R.string.torrent_file_invalid)
+ 31 -> context?.showToast(R.string.action_already_done)
+ 32 -> context?.showToast(R.string.image_resolution_error)
+ 33 -> context?.showToast(R.string.torrent_already_active)
+ else -> context?.showToast(R.string.error_unrestricting_download)
}
// re enable buttons to let the user take other actions
//todo: this needs to be done also for other errors. maybe throw another error from the ViewModel
@@ -159,16 +172,16 @@ class NewDownloadFragment : UnchainedFragment(), NewDownloadListener {
viewModel.fetchAddedMagnet(link)
}
link.isBlank()-> {
- showToast(R.string.please_insert_url)
+ context?.showToast(R.string.please_insert_url)
}
else -> {
- showToast(R.string.invalid_url)
+ context?.showToast(R.string.invalid_url)
}
}
}
else
- showToast(R.string.premium_needed)
+ context?.showToast(R.string.premium_needed)
}
downloadBinding.bPasteLink.setOnClickListener {
@@ -177,7 +190,7 @@ class NewDownloadFragment : UnchainedFragment(), NewDownloadListener {
if (pasteText.isWebUrl() || pasteText.isMagnet())
downloadBinding.tiLink.setText(pasteText, TextView.BufferType.EDITABLE)
else
- showToast(R.string.invalid_url)
+ context?.showToast(R.string.invalid_url)
}
downloadBinding.bPastePassword.setOnClickListener {
@@ -192,6 +205,38 @@ class NewDownloadFragment : UnchainedFragment(), NewDownloadListener {
downloadBinding.bUnrestrict.runRippleAnimation()
}
+ activityViewModel.externalLinkLiveData.observe(viewLifecycleOwner, {
+ it.getContentIfNotHandled()?.let { link ->
+ when (link.scheme) {
+ SCHEME_MAGNET -> {
+ context?.showToast(R.string.loading_magnet_link)
+ // set as text input text
+ downloadBinding.tiLink.setText(
+ link.toString(),
+ TextView.BufferType.EDITABLE
+ )
+ // simulate button click
+ downloadBinding.bUnrestrict.performClick()
+ }
+ SCHEME_CONTENT, SCHEME_FILE -> {
+ context?.showToast(R.string.loading_torrent_file)
+ loadTorrent(requireContext().contentResolver, link)
+ }
+ SCHEME_HTTP, SCHEME_HTTPS -> {
+ context?.showToast(R.string.loading_torrent_file)
+ downloadTorrent(link)
+ }
+ }
+ }
+ })
+
+ activityViewModel.downloadedTorrentLiveData.observe(viewLifecycleOwner, {
+ it.getContentIfNotHandled()?.let { fileName ->
+ val torrentFile = File(requireContext().getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS),fileName)
+ loadTorrent(requireContext().contentResolver, torrentFile.toUri())
+ }
+ })
+
return downloadBinding.root
}
@@ -200,7 +245,7 @@ class NewDownloadFragment : UnchainedFragment(), NewDownloadListener {
if (uri != null) {
loadTorrent(requireContext().contentResolver, uri)
} else {
- showToast(R.string.error_loading_torrent)
+ context?.showToast(R.string.error_loading_torrent)
}
}
@@ -209,7 +254,7 @@ class NewDownloadFragment : UnchainedFragment(), NewDownloadListener {
if (authState == AuthenticationState.AUTHENTICATED)
getTorrent.launch("application/x-bittorrent")
else
- showToast(R.string.premium_needed)
+ context?.showToast(R.string.premium_needed)
}
private fun loadTorrent(contentResolver: ContentResolver, uri: Uri) {
@@ -231,6 +276,22 @@ class NewDownloadFragment : UnchainedFragment(), NewDownloadListener {
}
}
+
+ private fun downloadTorrent(uri: Uri) {
+ val nameRegex= "/([^/]+.torrent)\$"
+ val m: Matcher = Pattern.compile(nameRegex).matcher(uri.toString())
+ val torrentName = if(m.find())m.group(1) else null
+ if (!torrentName.isNullOrBlank() && context!=null) {
+ val manager = requireContext().getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager
+ val request: DownloadManager.Request = DownloadManager.Request(uri)
+ .setTitle(getString(R.string.unchained_torrent_download))
+ .setDescription(getString(R.string.temporary_torrent_download))
+ .setDestinationInExternalFilesDir(requireContext(), Environment.DIRECTORY_DOWNLOADS,torrentName)
+ val downloadID = manager.enqueue(request)
+ activityViewModel.setDownload(downloadID, torrentName)
+ }
+
+ }
}
interface NewDownloadListener {
diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/start/viewmodel/MainActivityViewModel.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/start/viewmodel/MainActivityViewModel.kt
index 17e48ec2f..83fc845ec 100644
--- a/app/app/src/main/java/com/github/livingwithhippos/unchained/start/viewmodel/MainActivityViewModel.kt
+++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/start/viewmodel/MainActivityViewModel.kt
@@ -1,8 +1,11 @@
package com.github.livingwithhippos.unchained.start.viewmodel
import android.annotation.SuppressLint
+import android.net.Uri
+import androidx.hilt.Assisted
import androidx.hilt.lifecycle.ViewModelInject
import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.livingwithhippos.unchained.data.model.AuthenticationState
@@ -20,6 +23,7 @@ import kotlinx.coroutines.launch
* Shared between the fragments to observe the authentication status and update it.
*/
class MainActivityViewModel @ViewModelInject constructor(
+ @Assisted private val savedStateHandle: SavedStateHandle,
private val authRepository: AuthenticationRepository,
private val credentialRepository: CredentialsRepository,
private val userRepository: UserRepository
@@ -29,6 +33,10 @@ class MainActivityViewModel @ViewModelInject constructor(
val userLiveData = MutableLiveData()
+ val externalLinkLiveData = MutableLiveData>()
+
+ val downloadedTorrentLiveData = MutableLiveData>()
+
// fixme: this is here because userLiveData.postValue(user) is throwing an unsafe error
// but auto-correcting it changes the value of val authenticationState = MutableLiveData>() to a nullable one
@SuppressLint("NullSafeMutableLiveData")
@@ -129,4 +137,27 @@ class MainActivityViewModel @ViewModelInject constructor(
}
}
+ fun addLink(uri: Uri) {
+ externalLinkLiveData.postValue(Event(uri))
+ }
+
+ fun setDownload(downloadID: Long, filePath: String) {
+ savedStateHandle.set(KEY_TORRENT_DOWNLOAD_ID, downloadID)
+ savedStateHandle.set(KEY_TORRENT_PATH, filePath)
+ }
+
+ fun checkDownload(downloadID: Long) {
+ val id = savedStateHandle.get(KEY_TORRENT_DOWNLOAD_ID)
+ if (id == downloadID) {
+ val fileName = savedStateHandle.get(KEY_TORRENT_PATH)
+ if (fileName != null)
+ downloadedTorrentLiveData.postValue(Event(fileName))
+ }
+ }
+
+ companion object {
+ const val KEY_TORRENT_DOWNLOAD_ID = "torrent_download_id_key"
+ const val KEY_TORRENT_PATH = "torrent_path_key"
+ }
+
}
\ No newline at end of file
diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/Constants.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/Constants.kt
index 3fefad431..792e05b1a 100644
--- a/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/Constants.kt
+++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/Constants.kt
@@ -16,10 +16,15 @@ const val REMOTE_TRAFFIC_OFF: Int = 0
const val REMOTE_TRAFFIC_ON: Int = 1
const val MAGNET_PATTERN: String = "magnet:\\?xt=urn:btih:[a-zA-Z0-9]{32}"
+const val TORRENT_PATTERN: String = "https?://[^\\s]{7,}.torrent"
const val FEEDBACK_URL="https://github.com/LivingWithHippos/unchained-android"
const val GPLV3_URL="https://www.gnu.org/licenses/gpl-3.0.en.html"
+const val SCHEME_MAGNET="magnet"
+const val SCHEME_HTTP="http"
+const val SCHEME_HTTPS="https"
+
val errorMap = mapOf(
-1 to "Internal error",
1 to "Missing parameter",
diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/Extension.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/Extension.kt
index 19c56506d..d7217218c 100644
--- a/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/Extension.kt
+++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/Extension.kt
@@ -11,9 +11,13 @@ import android.net.Uri
import android.util.Log
import android.widget.Toast
import androidx.fragment.app.Fragment
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MediatorLiveData
+import androidx.lifecycle.Observer
import com.github.livingwithhippos.unchained.BuildConfig
import com.github.livingwithhippos.unchained.R
-import java.util.*
+import java.util.Locale
/**
* Show a toast message
@@ -21,17 +25,15 @@ import java.util.*
* @param length How long to display the message. Either {@link #LENGTH_SHORT} or
* {@link #LENGTH_LONG} Defaults to short
*/
-fun Fragment.showToast(stringResource: Int, length: Int = Toast.LENGTH_SHORT) {
- Toast.makeText(requireContext(), getString(stringResource), length).show()
-}
+fun Context.showToast(stringResource: Int, length: Int = Toast.LENGTH_SHORT) = this.showToast(getString(stringResource, length))
/**
* Show a toast message
* @param message: the message and shown
* @param length: the duration of the toast. Defaults to short
*/
-fun Fragment.showToast(message: String, length: Int = Toast.LENGTH_SHORT) {
- Toast.makeText(requireContext(), message, length).show()
+fun Context.showToast(message: String, length: Int = Toast.LENGTH_SHORT) {
+ Toast.makeText(this, message, length).show()
}
/**
@@ -83,7 +85,8 @@ fun Fragment.openExternalWebPage(url: String, showErrorToast: Boolean = true): B
return true
} else
if (showErrorToast)
- showToast(R.string.invalid_url)
+ context?.showToast(R.string.invalid_url)
+
return false
}
@@ -107,4 +110,50 @@ fun Activity.getUpdatedLocaleContext(context: Context, language: String): Contex
Locale.setDefault(locale)
configuration.setLocale(locale)
return context.createConfigurationContext(configuration)
+}
+
+fun LiveData.combineWith(
+ liveData: LiveData,
+ block: (T?, K?) -> R
+): LiveData {
+ val result = MediatorLiveData()
+ result.addSource(this) {
+ result.value = block(this.value, liveData.value)
+ }
+ result.addSource(liveData) {
+ result.value = block(this.value, liveData.value)
+ }
+ return result
+}
+
+fun zipLiveData(t: LiveData, k: LiveData): LiveData> {
+ return MediatorLiveData>().apply {
+ var lastT: T? = null
+ var lastK: K? = null
+
+ fun update() {
+ val localLastT = lastT
+ val localLastK = lastK
+ if (localLastT != null && localLastK != null)
+ this.value = Pair(localLastT, localLastK)
+ }
+
+ addSource(t) {
+ lastT = it
+ update()
+ }
+ addSource(k) {
+ lastK = it
+ update()
+ }
+ }
+}
+
+fun LiveData.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer) {
+ observe(lifecycleOwner, object : Observer {
+ override fun onChanged(t: T?) {
+ observer.onChanged(t)
+ removeObserver(this)
+ }
+ })
}
\ No newline at end of file
diff --git a/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/StringExtension.kt b/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/StringExtension.kt
index d357ba3c2..a55611ce1 100644
--- a/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/StringExtension.kt
+++ b/app/app/src/main/java/com/github/livingwithhippos/unchained/utilities/extension/StringExtension.kt
@@ -5,6 +5,7 @@ import android.icu.text.DateFormat
import android.icu.text.SimpleDateFormat
import android.util.Patterns
import com.github.livingwithhippos.unchained.utilities.MAGNET_PATTERN
+import com.github.livingwithhippos.unchained.utilities.TORRENT_PATTERN
import java.util.*
import java.util.regex.Matcher
import java.util.regex.Pattern
@@ -18,11 +19,23 @@ fun String.isWebUrl(): Boolean =
/**
* check if a String is a magnet link
*/
-fun String.isMagnet(): Boolean {
+fun String?.isMagnet(): Boolean {
+ if (this == null)
+ return false
val m: Matcher = Pattern.compile(MAGNET_PATTERN).matcher(this)
return m.lookingAt()
}
+/**
+ * check if a String is a torrent link
+ */
+fun String?.isTorrent(): Boolean {
+ if (this == null)
+ return false
+ val m: Matcher = Pattern.compile(TORRENT_PATTERN).matcher(this)
+ return m.matches()
+}
+
/**
* parse a string from a custom date pattern to the current Locale pattern
* @return the parsed String
diff --git a/app/app/src/main/res/layout/fragment_torrent_details.xml b/app/app/src/main/res/layout/fragment_torrent_details.xml
index d5cd5fc58..3c214eb93 100644
--- a/app/app/src/main/res/layout/fragment_torrent_details.xml
+++ b/app/app/src/main/res/layout/fragment_torrent_details.xml
@@ -53,7 +53,8 @@
In attesa di selezione files
In coda
- Scaricando
+ Scaricamento
Scaricato
Errore
Virus
@@ -144,4 +144,10 @@
Per utilizzare questa funzionalità è richiesto un account premium
Torrent in preparazione, attendere…
Inserisci un magnet o un link
+ Si prega di autenticarsi sull\'account
+ Per torrent e magnet è richiestoun abbonamento premium
+ Caricando link magnet...
+ Caricando file torrent...
+ Unchained Scaricamento Torrent
+ Scaricamento temporaneo di un file torrent
\ No newline at end of file
diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml
index 3f78de59d..61c8344e8 100644
--- a/app/app/src/main/res/values/strings.xml
+++ b/app/app/src/main/res/values/strings.xml
@@ -356,6 +356,12 @@
]]>
A premium subscription is required to use this
+ A premium subscription is required for magnet and torrents
Preparing torrent, please wait…
Please insert a magnet or a link
+ Please log in your account
+ Loading magnet link…
+ Loading torrent file…
+ Unchained Torrent Download
+ Temporary download of a torrent file
\ No newline at end of file