Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple account support #125

Merged
merged 43 commits into from
Dec 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
25351b7
implemented dao and database for accounts
itszechs Jan 17, 2024
bd8c67e
should also update token in accounts table
itszechs Jan 17, 2024
25b5f4a
implemented migration for existing users
itszechs Jan 19, 2024
1ee7e36
set migrated user as default
itszechs Jan 19, 2024
ee2dda4
implemented a recyclerview adapters for profiles list
itszechs Jan 19, 2024
57a0427
code refactored
itszechs Jan 21, 2024
58c1c1b
refactored accounts table for multiple clients
itszechs Jan 27, 2024
50720db
refactored accounts list adapter
itszechs Jan 27, 2024
49b92ad
created accounts list layout
itszechs Jan 27, 2024
9f8732a
implemented accounts lists
itszechs Jan 27, 2024
3dcc1fc
implemented new account dialog
itszechs Jan 27, 2024
5c011be
added a fade-in animation on dialogs
itszechs Jan 27, 2024
99305b2
configured new account dialog
itszechs Jan 27, 2024
c10166d
check conflict for account name
itszechs Jan 27, 2024
4e54a25
added profiles fragment to nav_graph
itszechs Jan 30, 2024
43fe029
set profiles fragment as start destination
itszechs Jan 30, 2024
7c38b13
defined path direction to and from profiles fragment
itszechs Jan 30, 2024
8c4bd9b
implemented account switching
itszechs Jan 30, 2024
cfc593b
updated home page menu, take to profiles page
itszechs Jan 30, 2024
3c4f4fe
double check before exiting home
itszechs Jan 30, 2024
f70abd7
added popup menu on account-item
itszechs Jan 30, 2024
dcd70bf
implemented `setAsDefault` account
itszechs Jan 30, 2024
990e6f0
implemented api to revoke token
itszechs Jan 30, 2024
19bd12c
implemented revoke token and delete account
itszechs Jan 30, 2024
7f1237c
option to just delete without revoking token
itszechs Jan 30, 2024
2b40f8c
implemented a edit dialog
itszechs Jan 31, 2024
7cf1d6c
implemented account update state and update method in viewModel
itszechs Jan 31, 2024
ba3a2b4
implemented edit account nickname feature
itszechs Jan 31, 2024
2e86fe1
designed layout for clients fragment
itszechs Feb 1, 2024
ef80ced
implemented clients fragment and routes in nav-graph
itszechs Feb 1, 2024
beea18e
implemented item-clients layout
itszechs Feb 1, 2024
1cd503e
implemented ListAdapter for clients list
itszechs Feb 1, 2024
7f9dc7f
implements clients list loading
itszechs Feb 1, 2024
2eba79c
delete sign-in fragment
itszechs Dec 25, 2024
b8b683a
implemented client add/edit dialog
itszechs Dec 25, 2024
6f8b27a
implemented login fragment
itszechs Dec 25, 2024
eb2d599
implemented profiles to clients navigation
itszechs Dec 25, 2024
ede6725
display selected profile in home fragment
itszechs Dec 25, 2024
04d7fc0
delete Auth code dialog
itszechs Dec 25, 2024
bfbc755
respect default account
itszechs Dec 25, 2024
5b32c61
refactored profile migration logic
itszechs Dec 25, 2024
1a30c41
added basic validation on client form
itszechs Dec 25, 2024
176ecfe
Merge pull request #126 from itszechs/multi-drive-client
itszechs Dec 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 58 additions & 0 deletions app/src/main/java/zechs/drive/stream/data/local/AccountsDao.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package zechs.drive.stream.data.local


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 {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addClient(client: Client)

@Insert(onConflict = OnConflictStrategy.REPLACE)
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<List<Client>>

@Transaction
@Query(
"SELECT accounts.*, clients.secret AS clientSecret, clients.redirectUri " +
"FROM accounts JOIN clients ON accounts.clientId = clients.id"
)
fun getAccounts(): Flow<List<AccountWithClient>>

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

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
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, Client::class],
version = 1,
exportSchema = false
)
abstract class AccountsDatabase : RoomDatabase() {

abstract fun getAccountsDao(): AccountsDao

}
Original file line number Diff line number Diff line change
@@ -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<TokenResponse>() {}.type
return Gson().fromJson(accessToken, type)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ data class DriveClient(
} catch (e: Exception) {
null
}

fun getClient() = Client(
id = clientId,
secret = clientSecret,
redirectUri = redirectUri
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package zechs.drive.stream.data.model

import com.google.errorprone.annotations.Keep

@Keep
data class TokenRequestBody(
val token: String
)
15 changes: 15 additions & 0 deletions app/src/main/java/zechs/drive/stream/data/remote/RevokeTokenApi.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package zechs.drive.stream.data.remote

import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
import zechs.drive.stream.data.model.TokenRequestBody

interface RevokeTokenApi {

@POST("/revoke")
suspend fun revokeToken(
@Body body: TokenRequestBody
): Response<Unit>

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,11 @@ class DriveRepository @Inject constructor(
)
)
Log.d(TAG, "Received access token (${token.accessToken})")
sessionManager.saveAccessToken(token)
val currentTimeInSeconds = System.currentTimeMillis() / 1000
val newToken = token.copy(
expiresIn = currentTimeInSeconds + token.expiresIn
)
sessionManager.saveAccessToken(newToken)
Resource.Success(token)
} catch (e: Exception) {
doOnError(e)
Expand Down Expand Up @@ -140,7 +144,11 @@ class DriveRepository @Inject constructor(
// saving in data store
sessionManager.saveClient(client)
sessionManager.saveRefreshToken(token.refreshToken)
sessionManager.saveAccessToken(token.toTokenResponse())
val currentTimeInSeconds = System.currentTimeMillis() / 1000
val newToken = token.toTokenResponse().copy(
expiresIn = currentTimeInSeconds + token.expiresIn
)
sessionManager.saveAccessToken(newToken)

Resource.Success(token)
} catch (e: Exception) {
Expand Down
17 changes: 17 additions & 0 deletions app/src/main/java/zechs/drive/stream/di/ApiModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import zechs.drive.stream.BuildConfig
import zechs.drive.stream.data.model.StarredAdapter
import zechs.drive.stream.data.remote.DriveApi
import zechs.drive.stream.data.remote.GithubApi
import zechs.drive.stream.data.remote.RevokeTokenApi
import zechs.drive.stream.data.remote.TokenApi
import zechs.drive.stream.data.repository.DriveRepository
import zechs.drive.stream.data.repository.GithubRepository
Expand All @@ -23,6 +24,7 @@ import zechs.drive.stream.utils.SessionManager
import zechs.drive.stream.utils.util.Constants.Companion.GITHUB_API
import zechs.drive.stream.utils.util.Constants.Companion.GOOGLE_ACCOUNTS_URL
import zechs.drive.stream.utils.util.Constants.Companion.GOOGLE_API
import zechs.drive.stream.utils.util.Constants.Companion.GOOGLE_OAUTH_URL
import javax.inject.Named
import javax.inject.Singleton

Expand Down Expand Up @@ -133,6 +135,21 @@ object ApiModule {
.create(GithubApi::class.java)
}

@Provides
@Singleton
fun provideRevokeTokenApi(
@Named("OkHttpClient")
client: OkHttpClient,
moshi: Moshi
): RevokeTokenApi {
return Retrofit.Builder()
.baseUrl(GOOGLE_OAUTH_URL)
.client(client)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
.create(RevokeTokenApi::class.java)
}

@Provides
@Singleton
fun provideDriveRepository(
Expand Down
20 changes: 17 additions & 3 deletions app/src/main/java/zechs/drive/stream/di/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import zechs.drive.stream.utils.SessionManager
import zechs.drive.stream.data.local.AccountsDao
import zechs.drive.stream.utils.AppSettings
import zechs.drive.stream.utils.FirstRunProfileMigrator
import zechs.drive.stream.utils.SessionManager
import javax.inject.Singleton


Expand All @@ -26,13 +28,25 @@ object AppModule {
@Provides
fun provideSessionDataStore(
@ApplicationContext appContext: Context,
gson: Gson
): SessionManager = SessionManager(appContext, gson)
gson: Gson,
accountsManager: AccountsDao
): SessionManager = SessionManager(appContext, gson, accountsManager)

@Singleton
@Provides
fun provideThemeDataStore(
@ApplicationContext appContext: Context
): AppSettings = AppSettings(appContext)

@Provides
@Singleton
fun provideFirstRunProfileMigrator(
@ApplicationContext appContext: Context,
gson: Gson,
sessionManager: SessionManager,
accountsManager: AccountsDao,
): FirstRunProfileMigrator {
return FirstRunProfileMigrator(appContext, gson, sessionManager, accountsManager)
}

}
18 changes: 18 additions & 0 deletions app/src/main/java/zechs/drive/stream/di/DatabaseModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.android.qualifiers.ApplicationContext
import dagger.hilt.components.SingletonComponent
import zechs.drive.stream.data.local.AccountsDatabase
import zechs.drive.stream.data.local.WatchListDao
import zechs.drive.stream.data.local.WatchListDatabase
import zechs.drive.stream.data.repository.WatchListRepository
Expand All @@ -17,6 +18,7 @@ import javax.inject.Singleton
object DatabaseModule {

private const val WATCHLIST_DATABASE_NAME = "watch_list.db"
private const val ACCOUNTS_DATABASE_NAME = "accounts.db"

@Singleton
@Provides
Expand All @@ -42,4 +44,20 @@ object DatabaseModule {
watchListDao: WatchListDao
) = WatchListRepository(watchListDao)

@Singleton
@Provides
fun provideAccountsDatabase(
@ApplicationContext appContext: Context
) = Room.databaseBuilder(
appContext,
AccountsDatabase::class.java,
ACCOUNTS_DATABASE_NAME
).build()

@Singleton
@Provides
fun provideAccountsDao(
db: AccountsDatabase
) = db.getAccountsDao()

}
Original file line number Diff line number Diff line change
@@ -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, R.style.ThemeOverlay_Fade_MaterialAlertDialog) {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
setContentView(R.layout.dialog_new_account)

val etNickname = findViewById<TextInputLayout>(R.id.tf_nickname).editText!!
val nextButton = findViewById<MaterialButton>(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()
}
}
}

}
Loading