diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt deleted file mode 100644 index c5c38dc0..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt +++ /dev/null @@ -1,110 +0,0 @@ -package com.lagradost.cloudstream3.ui - -import android.view.LayoutInflater -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.recyclerview.widget.DiffUtil -import androidx.recyclerview.widget.ListAdapter -import androidx.recyclerview.widget.RecyclerView -import androidx.viewbinding.ViewBinding -import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountAddBinding -import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountBinding -import com.lagradost.cloudstream3.ui.result.setImage -import com.lagradost.cloudstream3.utils.DataStoreHelper - -class WhoIsWatchingAdapter( - private val selectCallBack: (DataStoreHelper.Account) -> Unit = { }, - private val editCallBack: (DataStoreHelper.Account) -> Unit = { }, - private val addAccountCallback: () -> Unit = {} -) : - ListAdapter(DiffCallback()) { - - companion object { - const val FOOTER = 1 - const val NORMAL = 0 - } - - override fun getItemCount(): Int { - return currentList.size + 1 - } - - override fun getItemViewType(position: Int): Int = when (position) { - currentList.size -> FOOTER - else -> NORMAL - } - - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): WhoIsWatchingHolder = - WhoIsWatchingHolder( - binding = when (viewType) { - NORMAL -> WhoIsWatchingAccountBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - - FOOTER -> WhoIsWatchingAccountAddBinding.inflate( - LayoutInflater.from(parent.context), - parent, - false - ) - - else -> throw NotImplementedError() - }, - selectCallBack = selectCallBack, - addAccountCallback = addAccountCallback, - editCallBack = editCallBack, - ) - - override fun onBindViewHolder(holder: WhoIsWatchingHolder, position: Int) = - holder.bind(currentList.getOrNull(position)) - - class WhoIsWatchingHolder( - val binding: ViewBinding, - val selectCallBack: (DataStoreHelper.Account) -> Unit, - val addAccountCallback: () -> Unit, - val editCallBack: (DataStoreHelper.Account) -> Unit - ) : - RecyclerView.ViewHolder(binding.root) { - - fun bind(card: DataStoreHelper.Account?) { - when (binding) { - is WhoIsWatchingAccountBinding -> binding.apply { - if (card == null) return@apply - outline.isVisible = card.keyIndex == DataStoreHelper.selectedKeyIndex - profileText.text = card.name - profileImageBackground.setImage(card.image) - - // Handle the lock indicator - val isLocked = card.lockPin != null - lockIcon.isVisible = isLocked - - root.setOnClickListener { - selectCallBack(card) - } - root.setOnLongClickListener { - editCallBack(card) - return@setOnLongClickListener true - } - } - - is WhoIsWatchingAccountAddBinding -> binding.apply { - root.setOnClickListener { - addAccountCallback() - } - } - } - } - } - - class DiffCallback : DiffUtil.ItemCallback() { - override fun areItemsTheSame( - oldItem: DataStoreHelper.Account, - newItem: DataStoreHelper.Account - ): Boolean = oldItem.keyIndex == newItem.keyIndex - - override fun areContentsTheSame( - oldItem: DataStoreHelper.Account, - newItem: DataStoreHelper.Account - ): Boolean = oldItem == newItem - } -} \ No newline at end of file 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 72551199..72bf671b 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 @@ -4,61 +4,153 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView +import androidx.viewbinding.ViewBinding +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.AccountListItemAddBinding import com.lagradost.cloudstream3.databinding.AccountListItemBinding +import com.lagradost.cloudstream3.databinding.AccountListItemEditingBinding +import com.lagradost.cloudstream3.ui.account.AccountDialogs.showAccountEditDialog import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.DataStoreHelper class AccountAdapter( private val accounts: List, - private val onItemClick: (DataStoreHelper.Account) -> Unit + private val accountSelectCallback: (DataStoreHelper.Account) -> Unit, + private val accountCreateCallback: (DataStoreHelper.Account) -> Unit, + private val accountEditCallback: (DataStoreHelper.Account) -> Unit ) : RecyclerView.Adapter() { - inner class AccountViewHolder(private val binding: AccountListItemBinding) : + companion object { + private const val VIEW_TYPE_ACCOUNT = 0 + private const val VIEW_TYPE_ADD_ACCOUNT = 1 + const val VIEW_TYPE_EDIT_ACCOUNT = 2 + } + + inner class AccountViewHolder(private val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root) { - fun bind(account: DataStoreHelper.Account) { - val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex + fun bind(account: DataStoreHelper.Account?) { + when (binding) { + is AccountListItemBinding -> binding.apply { + if (account == null) return@apply - binding.accountName.text = account.name - binding.accountImage.setImage(account.image) - binding.lockIcon.isVisible = account.lockPin != null - binding.outline.isVisible = isLastUsedAccount + val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex - if (isTvSettings()) { - binding.root.isFocusableInTouchMode = true - if (isLastUsedAccount) { - binding.root.requestFocus() + accountName.text = account.name + accountImage.setImage(account.image) + lockIcon.isVisible = account.lockPin != null + outline.isVisible = isLastUsedAccount + + if (isTvSettings()) { + root.isFocusableInTouchMode = true + if (isLastUsedAccount) { + root.requestFocus() + } + } + + root.setOnClickListener { + accountSelectCallback.invoke(account) + } + } + + + is AccountListItemEditingBinding -> binding.apply { + if (account == null) return@apply + + val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex + + accountName.text = account.name + accountImage.setImage(account.image) + lockIcon.isVisible = account.lockPin != null + outline.isVisible = isLastUsedAccount + + if (isTvSettings()) { + root.isFocusableInTouchMode = true + if (isLastUsedAccount) { + root.requestFocus() + } + } + + root.setOnClickListener { + showAccountEditDialog(root.context, account, isNewAccount = false) { + accountEditCallback.invoke(it) + } + } + } + + is AccountListItemAddBinding -> binding.apply { + root.setOnClickListener { + val remainingImages = + DataStoreHelper.profileImages.toSet() - accounts.filter { it.customImage == null } + .mapNotNull { DataStoreHelper.profileImages.getOrNull(it.defaultImageIndex) }.toSet() + + val image = + DataStoreHelper.profileImages.indexOf(remainingImages.randomOrNull() ?: DataStoreHelper.profileImages.random()) + val keyIndex = (accounts.maxOfOrNull { it.keyIndex } ?: 0) + 1 + + val accountName = root.context.getString(R.string.account) + + showAccountEditDialog(root.context, DataStoreHelper.Account( + keyIndex = keyIndex, + name = "$accountName $keyIndex", + customImage = null, + defaultImageIndex = image + ), isNewAccount = true) { + accountCreateCallback.invoke(it) + } + } } } - - binding.root.setOnClickListener { - onItemClick(account) - } } } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { - val binding = AccountListItemBinding.inflate( - LayoutInflater.from(parent.context), parent, false + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder = + AccountViewHolder( + binding = when (viewType) { + VIEW_TYPE_ACCOUNT -> { + AccountListItemBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + } + VIEW_TYPE_ADD_ACCOUNT -> { + AccountListItemAddBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + } + VIEW_TYPE_EDIT_ACCOUNT -> { + AccountListItemEditingBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + } + else -> throw IllegalArgumentException("Invalid view type") + } ) - if (isTvSettings()) { - val layoutParams = binding.root.layoutParams as RecyclerView.LayoutParams - val marginInDp = 5 // Set the margin to 5dp - val marginInPixels = (marginInDp * parent.resources.displayMetrics.density).toInt() - layoutParams.setMargins(marginInPixels, marginInPixels, marginInPixels, marginInPixels) - binding.root.layoutParams = layoutParams - } - - return AccountViewHolder(binding) + override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { + holder.bind(accounts.getOrNull(position)) } - override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { - holder.bind(accounts[position]) + var viewType = 0 + + override fun getItemViewType(position: Int): Int { + if (viewType != 0 && position != accounts.count()) { + return viewType + } + + return when (position) { + accounts.count() -> VIEW_TYPE_ADD_ACCOUNT + else -> VIEW_TYPE_ACCOUNT + } } override fun getItemCount(): Int { - return accounts.size + return accounts.count() + 1 } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt deleted file mode 100644 index dfd8831b..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt +++ /dev/null @@ -1,115 +0,0 @@ -package com.lagradost.cloudstream3.ui.account - -import android.content.Context -import android.text.Editable -import android.text.TextWatcher -import android.view.LayoutInflater -import android.view.View -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import android.widget.TextView -import androidx.annotation.StringRes -import androidx.appcompat.app.AlertDialog -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.databinding.LockPinDialogBinding -import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe - -object AccountDialog { - // TODO add account creation dialog to allow creating accounts directly from AccountSelectActivity - - fun showPinInputDialog( - context: Context, - currentPin: String?, - editAccount: Boolean, - callback: (String?) -> Unit - ) { - fun TextView.visibleWithText(@StringRes textRes: Int) { - visibility = View.VISIBLE - setText(textRes) - } - - fun View.isVisible() = visibility == View.VISIBLE - - val binding = LockPinDialogBinding.inflate(LayoutInflater.from(context)) - - val isPinSet = currentPin != null - val isNewPin = editAccount && !isPinSet - val isEditPin = editAccount && isPinSet - - val titleRes = if (isEditPin) R.string.enter_current_pin else R.string.enter_pin - - val dialog = AlertDialog.Builder(context, R.style.AlertDialogCustom) - .setView(binding.root) - .setTitle(titleRes) - .setNegativeButton(R.string.cancel) { _, _ -> - callback.invoke(null) - } - .setOnCancelListener { - callback.invoke(null) - } - .setOnDismissListener { - if (binding.pinEditTextError.isVisible()) { - callback.invoke(null) - } - } - .create() - - var isPinValid = false - - binding.pinEditText.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - val enteredPin = s.toString() - val isEnteredPinValid = enteredPin.length == 4 - - if (isEnteredPinValid) { - if (isPinSet) { - if (enteredPin != currentPin) { - binding.pinEditTextError.visibleWithText(R.string.pin_error_incorrect) - binding.pinEditText.text = null - isPinValid = false - } else { - binding.pinEditTextError.visibility = View.GONE - isPinValid = true - - callback.invoke(enteredPin) - dialog.dismissSafe() - } - } else { - binding.pinEditTextError.visibility = View.GONE - isPinValid = true - } - } else if (isNewPin) { - binding.pinEditTextError.visibleWithText(R.string.pin_error_length) - isPinValid = false - } - } - - override fun afterTextChanged(s: Editable?) {} - }) - - // Detect IME_ACTION_DONE - binding.pinEditText.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_DONE && isPinValid) { - val enteredPin = binding.pinEditText.text.toString() - callback.invoke(enteredPin) - dialog.dismissSafe() - } - true - } - - // We don't want to accidentally have the dialog dismiss when clicking outside of it. - // That is what the cancel button is for. - dialog.setCanceledOnTouchOutside(false) - - dialog.show() - - // Auto focus on PIN input and show keyboard - binding.pinEditText.requestFocus() - binding.pinEditText.postDelayed({ - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.showSoftInput(binding.pinEditText, InputMethodManager.SHOW_IMPLICIT) - }, 200) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialogs.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialogs.kt new file mode 100644 index 00000000..a4310894 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialogs.kt @@ -0,0 +1,276 @@ +package com.lagradost.cloudstream3.ui.account + +import android.content.Context +import android.content.DialogInterface +import android.text.Editable +import android.text.TextWatcher +import android.view.LayoutInflater +import android.view.inputmethod.EditorInfo +import android.view.inputmethod.InputMethodManager +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import androidx.core.view.isGone +import androidx.core.view.isVisible +import androidx.core.widget.doOnTextChanged +import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.AccountEditDialogBinding +import com.lagradost.cloudstream3.databinding.LockPinDialogBinding +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.ui.result.setImage +import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount +import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount +import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe + +object AccountDialogs { + fun showAccountEditDialog( + context: Context, + account: DataStoreHelper.Account, + isNewAccount: Boolean, + callback: (DataStoreHelper.Account) -> Unit + ) { + val binding = AccountEditDialogBinding.inflate(LayoutInflater.from(context), null, false) + val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom) + .setView(binding.root) + + var currentEditAccount = account + val dialog = builder.show() + + // Set up the dialog content + binding.accountName.text = Editable.Factory.getInstance()?.newEditable(account.name) + binding.accountName.doOnTextChanged { text, _, _, _ -> + currentEditAccount = currentEditAccount.copy(name = text?.toString() ?: "") + } + + binding.deleteBtt.isGone = isNewAccount + binding.deleteBtt.setOnClickListener { + val dialogClickListener = DialogInterface.OnClickListener { _, which -> + when (which) { + DialogInterface.BUTTON_POSITIVE -> { + // Remove the account + removeKeys(account.keyIndex.toString()) + val currentAccounts = DataStoreHelper.accounts.toMutableList() + currentAccounts.removeIf { it.keyIndex == account.keyIndex } + DataStoreHelper.accounts = currentAccounts.toTypedArray() + + // Update UI + setAccount(getDefaultAccount(context), true) + dialog?.dismissSafe() + } + + DialogInterface.BUTTON_NEGATIVE -> { + dialog?.dismissSafe() + } + } + } + + try { + AlertDialog.Builder(context).setTitle(R.string.delete).setMessage( + context.getString(R.string.delete_message).format( + currentEditAccount.name + ) + ) + .setPositiveButton(R.string.delete, dialogClickListener) + .setNegativeButton(R.string.cancel, dialogClickListener) + .show().setDefaultFocus() + } catch (t: Throwable) { + logError(t) + } + } + + binding.cancelBtt.setOnClickListener { + dialog?.dismissSafe() + } + + // Handle the profile picture and its interactions + binding.profilePic.setImage(account.image) + binding.profilePic.setOnClickListener { + // Roll the image forwards once + currentEditAccount = + currentEditAccount.copy(defaultImageIndex = (currentEditAccount.defaultImageIndex + 1) % DataStoreHelper.profileImages.size) + binding.profilePic.setImage(currentEditAccount.image) + } + + // Handle applying changes + binding.applyBtt.setOnClickListener { + if (currentEditAccount.lockPin != null) { + // Ask for the current PIN + showPinInputDialog(context, currentEditAccount.lockPin, false) { pin -> + if (pin == null) return@showPinInputDialog + // PIN is correct, proceed to update the account + callback.invoke(currentEditAccount) + dialog.dismissSafe() + } + } else { + // No lock PIN set, proceed to update the account + callback.invoke(currentEditAccount) + dialog.dismissSafe() + } + } + + // Handle setting or changing the PIN + if (currentEditAccount.keyIndex == getDefaultAccount(context).keyIndex) { + binding.lockProfileCheckbox.isVisible = false + if (currentEditAccount.lockPin != null) { + currentEditAccount = currentEditAccount.copy(lockPin = null) + } + } + + var canSetPin = true + + binding.lockProfileCheckbox.isChecked = currentEditAccount.lockPin != null + + binding.lockProfileCheckbox.setOnCheckedChangeListener { _, isChecked -> + if (isChecked) { + if (canSetPin) { + showPinInputDialog(context, null, true) { pin -> + if (pin == null) { + binding.lockProfileCheckbox.isChecked = false + return@showPinInputDialog + } + + currentEditAccount = currentEditAccount.copy(lockPin = pin) + } + } + } else { + if (currentEditAccount.lockPin != null) { + // Ask for the current PIN + showPinInputDialog(context, currentEditAccount.lockPin, true) { pin -> + if (pin == null || pin != currentEditAccount.lockPin) { + canSetPin = false + binding.lockProfileCheckbox.isChecked = true + } else { + currentEditAccount = currentEditAccount.copy(lockPin = null) + } + } + } + } + } + + canSetPin = true + } + + fun showPinInputDialog( + context: Context, + currentPin: String?, + editAccount: Boolean, + errorText: String? = null, + callback: (String?) -> Unit + ) { + fun TextView.visibleWithText(@StringRes textRes: Int) { + isVisible = true + setText(textRes) + } + + fun TextView.visibleWithText(text: String?) { + isVisible = true + setText(text) + } + + val binding = LockPinDialogBinding.inflate(LayoutInflater.from(context)) + + val isPinSet = currentPin != null + val isNewPin = editAccount && !isPinSet + val isEditPin = editAccount && isPinSet + + val titleRes = if (isEditPin) R.string.enter_current_pin else R.string.enter_pin + + val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom) + .setView(binding.root) + .setTitle(titleRes) + .setNegativeButton(R.string.cancel) { _, _ -> + callback.invoke(null) + } + .setOnCancelListener { + callback.invoke(null) + } + .setOnDismissListener { + if (binding.pinEditTextError.isVisible) { + callback.invoke(null) + } + } + + if (isNewPin) { + if (errorText != null) binding.pinEditTextError.visibleWithText(errorText) + builder.setPositiveButton(R.string.setup_done) { _, _ -> + if (binding.pinEditTextError.isVisible) { + // If the done button is pressed and there is a error, + // ask again, and mention the error that caused this. + showPinInputDialog( + context = binding.root.context, + currentPin = null, + editAccount = true, + errorText = binding.pinEditTextError.text.toString(), + callback = callback + ) + } else { + val enteredPin = binding.pinEditText.text.toString() + callback.invoke(enteredPin) + } + } + } + + val dialog = builder.create() + + var isPinValid = false + + binding.pinEditText.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { + val enteredPin = s.toString() + val isEnteredPinValid = enteredPin.length == 4 + + if (isEnteredPinValid) { + if (isPinSet) { + if (enteredPin != currentPin) { + binding.pinEditTextError.visibleWithText(R.string.pin_error_incorrect) + binding.pinEditText.text = null + isPinValid = false + } else { + binding.pinEditTextError.isVisible = false + isPinValid = true + + callback.invoke(enteredPin) + dialog.dismissSafe() + } + } else { + binding.pinEditTextError.isVisible = false + isPinValid = true + } + } else if (isNewPin) { + binding.pinEditTextError.visibleWithText(R.string.pin_error_length) + isPinValid = false + } + } + + override fun afterTextChanged(s: Editable?) {} + }) + + // Detect IME_ACTION_DONE + binding.pinEditText.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_DONE && isPinValid) { + val enteredPin = binding.pinEditText.text.toString() + callback.invoke(enteredPin) + dialog.dismissSafe() + } + true + } + + // We don't want to accidentally have the dialog dismiss when clicking outside of it. + // That is what the cancel button is for. + dialog.setCanceledOnTouchOutside(false) + + dialog.show() + + // Auto focus on PIN input and show keyboard + binding.pinEditText.requestFocus() + binding.pinEditText.postDelayed({ + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + imm.showSoftInput(binding.pinEditText, InputMethodManager.SHOW_IMPLICIT) + }, 200) + } +} \ 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 a2c34bf0..36c6376f 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 @@ -4,15 +4,14 @@ import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.loadThemes import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.ActivityAccountSelectBinding -import com.lagradost.cloudstream3.databinding.ActivityAccountSelectTvBinding -import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog +import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT +import com.lagradost.cloudstream3.ui.account.AccountDialogs.showPinInputDialog import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.getAccounts @@ -36,24 +35,34 @@ class AccountSelectActivity : AppCompatActivity() { window.navigationBarColor = colorFromAttribute(R.attr.primaryBlackBackground) - val binding = if (isTvSettings()) { - ActivityAccountSelectTvBinding.inflate(layoutInflater) - } else ActivityAccountSelectBinding.inflate(layoutInflater) - + val binding = ActivityAccountSelectBinding.inflate(layoutInflater) setContentView(binding.root) - val recyclerView: RecyclerView = binding.root.findViewById(R.id.account_recycler_view) + val recyclerView: RecyclerView = binding.accountRecyclerView - - val adapter = AccountAdapter(accounts) { selectedAccount -> + val adapter = AccountAdapter( + accounts, // Handle the selected account - onAccountSelected(selectedAccount) - } + accountSelectCallback = { onAccountSelected(it) }, + // Handle the selected account + accountCreateCallback = { onAccountUpdated(it) }, + accountEditCallback = { onAccountUpdated(it) } + ) + recyclerView.adapter = adapter - recyclerView.layoutManager = if (isTvSettings()) { - LinearLayoutManager(this) - } else GridLayoutManager(this, 2) + binding.editAccountButton.setOnClickListener { + adapter.viewType = VIEW_TYPE_EDIT_ACCOUNT + adapter.notifyDataSetChanged() + } + + if (isTvSettings()) { + val spanSize = if (accounts.count() <= 6) { + accounts.count() + } else 6 + + recyclerView.layoutManager = GridLayoutManager(this, spanSize) + } } private fun onAccountSelected(selectedAccount: DataStoreHelper.Account) { @@ -72,6 +81,23 @@ class AccountSelectActivity : AppCompatActivity() { } } + private fun onAccountUpdated(account: DataStoreHelper.Account) { + val currentAccounts = DataStoreHelper.accounts.toMutableList() + + val overrideIndex = currentAccounts.indexOfFirst { it.keyIndex == account.keyIndex } + if (overrideIndex != -1) { + currentAccounts[overrideIndex] = account + } else currentAccounts.add(account) + + val currentHomePage = DataStoreHelper.currentHomePage + setAccount(account) + + DataStoreHelper.accounts = currentAccounts.toTypedArray() + DataStoreHelper.currentHomePage = currentHomePage + + this.recreate() + } + private fun setAccount(account: DataStoreHelper.Account) { // Don't reload if it is the same account if (DataStoreHelper.selectedKeyIndex == account.keyIndex) { 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 4d940123..0fc80e91 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 @@ -38,6 +38,7 @@ import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observeNullable import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi +import com.lagradost.cloudstream3.ui.account.AccountSelectActivity import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.search.* import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback @@ -496,7 +497,8 @@ class HomeFragment : Fragment() { homeApiFab.setOnClickListener(apiChangeClickListener) homeChangeApi.setOnClickListener(apiChangeClickListener) homeSwitchAccount.setOnClickListener { v -> - DataStoreHelper.showWhoIsWatching(v?.context ?: return@setOnClickListener) + val accountSelectIntent = Intent(v.context, AccountSelectActivity::class.java) + v.context.startActivity(accountSelectIntent) } homeRandom.setOnClickListener { if (listHomepageItems.isNotEmpty()) { 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 5f194f1f..22d46b6b 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 @@ -1,5 +1,6 @@ package com.lagradost.cloudstream3.ui.home +import android.content.Intent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -27,6 +28,7 @@ import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.debugException import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.WatchType +import com.lagradost.cloudstream3.ui.account.AccountSelectActivity import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.ResultViewModel2 @@ -478,7 +480,8 @@ class HomeParentItemAdapterPreview( } homeAccount?.setOnClickListener { v -> - DataStoreHelper.showWhoIsWatching(v?.context ?: return@setOnClickListener) + val accountSelectIntent = Intent(v.context, AccountSelectActivity::class.java) + v.context.startActivity(accountSelectIntent) } (binding as? FragmentHomeHeadTvBinding)?.apply { 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 e687bcfb..37f07b18 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -1,15 +1,7 @@ package com.lagradost.cloudstream3.utils import android.content.Context -import android.content.DialogInterface -import android.text.Editable -import android.view.LayoutInflater -import androidx.appcompat.app.AlertDialog -import androidx.core.view.isGone -import androidx.core.view.isVisible -import androidx.core.widget.doOnTextChanged import com.fasterxml.jackson.annotation.JsonProperty -import com.google.android.material.bottomsheet.BottomSheetDialog import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia import com.lagradost.cloudstream3.APIHolder.unixTimeMS @@ -20,21 +12,12 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.CommonActivity.showToast -import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountEditBinding -import com.lagradost.cloudstream3.databinding.WhoIsWatchingBinding -import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.syncproviders.AccountManager import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.WatchType -import com.lagradost.cloudstream3.ui.WhoIsWatchingAdapter -import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog import com.lagradost.cloudstream3.ui.library.ListSorting import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.ui.result.VideoWatchState -import com.lagradost.cloudstream3.ui.result.setImage -import com.lagradost.cloudstream3.ui.result.setLinearListLayout -import com.lagradost.cloudstream3.utils.AppUtils.setDefaultFocus -import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import kotlin.reflect.KClass import kotlin.reflect.KProperty @@ -75,7 +58,7 @@ class UserPreferenceDelegate( object DataStoreHelper { // be aware, don't change the index of these as Account uses the index for the art - private val profileImages = arrayOf( + val profileImages = arrayOf( R.drawable.profile_bg_dark_blue, R.drawable.profile_bg_blue, R.drawable.profile_bg_orange, @@ -147,7 +130,7 @@ object DataStoreHelper { } const val TAG = "data_store_helper" - private var accounts by PreferenceDelegate("$TAG/account", arrayOf()) + var accounts by PreferenceDelegate("$TAG/account", arrayOf()) var selectedKeyIndex by PreferenceDelegate("$TAG/account_key_index", 0) val currentAccount: String get() = selectedKeyIndex.toString() @@ -166,7 +149,7 @@ object DataStoreHelper { } } - private fun setAccount(account: Account, refreshHomePage: Boolean) { + fun setAccount(account: Account, refreshHomePage: Boolean) { selectedKeyIndex = account.keyIndex showToast(account.name) MainActivity.bookmarksUpdatedEvent(true) @@ -175,147 +158,7 @@ object DataStoreHelper { } } - private fun editAccount(context: Context, account: Account, isNewAccount: Boolean) { - val binding = - WhoIsWatchingAccountEditBinding.inflate(LayoutInflater.from(context), null, false) - val builder = - AlertDialog.Builder(context, R.style.AlertDialogCustom) - .setView(binding.root) - - var currentEditAccount = account - val dialog = builder.show() - binding.accountName.text = Editable.Factory.getInstance()?.newEditable(account.name) - binding.accountName.doOnTextChanged { text, _, _, _ -> - currentEditAccount = currentEditAccount.copy(name = text?.toString() ?: "") - } - - binding.deleteBtt.isGone = isNewAccount - binding.deleteBtt.setOnClickListener { - val dialogClickListener = - DialogInterface.OnClickListener { _, which -> - when (which) { - DialogInterface.BUTTON_POSITIVE -> { - // remove all keys as well as the account, note that default wont get - // deleted from currentAccounts, as it is not part of "accounts", - // but the watch keys will - removeKeys(account.keyIndex.toString()) - val currentAccounts = accounts.toMutableList() - currentAccounts.removeIf { it.keyIndex == account.keyIndex } - accounts = currentAccounts.toTypedArray() - - // update UI - setAccount(getDefaultAccount(context), true) - dialog?.dismissSafe() - } - - DialogInterface.BUTTON_NEGATIVE -> {} - } - } - - try { - AlertDialog.Builder(context).setTitle(R.string.delete).setMessage( - context.getString(R.string.delete_message).format( - currentEditAccount.name - ) - ) - .setPositiveButton(R.string.delete, dialogClickListener) - .setNegativeButton(R.string.cancel, dialogClickListener) - .show().setDefaultFocus() - } catch (t: Throwable) { - logError(t) - // ye you somehow fucked up formatting did you? - } - } - - binding.cancelBtt.setOnClickListener { - dialog?.dismissSafe() - } - - binding.profilePic.setImage(account.image) - binding.profilePic.setOnClickListener { - // Roll the image forwards once - currentEditAccount = - currentEditAccount.copy(defaultImageIndex = (currentEditAccount.defaultImageIndex + 1) % profileImages.size) - binding.profilePic.setImage(currentEditAccount.image) - } - - binding.applyBtt.setOnClickListener { - if (currentEditAccount.lockPin != null) { - // Ask for the current PIN - showPinInputDialog(context, currentEditAccount.lockPin, false) { pin -> - if (pin == null) return@showPinInputDialog - // PIN is correct, proceed to update the account - performAccountUpdate(currentEditAccount) - dialog.dismissSafe() - } - } else { - // No lock PIN set, proceed to update the account - performAccountUpdate(currentEditAccount) - dialog.dismissSafe() - } - } - - // Handle setting or changing the PIN - - if (currentEditAccount.keyIndex == getDefaultAccount(context).keyIndex) { - binding.lockProfileCheckbox.isVisible = false - if (currentEditAccount.lockPin != null) { - currentEditAccount = currentEditAccount.copy(lockPin = null) - } - } - - var canSetPin = true - - binding.lockProfileCheckbox.isChecked = currentEditAccount.lockPin != null - - binding.lockProfileCheckbox.setOnCheckedChangeListener { _, isChecked -> - if (isChecked) { - if (canSetPin) { - showPinInputDialog(context, null, true) { pin -> - if (pin == null) { - binding.lockProfileCheckbox.isChecked = false - return@showPinInputDialog - } - - currentEditAccount = currentEditAccount.copy(lockPin = pin) - } - } - } else { - if (currentEditAccount.lockPin != null) { - // Ask for the current PIN - showPinInputDialog(context, currentEditAccount.lockPin, true) { pin -> - if (pin == null || pin != currentEditAccount.lockPin) { - canSetPin = false - binding.lockProfileCheckbox.isChecked = true - } else { - currentEditAccount = currentEditAccount.copy(lockPin = null) - } - } - } - } - } - - canSetPin = true - } - - private fun performAccountUpdate(account: Account) { - val currentAccounts = accounts.toMutableList() - - val overrideIndex = currentAccounts.indexOfFirst { it.keyIndex == account.keyIndex } - - if (overrideIndex != -1) { - currentAccounts[overrideIndex] = account - } else { - currentAccounts.add(account) - } - - val currentHomePage = this.currentHomePage - setAccount(account, false) - this.currentHomePage = currentHomePage - accounts = currentAccounts.toTypedArray() - } - - private fun getDefaultAccount(context: Context): Account { + fun getDefaultAccount(context: Context): Account { return accounts.let { currentAccounts -> currentAccounts.getOrNull(currentAccounts.indexOfFirst { it.keyIndex == 0 }) ?: Account( keyIndex = 0, @@ -333,71 +176,6 @@ object DataStoreHelper { } } - fun showWhoIsWatching(context: Context) { - val binding: WhoIsWatchingBinding = WhoIsWatchingBinding.inflate(LayoutInflater.from(context)) - val builder = BottomSheetDialog(context) - builder.setContentView(binding.root) - - val showAccount = accounts.toMutableList().apply { - val item = getDefaultAccount(context) - remove(item) - add(0, item) - } - - val accountName = context.getString(R.string.account) - - binding.profilesRecyclerview.setLinearListLayout(isHorizontal = true) - binding.profilesRecyclerview.adapter = WhoIsWatchingAdapter( - selectCallBack = { account -> - // Check if the selected account has a lock PIN set - if (account.lockPin != null) { - // Prompt for the lock pin - showPinInputDialog(context, account.lockPin, false) { pin -> - if (pin == null) return@showPinInputDialog - // Pin is correct, unlock the profile - setAccount(account, true) - builder.dismissSafe() - } - } else { - // No lock PIN set, directly set the account - setAccount(account, true) - builder.dismissSafe() - } - }, - addAccountCallback = { - val currentAccounts = accounts - val remainingImages = - profileImages.toSet() - currentAccounts.filter { it.customImage == null } - .mapNotNull { profileImages.getOrNull(it.defaultImageIndex) }.toSet() - val image = - profileImages.indexOf(remainingImages.randomOrNull() ?: profileImages.random()) - val keyIndex = (currentAccounts.maxOfOrNull { it.keyIndex } ?: 0) + 1 - - // create a new dummy account - editAccount( - context, - Account( - keyIndex = keyIndex, - name = "$accountName $keyIndex", - customImage = null, - defaultImageIndex = image - ), isNewAccount = true - ) - builder.dismissSafe() - }, - editCallBack = { account -> - editAccount( - context, account, isNewAccount = false - ) - builder.dismissSafe() - } - ).apply { - submitList(showAccount) - } - - builder.show() - } - data class PosDur( @JsonProperty("position") val position: Long, @JsonProperty("duration") val duration: Long diff --git a/app/src/main/res/layout/who_is_watching_account_edit.xml b/app/src/main/res/layout/account_edit_dialog.xml similarity index 89% rename from app/src/main/res/layout/who_is_watching_account_edit.xml rename to app/src/main/res/layout/account_edit_dialog.xml index 9fe84966..cd9c07d3 100644 --- a/app/src/main/res/layout/who_is_watching_account_edit.xml +++ b/app/src/main/res/layout/account_edit_dialog.xml @@ -1,4 +1,5 @@ + - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/account_list_item.xml b/app/src/main/res/layout/account_list_item.xml index 3331b85b..4307bf65 100644 --- a/app/src/main/res/layout/account_list_item.xml +++ b/app/src/main/res/layout/account_list_item.xml @@ -44,6 +44,14 @@ android:src="@drawable/video_locked" android:visibility="gone" /> + + - \ No newline at end of file + + diff --git a/app/src/main/res/layout/who_is_watching_account.xml b/app/src/main/res/layout/account_list_item_editing.xml similarity index 75% rename from app/src/main/res/layout/who_is_watching_account.xml rename to app/src/main/res/layout/account_list_item_editing.xml index 8152ed27..64f4d15f 100644 --- a/app/src/main/res/layout/who_is_watching_account.xml +++ b/app/src/main/res/layout/account_list_item_editing.xml @@ -4,13 +4,13 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/card_view" - android:layout_width="100dp" - android:layout_height="100dp" + android:layout_width="110dp" + android:layout_height="110dp" android:animateLayoutChanges="true" android:backgroundTint="?attr/primaryGrayBackground" - android:foreground="?attr/selectableItemBackgroundBorderless" + android:foreground="?attr/selectableItemBackground" app:cardCornerRadius="@dimen/rounded_image_radius" - android:layout_margin="5dp" + android:layout_margin="10dp" android:focusable="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1" @@ -20,10 +20,10 @@ app:layout_constraintTop_toTopOf="parent"> @@ -44,9 +44,16 @@ android:src="@drawable/video_locked" android:visibility="gone" /> + + - - \ No newline at end of file + + + + diff --git a/app/src/main/res/layout/activity_account_select_tv.xml b/app/src/main/res/layout/activity_account_select_tv.xml deleted file mode 100644 index 87340ad2..00000000 --- a/app/src/main/res/layout/activity_account_select_tv.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/who_is_watching.xml b/app/src/main/res/layout/who_is_watching.xml deleted file mode 100644 index f61cf6e4..00000000 --- a/app/src/main/res/layout/who_is_watching.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file