diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e0d43338..072ebb48 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -96,12 +96,6 @@ android:launchMode="singleTask" android:resizeableActivity="true" android:supportsPictureInPicture="true"> - - - - - - @@ -165,6 +159,23 @@ + + + + + + + + + + + + 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 new file mode 100644 index 00000000..c1566ce4 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountAdapter.kt @@ -0,0 +1,47 @@ +package com.lagradost.cloudstream3.ui.account + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.result.setImage +import com.lagradost.cloudstream3.utils.DataStoreHelper + +class AccountAdapter(private val accounts: List, private val onItemClick: (DataStoreHelper.Account) -> Unit) : + RecyclerView.Adapter() { + + inner class AccountViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + val accountName: TextView = itemView.findViewById(R.id.account_name) + val accountImage: ImageView = itemView.findViewById(R.id.account_image) + val lockIcon: ImageView = itemView.findViewById(R.id.lock_icon) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AccountViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.account_list_item, parent, false) + return AccountViewHolder(view) + } + + override fun onBindViewHolder(holder: AccountViewHolder, position: Int) { + val account = accounts[position] + + // Populate data into the UI elements + holder.accountName.text = account.name + holder.accountImage.setImage(account.image) + + if (account.lockPin != null) { + holder.lockIcon.isVisible = true + } + + holder.itemView.setOnClickListener { + onItemClick(account) + } + } + + override fun getItemCount(): Int { + return accounts.size + } +} 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 new file mode 100644 index 00000000..fda311ee --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountDialog.kt @@ -0,0 +1,108 @@ +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.widget.TextView +import androidx.annotation.StringRes +import androidx.appcompat.app.AlertDialog +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.databinding.LockPinDialogBinding + +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)) + + binding.pinEditTextError.visibility = View.GONE + + 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.dismiss() + } + } 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.dismiss() + } + 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() + } +} \ 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 new file mode 100644 index 00000000..9e5da5af --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/account/AccountSelectActivity.kt @@ -0,0 +1,56 @@ +package com.lagradost.cloudstream3.ui.account + +import android.content.Intent +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.lagradost.cloudstream3.MainActivity +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.ui.account.AccountDialog.showPinInputDialog +import com.lagradost.cloudstream3.utils.DataStoreHelper +import com.lagradost.cloudstream3.utils.DataStoreHelper.getAccounts + +class AccountSelectActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_account_select) + + val recyclerView: RecyclerView = findViewById(R.id.accountRecyclerView) + + val accounts = getAccounts(this@AccountSelectActivity) + + val adapter = AccountAdapter(accounts) { selectedAccount -> + // Handle the selected account + onAccountSelected(selectedAccount) + } + recyclerView.adapter = adapter + + recyclerView.layoutManager = GridLayoutManager(this, 3) + } + + 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 + DataStoreHelper.selectedKeyIndex = selectedAccount.keyIndex + navigateToMainActivity() + } + } else { + // No PIN set for the selected account, proceed to main activity + DataStoreHelper.selectedKeyIndex = selectedAccount.keyIndex + navigateToMainActivity() + } + } + + private fun navigateToMainActivity() { + val mainIntent = Intent(this, MainActivity::class.java) + startActivity(mainIntent) + finish() // Finish the account selection activity + } + + +} 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 1736c5a9..1adcac1c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/DataStoreHelper.kt @@ -3,12 +3,7 @@ package com.lagradost.cloudstream3.utils import android.content.Context import android.content.DialogInterface import android.text.Editable -import android.text.TextWatcher import android.view.LayoutInflater -import android.view.View -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 @@ -25,7 +20,6 @@ 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.LockPinDialogBinding import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountEditBinding import com.lagradost.cloudstream3.databinding.WhoIsWatchingBinding import com.lagradost.cloudstream3.mvvm.logError @@ -33,6 +27,7 @@ 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 @@ -330,6 +325,14 @@ object DataStoreHelper { } } + fun getAccounts(context: Context): List { + return accounts.toMutableList().apply { + val item = getDefaultAccount(context) + remove(item) + add(0, item) + } + } + fun showWhoIsWatching(context: Context) { val binding: WhoIsWatchingBinding = WhoIsWatchingBinding.inflate(LayoutInflater.from(context)) val builder = BottomSheetDialog(context) @@ -395,97 +398,6 @@ object DataStoreHelper { builder.show() } - private 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)) - - binding.pinEditTextError.visibility = View.GONE - - 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.dismiss() - } - } 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.dismiss() - } - 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() - } - data class PosDur( @JsonProperty("position") val position: Long, @JsonProperty("duration") val duration: Long diff --git a/app/src/main/res/layout/account_list_item.xml b/app/src/main/res/layout/account_list_item.xml new file mode 100644 index 00000000..a6f96242 --- /dev/null +++ b/app/src/main/res/layout/account_list_item.xml @@ -0,0 +1,46 @@ + + + + + + + + + \ 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 new file mode 100644 index 00000000..d7e73f32 --- /dev/null +++ b/app/src/main/res/layout/activity_account_select.xml @@ -0,0 +1,27 @@ + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c5694793..9efca7f2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -722,4 +722,5 @@ PIN Incorrect PIN. Please try again. PIN must be 4 characters + Select an Account