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
34 changes: 27 additions & 7 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 @@ -39,18 +41,11 @@ import com.lagradost.cloudstream3.actions.VideoClickActionHolder
import com.lagradost.cloudstream3.databinding.ToastBinding
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager
import com.lagradost.cloudstream3.ui.home.HomeChildItemAdapter
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
import com.lagradost.cloudstream3.ui.player.PlayerPipHelper.isPIPPossible
import com.lagradost.cloudstream3.ui.player.Torrent
import com.lagradost.cloudstream3.ui.result.ActorAdaptor
import com.lagradost.cloudstream3.ui.result.EpisodeAdapter
import com.lagradost.cloudstream3.ui.result.ImageAdapter
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.updateTv
import com.lagradost.cloudstream3.ui.settings.extensions.PluginAdapter
import com.lagradost.cloudstream3.utils.AppContextUtils.isRtl
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Event
Expand Down Expand Up @@ -241,6 +236,21 @@ object CommonActivity {
setLocale(this, localeCode)
}


private var activityFileLauncher: ActivityResultLauncher<Array<String>>? = null
private var activityFileLauncherCallback: Pair<Int, ((Uri?) -> Unit)>? = null

// Ensure that previous launches do not trigger current callbacks.
private var fileLaunchCounter = 0

/** Use the ActivityResultContracts.OpenDocument() file picker to select a file.
* Note: Is only able to handle ONE callback at once. Multiple calls will overwrite the old calls.
*/
fun selectFile(mimetypes: Array<String>, callback: (Uri?) -> Unit) {
activityFileLauncherCallback = ++fileLaunchCounter to callback
activityFileLauncher?.launch(mimetypes)
}

fun init(act: Activity) {
setActivityInstance(act)
ioSafe { Torrent.deleteAllFiles() }
Expand All @@ -265,6 +275,16 @@ object CommonActivity {
}
}

activityFileLauncher =
componentActivity.registerForActivityResult(ActivityResultContracts.OpenDocument()) { result ->
val callback = activityFileLauncherCallback ?: return@registerForActivityResult
activityFileLauncherCallback = null

if (fileLaunchCounter == callback.first) {
callback.second.invoke(result)
}
}

// Ask for notification permissions on Android 13
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU &&
ContextCompat.checkSelfPermission(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.account
import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
Expand All @@ -22,6 +23,7 @@ import coil3.request.ImageRequest
import coil3.request.allowHardware
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.CloudStreamApp.Companion.getActivity
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
Expand All @@ -46,6 +48,42 @@ import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout

private object ProfileImagePicker {
fun pickImage(context: Context, callback: (String) -> Unit) {
runCatching {
CommonActivity.selectFile(arrayOf("image/*")) { uri ->
if (uri == null) return@selectFile
// Ensure context lifecycle
val ctx = context.applicationContext

try {
ctx.contentResolver.takePersistableUriPermission(
uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
)
} catch (error: Exception) {
logError(error)
showToast(R.string.edit_profile_image_error_invalid)
}

ImageLoader(ctx).enqueue(
ImageRequest.Builder(ctx).data(uri)
.allowHardware(false).size(512, 512).listener(
onSuccess = { _, _ ->
callback(uri.toString())
showToast(R.string.edit_profile_image_success, Toast.LENGTH_SHORT)
},
onError = { _, _ ->
showToast(R.string.edit_profile_image_error_invalid)
}
).build()
)
}
}.onFailure {
showToast(R.string.error)
}
}
}

object AccountHelper {
fun showAccountEditDialog(
context: Context,
Expand All @@ -60,12 +98,6 @@ object AccountHelper {

var currentEditAccount = account
val dialog = builder.show()
binding.accountImageHolder.requestFocus()

if (!isLayout(TV or EMULATOR)) {
binding.accountImageHolder.setRippleForeground()
binding.editProfilePhotoButtonHolder.setRippleForeground()
}

if (!isNewAccount) binding.title.setText(R.string.edit_account)

Expand Down Expand Up @@ -176,12 +208,12 @@ object AccountHelper {

canSetPin = true

binding.editProfilePhotoButtonHolder.setOnClickListener {
val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom)
val showProfileImageUrlDialog = { callback: (String) -> Unit ->
val bottomSheetDialog = BottomSheetDialog(context)
val sheetBinding = BottomInputDialogBinding.inflate(LayoutInflater.from(context))
builder.setView(sheetBinding.root)
val dialog = builder.create()
dialog.show()
bottomSheetDialog.setContentView(sheetBinding.root)
bottomSheetDialog.show()


sheetBinding.apply {
text1.text = context.getString(R.string.edit_profile_image_title)
Expand All @@ -200,13 +232,14 @@ object AccountHelper {
.allowHardware(false)
.listener(
onSuccess = { _, _ ->
currentEditAccount = currentEditAccount.copy(customImage = url)
binding.accountImage.loadImage(url)

callback(url)
showToast(
R.string.edit_profile_image_success,
Toast.LENGTH_SHORT
)
dialog.dismissSafe()
bottomSheetDialog.dismissSafe()

},
onError = { _, _ ->
showToast(
Expand All @@ -227,6 +260,31 @@ object AccountHelper {
}
}
}

binding.editProfilePhotoButton.setOnClickListener {
AlertDialog.Builder(context)
.setTitle(R.string.edit_profile_image_title)
.setItems(
arrayOf(
context.getString(R.string.edit_profile_image_from_file),
context.getString(R.string.edit_profile_image_hint),
)
) { _, selection ->
if (selection == 0) {
ProfileImagePicker.pickImage(context) { image ->
if (!dialog.isShowing) return@pickImage
currentEditAccount = currentEditAccount.copy(customImage = image)
binding.accountImage.loadImage(image)
}
} else {
showProfileImageUrlDialog { image ->
currentEditAccount = currentEditAccount.copy(customImage = image)
binding.accountImage.loadImage(image)
}
}
}
.show()
}
}

fun showPinInputDialog(
Expand Down Expand Up @@ -289,7 +347,10 @@ object AccountHelper {
val activity = context.getActivity()
if (activity is AccountSelectActivity) {
isPinValid = true
activity.accountViewModel.handleAccountSelect(getDefaultAccount(context), activity)
activity.accountViewModel.handleAccountSelect(
getDefaultAccount(context),
activity
)
}
}
}
Expand Down Expand Up @@ -427,4 +488,4 @@ object AccountHelper {
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,47 @@
package com.lagradost.cloudstream3.ui.account

import android.content.ContentResolver
import android.content.Context
import android.content.Intent
import androidx.core.net.toUri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.lagradost.cloudstream3.CloudStreamApp.Companion.context
import com.lagradost.cloudstream3.CloudStreamApp.Companion.removeKeys
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.account.AccountHelper.showPinInputDialog
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAccounts
import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount
import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount

class AccountViewModel : ViewModel() {
private fun releaseUnusedProfileImagePermission(
context: Context,
image: String?,
accounts: List<DataStoreHelper.Account>,
) {
if (image == null || accounts.any { it.customImage == image }) return
val uri = image.toUri()
if (uri.scheme != ContentResolver.SCHEME_CONTENT) return

try {
val resolver = context.applicationContext.contentResolver
val hasReadPermission = resolver.persistedUriPermissions.any {
it.uri == uri && it.isReadPermission
}
if (hasReadPermission) {
resolver.releasePersistableUriPermission(
uri, Intent.FLAG_GRANT_READ_URI_PERMISSION
)
}
} catch (error: Exception) {
logError(error)
}
}

private fun getAllAccounts(): List<DataStoreHelper.Account> {
return context?.let { getAccounts(it) } ?: DataStoreHelper.accounts.toList()
}
Expand Down Expand Up @@ -49,6 +77,7 @@ class AccountViewModel : ViewModel() {
val currentAccounts = getAccounts(context).toMutableList()

val overrideIndex = currentAccounts.indexOfFirst { it.keyIndex == account.keyIndex }
val oldCustomImage = currentAccounts.getOrNull(overrideIndex)?.customImage

if (overrideIndex != -1) {
currentAccounts[overrideIndex] = account
Expand All @@ -60,6 +89,7 @@ class AccountViewModel : ViewModel() {

DataStoreHelper.currentHomePage = currentHomePage
DataStoreHelper.accounts = currentAccounts.toTypedArray()
releaseUnusedProfileImagePermission(context, oldCustomImage, currentAccounts)

_accounts.postValue(getAccounts(context))
_selectedKeyIndex.postValue(getAccounts(context).indexOf(account))
Expand All @@ -73,10 +103,13 @@ class AccountViewModel : ViewModel() {
removeKeys(account.keyIndex.toString())

val currentAccounts = getAccounts(context).toMutableList()
val deletedCustomImage =
currentAccounts.firstOrNull { it.keyIndex == account.keyIndex }?.customImage

currentAccounts.removeIf { it.keyIndex == account.keyIndex }

DataStoreHelper.accounts = currentAccounts.toTypedArray()
releaseUnusedProfileImagePermission(context, deletedCustomImage, currentAccounts)

if (account.keyIndex == DataStoreHelper.selectedKeyIndex) {
setAccount(getDefaultAccount(context))
Expand Down Expand Up @@ -126,4 +159,4 @@ class AccountViewModel : ViewModel() {
MainActivity.reloadAccountEvent(true)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,10 @@ object DataStoreHelper {
@JsonProperty("lockPin") @SerialName("lockPin") val lockPin: String? = null,
) {
@get:JsonIgnore
val image get() = customImage?.let { UiImage.Image(it) } ?:
val image get() = customImage?.takeUnless { image ->
image.startsWith("content://") && context?.contentResolver?.persistedUriPermissions
?.none { it.uri.toString() == image } != false
}?.let { UiImage.Image(it) } ?:
profileImages.getOrNull(defaultImageIndex)?.let {
UiImage.Drawable(it)
} ?: UiImage.Drawable(profileImages.first())
Expand Down
7 changes: 4 additions & 3 deletions app/src/main/res/layout/account_edit_dialog.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@
android:layout_gravity="center"
android:foreground="@drawable/outline_drawable_forced_round"
android:defaultFocusHighlightEnabled="false"
android:nextFocusRight="@id/edit_profile_photo_button_holder"
android:nextFocusDown="@id/edit_profile_photo_button_holder"
android:nextFocusRight="@id/edit_profile_photo_button"
android:nextFocusDown="@id/edit_profile_photo_button"
app:cardCornerRadius="100dp"
app:cardElevation="0dp">

Expand Down Expand Up @@ -158,4 +158,5 @@
android:text="@string/sort_cancel" />

</RelativeLayout>
</LinearLayout>
</LinearLayout>

1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -763,6 +763,7 @@
<string name="speedup_title">LongPress Speed Toggle</string>
<string name="speedup_summary">Hold to get 2x speed</string>
<string name="edit_profile_image_title">Edit Profile Image</string>
<string name="edit_profile_image_from_file">@string/player_load_subtitles</string>
<string name="edit_profile_image_hint">Enter Profile Image URL</string>
<string name="edit_profile_image_error_empty">No URL Found</string>
<string name="edit_profile_image_error_invalid">Invalid URL or Image</string>
Expand Down
Loading