diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 5d39f6554cd..9a2a145426a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -24,6 +24,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.Toast import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts import androidx.annotation.IdRes import androidx.annotation.MainThread import androidx.appcompat.app.AlertDialog @@ -104,6 +105,7 @@ import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.SyncWatchType import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear +import com.lagradost.cloudstream3.ui.account.ProfileImagePicker import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO import com.lagradost.cloudstream3.ui.home.HomeViewModel import com.lagradost.cloudstream3.ui.library.LibraryViewModel @@ -440,6 +442,15 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener, BiometricCa } } + private val profileImagePicker = ProfileImagePicker(this) + private val profileImageLauncher = registerForActivityResult( + ActivityResultContracts.OpenDocument(), + profileImagePicker::onImagePicked, + ) + + internal fun pickProfileImage(callback: (String) -> Unit) { + profileImagePicker.launch(profileImageLauncher, callback) + } var lastPopup: SearchResponse? = null var lastPopupJob: Job? = null diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt index 92d33d0f349..f18035117e7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt @@ -23,7 +23,8 @@ class AccountAdapter( private val accountSelectCallback: (DataStoreHelper.Account) -> Unit, private val accountCreateCallback: (DataStoreHelper.Account) -> Unit, private val accountEditCallback: (DataStoreHelper.Account) -> Unit, - private val accountDeleteCallback: (DataStoreHelper.Account) -> Unit + private val accountDeleteCallback: (DataStoreHelper.Account) -> Unit, + private val pickProfileImage: ((String) -> Unit) -> Unit, ) : NoStateAdapter() { companion object { @@ -74,6 +75,7 @@ class AccountAdapter( context = root.context, account = item, isNewAccount = false, + pickProfileImage = pickProfileImage, accountEditCallback = { account -> accountEditCallback.invoke( account @@ -127,6 +129,7 @@ class AccountAdapter( context = root.context, account = item, isNewAccount = false, + pickProfileImage = pickProfileImage, accountEditCallback = { account -> accountEditCallback.invoke(account) }, accountDeleteCallback = { account -> accountDeleteCallback.invoke( @@ -168,6 +171,7 @@ class AccountAdapter( defaultImageIndex = image ), isNewAccount = true, + pickProfileImage = pickProfileImage, accountEditCallback = { account -> accountCreateCallback.invoke(account) }, accountDeleteCallback = {} ) @@ -208,4 +212,4 @@ class AccountAdapter( } ) } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt index 7725bad9128..6cbf6141b5f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt @@ -3,12 +3,15 @@ package com.lagradost.cloudstream3.ui.account import android.app.Activity import android.content.Context import android.content.DialogInterface +import android.content.Intent +import android.net.Uri import android.os.Bundle import android.text.Editable import android.view.LayoutInflater import android.view.inputmethod.EditorInfo import android.widget.TextView import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher import androidx.annotation.StringRes import androidx.appcompat.app.AlertDialog import androidx.core.view.isGone @@ -42,11 +45,59 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod import com.lagradost.cloudstream3.utils.UIHelper.showProgress +internal class ProfileImagePicker(private val context: Context) { + private var callback: ((String) -> Unit)? = null + + fun launch( + launcher: ActivityResultLauncher>, + callback: (String) -> Unit, + ) { + this.callback = callback + try { + launcher.launch(arrayOf("image/*")) + } catch (error: Exception) { + this.callback = null + logError(error) + showToast(R.string.edit_profile_image_error_invalid, Toast.LENGTH_SHORT) + } + } + + fun onImagePicked(uri: Uri?) { + val callback = callback ?: return + this.callback = null + if (uri == null) return + + try { + context.contentResolver.takePersistableUriPermission( + uri, Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } catch (error: Exception) { + logError(error) + showToast(R.string.edit_profile_image_error_invalid) + return + } + + ImageLoader(context).enqueue( + ImageRequest.Builder(context).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() + ) + } +} + object AccountHelper { fun showAccountEditDialog( context: Context, account: DataStoreHelper.Account, isNewAccount: Boolean, + pickProfileImage: ((String) -> Unit) -> Unit, accountEditCallback: (DataStoreHelper.Account) -> Unit, accountDeleteCallback: (DataStoreHelper.Account) -> Unit ) { @@ -166,7 +217,7 @@ object AccountHelper { canSetPin = true - binding.editProfilePhotoButton.setOnClickListener { + val showProfileImageUrlDialog = { callback: (String) -> Unit -> val bottomSheetDialog = BottomSheetDialog(context) val sheetBinding = BottomInputDialogBinding.inflate(LayoutInflater.from(context)) bottomSheetDialog.setContentView(sheetBinding.root) @@ -189,8 +240,7 @@ 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 @@ -216,6 +266,29 @@ 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) { + pickProfileImage { image -> + if (!dialog.isShowing) return@pickProfileImage + currentEditAccount = currentEditAccount.copy(customImage = image) + binding.accountImage.loadImage(image) + } + } else { + showProfileImageUrlDialog { image -> + currentEditAccount = currentEditAccount.copy(customImage = image) + binding.accountImage.loadImage(image) + } + } + } + .show() + } } fun showPinInputDialog( @@ -404,7 +477,8 @@ object AccountHelper { }, accountCreateCallback = { viewModel.handleAccountUpdate(it, activity) }, accountEditCallback = { viewModel.handleAccountUpdate(it, activity) }, - accountDeleteCallback = { viewModel.handleAccountDelete(it, activity) } + accountDeleteCallback = { viewModel.handleAccountDelete(it, activity) }, + pickProfileImage = activity::pickProfileImage, ).apply { submitList(liveAccounts) } @@ -416,4 +490,4 @@ object AccountHelper { } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt index ad323c7d124..c170ec9bdf5 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui.account import android.annotation.SuppressLint import android.os.Bundle import android.util.Log +import androidx.activity.result.contract.ActivityResultContracts import androidx.fragment.app.FragmentActivity import androidx.activity.viewModels import androidx.preference.PreferenceManager @@ -44,6 +45,16 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback { val accountViewModel: AccountViewModel by viewModels() + private val profileImagePicker = ProfileImagePicker(this) + private val profileImageLauncher = registerForActivityResult( + ActivityResultContracts.OpenDocument(), + profileImagePicker::onImagePicked, + ) + + private fun pickProfileImage(callback: (String) -> Unit) { + profileImagePicker.launch(profileImageLauncher, callback) + } + @SuppressLint("NotifyDataSetChanged") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -143,7 +154,8 @@ class AccountSelectActivity : FragmentActivity(), BiometricCallback { navigateToMainActivity() } }, - accountDeleteCallback = { accountViewModel.handleAccountDelete(it, this) } + accountDeleteCallback = { accountViewModel.handleAccountDelete(it, this) }, + pickProfileImage = ::pickProfileImage, ).apply { submitList(liveAccounts) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt index 96c5f9f565e..9d0f30f4053 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeFragment.kt @@ -32,6 +32,7 @@ import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.AllLanguagesName import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainAPI +import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.SearchResponse import com.lagradost.cloudstream3.TvType @@ -658,7 +659,11 @@ class HomeFragment : BaseFragment( } homeMasterAdapter = HomeParentItemAdapterPreview( - homeViewModel, accountViewModel + homeViewModel, + accountViewModel, + pickProfileImage = { callback -> + (activity as? MainActivity)?.pickProfileImage(callback) + }, ) homeMasterRecycler.setRecycledViewPool(ParentItemAdapter.sharedPool) homeMasterRecycler.adapter = homeMasterAdapter diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt index 959806e566c..ec316a6988a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPreview.kt @@ -64,7 +64,8 @@ import com.lagradost.cloudstream3.ui.setRecycledViewPool class HomeParentItemAdapterPreview( private val viewModel: HomeViewModel, - private val accountViewModel: AccountViewModel + private val accountViewModel: AccountViewModel, + private val pickProfileImage: ((String) -> Unit) -> Unit, ) : ParentItemAdapter( id = "HomeParentItemAdapterPreview".hashCode(), clickCallback = { @@ -104,7 +105,7 @@ class HomeParentItemAdapterPreview( ) } - return HeaderViewHolder(binding, viewModel, accountViewModel) + return HeaderViewHolder(binding, viewModel, accountViewModel, pickProfileImage) } override fun onBindHeader(holder: ViewHolderState) { @@ -131,6 +132,7 @@ class HomeParentItemAdapterPreview( val binding: ViewBinding, val viewModel: HomeViewModel, accountViewModel: AccountViewModel, + private val pickProfileImage: ((String) -> Unit) -> Unit, ) : ViewHolderState(binding) { @@ -558,6 +560,7 @@ class HomeParentItemAdapterPreview( context = context, account = currentAccount, isNewAccount = false, + pickProfileImage = pickProfileImage, accountEditCallback = { accountViewModel.handleAccountUpdate(it, context) }, accountDeleteCallback = { accountViewModel.handleAccountDelete( diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt index 340266f6c52..37bfb37ba81 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -169,7 +169,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()) diff --git a/app/src/main/res/layout/account_edit_dialog.xml b/app/src/main/res/layout/account_edit_dialog.xml index f52c8ea5196..df8f9e8c002 100644 --- a/app/src/main/res/layout/account_edit_dialog.xml +++ b/app/src/main/res/layout/account_edit_dialog.xml @@ -51,6 +51,7 @@ android:contentDescription="@string/preview_background_img_des" android:focusable="true" android:foreground="@drawable/outline_drawable_forced_round" + android:nextFocusRight="@id/edit_profile_photo_button" android:scaleType="centerCrop" android:src="@drawable/profile_bg_blue" /> @@ -60,6 +61,11 @@ android:layout_height="wrap_content" android:layout_gravity="bottom|end" android:background="@color/skipOpTransparent" + android:contentDescription="@string/edit_profile_image_title" + android:focusable="true" + android:foreground="@drawable/outline_drawable_forced_round" + android:nextFocusLeft="@id/account_image" + android:nextFocusDown="@id/account_name" android:padding="5dp" android:src="@drawable/ic_baseline_edit_24" /> @@ -127,4 +133,4 @@ android:text="@string/sort_cancel" /> - \ No newline at end of file + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 31cf951cf5f..aae373930cd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -749,6 +749,7 @@ LongPress Speed Toggle Hold to get 2x speed Edit Profile Image + @string/player_load_subtitles Enter Profile Image URL No URL Found Invalid URL or Image