From 5bf2b4ead21235638e6e4f24f51b1a20beac4333 Mon Sep 17 00:00:00 2001 From: Luna712 <142361265+Luna712@users.noreply.github.com> Date: Sat, 11 Nov 2023 15:47:23 -0700 Subject: [PATCH] Massive changes to account flow (#741) * Massive changes to account flow --- app/src/main/AndroidManifest.xml | 5 +- .../lagradost/cloudstream3/MainActivity.kt | 6 +- .../cloudstream3/ui/WhoIsWatchingAdapter.kt | 110 ------ .../cloudstream3/ui/account/AccountAdapter.kt | 186 +++++++-- .../cloudstream3/ui/account/AccountDialog.kt | 132 ------- .../cloudstream3/ui/account/AccountHelper.kt | 356 ++++++++++++++++++ .../ui/account/AccountSelectActivity.kt | 178 ++++++--- .../AccountSelectLinearItemDecoration.kt | 14 + .../ui/account/AccountViewModel.kt | 123 ++++++ .../cloudstream3/ui/home/HomeFragment.kt | 6 +- .../ui/home/HomeParentItemAdapterPreview.kt | 5 +- .../ui/library/LibraryViewModel.kt | 4 +- .../cloudstream3/utils/DataStoreHelper.kt | 239 +----------- .../main/res/drawable/ic_baseline_edit_24.xml | 11 + ...count_edit.xml => account_edit_dialog.xml} | 64 +--- app/src/main/res/layout/account_list_item.xml | 64 ++-- ...ount_add.xml => account_list_item_add.xml} | 11 +- ...account.xml => account_list_item_edit.xml} | 32 +- .../main/res/layout/account_select_linear.xml | 44 +++ .../res/layout/activity_account_select.xml | 63 ++-- app/src/main/res/layout/who_is_watching.xml | 55 --- app/src/main/res/values/dimens.xml | 2 + app/src/main/res/values/strings.xml | 7 + app/src/main/res/xml/settings_account.xml | 6 + 24 files changed, 998 insertions(+), 725 deletions(-) delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/WhoIsWatchingAdapter.kt delete mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt create mode 100644 app/src/main/res/drawable/ic_baseline_edit_24.xml rename app/src/main/res/layout/{who_is_watching_account_edit.xml => account_edit_dialog.xml} (70%) rename app/src/main/res/layout/{who_is_watching_account_add.xml => account_list_item_add.xml} (79%) rename app/src/main/res/layout/{who_is_watching_account.xml => account_list_item_edit.xml} (70%) create mode 100644 app/src/main/res/layout/account_select_linear.xml delete mode 100644 app/src/main/res/layout/who_is_watching.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a71c5ecb..e665c3bc 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -167,9 +167,8 @@ + android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden" + android:exported="true"> diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 3a10aa10..2819ab98 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -309,9 +309,13 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { // kinda shitty solution, but cant com main->home otherwise for popups val bookmarksUpdatedEvent = Event() /** - * Used by data store helper to fully reload home when switching accounts + * Used by DataStoreHelper to fully reload home when switching accounts */ val reloadHomeEvent = Event() + /** + * Used by DataStoreHelper to fully reload library when switching accounts + */ + val reloadLibraryEvent = Event() /** 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 aea55392..60260edf 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 @@ -2,55 +2,197 @@ package com.lagradost.cloudstream3.ui.account import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.content.ContextCompat 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.AccountListItemEditBinding +import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountEditDialog import com.lagradost.cloudstream3.ui.result.setImage import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.UIHelper.setImage 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, + private val accountDeleteCallback: (DataStoreHelper.Account) -> Unit ) : RecyclerView.Adapter() { - inner class AccountViewHolder(private val binding: AccountListItemBinding) : + companion object { + const val VIEW_TYPE_SELECT_ACCOUNT = 0 + 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 isTv = isTvSettings() || !root.isInTouchMode - if (isTvSettings()) { - binding.root.isFocusableInTouchMode = true - if (isLastUsedAccount) { - binding.root.requestFocus() + val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex + + accountName.text = account.name + accountImage.setImage(account.image) + lockIcon.isVisible = account.lockPin != null + outline.isVisible = !isTv && isLastUsedAccount + + if (isTv) { + // For emulator but this is fine on TV also + root.isFocusableInTouchMode = true + if (isLastUsedAccount) { + root.requestFocus() + } + + root.foreground = ContextCompat.getDrawable( + root.context, + R.drawable.outline_drawable + ) + } else { + root.setOnLongClickListener { + showAccountEditDialog( + context = root.context, + account = account, + isNewAccount = false, + accountEditCallback = { account -> accountEditCallback.invoke(account) }, + accountDeleteCallback = { account -> accountDeleteCallback.invoke(account) } + ) + + true + } + } + + root.setOnClickListener { + accountSelectCallback.invoke(account) + } } - } - binding.root.setOnClickListener { - onItemClick(account) + is AccountListItemEditBinding -> binding.apply { + if (account == null) return@apply + + val isTv = isTvSettings() || !root.isInTouchMode + + val isLastUsedAccount = account.keyIndex == DataStoreHelper.selectedKeyIndex + + accountName.text = account.name + accountImage.setImage( + account.image, + fadeIn = false, + radius = 10 + ) + lockIcon.isVisible = account.lockPin != null + outline.isVisible = !isTv && isLastUsedAccount + + if (isTv) { + // For emulator but this is fine on TV also + root.isFocusableInTouchMode = true + if (isLastUsedAccount) { + root.requestFocus() + } + + root.foreground = ContextCompat.getDrawable( + root.context, + R.drawable.outline_drawable + ) + } + + root.setOnClickListener { + showAccountEditDialog( + context = root.context, + account = account, + isNewAccount = false, + accountEditCallback = { account -> accountEditCallback.invoke(account) }, + accountDeleteCallback = { account -> accountDeleteCallback.invoke(account) } + ) + } + } + + 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, + accountEditCallback = { account -> accountCreateCallback.invoke(account) }, + accountDeleteCallback = {} + ) + } + } } } } - 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_SELECT_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 -> { + AccountListItemEditBinding.inflate( + LayoutInflater.from(parent.context), + parent, + false + ) + } + else -> throw IllegalArgumentException("Invalid view type") + } ) - 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_SELECT_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 76686aef..00000000 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt +++ /dev/null @@ -1,132 +0,0 @@ -package com.lagradost.cloudstream3.ui.account - -import android.content.Context -import android.view.LayoutInflater -import android.view.inputmethod.EditorInfo -import android.widget.TextView -import androidx.annotation.StringRes -import androidx.appcompat.app.AlertDialog -import androidx.core.view.isVisible -import androidx.core.widget.doOnTextChanged -import com.lagradost.cloudstream3.R -import com.lagradost.cloudstream3.databinding.LockPinDialogBinding -import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe -import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod - -object AccountDialog { - // TODO add account creation dialog to allow creating accounts directly from AccountSelectActivity - - 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 - - var isPinValid = false - - 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 (!isPinValid) { - callback.invoke(null) - } - } - - if (isNewPin) { - if (errorText != null) binding.pinEditTextError.visibleWithText(errorText) - builder.setPositiveButton(R.string.setup_done) { _, _ -> - if (!isPinValid) { - // If the done button is pressed and there is an 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() - - binding.pinEditText.doOnTextChanged { text, _, _, _ -> - val enteredPin = text.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 - } - } - - // 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({ - showInputMethod(binding.pinEditText) - }, 200) - } -} \ 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 new file mode 100644 index 00000000..1db49e27 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountHelper.kt @@ -0,0 +1,356 @@ +package com.lagradost.cloudstream3.ui.account + +import android.app.Activity +import android.content.Context +import android.content.DialogInterface +import android.content.Intent +import android.text.Editable +import android.view.LayoutInflater +import android.view.inputmethod.EditorInfo +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 androidx.lifecycle.ViewModelProvider +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity +import com.lagradost.cloudstream3.MainActivity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.AccountEditDialogBinding +import com.lagradost.cloudstream3.databinding.AccountSelectLinearBinding +import com.lagradost.cloudstream3.databinding.LockPinDialogBinding +import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.cloudstream3.mvvm.observe +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.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.getDefaultAccount +import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe +import com.lagradost.cloudstream3.utils.UIHelper.showInputMethod + +object AccountHelper { + fun showAccountEditDialog( + context: Context, + account: DataStoreHelper.Account, + isNewAccount: Boolean, + accountEditCallback: (DataStoreHelper.Account) -> Unit, + accountDeleteCallback: (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() + + if (!isNewAccount) binding.title.setText(R.string.edit_account) + + // 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 -> { + accountDeleteCallback.invoke(account) + 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.accountImage.setImage(account.image) + binding.accountImage.setOnClickListener { + // Roll the image forwards once + currentEditAccount = + currentEditAccount.copy(defaultImageIndex = (currentEditAccount.defaultImageIndex + 1) % DataStoreHelper.profileImages.size) + binding.accountImage.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 + accountEditCallback.invoke(currentEditAccount) + dialog.dismissSafe() + } + } else { + // No lock PIN set, proceed to update the account + accountEditCallback.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, + forStartup: Boolean = false, + 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 + + var isPinValid = false + + 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 (!isPinValid) { + callback.invoke(null) + } + } + + if (forStartup) { + val currentAccount = DataStoreHelper.accounts.firstOrNull { + it.keyIndex == DataStoreHelper.selectedKeyIndex + } + + builder.setTitle(context.getString(R.string.enter_pin_with_name, currentAccount?.name)) + builder.setOnDismissListener { + if (!isPinValid) { + context.getActivity()?.finish() + } + } + // So that if they don't know the PIN for the current account, + // they don't get completely locked out + builder.setNeutralButton(R.string.use_default_account) { _, _ -> + val activity = context.getActivity() + if (activity is AccountSelectActivity) { + isPinValid = true + activity.viewModel.handleAccountSelect(getDefaultAccount(context), activity) + } + } + } + + if (isNewPin) { + if (errorText != null) binding.pinEditTextError.visibleWithText(errorText) + builder.setPositiveButton(R.string.setup_done) { _, _ -> + if (!isPinValid) { + // If the done button is pressed and there is an 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() + + binding.pinEditText.doOnTextChanged { text, _, _, _ -> + val enteredPin = text.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 + } + } + + // 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({ + showInputMethod(binding.pinEditText) + }, 200) + } + + fun Activity?.showAccountSelectLinear() { + val activity = this as? MainActivity ?: return + val viewModel = ViewModelProvider(activity)[AccountViewModel::class.java] + + val binding: AccountSelectLinearBinding = AccountSelectLinearBinding.inflate( + LayoutInflater.from(activity) + ) + + val builder = BottomSheetDialog(activity) + builder.setContentView(binding.root) + builder.show() + + binding.manageAccountsButton.setOnClickListener { + val accountSelectIntent = Intent(activity, AccountSelectActivity::class.java) + accountSelectIntent.putExtra("isEditingFromMainActivity", true) + activity.startActivity(accountSelectIntent) + builder.dismissSafe() + } + + val recyclerView: RecyclerView = binding.accountRecyclerView + + val itemSize = recyclerView.resources.getDimensionPixelSize( + R.dimen.account_select_linear_item_size + ) + + recyclerView.addItemDecoration(AccountSelectLinearItemDecoration(itemSize)) + + recyclerView.setLinearListLayout(isHorizontal = true) + + val currentAccount = DataStoreHelper.accounts.firstOrNull { + it.keyIndex == DataStoreHelper.selectedKeyIndex + } ?: getDefaultAccount(activity) + + // We want to make sure the accounts are up-to-date + viewModel.handleAccountSelect( + currentAccount, + activity, + reloadForActivity = true + ) + + activity.observe(viewModel.accounts) { liveAccounts -> + recyclerView.adapter = AccountAdapter( + liveAccounts, + accountSelectCallback = { account -> + viewModel.handleAccountSelect(account, activity) + builder.dismissSafe() + }, + accountCreateCallback = { viewModel.handleAccountUpdate(it, activity) }, + accountEditCallback = { viewModel.handleAccountUpdate(it, activity) }, + accountDeleteCallback = { viewModel.handleAccountDelete(it, activity) } + ) + + activity.observe(viewModel.selectedKeyIndex) { selectedKeyIndex -> + // Scroll to current account (which is focused by default) + val layoutManager = recyclerView.layoutManager as LinearLayoutManager + layoutManager.scrollToPositionWithOffset(selectedKeyIndex, 0) + } + } + } +} \ 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 457d4b81..23071f59 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 @@ -1,87 +1,163 @@ package com.lagradost.cloudstream3.ui.account +import android.annotation.SuppressLint import android.content.Intent import android.os.Bundle import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.ViewModelProvider +import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager -import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.CommonActivity import com.lagradost.cloudstream3.CommonActivity.loadThemes +import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.MainActivity import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.databinding.ActivityAccountSelectBinding -import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog +import com.lagradost.cloudstream3.mvvm.observe +import com.lagradost.cloudstream3.ui.AutofitRecyclerView +import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_EDIT_ACCOUNT +import com.lagradost.cloudstream3.ui.account.AccountAdapter.Companion.VIEW_TYPE_SELECT_ACCOUNT import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings -import com.lagradost.cloudstream3.utils.DataStoreHelper -import com.lagradost.cloudstream3.utils.DataStoreHelper.getAccounts +import com.lagradost.cloudstream3.utils.DataStoreHelper.accounts +import com.lagradost.cloudstream3.utils.DataStoreHelper.selectedKeyIndex +import com.lagradost.cloudstream3.utils.DataStoreHelper.setAccount import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute class AccountSelectActivity : AppCompatActivity() { + lateinit var viewModel: AccountViewModel + + @SuppressLint("NotifyDataSetChanged") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val accounts = getAccounts(this@AccountSelectActivity) - - // Don't show account selection if there is only - // one account that exists - if (accounts.count() <= 1) { - navigateToMainActivity() - return - } - - CommonActivity.init(this) loadThemes(this) window.navigationBarColor = colorFromAttribute(R.attr.primaryBlackBackground) - val binding = ActivityAccountSelectBinding.inflate(layoutInflater) + // Are we editing and coming from MainActivity? + val isEditingFromMainActivity = intent.getBooleanExtra( + "isEditingFromMainActivity", + false + ) - setContentView(binding.root) + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) + val skipStartup = settingsManager.getBoolean( + getString(R.string.skip_startup_account_select_key), + false + ) || accounts.count() <= 1 - val recyclerView: RecyclerView = binding.accountRecyclerView + viewModel = ViewModelProvider(this)[AccountViewModel::class.java] + // Don't show account selection if there is only + // one account that exists + if (!isEditingFromMainActivity && skipStartup) { + val currentAccount = accounts.firstOrNull { it.keyIndex == selectedKeyIndex } + if (currentAccount?.lockPin != null) { + CommonActivity.init(this) + viewModel.handleAccountSelect(currentAccount, this, true) + observe(viewModel.isAllowedLogin) { isAllowedLogin -> + if (isAllowedLogin) { + // We are allowed to continue to MainActivity + navigateToMainActivity() + } + } + } else { + if (accounts.count() > 1) { + showToast(this, getString( + R.string.logged_account, + currentAccount?.name + )) + } - val adapter = AccountAdapter(accounts) { selectedAccount -> - // Handle the selected account - onAccountSelected(selectedAccount) - } - recyclerView.adapter = adapter - - if (isTvSettings()) { - val spanSize = if (accounts.count() <= 6) { - accounts.count() - } else 6 - - recyclerView.layoutManager = GridLayoutManager(this, spanSize) - } - } - - private fun onAccountSelected(selectedAccount: DataStoreHelper.Account) { - if (selectedAccount.lockPin != null) { - // The selected account has a PIN set, prompt the user to enter the PIN - showPinInputDialog(this@AccountSelectActivity, selectedAccount.lockPin, false) { pin -> - if (pin == null) return@showPinInputDialog - // Pin is correct, proceed to main activity - setAccount(selectedAccount) navigateToMainActivity() } - } else { - // No PIN set for the selected account, proceed to main activity - setAccount(selectedAccount) - navigateToMainActivity() - } - } - private fun setAccount(account: DataStoreHelper.Account) { - // Don't reload if it is the same account - if (DataStoreHelper.selectedKeyIndex == account.keyIndex) { return } - DataStoreHelper.selectedKeyIndex = account.keyIndex + CommonActivity.init(this) - MainActivity.bookmarksUpdatedEvent(true) - MainActivity.reloadHomeEvent(true) + val binding = ActivityAccountSelectBinding.inflate(layoutInflater) + setContentView(binding.root) + + val recyclerView: AutofitRecyclerView = binding.accountRecyclerView + + observe(viewModel.accounts) { liveAccounts -> + val adapter = AccountAdapter( + liveAccounts, + // Handle the selected account + accountSelectCallback = { + viewModel.handleAccountSelect(it, this) + observe(viewModel.isAllowedLogin) { isAllowedLogin -> + if (isAllowedLogin) { + // We are allowed to continue to MainActivity + navigateToMainActivity() + } + } + }, + accountCreateCallback = { viewModel.handleAccountUpdate(it, this) }, + accountEditCallback = { + viewModel.handleAccountUpdate(it, this) + + // We came from MainActivity, return there + // and switch to the edited account + if (isEditingFromMainActivity) { + setAccount(it) + navigateToMainActivity() + } + }, + accountDeleteCallback = { viewModel.handleAccountDelete(it,this) } + ) + + recyclerView.adapter = adapter + + if (isTvSettings()) { + binding.editAccountButton.setBackgroundResource( + R.drawable.player_button_tv_attr_no_bg + ) + } + + observe(viewModel.selectedKeyIndex) { selectedKeyIndex -> + // Scroll to current account (which is focused by default) + val layoutManager = recyclerView.layoutManager as GridLayoutManager + layoutManager.scrollToPositionWithOffset(selectedKeyIndex, 0) + } + + observe(viewModel.isEditing) { isEditing -> + if (isEditing) { + binding.editAccountButton.setImageResource(R.drawable.ic_baseline_close_24) + binding.title.setText(R.string.manage_accounts) + adapter.viewType = VIEW_TYPE_EDIT_ACCOUNT + } else { + binding.editAccountButton.setImageResource(R.drawable.ic_baseline_edit_24) + binding.title.setText(R.string.select_an_account) + adapter.viewType = VIEW_TYPE_SELECT_ACCOUNT + } + + adapter.notifyDataSetChanged() + } + + if (isEditingFromMainActivity) { + viewModel.setIsEditing(true) + } + + binding.editAccountButton.setOnClickListener { + // We came from MainActivity, return there + // and resume its state + if (isEditingFromMainActivity) { + navigateToMainActivity() + return@setOnClickListener + } + + viewModel.toggleIsEditing() + } + + if (isTvSettings()) { + recyclerView.spanCount = if (liveAccounts.count() + 1 <= 6) { + liveAccounts.count() + 1 + } else 6 + } + } } private fun navigateToMainActivity() { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt new file mode 100644 index 00000000..eb907b34 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectLinearItemDecoration.kt @@ -0,0 +1,14 @@ +package com.lagradost.cloudstream3.ui.account + +import android.graphics.Rect +import android.view.View +import androidx.recyclerview.widget.RecyclerView + +class AccountSelectLinearItemDecoration(private val size: Int) : RecyclerView.ItemDecoration() { + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { + val layoutParams = view.layoutParams as RecyclerView.LayoutParams + layoutParams.width = size + layoutParams.height = size + view.layoutParams = layoutParams + } +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt new file mode 100644 index 00000000..14559607 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountViewModel.kt @@ -0,0 +1,123 @@ +package com.lagradost.cloudstream3.ui.account + +import android.content.Context +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import com.lagradost.cloudstream3.AcraApplication.Companion.context +import com.lagradost.cloudstream3.AcraApplication.Companion.removeKeys +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 getAllAccounts(): List { + return context?.let { getAccounts(it) } ?: DataStoreHelper.accounts.toList() + } + + private val _accounts: MutableLiveData> = MutableLiveData(getAllAccounts()) + val accounts: LiveData> = _accounts + + private val _isEditing = MutableLiveData(false) + val isEditing: LiveData = _isEditing + + private val _isAllowedLogin = MutableLiveData(false) + val isAllowedLogin: LiveData = _isAllowedLogin + + private val _selectedKeyIndex = MutableLiveData( + getAllAccounts().indexOfFirst { + it.keyIndex == DataStoreHelper.selectedKeyIndex + } + ) + val selectedKeyIndex: LiveData = _selectedKeyIndex + + fun setIsEditing(value: Boolean) { + _isEditing.postValue(value) + } + + fun toggleIsEditing() { + _isEditing.postValue(!(_isEditing.value ?: false)) + } + + fun handleAccountUpdate( + account: DataStoreHelper.Account, + context: Context + ) { + val currentAccounts = getAccounts(context).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.currentHomePage = currentHomePage + DataStoreHelper.accounts = currentAccounts.toTypedArray() + + _accounts.postValue(getAccounts(context)) + _selectedKeyIndex.postValue(getAccounts(context).indexOf(account)) + } + + fun handleAccountDelete( + account: DataStoreHelper.Account, + context: Context + ) { + removeKeys(account.keyIndex.toString()) + + val currentAccounts = getAccounts(context).toMutableList() + + currentAccounts.removeIf { it.keyIndex == account.keyIndex } + + DataStoreHelper.accounts = currentAccounts.toTypedArray() + + if (account.keyIndex == DataStoreHelper.selectedKeyIndex) { + setAccount(getDefaultAccount(context)) + } + + _accounts.postValue(getAccounts(context)) + _selectedKeyIndex.postValue(getAllAccounts().indexOfFirst { + it.keyIndex == DataStoreHelper.selectedKeyIndex + }) + } + + fun handleAccountSelect( + account: DataStoreHelper.Account, + context: Context, + forStartup: Boolean = false, + reloadForActivity: Boolean = false + ) { + if (reloadForActivity) { + _accounts.postValue(getAccounts(context)) + _selectedKeyIndex.postValue(getAccounts(context).indexOf(account)) + return + } + + // Check if the selected account has a lock PIN set + if (account.lockPin != null) { + // The selected account has a PIN set, prompt the user to enter the PIN + showPinInputDialog( + context, + account.lockPin, + false, + forStartup + ) { pin -> + if (pin == null) return@showPinInputDialog + // Pin is correct, proceed + _isAllowedLogin.postValue(true) + _selectedKeyIndex.postValue(getAccounts(context).indexOf(account)) + setAccount(account) + } + } else { + // No PIN set for the selected account, proceed + _isAllowedLogin.postValue(true) + _selectedKeyIndex.postValue(getAccounts(context).indexOf(account)) + setAccount(account) + } + } +} \ No newline at end of file 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..d54ea488 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.AccountHelper.showAccountSelectLinear import com.lagradost.cloudstream3.ui.result.txt import com.lagradost.cloudstream3.ui.search.* import com.lagradost.cloudstream3.ui.search.SearchHelper.handleSearchClickCallback @@ -495,9 +496,10 @@ class HomeFragment : Fragment() { //homeChangeApiLoading.setOnClickListener(apiChangeClickListener) homeApiFab.setOnClickListener(apiChangeClickListener) homeChangeApi.setOnClickListener(apiChangeClickListener) - homeSwitchAccount.setOnClickListener { v -> - DataStoreHelper.showWhoIsWatching(v?.context ?: return@setOnClickListener) + homeSwitchAccount.setOnClickListener { + activity?.showAccountSelectLinear() } + homeRandom.setOnClickListener { if (listHomepageItems.isNotEmpty()) { activity.loadSearchResult(listHomepageItems.random()) 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..dfe768d2 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 @@ -27,6 +27,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.AccountHelper.showAccountSelectLinear import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.ResultViewModel2 @@ -477,8 +478,8 @@ class HomeParentItemAdapterPreview( } } - homeAccount?.setOnClickListener { v -> - DataStoreHelper.showWhoIsWatching(v?.context ?: return@setOnClickListener) + homeAccount?.setOnClickListener { + activity?.showAccountSelectLinear() } (binding as? FragmentHomeHeadTvBinding)?.apply { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt index b44913d9..037903de 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/library/LibraryViewModel.kt @@ -120,11 +120,11 @@ class LibraryViewModel : ViewModel() { } init { - MainActivity.reloadHomeEvent += ::reloadPages + MainActivity.reloadLibraryEvent += ::reloadPages } override fun onCleared() { - MainActivity.reloadHomeEvent -= ::reloadPages + MainActivity.reloadLibraryEvent -= ::reloadPages super.onCleared() } } \ No newline at end of file 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..6babe273 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,156 +149,21 @@ object DataStoreHelper { } } - private fun setAccount(account: Account, refreshHomePage: Boolean) { + fun setAccount(account: Account) { + val homepage = currentHomePage + selectedKeyIndex = account.keyIndex - showToast(account.name) + showToast(context?.getString(R.string.logged_account, account.name) ?: account.name) MainActivity.bookmarksUpdatedEvent(true) - if (refreshHomePage) { + MainActivity.reloadLibraryEvent(true) + val oldAccount = accounts.find { it.keyIndex == account.keyIndex } + if (oldAccount != null && currentHomePage != homepage) { + // This is not a new account, and the homepage has changed, reload it MainActivity.reloadHomeEvent(true) } } - 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 +181,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/drawable/ic_baseline_edit_24.xml b/app/src/main/res/drawable/ic_baseline_edit_24.xml new file mode 100644 index 00000000..dba3e567 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_edit_24.xml @@ -0,0 +1,11 @@ + + + + + \ No newline at end of file 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 70% 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..9d39425a 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 @@ + - - - - - - - - - - - + android:foreground="@drawable/outline_drawable_forced_round" + android:layout_width="match_parent" + android:layout_height="60dp" + android:layout_gravity="center" /> + + android:layout_marginTop="-60dp"> + style="@style/BlackButton" /> + style="@style/WhiteButton" /> + style="@style/BlackButton" /> + \ 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..f133d6c3 100644 --- a/app/src/main/res/layout/account_list_item.xml +++ b/app/src/main/res/layout/account_list_item.xml @@ -9,9 +9,9 @@ android:animateLayoutChanges="true" android:backgroundTint="?attr/primaryGrayBackground" android:foreground="?attr/selectableItemBackground" - app:cardCornerRadius="@dimen/rounded_image_radius" android:layout_margin="10dp" android:focusable="true" + app:cardCornerRadius="@dimen/rounded_image_radius" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1" app:layout_constraintEnd_toEndOf="parent" @@ -19,38 +19,38 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent"> - + - + - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/who_is_watching_account_add.xml b/app/src/main/res/layout/account_list_item_add.xml similarity index 79% rename from app/src/main/res/layout/who_is_watching_account_add.xml rename to app/src/main/res/layout/account_list_item_add.xml index 91c7e419..dea64484 100644 --- a/app/src/main/res/layout/who_is_watching_account_add.xml +++ b/app/src/main/res/layout/account_list_item_add.xml @@ -2,16 +2,15 @@ + android:visibility="gone" + tools:visibility="visible" /> + android:visibility="gone" + tools:visibility="visible" /> + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_account_select.xml b/app/src/main/res/layout/activity_account_select.xml index d5870f24..bd6007dc 100644 --- a/app/src/main/res/layout/activity_account_select.xml +++ b/app/src/main/res/layout/activity_account_select.xml @@ -1,28 +1,49 @@ - + android:layout_height="match_parent"> - + - + - \ No newline at end of file + + + + + + \ 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 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b349aecc..631201b1 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -20,4 +20,6 @@ 50dp 1dp + + 100dp \ 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 9e0575da..ce660a67 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,6 +65,7 @@ filter_sub_lang_key pref_filter_search_quality_key enable_nsfw_on_providers_key + skip_startup_account_select_key enable_skip_op_from_database %d %s | %s @@ -720,10 +721,16 @@ Enter PIN + Enter PIN for %s Enter Current PIN Lock Profile PIN Incorrect PIN. Please try again. PIN must be 4 characters Select an Account + Manage Accounts + Edit account + Logged in as %s + Skip account selection at startup + Use Default Account diff --git a/app/src/main/res/xml/settings_account.xml b/app/src/main/res/xml/settings_account.xml index d3dbcb31..ec882088 100644 --- a/app/src/main/res/xml/settings_account.xml +++ b/app/src/main/res/xml/settings_account.xml @@ -1,6 +1,12 @@ + +