diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 32ad104f..1f50dfaf 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -29,6 +29,7 @@ import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.network.initRequestClient import com.lagradost.cloudstream3.receivers.VideoDownloadRestartReceiver import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2Apis +import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.OAuth2accountApis import com.lagradost.cloudstream3.syncproviders.OAuth2Interface.Companion.appString import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.download.DOWNLOAD_NAVIGATE_TO @@ -371,6 +372,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } override fun onCreate(savedInstanceState: Bundle?) { + // init accounts + for (api in OAuth2accountApis) { + api.init(this) + } + val settingsManager = PreferenceManager.getDefaultSharedPreferences(this) val currentTheme = when (settingsManager.getString(getString(R.string.app_theme_key), "Black")) { @@ -593,6 +599,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { } APIRepository.dubStatusActive = getApiDubstatusSettings() + /* val relativePath = (Environment.DIRECTORY_DOWNLOADS) + File.separatorChar val displayName = "output.dex" //""output.dex" diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AniListApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AniListApi.kt index 8d6d6c18..0db0fd78 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AniListApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/AniListApi.kt @@ -17,33 +17,34 @@ import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKeys -import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject import java.net.URL import java.util.* import java.util.concurrent.TimeUnit -class AniListApi(var accountId: String) : OAuth2Interface { +class AniListApi(index : Int) : OAuth2Interface.AccountManager(index) { override val name: String get() = "AniList" override val key: String get() = "6871" override val redirectUrl: String get() = "anilistlogin" - - override fun logOut(context: Context) { - context.removeKeys(accountId) - } + override val idPrefix: String + get() = "anilist" override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? { // context.getUser(true)?. context.getKey(accountId, ANILIST_USER_KEY)?.let { user -> - return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name) + return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex) } return null } + override fun logOut(context: Context) { + context.removeAccountKeys() + } + override fun authenticate(context: Context) { val request = "https://anilist.co/api/v2/oauth/authorize?client_id=$key&response_type=token" context.openBrowser(request) @@ -58,6 +59,7 @@ class AniListApi(var accountId: String) : OAuth2Interface { val endTime = unixTime + expiresIn.toLong() + context.switchToNewAccount() context.setKey(accountId, ANILIST_UNIXTIME_KEY, endTime) context.setKey(accountId, ANILIST_TOKEN_KEY, token) context.setKey(ANILIST_SHOULD_UPDATE_LIST, true) @@ -552,7 +554,7 @@ class AniListApi(var accountId: String) : OAuth2Interface { } } - fun Context.getUser(setSettings: Boolean = true): AniListUser? { + private fun Context.getUser(setSettings: Boolean = true): AniListUser? { val q = """ { Viewer { @@ -582,6 +584,7 @@ class AniListApi(var accountId: String) : OAuth2Interface { ) if (setSettings) { setKey(accountId, ANILIST_USER_KEY, user) + registerAccount() } /* // TODO FIX FAVS for(i in u.favourites.anime.nodes) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/MALApi.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/MALApi.kt index b8e6f4d0..1adafe09 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/MALApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/MALApi.kt @@ -19,7 +19,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.openBrowser import com.lagradost.cloudstream3.utils.AppUtils.splitQuery import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStore.getKey -import com.lagradost.cloudstream3.utils.DataStore.removeKeys import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.toKotlinObject import java.net.URL @@ -28,22 +27,24 @@ import java.text.ParseException import java.text.SimpleDateFormat import java.util.* -class MALApi(var accountId: String) : OAuth2Interface { +class MALApi(index : Int) : OAuth2Interface.AccountManager(index) { override val name: String get() = "MAL" override val key: String get() = "1714d6f2f4f7cc19644384f8c4629910" override val redirectUrl: String get() = "mallogin" + override val idPrefix: String + get() = "mal" override fun logOut(context: Context) { - context.removeKeys(accountId) + context.removeAccountKeys() } override fun loginInfo(context: Context): OAuth2Interface.LoginInfo? { //context.getMalUser(true)? context.getKey(accountId, MAL_USER_KEY)?.let { user -> - return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name) + return OAuth2Interface.LoginInfo(profilePicture = user.picture, name = user.name, accountIndex = accountIndex) } return null } @@ -84,6 +85,7 @@ class MALApi(var accountId: String) : OAuth2Interface { } if (res != "") { + context.switchToNewAccount() context.storeToken(res) context.getMalUser() context.setKey(MAL_SHOULD_UPDATE_LIST, true) @@ -383,7 +385,7 @@ class MALApi(var accountId: String) : OAuth2Interface { } } - fun Context.getMalUser(setSettings: Boolean = true): MalUser? { + private fun Context.getMalUser(setSettings: Boolean = true): MalUser? { checkMalToken() return try { val res = get( @@ -399,6 +401,7 @@ class MALApi(var accountId: String) : OAuth2Interface { val user = mapper.readValue(res) if (setSettings) { setKey(accountId, MAL_USER_KEY, user) + registerAccount() } user } catch (e: Exception) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2Interface.kt b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2Interface.kt index 62cc5763..95478555 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2Interface.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/syncproviders/OAuth2Interface.kt @@ -1,31 +1,99 @@ package com.lagradost.cloudstream3.syncproviders import android.content.Context +import com.lagradost.cloudstream3.utils.DataStore.getKey +import com.lagradost.cloudstream3.utils.DataStore.removeKeys +import com.lagradost.cloudstream3.utils.DataStore.setKey import java.util.concurrent.TimeUnit interface OAuth2Interface { - val key : String - val name : String - val redirectUrl : String + val key: String + val name: String + val redirectUrl: String - fun handleRedirect(context: Context, url : String) + fun handleRedirect(context: Context, url: String) fun authenticate(context: Context) - fun loginInfo(context: Context) : LoginInfo? + fun loginInfo(context: Context): LoginInfo? fun logOut(context: Context) class LoginInfo( - val profilePicture : String?, - val name : String?, + val profilePicture: String?, + val name: String?, + + val accountIndex: Int, ) - companion object { - val malApi = MALApi("mal_account_0") - val aniListApi = AniListApi("anilist_account_0") + abstract class AccountManager(private val defIndex: Int) : OAuth2Interface { + // don't change this as all keys depend on it + open val idPrefix: String + get() { + throw(NotImplementedError()) + } - val OAuth2Apis get() = listOf( - malApi, aniListApi - ) + var accountIndex = defIndex + protected val accountId get() = "${idPrefix}_account_$accountIndex" + private val accountActiveKey get() = "${idPrefix}_active" + + // int array of all accounts indexes + private val accountsKey get() = "${idPrefix}_accounts" + + protected fun Context.removeAccountKeys() { + this.removeKeys(accountId) + val accounts = getAccounts(this).toMutableList() + accounts.remove(accountIndex) + this.setKey(accountsKey, accounts.toIntArray()) + + init(this) + } + + fun getAccounts(context: Context): IntArray { + return context.getKey(accountsKey, intArrayOf())!! + } + + fun init(context: Context) { + accountIndex = context.getKey(accountActiveKey, defIndex)!! + val accounts = getAccounts(context) + if (accounts.isNotEmpty() && this.loginInfo(context) == null) { + accountIndex = accounts.first() + } + } + + protected fun Context.switchToNewAccount() { + val accounts = getAccounts(this) + accountIndex = (accounts.maxOrNull() ?: 0) + 1 + } + + protected fun Context.registerAccount() { + this.setKey(accountActiveKey, accountIndex) + val accounts = getAccounts(this).toMutableList() + if (!accounts.contains(accountIndex)) { + accounts.add(accountIndex) + } + + this.setKey(accountsKey, accounts.toIntArray()) + } + + fun changeAccount(context: Context, index: Int) { + accountIndex = index + context.setKey(accountActiveKey, index) + } + } + + companion object { + val malApi = MALApi(0) + val aniListApi = AniListApi(0) + + val OAuth2Apis + get() = listOf( + malApi, aniListApi + ) + + // this needs init with context + val OAuth2accountApis + get() = listOf( + malApi, aniListApi + ) const val appString = "cloudstreamapp" diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt new file mode 100644 index 00000000..9764642d --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/AccountAdapter.kt @@ -0,0 +1,66 @@ +package com.lagradost.cloudstream3.ui.settings + +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.syncproviders.OAuth2Interface +import com.lagradost.cloudstream3.utils.UIHelper.setImage + +class AccountClickCallback(val action: Int, val view : View, val card: OAuth2Interface.LoginInfo) + +class AccountAdapter( + val cardList: List, + val layout: Int = R.layout.account_single, + private val clickCallback: (AccountClickCallback) -> Unit +) : + RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return CardViewHolder( + LayoutInflater.from(parent.context).inflate(layout, parent, false), clickCallback + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is CardViewHolder -> { + holder.bind(cardList[position]) + } + } + } + + override fun getItemCount(): Int { + return cardList.size + } + + override fun getItemId(position: Int): Long { + return cardList[position].accountIndex.toLong() + } + + class CardViewHolder + constructor(itemView: View, private val clickCallback: (AccountClickCallback) -> Unit) : + RecyclerView.ViewHolder(itemView) { + private val pfp: ImageView = itemView.findViewById(R.id.account_profile_picture)!! + private val accountName: TextView = itemView.findViewById(R.id.account_name)!! + + fun bind(card: OAuth2Interface.LoginInfo) { + // just in case name is null account index will show, should never happened + accountName.text = card.name ?: "%s %d".format(accountName.context.getString(R.string.account), card.accountIndex) + if(card.profilePicture.isNullOrEmpty()) { + pfp.isVisible = false + } else { + pfp.isVisible = true + pfp.setImage(card.profilePicture) + } + + itemView.setOnClickListener { + clickCallback.invoke(AccountClickCallback(0, itemView, card)) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index f69429c5..1dfafcd9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -16,6 +16,7 @@ import androidx.appcompat.app.AlertDialog import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager +import androidx.recyclerview.widget.RecyclerView import com.hippo.unifile.UniFile import com.lagradost.cloudstream3.APIHolder.apis import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings @@ -119,7 +120,37 @@ class SettingsFragment : PreferenceFragmentCompat() { Triple("🇹🇷", "Turkish", "tr") ).sortedBy { it.second } //ye, we go alphabetical, so ppl don't put their lang on top - private fun showLoginInfo(context: Context, api: OAuth2Interface, info: OAuth2Interface.LoginInfo) { + private fun showAccountSwitch(context: Context, api: OAuth2Interface.AccountManager) { + val builder = + AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_switch) + val dialog = builder.show() + + val accounts = api.getAccounts(context) + dialog.findViewById(R.id.account_add)?.setOnClickListener { + api.authenticate(it.context) + } + + val ogIndex = api.accountIndex + + val items = ArrayList() + + for (index in accounts) { + api.accountIndex = index + val accountInfo = api.loginInfo(context) + if (accountInfo != null) { + items.add(accountInfo) + } + } + api.accountIndex = ogIndex + val adapter = AccountAdapter(items, R.layout.account_single) { + dialog?.dismiss() + api.changeAccount(it.view.context, it.card.accountIndex) + } + val list = dialog.findViewById(R.id.account_list) + list?.adapter = adapter + } + + private fun showLoginInfo(context: Context, api: OAuth2Interface.AccountManager, info: OAuth2Interface.LoginInfo) { val builder = AlertDialog.Builder(context, R.style.AlertDialogCustom).setView(R.layout.account_managment) val dialog = builder.show() @@ -134,6 +165,10 @@ class SettingsFragment : PreferenceFragmentCompat() { dialog.findViewById(R.id.account_name)?.text = info.name ?: context.getString(R.string.no_data) dialog.findViewById(R.id.account_site)?.text = api.name + dialog.findViewById(R.id.account_switch_account)?.setOnClickListener { + dialog.dismiss() + showAccountSwitch(it.context, api) + } } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { diff --git a/app/src/main/res/layout/account_managment.xml b/app/src/main/res/layout/account_managment.xml index 84ae6051..f163a8e2 100644 --- a/app/src/main/res/layout/account_managment.xml +++ b/app/src/main/res/layout/account_managment.xml @@ -56,6 +56,11 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/account_switch.xml b/app/src/main/res/layout/account_switch.xml new file mode 100644 index 00000000..11a75b1e --- /dev/null +++ b/app/src/main/res/layout/account_switch.xml @@ -0,0 +1,24 @@ + + + + + + + + + \ 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 2dc5a57e..e214a366 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -326,6 +326,8 @@ account Logout Login + Switch account + Add account