[WiP] Add account selection page

This commit is contained in:
Luna712 2023-10-29 16:31:29 -06:00
parent 9d957d7cc7
commit 512a58b49c
8 changed files with 311 additions and 103 deletions

View file

@ -96,12 +96,6 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:resizeableActivity="true" android:resizeableActivity="true"
android:supportsPictureInPicture="true"> android:supportsPictureInPicture="true">
<intent-filter android:exported="true">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<!-- cloudstreamplayer://encodedUrl?name=Dune --> <!-- cloudstreamplayer://encodedUrl?name=Dune -->
<intent-filter> <intent-filter>
@ -165,6 +159,23 @@
</intent-filter> </intent-filter>
</activity> </activity>
<activity
android:name=".ui.account.AccountSelectActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|keyboard|keyboardHidden|navigation"
android:exported="true"
android:launchMode="singleTask"
android:resizeableActivity="true">
<intent-filter android:exported="true">
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
<category android:name="android.intent.category.LEANBACK_LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<activity <activity
android:name=".ui.EasterEggMonke" android:name=".ui.EasterEggMonke"
android:exported="true" /> android:exported="true" />

View file

@ -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<DataStoreHelper.Account>, private val onItemClick: (DataStoreHelper.Account) -> Unit) :
RecyclerView.Adapter<AccountAdapter.AccountViewHolder>() {
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
}
}

View file

@ -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()
}
}

View file

@ -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
}
}

View file

@ -3,12 +3,7 @@ package com.lagradost.cloudstream3.utils
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.text.Editable import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater 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.appcompat.app.AlertDialog
import androidx.core.view.isGone import androidx.core.view.isGone
import androidx.core.view.isVisible 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.removeKeys
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.databinding.LockPinDialogBinding
import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountEditBinding import com.lagradost.cloudstream3.databinding.WhoIsWatchingAccountEditBinding
import com.lagradost.cloudstream3.databinding.WhoIsWatchingBinding import com.lagradost.cloudstream3.databinding.WhoIsWatchingBinding
import com.lagradost.cloudstream3.mvvm.logError 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.syncproviders.SyncAPI
import com.lagradost.cloudstream3.ui.WatchType import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.WhoIsWatchingAdapter 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.library.ListSorting
import com.lagradost.cloudstream3.ui.result.UiImage import com.lagradost.cloudstream3.ui.result.UiImage
import com.lagradost.cloudstream3.ui.result.VideoWatchState import com.lagradost.cloudstream3.ui.result.VideoWatchState
@ -330,6 +325,14 @@ object DataStoreHelper {
} }
} }
fun getAccounts(context: Context): List<Account> {
return accounts.toMutableList().apply {
val item = getDefaultAccount(context)
remove(item)
add(0, item)
}
}
fun showWhoIsWatching(context: Context) { fun showWhoIsWatching(context: Context) {
val binding: WhoIsWatchingBinding = WhoIsWatchingBinding.inflate(LayoutInflater.from(context)) val binding: WhoIsWatchingBinding = WhoIsWatchingBinding.inflate(LayoutInflater.from(context))
val builder = BottomSheetDialog(context) val builder = BottomSheetDialog(context)
@ -395,97 +398,6 @@ object DataStoreHelper {
builder.show() 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( data class PosDur(
@JsonProperty("position") val position: Long, @JsonProperty("position") val position: Long,
@JsonProperty("duration") val duration: Long @JsonProperty("duration") val duration: Long

View file

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/card_view"
android:layout_width="100dp"
android:layout_height="100dp"
android:animateLayoutChanges="true"
android:backgroundTint="?attr/primaryGrayBackground"
android:foreground="?attr/selectableItemBackgroundBorderless"
app:cardCornerRadius="@dimen/rounded_image_radius"
android:layout_margin="5dp"
android:focusable="true"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_percent="0.4"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:id="@+id/account_image"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:alpha="0.4"
android:contentDescription="@string/profile_background_des"
android:scaleType="centerCrop" />
<ImageView
android:id="@+id/lock_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="top|end"
android:layout_margin="4dp"
android:src="@drawable/video_locked"
android:visibility="gone" />
<TextView
android:id="@+id/account_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="10dp"
android:textSize="16sp" />
</androidx.cardview.widget.CardView>

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="?attr/primaryBlackBackground"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:gravity="center">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/select_an_account"
android:textSize="24sp"
android:textStyle="bold"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/accountRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="16dp" />
</LinearLayout>

View file

@ -722,4 +722,5 @@
<string name="pin">PIN</string> <string name="pin">PIN</string>
<string name="pin_error_incorrect">Incorrect PIN. Please try again.</string> <string name="pin_error_incorrect">Incorrect PIN. Please try again.</string>
<string name="pin_error_length">PIN must be 4 characters</string> <string name="pin_error_length">PIN must be 4 characters</string>
<string name="select_an_account">Select an Account</string>
</resources> </resources>