From 58c1c1b1590969902bec2c40a841960ecbe21977 Mon Sep 17 00:00:00 2001 From: zechs Date: Sat, 27 Jan 2024 12:47:52 +0530 Subject: [PATCH 01/36] refactored accounts table for multiple clients --- .../drive/stream/data/local/AccountsDao.kt | 54 ++++++++++++---- .../stream/data/local/AccountsDatabase.kt | 3 +- .../zechs/drive/stream/data/model/Account.kt | 35 ----------- .../stream/data/model/AccountWithClient.kt | 63 +++++++++++++++++++ 4 files changed, 107 insertions(+), 48 deletions(-) delete mode 100644 app/src/main/java/zechs/drive/stream/data/model/Account.kt create mode 100644 app/src/main/java/zechs/drive/stream/data/model/AccountWithClient.kt diff --git a/app/src/main/java/zechs/drive/stream/data/local/AccountsDao.kt b/app/src/main/java/zechs/drive/stream/data/local/AccountsDao.kt index b6b916f..38a7643 100644 --- a/app/src/main/java/zechs/drive/stream/data/local/AccountsDao.kt +++ b/app/src/main/java/zechs/drive/stream/data/local/AccountsDao.kt @@ -5,24 +5,54 @@ import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy import androidx.room.Query +import androidx.room.Transaction +import kotlinx.coroutines.flow.Flow import zechs.drive.stream.data.model.Account +import zechs.drive.stream.data.model.AccountWithClient +import zechs.drive.stream.data.model.Client @Dao interface AccountsDao { - @Query("SELECT * FROM `accounts`") - suspend fun getAccounts(): List - - @Query("SELECT * FROM `accounts` WHERE name = :name LIMIT 1") - suspend fun getAccount(name: String): Account? - - @Query("UPDATE `accounts` SET accessToken = :accessToken WHERE refreshToken = :refreshToken") - suspend fun updateAccessToken(refreshToken: String, accessToken: String) - - @Query("DELETE FROM `accounts` WHERE name = :name") - suspend fun deleteAccount(name: String) + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun addClient(client: Client) @Insert(onConflict = OnConflictStrategy.REPLACE) - suspend fun addAccount(account: Account): Long + suspend fun addAccount(account: Account) + + @Transaction + @Query("DELETE FROM clients WHERE id = :clientId") + suspend fun deleteClient(clientId: String) + + @Query("SELECT * FROM clients") + fun getClients(): Flow> + + @Transaction + @Query( + "SELECT accounts.*, clients.secret AS clientSecret, clients.redirectUri " + + "FROM accounts JOIN clients ON accounts.clientId = clients.id" + ) + fun getAccounts(): Flow> + + @Transaction + @Query( + "SELECT accounts.*, clients.secret AS clientSecret, clients.redirectUri " + + "FROM accounts JOIN clients ON accounts.clientId = clients.id " + + "WHERE accounts.name = :accountName" + ) + suspend fun getAccount(accountName: String): AccountWithClient? + + @Query("UPDATE accounts SET name = :newName WHERE name = :oldName") + suspend fun updateAccountName(oldName: String, newName: String) + + @Query("DELETE FROM accounts WHERE name = :accountName") + suspend fun deleteAccount(accountName: String) + + @Transaction + @Query("UPDATE clients SET secret = :secret, redirectUri = :redirectUri WHERE id = :clientId") + suspend fun updateClient(clientId: String, secret: String, redirectUri: String) + + @Query("UPDATE accounts SET accessToken = :newToken WHERE name = :accountName") + suspend fun updateAccessToken(accountName: String, newToken: String) } \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/data/local/AccountsDatabase.kt b/app/src/main/java/zechs/drive/stream/data/local/AccountsDatabase.kt index 3e85088..d88fdcf 100644 --- a/app/src/main/java/zechs/drive/stream/data/local/AccountsDatabase.kt +++ b/app/src/main/java/zechs/drive/stream/data/local/AccountsDatabase.kt @@ -3,10 +3,11 @@ package zechs.drive.stream.data.local import androidx.room.Database import androidx.room.RoomDatabase import zechs.drive.stream.data.model.Account +import zechs.drive.stream.data.model.Client @Database( - entities = [Account::class], + entities = [Account::class, Client::class], version = 1, exportSchema = false ) diff --git a/app/src/main/java/zechs/drive/stream/data/model/Account.kt b/app/src/main/java/zechs/drive/stream/data/model/Account.kt deleted file mode 100644 index 8398d70..0000000 --- a/app/src/main/java/zechs/drive/stream/data/model/Account.kt +++ /dev/null @@ -1,35 +0,0 @@ -package zechs.drive.stream.data.model - -import androidx.annotation.Keep -import androidx.room.Entity -import androidx.room.Ignore -import androidx.room.PrimaryKey -import com.google.gson.Gson -import com.google.gson.reflect.TypeToken - -@Keep -@Entity(tableName = "accounts") -data class Account( - @PrimaryKey val name: String, - val clientId: String, - val clientSecret: String, - val redirectUri: String, - val refreshToken: String, - val accessToken: String -) { - - @Ignore var isDefault: Boolean = false - - fun getDriveClient() = DriveClient( - clientId = clientId, - clientSecret = clientSecret, - redirectUri = redirectUri, - scopes = listOf("https://www.googleapis.com/auth/drive") - ) - - fun getAccessTokenResponse(): TokenResponse { - val type = object : TypeToken() {}.type - return Gson().fromJson(accessToken, type) - } - -} diff --git a/app/src/main/java/zechs/drive/stream/data/model/AccountWithClient.kt b/app/src/main/java/zechs/drive/stream/data/model/AccountWithClient.kt new file mode 100644 index 0000000..3585c10 --- /dev/null +++ b/app/src/main/java/zechs/drive/stream/data/model/AccountWithClient.kt @@ -0,0 +1,63 @@ +package zechs.drive.stream.data.model + +import androidx.annotation.Keep +import androidx.room.Entity +import androidx.room.ForeignKey +import androidx.room.Ignore +import androidx.room.Index +import androidx.room.PrimaryKey +import com.google.gson.Gson +import com.google.gson.reflect.TypeToken + +@Entity(tableName = "clients") +data class Client( + @PrimaryKey val id: String, + val secret: String, + val redirectUri: String +) { + fun isEmpty() = id.isEmpty() || secret.isEmpty() || redirectUri.isEmpty() +} + +@Entity( + tableName = "accounts", + foreignKeys = [ForeignKey( + entity = Client::class, + parentColumns = ["id"], + childColumns = ["clientId"], + onDelete = ForeignKey.CASCADE + )], + indices = [Index("clientId")] +) +data class Account( + @PrimaryKey val name: String, + val refreshToken: String, + val accessToken: String, + val clientId: String +) + +@Keep +data class AccountWithClient( + val name: String, + val clientId: String, + val clientSecret: String, + val redirectUri: String, + val refreshToken: String, + val accessToken: String +) { + + @Ignore + var isDefault: Boolean = false + + fun getDriveClient() = DriveClient( + clientId = clientId, + clientSecret = clientSecret, + redirectUri = redirectUri, + scopes = listOf("https://www.googleapis.com/auth/drive") + ) + + fun getAccessTokenResponse(): TokenResponse { + val type = object : TypeToken() {}.type + return Gson().fromJson(accessToken, type) + } + +} From 50720dbe25a3dc50fb10cf202893ed284120237d Mon Sep 17 00:00:00 2001 From: zechs Date: Sat, 27 Jan 2024 12:49:59 +0530 Subject: [PATCH 02/36] refactored accounts list adapter --- .../ui/profile/adapter/AccountItemDiffCallback.kt | 10 +++++----- .../drive/stream/ui/profile/adapter/AccountsAdapter.kt | 9 ++++----- .../stream/ui/profile/adapter/AccountsViewHolder.kt | 3 ++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountItemDiffCallback.kt b/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountItemDiffCallback.kt index 8e56d53..5f2310f 100644 --- a/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountItemDiffCallback.kt +++ b/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountItemDiffCallback.kt @@ -1,17 +1,17 @@ package zechs.drive.stream.ui.profile.adapter import androidx.recyclerview.widget.DiffUtil -import zechs.drive.stream.data.model.Account +import zechs.drive.stream.data.model.AccountWithClient -class AccountItemDiffCallback : DiffUtil.ItemCallback() { +class AccountItemDiffCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame( - oldItem: Account, - newItem: Account + oldItem: AccountWithClient, + newItem: AccountWithClient ): Boolean = oldItem.name == newItem.name override fun areContentsTheSame( - oldItem: Account, newItem: Account + oldItem: AccountWithClient, newItem: AccountWithClient ) = oldItem == newItem } \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsAdapter.kt b/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsAdapter.kt index 3951b23..a6c6efe 100644 --- a/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsAdapter.kt +++ b/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsAdapter.kt @@ -5,13 +5,13 @@ import android.view.View import android.view.ViewGroup import androidx.recyclerview.widget.ListAdapter import zechs.drive.stream.R -import zechs.drive.stream.data.model.Account +import zechs.drive.stream.data.model.AccountWithClient import zechs.drive.stream.databinding.ItemTextBinding class AccountsAdapter( - val onClickListener: (Account) -> Unit, - val onMenuClickListener: (View, Account) -> Unit -) : ListAdapter(AccountItemDiffCallback()) { + val onClickListener: (AccountWithClient) -> Unit, + val onMenuClickListener: (View, AccountWithClient) -> Unit +) : ListAdapter(AccountItemDiffCallback()) { override fun onCreateViewHolder( parent: ViewGroup, viewType: Int @@ -26,7 +26,6 @@ class AccountsAdapter( override fun onBindViewHolder(holder: AccountsViewHolder, position: Int) { val item = getItem(position) return holder.bind(item) - } override fun getItemViewType( diff --git a/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsViewHolder.kt b/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsViewHolder.kt index 2e34c2a..c523d9d 100644 --- a/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsViewHolder.kt +++ b/app/src/main/java/zechs/drive/stream/ui/profile/adapter/AccountsViewHolder.kt @@ -3,6 +3,7 @@ package zechs.drive.stream.ui.profile.adapter import androidx.core.view.isGone import androidx.recyclerview.widget.RecyclerView import zechs.drive.stream.data.model.Account +import zechs.drive.stream.data.model.AccountWithClient import zechs.drive.stream.databinding.ItemTextBinding class AccountsViewHolder( @@ -10,7 +11,7 @@ class AccountsViewHolder( val accountsAdapter: AccountsAdapter ) : RecyclerView.ViewHolder(itemBinding.root) { - fun bind(account: Account) { + fun bind(account: AccountWithClient) { itemBinding.apply { textView.text = account.name root.setOnClickListener { From 49b92adf70601f7c3a5b06b302e9d4eeda4e4ace Mon Sep 17 00:00:00 2001 From: zechs Date: Sat, 27 Jan 2024 12:56:44 +0530 Subject: [PATCH 03/36] created accounts list layout --- .../main/res/drawable/ic_person_add_24.xml | 10 ++++ app/src/main/res/layout/fragment_profile.xml | 50 +++++++++++++++++++ app/src/main/res/values/strings.xml | 2 + 3 files changed, 62 insertions(+) create mode 100644 app/src/main/res/drawable/ic_person_add_24.xml create mode 100644 app/src/main/res/layout/fragment_profile.xml diff --git a/app/src/main/res/drawable/ic_person_add_24.xml b/app/src/main/res/drawable/ic_person_add_24.xml new file mode 100644 index 0000000..6bfaaea --- /dev/null +++ b/app/src/main/res/drawable/ic_person_add_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml new file mode 100644 index 0000000..048485d --- /dev/null +++ b/app/src/main/res/layout/fragment_profile.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5466297..4c40fee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -72,4 +72,6 @@ Disable Ads Are you sure you want to disable ads?\n\nThis will remove all ads from the app. Ads help support the development of this app. Thank you for supporting the app! + Profiles + Add account \ No newline at end of file From 9f8732a8942e6c40a2ae47c3eff7ce1e628beaa2 Mon Sep 17 00:00:00 2001 From: zechs Date: Sat, 27 Jan 2024 12:58:57 +0530 Subject: [PATCH 04/36] implemented accounts lists --- .../stream/ui/profile/ProfileFragment.kt | 87 +++++++++++++++++++ .../stream/ui/profile/ProfileViewModel.kt | 26 ++++++ 2 files changed, 113 insertions(+) create mode 100644 app/src/main/java/zechs/drive/stream/ui/profile/ProfileFragment.kt create mode 100644 app/src/main/java/zechs/drive/stream/ui/profile/ProfileViewModel.kt diff --git a/app/src/main/java/zechs/drive/stream/ui/profile/ProfileFragment.kt b/app/src/main/java/zechs/drive/stream/ui/profile/ProfileFragment.kt new file mode 100644 index 0000000..a55a538 --- /dev/null +++ b/app/src/main/java/zechs/drive/stream/ui/profile/ProfileFragment.kt @@ -0,0 +1,87 @@ +package zechs.drive.stream.ui.profile + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.lifecycleScope +import androidx.lifecycle.repeatOnLifecycle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import kotlinx.coroutines.launch +import zechs.drive.stream.data.model.AccountWithClient +import zechs.drive.stream.databinding.FragmentProfileBinding +import zechs.drive.stream.ui.BaseFragment +import zechs.drive.stream.ui.profile.adapter.AccountsAdapter + + +class ProfileFragment : BaseFragment() { + + companion object { + const val TAG = "ProfileFragment" + } + + private var _binding: FragmentProfileBinding? = null + private val binding get() = _binding!! + + private val viewModel by activityViewModels() + + private val accountsAdapter by lazy { + AccountsAdapter( + onClickListener = { switchAccount(it) }, + onMenuClickListener = { view, account -> + showAccountMenu(view, account) + } + ) + } + + private fun showAccountMenu(view: View, account: AccountWithClient) { + } + + private fun switchAccount(account: AccountWithClient) { + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + _binding = FragmentProfileBinding.inflate( + inflater, container, /* attachToParent */false + ) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + _binding = FragmentProfileBinding.bind(view) + + setupRecyclerView() + setupAccountsObserver() + } + + private fun setupAccountsObserver() { + viewLifecycleOwner.lifecycleScope.launch { + repeatOnLifecycle(Lifecycle.State.STARTED) { + viewModel.accounts.collect { accounts -> + accountsAdapter.submitList(accounts) + } + } + } + } + + private fun setupRecyclerView() { + val linearLayoutManager = LinearLayoutManager( + /* context */ context, + /* orientation */ RecyclerView.VERTICAL, + /* reverseLayout */ false + ) + binding.rvList.apply { + adapter = accountsAdapter + layoutManager = linearLayoutManager + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/zechs/drive/stream/ui/profile/ProfileViewModel.kt b/app/src/main/java/zechs/drive/stream/ui/profile/ProfileViewModel.kt new file mode 100644 index 0000000..785192a --- /dev/null +++ b/app/src/main/java/zechs/drive/stream/ui/profile/ProfileViewModel.kt @@ -0,0 +1,26 @@ +package zechs.drive.stream.ui.profile + +import androidx.lifecycle.ViewModel +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.map +import zechs.drive.stream.data.local.AccountsDao +import zechs.drive.stream.utils.SessionManager +import javax.inject.Inject + +@HiltViewModel +class ProfileViewModel @Inject constructor( + private val sessionManager: SessionManager, + accountsManager: AccountsDao +) : ViewModel() { + + val accounts = accountsManager + .getAccounts() + .map { accounts -> + val default = sessionManager.fetchDefault() + accounts.map { account -> + account.isDefault = account.name == default + account + } + } + +} \ No newline at end of file From 3dcc1fca4675c0cbccccd69832988f836a10f14f Mon Sep 17 00:00:00 2001 From: zechs Date: Sat, 27 Jan 2024 13:03:05 +0530 Subject: [PATCH 05/36] implemented new account dialog --- .../stream/ui/add_account/DialogAddAccount.kt | 39 ++++++++++++ .../main/res/layout/dialog_new_account.xml | 63 +++++++++++++++++++ app/src/main/res/values/strings.xml | 4 ++ 3 files changed, 106 insertions(+) create mode 100644 app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt create mode 100644 app/src/main/res/layout/dialog_new_account.xml diff --git a/app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt b/app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt new file mode 100644 index 0000000..f73c8dd --- /dev/null +++ b/app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt @@ -0,0 +1,39 @@ +package zechs.drive.stream.ui.add_account + +import android.app.Dialog +import android.content.Context +import android.os.Bundle +import android.view.Window +import android.widget.Toast +import com.google.android.material.button.MaterialButton +import com.google.android.material.textfield.TextInputLayout +import zechs.drive.stream.R + +class DialogAddAccount( + context: Context, + val onNextClickListener: (String) -> Unit +) : Dialog(context) { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + requestWindowFeature(Window.FEATURE_NO_TITLE) + setContentView(R.layout.dialog_new_account) + + val etNickname = findViewById(R.id.tf_nickname).editText!! + val nextButton = findViewById(R.id.btn_next) + + nextButton.setOnClickListener { + if (etNickname.text.toString().isEmpty()) { + Toast.makeText( + context, + context.getString(R.string.please_enter_a_nickname), + Toast.LENGTH_SHORT + ).show() + } else { + onNextClickListener.invoke(etNickname.text.toString()) + dismiss() + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_new_account.xml b/app/src/main/res/layout/dialog_new_account.xml new file mode 100644 index 0000000..5c8d172 --- /dev/null +++ b/app/src/main/res/layout/dialog_new_account.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + \ 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 4c40fee..7d5a6fc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -74,4 +74,8 @@ Thank you for supporting the app! Profiles Add account + New account + Account nickname + Next + Please enter a nickname \ No newline at end of file From 5c011be03c2c3cd73c948aef5f4980f2ad6160fe Mon Sep 17 00:00:00 2001 From: zechs Date: Sat, 27 Jan 2024 13:04:00 +0530 Subject: [PATCH 06/36] added a fade-in animation on dialogs --- .../stream/ui/add_account/DialogAddAccount.kt | 2 +- app/src/main/res/anim/dialog_enter_anim.xml | 18 ++++++++++++++++++ app/src/main/res/anim/dialog_exit_anim.xml | 18 ++++++++++++++++++ app/src/main/res/values/themes.xml | 10 ++++++++++ 4 files changed, 47 insertions(+), 1 deletion(-) create mode 100644 app/src/main/res/anim/dialog_enter_anim.xml create mode 100644 app/src/main/res/anim/dialog_exit_anim.xml diff --git a/app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt b/app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt index f73c8dd..340c710 100644 --- a/app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt +++ b/app/src/main/java/zechs/drive/stream/ui/add_account/DialogAddAccount.kt @@ -12,7 +12,7 @@ import zechs.drive.stream.R class DialogAddAccount( context: Context, val onNextClickListener: (String) -> Unit -) : Dialog(context) { +) : Dialog(context, R.style.ThemeOverlay_Fade_MaterialAlertDialog) { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) diff --git a/app/src/main/res/anim/dialog_enter_anim.xml b/app/src/main/res/anim/dialog_enter_anim.xml new file mode 100644 index 0000000..1d04ac4 --- /dev/null +++ b/app/src/main/res/anim/dialog_enter_anim.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/dialog_exit_anim.xml b/app/src/main/res/anim/dialog_exit_anim.xml new file mode 100644 index 0000000..1ea0f83 --- /dev/null +++ b/app/src/main/res/anim/dialog_exit_anim.xml @@ -0,0 +1,18 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index 6f39f37..77fd6fd 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -10,6 +10,16 @@ + + + +