Skip to content
Open
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
160 changes: 160 additions & 0 deletions app/src/main/java/com/lagradost/cloudstream3/CommonActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import android.content.pm.PackageManager
import android.content.res.Configuration
import android.content.res.Resources
import android.Manifest
import android.net.Uri
import android.os.Build
import android.os.Handler
import android.os.Looper
Expand All @@ -20,6 +21,7 @@ import android.view.View.NO_ID
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.MainThread
import androidx.annotation.StringRes
Expand All @@ -28,7 +30,13 @@ import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.view.children
import androidx.core.view.isNotEmpty
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import androidx.preference.PreferenceManager
import coil3.ImageLoader
import coil3.request.Disposable
import coil3.request.ImageRequest
import coil3.request.allowHardware
import com.google.android.gms.cast.framework.CastSession
import com.google.android.material.chip.ChipGroup
import com.google.android.material.navigationrail.NavigationRailView
Expand Down Expand Up @@ -59,6 +67,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.toPx
import com.lagradost.cloudstream3.utils.UiText
import java.lang.ref.WeakReference
import java.util.Locale
import java.util.WeakHashMap
import kotlin.math.max
import kotlin.math.min
import org.schabi.newpipe.extractor.NewPipe
Expand All @@ -79,9 +88,129 @@ object CommonActivity {
_activity = WeakReference(value)
}

private class ProfileImagePicker {
private class Attempt(val callback: (String) -> Boolean) {
var validation: Disposable? = null
var isValid = true

fun invalidate() {
isValid = false
validation?.dispose()
validation = null
}

fun complete() {
isValid = false
validation = null
}
}

lateinit var launcher: ActivityResultLauncher<Array<String>>
private var attempt: Attempt? = null
private var isDestroyed = false

private fun cancelAttempt() {
val cancelledAttempt = attempt
attempt = null
cancelledAttempt?.invalidate()
}

private fun completeAttempt(completedAttempt: Attempt): Boolean {
if (attempt !== completedAttempt || !completedAttempt.isValid) return false
attempt = null
completedAttempt.complete()
return true
}

fun launch(callback: (String) -> Boolean) {
if (isDestroyed) return
if (attempt?.validation == null && attempt != null) return
cancelAttempt()
val newAttempt = Attempt(callback)
attempt = newAttempt
try {
launcher.launch(arrayOf("image/*"))
} catch (error: Exception) {
completeAttempt(newAttempt)
logError(error)
CommonActivity.showToast(
R.string.edit_profile_image_error_invalid,
Toast.LENGTH_SHORT
)
}
}

fun onImagePicked(context: Context, uri: Uri?) {
// Results restored after recreation have no live dialog callback and are ignored.
val currentAttempt = attempt ?: return
if (uri == null) {
completeAttempt(currentAttempt)
return
}

val validation = ImageLoader(context).enqueue(
ImageRequest.Builder(context).data(uri)
.allowHardware(false).size(512, 512).listener(
onSuccess = { _, _ ->
if (!completeAttempt(currentAttempt)) return@listener
if (currentAttempt.callback(uri.toString())) {
CommonActivity.showToast(
R.string.edit_profile_image_success,
Toast.LENGTH_SHORT
)
}
},
onError = { _, _ ->
if (!completeAttempt(currentAttempt)) return@listener
CommonActivity.showToast(R.string.edit_profile_image_error_invalid)
},
onCancel = {
completeAttempt(currentAttempt)
}
).build()
)
currentAttempt.validation = validation
if (attempt !== currentAttempt || !currentAttempt.isValid) {
currentAttempt.validation = null
validation.dispose()
}
}

fun cancel() {
cancelAttempt()
}

fun destroy() {
isDestroyed = true
cancelAttempt()
launcher.unregister()
}
}

private val profileImagePickers =
WeakHashMap<ComponentActivity, WeakReference<ProfileImagePicker>>()
private var profileImagePicker: WeakReference<ProfileImagePicker>? = null

@MainThread
fun setActivityInstance(newActivity: Activity?) {
activity = newActivity
profileImagePicker =
(newActivity as? ComponentActivity)?.let(profileImagePickers::get)
}

@MainThread
fun pickProfileImage(callback: (String) -> Boolean) {
val picker = profileImagePicker?.get()
if (picker == null) {
showToast(R.string.edit_profile_image_error_invalid, Toast.LENGTH_SHORT)
return
}
picker.launch(callback)
}

@MainThread
fun cancelProfileImagePick() {
profileImagePicker?.get()?.cancel()
}

@MainThread
Expand Down Expand Up @@ -265,6 +394,37 @@ object CommonActivity {
}
}

val picker = ProfileImagePicker()
val applicationContext = componentActivity.applicationContext
picker.launcher =
componentActivity.registerForActivityResult(ActivityResultContracts.OpenDocument()) {
picker.onImagePicked(applicationContext, it)
}
profileImagePickers[componentActivity] = WeakReference(picker)
componentActivity.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
setActivityInstance(owner as? Activity)
}

override fun onPause(owner: LifecycleOwner) {
if (profileImagePicker?.get() === picker) {
profileImagePicker = null
}
}

override fun onDestroy(owner: LifecycleOwner) {
if (profileImagePicker?.get() === picker) {
profileImagePicker = null
}
val ownerActivity = owner as? ComponentActivity
ownerActivity?.let(profileImagePickers::remove)
if (activity === ownerActivity) {
setActivityInstance(null)
}
picker.destroy()
}
})

// Ask for notification permissions on Android 13
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(
Expand Down
Loading