Receiving response, still have to handle it.

This commit is contained in:
AlbYoda 2025-03-13 01:56:36 +01:00
parent a218600761
commit e202eeafeb
Signed by untrusted user who does not match committer: AlbYoda
GPG key ID: A539D876452F16FF
19 changed files with 733 additions and 46 deletions

View file

@ -39,7 +39,7 @@ android {
compose = true
}
packaging {
resources.excludes.add("META-INF-DEPENDENCIES")
resources.pickFirsts.add("META-INF/DEPENDENCIES")
}
}
@ -69,11 +69,7 @@ dependencies {
implementation(libs.appauth)
implementation(libs.androidx.security.crypto)
// implementation(libs.google.api.client.android)
// implementation(libs.google.api.services.drive)
// implementation(libs.google.oauth.client.jetty)
implementation(libs.google.auth.library.oauth2.http)
implementation(libs.kotlinx.serialization.json)
}

View file

@ -15,16 +15,26 @@
android:theme="@style/Theme.DeNaDrive"
tools:targetApi="31">
<activity
android:name=".HomeNotAuthActivity"
android:name=".oauth.LoadingActivity"
android:exported="false"
android:label="@string/title_activity_loading"
android:theme="@style/Theme.DeNaDrive" />
<activity
android:name=".home.NeverLoggedInLogic"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.DeNaDrive" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
android:theme="@style/Theme.DeNaDrive">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".HomeNotAuthActivity"
android:exported="false"
android:label="@string/app_name"
android:theme="@style/Theme.DeNaDrive" />
<activity
android:name=".TestBah"
android:exported="false"
@ -32,14 +42,16 @@
android:theme="@style/Theme.DeNaDrive" />
<activity
android:name=".MainActivity"
android:exported="true"
android:exported="false"
android:label="@string/app_name"
android:theme="@style/Theme.DeNaDrive">
<!-- <intent-filter>-->
<!-- <action android:name="android.intent.action.MAIN" />-->
<!-- <category android:name="android.intent.category.LAUNCHER" />-->
<!-- </intent-filter>-->
<!-- <intent-filter> -->
<!-- <action android:name="android.intent.action.MAIN" /> -->
<!-- <category android:name="android.intent.category.LAUNCHER" /> -->
<!-- </intent-filter> -->
<!-- <intent-filter android:autoVerify="true"> -->
<!-- <action android:name="android.intent.action.VIEW" /> -->

View file

@ -2,6 +2,7 @@ package com.bbc.denadrive
import android.app.PendingIntent
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -25,16 +26,36 @@ import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.net.toUri
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.bbc.denadrive.oauth.LoadingActivity
import com.bbc.denadrive.oauth.retrieveAuthServConf
import com.bbc.denadrive.ui.theme.DeNaDriveTheme
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationRequest
import net.openid.appauth.AuthorizationService
import net.openid.appauth.ResponseTypeValues
class HomeNotAuthActivity : ComponentActivity() {
private lateinit var preferences: SharedPreferences
private lateinit var masterKey: MasterKey
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
masterKey = MasterKey.Builder(this)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
preferences = EncryptedSharedPreferences.create(
this,
"authState",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
enableEdgeToEdge()
setContent {
DeNaDriveTheme {
@ -42,18 +63,22 @@ class HomeNotAuthActivity : ComponentActivity() {
val logo = painterResource(id = R.drawable.vecchiaicona_)
LogoWithCaptionAndButton (
logo = logo,
caption = "Android",
buttonText = "PressMe",
caption = "DeNa Drive",
buttonText = "Log in",
modifier = Modifier.padding(innerPadding)
) { handleAuthStuff() }
) { handleAuthStuff(preferences) }
}
}
}
}
private fun handleAuthStuff() {
private fun handleAuthStuff(preferences: SharedPreferences) {
val serviceConfig = retrieveAuthServConf()
// val authState = AuthState(serviceConfig)
val authState = AuthState(serviceConfig)
with (preferences.edit()) {
this.putString("authState", authState.jsonSerializeString())
this.apply()
}
val authRequestBuilder = AuthorizationRequest.Builder(
serviceConfig,
"722393551256-pum20gbvb1es723kt3ek9lmtrdimr0os.apps.googleusercontent.com",
@ -67,7 +92,7 @@ class HomeNotAuthActivity : ComponentActivity() {
val authService = AuthorizationService(this)
authService.performAuthorizationRequest(
authRequest,
PendingIntent.getActivity(this, 0, Intent(this, MainActivity::class.java),
PendingIntent.getActivity(this, 0, Intent(this, LoadingActivity::class.java),
PendingIntent.FLAG_MUTABLE),
PendingIntent.getActivity(this, 0, Intent(this, HomeNotAuthActivity::class.java),
PendingIntent.FLAG_MUTABLE)
@ -110,7 +135,7 @@ fun LogoWithCaptionAndButton(
Image(
painter = logo,
contentDescription = "Logo",
modifier = Modifier.size(100.dp) // Adjust size as needed
modifier = Modifier.size(200.dp) // Adjust size as needed
)
Spacer(modifier = Modifier.height(16.dp)) // Space between logo and caption

View file

@ -2,7 +2,9 @@ package com.bbc.denadrive
import android.app.PendingIntent
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
@ -12,9 +14,13 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.core.net.toUri
import androidx.navigation.compose.rememberNavController
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.bbc.denadrive.apihandlers.makeDriveApiCall
import com.bbc.denadrive.home.ScaffoldWithSidebar
import com.bbc.denadrive.oauth.retrieveAuthServConf
import com.bbc.denadrive.ui.theme.DeNaDriveTheme
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationRequest
import net.openid.appauth.AuthorizationResponse
@ -22,27 +28,65 @@ import net.openid.appauth.AuthorizationService
import net.openid.appauth.ResponseTypeValues
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
private lateinit var masterKey: MasterKey
private lateinit var preferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// handleAuthStuff()
masterKey = MasterKey.Builder(this)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
preferences = EncryptedSharedPreferences.create(
this,
"authState",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val authState = preferences.getString("authState", "bah")
?.let { AuthState.jsonDeserialize(it) }
val authService = AuthorizationService(this)
// authState?.let {
// it.performActionWithFreshTokens(authService) { accessToken, idToken, ex ->
// if (ex != null) {
// Log.e("TELLINGYA", "${ex.error}")
// return@performActionWithFreshTokens
// }
// Log.e("TOLD", "okay I guess")
// }
// }
authState?.let {
val myUrl = "https://www.googleapis.com/drive/v3/files?trashed=false&orderBy=folder"
makeDriveApiCall(this, it, myUrl, "root") {
response, exception ->
if (exception != null) {
Log.e("ECCOLO", "${exception.cause}")
Log.e("ECCOLO", "${exception.message}")
}
else {
Log.e("UOOOO", "$response")
}
}
with(preferences.edit()) {
this.remove("authState")
this.putString("authState", it.jsonSerializeString())
this.apply()
}
}
setContent {
val navController = rememberNavController()
ScaffoldWithSidebar(navController) { }
// NavigationGraph(navController)
ScaffoldWithSidebar(navController) {
Log.e("LETTINGYOUKNOW", "authstate: ${authState?.jsonSerializeString()}")
}
}
// enableEdgeToEdge()
// setContent {
// DeNaDriveTheme {
// Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
// Greeting(
// name = "Android",
// modifier = Modifier.padding(innerPadding)
// )
// }
// }
// }
}
@ -62,10 +106,10 @@ override fun onCreate(savedInstanceState: Bundle?) {
val authService = AuthorizationService(this)
authService.performAuthorizationRequest(
authRequest,
PendingIntent.getActivity(this, 0, Intent(this, TestActivity::class.java),
PendingIntent.FLAG_MUTABLE),
PendingIntent.getActivity(this, 0, Intent(this, TestBah::class.java),
PendingIntent.FLAG_MUTABLE)
PendingIntent.getActivity(this, 0, Intent(this,
TestActivity::class.java), PendingIntent.FLAG_MUTABLE),
PendingIntent.getActivity(this, 0, Intent(this,
TestBah::class.java), PendingIntent.FLAG_MUTABLE)
)
}
@ -83,8 +127,9 @@ override fun onCreate(savedInstanceState: Bundle?) {
}
setContent {
val navController = rememberNavController()
ScaffoldWithSidebar(navController) { }
// val navController = rememberNavController()
// ScaffoldWithSidebar(navController) { }
Greeting("bah")
}
}
}

View file

@ -0,0 +1,45 @@
package com.bbc.denadrive.apihandlers
import android.content.Context
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationService
import okhttp3.Call
import okhttp3.Callback
import okhttp3.OkHttpClient
import okhttp3.Request
import okhttp3.Response
import okhttp3.HttpUrl.Companion.toHttpUrl
import java.io.IOException
fun makeDriveApiCall(context: Context, authState: AuthState, url: String, folderId: String,
callback: (String?, Exception?) -> Unit) {
val authService = AuthorizationService(context)
// _ = idToken
authState.performActionWithFreshTokens(authService) { accessToken, _, ex ->
authService.dispose()
if (ex != null) {
callback(null, ex)
return@performActionWithFreshTokens
}
val urlBuilder = url.toHttpUrl().newBuilder()
urlBuilder.addQueryParameter("q", "'$folderId' in parents")
val updatedUrl = urlBuilder.build()
val request = Request.Builder()
.url(updatedUrl)
.header("Authorization", "Bearer $accessToken")
.build()
OkHttpClient().newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
callback(null, e)
}
override fun onResponse(call: Call, response: Response) {
val responseBody = response.body?.string()
callback(responseBody, null)
}
})
}
}

View file

@ -0,0 +1,8 @@
package com.bbc.denadrive.apihandlers
data class DriveResponseBody(
val nextPageToken: String?,
val kind: String,
val incompleteSearch: Boolean,
val files: List<MyFile>
)

View file

@ -0,0 +1,8 @@
package com.bbc.denadrive.apihandlers
data class MyFile(
val kind: String,
val mimeType: String,
val id: String,
val name: String
)

View file

@ -0,0 +1,42 @@
package com.bbc.denadrive.apihandlers
import androidx.lifecycle.ViewModel
import kotlinx.serialization.SerializationException
import kotlinx.serialization.json.Json
import okhttp3.Response
class ResponsesHandler: ViewModel() {
var lastResponse: String = ""
var isComplete: Boolean = true
private lateinit var respBuilder: StringBuilder
private lateinit var lastResponseBody: DriveResponseBody
fun newResponseArrived(response: Response) {
if (isComplete) {
respBuilder = StringBuilder("$response")
}
else {
respBuilder.append("$response")
updateCompleteness()
}
}
private fun checkForCompleteness(): Pair<Boolean, DriveResponseBody?> {
return try {
Pair(true, Json.decodeFromString<DriveResponseBody>(respBuilder.toString()))
} catch (e: SerializationException) {
Pair(false, null)
}
}
private fun updateCompleteness() {
val pair = checkForCompleteness()
isComplete = pair.first
if (isComplete) {
lastResponse = respBuilder.toString()
lastResponseBody = pair.second!!
}
}
}

View file

@ -0,0 +1,92 @@
package com.bbc.denadrive.home
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.bbc.denadrive.HomeNotAuthActivity
import com.bbc.denadrive.MainActivity
import com.bbc.denadrive.home.ui.theme.DeNaDriveTheme
import net.openid.appauth.AuthState
class NeverLoggedInLogic : ComponentActivity() {
private lateinit var preferences: SharedPreferences
private lateinit var masterKey: MasterKey
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
masterKey = MasterKey.Builder(this)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
preferences = EncryptedSharedPreferences.create(
this,
"authState",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val authStateString = preferences.getString("authState", "unset")
val intent: Intent
if (authStateString == "unset") {
intent = Intent(this, HomeNotAuthActivity::class.java)
} else {
val authState = authStateString?.let { AuthState.jsonDeserialize(it) }
intent = if (authState?.isAuthorized == true) {
Intent(this, MainActivity::class.java)
}
else {
Toast.makeText(this, "Session expired, please log in again",
Toast.LENGTH_SHORT).show()
Intent(this, HomeNotAuthActivity::class.java)
}
}
startActivity(intent)
// Toast.makeText(this, "should not make it here", Toast.LENGTH_SHORT).show()
enableEdgeToEdge()
setContent {
DeNaDriveTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
Greeting5(
name = "Android",
modifier = Modifier.padding(innerPadding)
)
}
}
}
}
}
@Composable
fun Greeting5(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview5() {
DeNaDriveTheme {
Greeting5("Android")
}
}

View file

@ -0,0 +1,11 @@
package com.bbc.denadrive.home.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View file

@ -0,0 +1,58 @@
package com.bbc.denadrive.home.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun DeNaDriveTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View file

@ -0,0 +1,34 @@
package com.bbc.denadrive.home.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View file

@ -1,2 +1,29 @@
package com.bbc.denadrive.oauth
import android.content.Context
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import net.openid.appauth.AuthState
object AuthStatePersistence {
fun retrieveAuthState(context: Context): AuthState {
val masterKey = MasterKey.Builder(context)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
val preferences = EncryptedSharedPreferences.create(
context,
"authState",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
preferences.getString("authState", null)?.let {
return AuthState.jsonDeserialize(it)
}
return AuthState(retrieveAuthServConf())
}
}

View file

@ -0,0 +1,175 @@
package com.bbc.denadrive.oauth
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKey
import com.bbc.denadrive.HomeNotAuthActivity
import com.bbc.denadrive.MainActivity
import com.bbc.denadrive.oauth.ui.theme.DeNaDriveTheme
import net.openid.appauth.AuthState
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationResponse
import net.openid.appauth.AuthorizationService
class LoadingActivity : ComponentActivity() {
private lateinit var masterKey: MasterKey
private lateinit var preferences: SharedPreferences
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
DeNaDriveTheme {
Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding ->
LoadingScreen(modifier = Modifier.padding(innerPadding))
}
}
}
masterKey = MasterKey.Builder(this)
.setKeyScheme(MasterKey.KeyScheme.AES256_GCM)
.build()
preferences = EncryptedSharedPreferences.create(
this,
"authState",
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)
val authState = preferences.getString("authState", "bah")
?.let { AuthState.jsonDeserialize(it) }
val response = AuthorizationResponse.fromIntent(intent)
val exception = AuthorizationException.fromIntent(intent)
if (response != null) {
Toast.makeText(this, "Done!", Toast.LENGTH_SHORT).show()
authState?.update(response, exception)
Log.e("MainActivity", "authState: ${authState?.jsonSerializeString()}", )
with (preferences.edit()) {
this.remove("authState")
this.putString("authState", "${authState?.jsonSerializeString()}")
this.apply()
}
} else {
Toast.makeText(this, "Not done :( ${exception?.error}", Toast.LENGTH_SHORT).show()
}
val authService = AuthorizationService(this)
response?.createTokenExchangeRequest()?.let {
authService.performTokenRequest(
it
) { resp, ex ->
if (resp != null) {
authState?.update(resp, ex)
with(preferences.edit()) {
this.remove("authState")
this.putString("authState", "${authState?.jsonSerializeString()}")
this.apply()
}
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
} else {
val intent = Intent(this, HomeNotAuthActivity::class.java)
startActivity(intent)
}
}
}
}
}
@Composable
fun LoadingScreen(
modifier: Modifier = Modifier
) {
// State to control the rotation animation
val rotation = remember { mutableFloatStateOf(0f) }
// Animate the rotation
LaunchedEffect(Unit) {
while (true) {
rotation.floatValue += 10f
if (rotation.floatValue >= 360f) rotation.floatValue = 0f
kotlinx.coroutines.delay(16) // Approximately 60 FPS
}
}
Box(
modifier = modifier
.fillMaxSize()
.padding(16.dp),
contentAlignment = Alignment.Center
) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
// Rotating Circular Progress Indicator
CircularProgressIndicator(
modifier = Modifier
.size(64.dp)
.graphicsLayer(rotationZ = rotation.floatValue),
color = Color.Blue
)
Spacer(modifier = Modifier.height(16.dp))
// Loading text
Text(
text = "Loading",
fontSize = 20.sp,
fontWeight = FontWeight.Bold,
color = Color.Black
)
}
}
}
@Composable
fun Greeting6(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier
)
}
@Preview(showBackground = true)
@Composable
fun GreetingPreview6() {
DeNaDriveTheme {
Greeting6("Android")
}
}

View file

@ -0,0 +1,11 @@
package com.bbc.denadrive.oauth.ui.theme
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)

View file

@ -0,0 +1,58 @@
package com.bbc.denadrive.oauth.ui.theme
import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = Purple80,
secondary = PurpleGrey80,
tertiary = Pink80
)
private val LightColorScheme = lightColorScheme(
primary = Purple40,
secondary = PurpleGrey40,
tertiary = Pink40
/* Other default colors to override
background = Color(0xFFFFFBFE),
surface = Color(0xFFFFFBFE),
onPrimary = Color.White,
onSecondary = Color.White,
onTertiary = Color.White,
onBackground = Color(0xFF1C1B1F),
onSurface = Color(0xFF1C1B1F),
*/
)
@Composable
fun DeNaDriveTheme(
darkTheme: Boolean = isSystemInDarkTheme(),
// Dynamic color is available on Android 12+
dynamicColor: Boolean = true,
content: @Composable () -> Unit
) {
val colorScheme = when {
dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
val context = LocalContext.current
if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
}
darkTheme -> DarkColorScheme
else -> LightColorScheme
}
MaterialTheme(
colorScheme = colorScheme,
typography = Typography,
content = content
)
}

View file

@ -0,0 +1,34 @@
package com.bbc.denadrive.oauth.ui.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)

View file

@ -4,4 +4,6 @@
<string name="title_activity_test_bah">TestBah</string>
<string name="title_activity_speremm">Speremm</string>
<string name="title_activity_home_not_auth">HomeNotAuthActivity</string>
<string name="title_activity_never_logged_in_logic">NeverLoggedInLogic</string>
<string name="title_activity_loading">LoadingActivity</string>
</resources>

View file

@ -2,11 +2,13 @@
agp = "8.9.0"
appauth = "0.11.1"
converterGson = "2.9.0"
googleAuthLibraryOauth2Http = "1.15.0"
kotlin = "2.0.21"
coreKtx = "1.15.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
kotlinxSerializationJson = "1.5.0"
lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.1"
composeBom = "2025.02.00"
@ -20,6 +22,7 @@ androidx-navigation-compose = { module = "androidx.navigation:navigation-compose
androidx-security-crypto = { module = "androidx.security:security-crypto", version.ref = "securityCrypto" }
appauth = { module = "net.openid:appauth", version.ref = "appauth" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "converterGson" }
google-auth-library-oauth2-http = { module = "com.google.auth:google-auth-library-oauth2-http", version.ref = "googleAuthLibraryOauth2Http" }
junit = { group = "junit", name = "junit", version.ref = "junit" }
androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
@ -33,6 +36,7 @@ androidx-ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-toolin
androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinxSerializationJson" }
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "converterGson" }