Adding Authentication with AllIn API
continuous-integration/drone/push Build is passing Details

pull/3/head
Arthur VALIN 1 year ago
parent 7dfe35caeb
commit b7337e679a

@ -5,7 +5,18 @@ plugins {
id "dagger.hilt.android.plugin"
}
def keystorePropertiesFile = rootProject.file("app/keys/keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
signingConfigs {
release {
storeFile file(keystoreProperties['store'])
storePassword keystoreProperties['password']
keyPassword keystoreProperties['password']
}
}
namespace 'fr.iut.alldev.allin'
compileSdk 34
@ -24,8 +35,15 @@ android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
compileOptions {

Binary file not shown.

Binary file not shown.

@ -0,0 +1,2 @@
store=keys/keystore.jks
password=placeYourBets

Binary file not shown.

Binary file not shown.

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name" translatable="false">Allin_DEBUG</string>
</resources>

@ -2,6 +2,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.INTERNET"/>
<application
android:name=".AllInApplication"
android:allowBackup="true"

@ -37,7 +37,6 @@ class MainActivity : ComponentActivity() {
}
}
}
AllInNavHost()
}
}

@ -0,0 +1,71 @@
package fr.iut.alldev.allin.ui.core
import android.content.res.Configuration
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.sp
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ui.theme.AllInTheme
@Composable
fun AllInAlertDialog(
enabled: Boolean = true,
title: String,
text: String,
confirmText: String = stringResource(id = R.string.Ok),
onDismiss: ()->Unit,
onConfirm: ()->Unit = onDismiss
) {
if(enabled) {
AlertDialog(
title = {
Text(
text = title,
fontSize = 20.sp,
style = AllInTheme.typography.h1
)
},
text = {
Text(
text = text,
style = AllInTheme.typography.r
)
},
confirmButton = {
TextButton(
onClick = onConfirm
) {
Text(
text = confirmText,
fontSize = 15.sp,
style = AllInTheme.typography.h1.copy(
brush = AllInTheme.colors.allIn_MainGradient
)
)
}
},
onDismissRequest = onDismiss,
containerColor = AllInTheme.themeColors.main_surface,
titleContentColor = AllInTheme.themeColors.on_main_surface,
textContentColor = AllInTheme.themeColors.on_background_2,
)
}
}
@Preview
@Preview(uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable
private fun AllInAlertDialogPreview() {
AllInTheme {
AllInAlertDialog(
title = "Titre",
text = "Lorem Ipsum",
onDismiss = {}
)
}
}

@ -1,12 +1,19 @@
package fr.iut.alldev.allin.ui.login
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.*
import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.text.ClickableText
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Text
import androidx.compose.runtime.*
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
@ -17,13 +24,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ui.core.AllInGradientButton
import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.core.AllInPasswordField
import fr.iut.alldev.allin.ui.core.AllInTextField
import fr.iut.alldev.allin.ui.core.*
import fr.iut.alldev.allin.ui.theme.AllInTheme
@OptIn(ExperimentalFoundationApi::class)
@OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
@Composable
fun LoginScreen(
navigateToDashboard: ()->Unit,
@ -33,6 +37,10 @@ fun LoginScreen(
val bringIntoViewRequester = BringIntoViewRequester()
val loading by remember{ loginViewModel.loading }
var hasLoginError by remember{ loginViewModel.hasError }
val (username, setUsername) = remember{ loginViewModel.username }
val (password, setPassword) = remember{ loginViewModel.password }
Box(
Modifier
@ -68,15 +76,15 @@ fun LoginScreen(
AllInTextField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.username),
value = "",
onValueChange = { },
value = username,
onValueChange = setUsername,
bringIntoViewRequester = bringIntoViewRequester
)
AllInPasswordField(
modifier = Modifier.fillMaxWidth(),
placeholder = stringResource(id = R.string.password),
value = "",
onValueChange = { },
value = password,
onValueChange = setPassword,
bringIntoViewRequester = bringIntoViewRequester
)
}
@ -135,4 +143,10 @@ fun LoginScreen(
AnimatedVisibility(visible = loading) {
AllInLoading()
}
AllInAlertDialog(
enabled = hasLoginError,
title = stringResource(id = R.string.Login_Error_Title),
text = stringResource(id = R.string.Login_Error_Content),
onDismiss = { hasLoginError = false }
)
}

@ -4,6 +4,7 @@ import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
@ -11,18 +12,30 @@ import javax.inject.Inject
@HiltViewModel
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository
) : ViewModel() {
var loading = mutableStateOf(false)
var hasError = mutableStateOf(false)
val username = mutableStateOf("")
val password = mutableStateOf("")
fun onLogin(
navigateToDashboard: ()->Unit
){
viewModelScope.launch {
loading.value = true
withContext(Dispatchers.IO) {
Thread.sleep(3000)
try{
userRepository.login(username.value, password.value)
} catch (e: retrofit2.HttpException){
hasError.value = true
}
}
if(!hasError.value){
navigateToDashboard()
}
navigateToDashboard()
loading.value = false
}
}

@ -1,5 +1,6 @@
package fr.iut.alldev.allin.ui.register
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.foundation.*
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.relocation.BringIntoViewRequester
@ -19,6 +20,7 @@ import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import fr.iut.alldev.allin.R
import fr.iut.alldev.allin.ui.core.AllInGradientButton
import fr.iut.alldev.allin.ui.core.AllInLoading
import fr.iut.alldev.allin.ui.core.AllInPasswordField
import fr.iut.alldev.allin.ui.core.AllInTextField
import fr.iut.alldev.allin.ui.theme.AllInTheme
@ -30,6 +32,8 @@ fun RegisterScreen(
navigateToLogin: () -> Unit,
registerViewModel: RegisterViewModel = hiltViewModel(),
) {
val loading by remember{ registerViewModel.loading }
val (username, setUsername) = remember{ registerViewModel.username }
val (email, setEmail) = remember{ registerViewModel.email }
val (password, setPassword) = remember{ registerViewModel.password }
@ -118,7 +122,9 @@ fun RegisterScreen(
) {
AllInGradientButton(
text = stringResource(id = R.string.Register),
onClick = navigateToDashboard
onClick = {
registerViewModel.onRegister(navigateToDashboard)
}
)
Spacer(modifier = Modifier.height(30.dp))
Row(
@ -145,4 +151,7 @@ fun RegisterScreen(
}
}
}
AnimatedVisibility(visible = loading) {
AllInLoading()
}
}

@ -2,8 +2,12 @@ package fr.iut.alldev.allin.ui.register
import androidx.compose.runtime.mutableStateOf
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import fr.iut.alldev.allin.data.repository.UserRepository
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import javax.inject.Inject
@HiltViewModel
@ -11,8 +15,28 @@ class RegisterViewModel @Inject constructor(
val userRepository: UserRepository
) : ViewModel() {
var loading = mutableStateOf(false)
val username = mutableStateOf("")
val email = mutableStateOf("")
val password = mutableStateOf("")
val passwordValidation = mutableStateOf("")
fun onRegister(
navigateToDashboard: ()->Unit
){
viewModelScope.launch {
loading.value = true
withContext(Dispatchers.IO) {
userRepository.register(
username.value,
email.value,
password.value
)
}
navigateToDashboard()
loading.value = false
}
}
}

@ -11,6 +11,7 @@
<string name="Register">S\'inscrire</string>
<string name="Validate">Valider</string>
<string name="Cancel">Annuler</string>
<string name="Ok">OK</string>
<!--Drawer-->
<string name="bets">Bets</string>
@ -36,6 +37,8 @@
<!--Login Page-->
<string name="Login_title">Te revoilà !</string>
<string name="Login_subtitle">Bon retour parmis nous tu nous as manqué !</string>
<string name="Login_Error_Title">Erreur de connexion</string>
<string name="Login_Error_Content">La connexion a échoué\nVeuillez réessayer.</string>
<!--Bet Creation Page-->
<string name="Theme_tooltip">Le sujet du bet.</string>
<string name="Phrase_tooltip">Généralement la question à laquelle les participants devront répondre.</string>

@ -14,6 +14,7 @@
<string name="Register">Register</string>
<string name="Validate">Validate</string>
<string name="Cancel">Cancel</string>
<string name="Ok">OK</string>
<!--Drawer-->
<string name="bets">Bets</string>
@ -40,6 +41,8 @@
<!--Login Page-->
<string name="Login_title">Welcome back !</string>
<string name="Login_subtitle">We missed you.</string>
<string name="Login_Error_Title">Connection error</string>
<string name="Login_Error_Content">Failed to login\nPlease try again.</string>
<!--Bet Creation Page-->
<string name="Question">Question</string>
<string name="Answer">Answer</string>

@ -4,11 +4,13 @@ buildscript {
accompanist_version = '0.25.1'
hilt_version = "2.45"
}
}// Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
id 'com.android.application' version '7.4.2' apply false
id 'com.android.library' version '7.4.2' apply false
id 'org.jetbrains.kotlin.android' version '1.7.0' apply false
id("com.google.dagger.hilt.android") version "$hilt_version" apply false
id 'com.google.dagger.hilt.android' version "$hilt_version" apply false
id 'org.jetbrains.kotlin.plugin.serialization' version '1.7.0' apply false
}
}

@ -2,9 +2,21 @@ plugins {
id 'com.android.library'
id 'org.jetbrains.kotlin.android'
id "kotlin-kapt"
id "kotlinx-serialization"
}
def keystorePropertiesFile = rootProject.file("app/keys/keystore.properties")
def keystoreProperties = new Properties()
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
signingConfigs {
release {
storeFile file(keystoreProperties['store'])
storePassword keystoreProperties['password']
keyPassword keystoreProperties['password']
}
}
namespace 'fr.iut.alldev.allin.data'
compileSdk 34
@ -18,8 +30,15 @@ android {
buildTypes {
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.debug
}
}
compileOptions {
@ -43,4 +62,11 @@ dependencies {
//Hilt
implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
// Retrofit
api "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.okhttp3:okhttp:4.11.0"
implementation "com.squareup.okhttp3:logging-interceptor:4.11.0"
api "org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.1"
implementation "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0"
}

@ -0,0 +1,9 @@
package fr.iut.alldev.allin.data.di
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module(includes = [DebugNetworkModule::class])
@InstallIn(SingletonComponent::class)
object DebugModule

@ -0,0 +1,25 @@
package fr.iut.alldev.allin.data.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object DebugNetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient =
OkHttpClient.Builder()
.addInterceptor(
HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY
}
)
.build()
}

@ -0,0 +1,19 @@
package fr.iut.alldev.allin.data.api
import fr.iut.alldev.allin.data.api.model.CheckUser
import fr.iut.alldev.allin.data.api.model.ResponseUser
import retrofit2.http.Body
import retrofit2.http.POST
interface AllInApi {
@POST("users/login")
suspend fun login(
@Body body: CheckUser
): ResponseUser
@POST("users/register")
suspend fun register(
@Body body: ResponseUser
): ResponseUser
}

@ -0,0 +1,20 @@
package fr.iut.alldev.allin.data.api.model
import androidx.annotation.Keep
import kotlinx.serialization.Serializable
@Keep
@Serializable
data class ResponseUser(
val username: String,
val email: String,
val password: String,
var nbCoins: Int,
)
@Keep
@Serializable
data class CheckUser(
val username: String,
val password: String,
)

@ -0,0 +1,23 @@
package fr.iut.alldev.allin.data.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import fr.iut.alldev.allin.data.api.AllInApi
import fr.iut.alldev.allin.data.di.NetworkModule.createRetrofit
import okhttp3.HttpUrl
import okhttp3.OkHttpClient
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
class ApiModule {
@Provides
@Singleton
fun provideAllInApi(@AllInUrl url: HttpUrl, okHttpClient: OkHttpClient): AllInApi {
val retrofit = createRetrofit(url = url, okHttpClient = okHttpClient)
return retrofit.create(AllInApi::class.java)
}
}

@ -0,0 +1,41 @@
package fr.iut.alldev.allin.data.di
import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import kotlinx.serialization.json.Json
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import javax.inject.Qualifier
internal val json by lazy {
Json {
ignoreUnknownKeys = true
encodeDefaults = false
}
}
@Qualifier
@Retention(AnnotationRetention.BINARY)
annotation class AllInUrl
@Module
@InstallIn(SingletonComponent::class)
internal object NetworkModule {
@AllInUrl
@Provides
fun provideUrl(): HttpUrl = "https://codefirst.iut.uca.fr/containers/AllDev-api/".toHttpUrl()
fun createRetrofit(url: HttpUrl, okHttpClient: OkHttpClient): Retrofit =
Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.baseUrl(url)
.build()
}

@ -1,4 +1,13 @@
package fr.iut.alldev.allin.data.repository
abstract class UserRepository {
abstract suspend fun login(
username: String,
password: String
)
abstract suspend fun register(
username: String,
email: String,
password: String
)
}

@ -1,10 +1,32 @@
package fr.iut.alldev.allin.data.repository.impl
import fr.iut.alldev.allin.data.api.AllInApi
import fr.iut.alldev.allin.data.api.model.CheckUser
import fr.iut.alldev.allin.data.api.model.ResponseUser
import fr.iut.alldev.allin.data.repository.UserRepository
import javax.inject.Inject
class UserRepositoryImpl @Inject constructor(
private val api: AllInApi,
) : UserRepository() {
override suspend fun login(username: String, password: String) {
api.login(
CheckUser(
username = username,
password = password
)
)
}
override suspend fun register(username: String, email: String, password: String) {
api.register(
ResponseUser(
username = username,
email = email,
password = password,
nbCoins = 0
)
)
}
}

@ -0,0 +1,13 @@
package fr.iut.alldev.allin.data.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton
@Module(includes = [ReleaseNetworkModule::class])
@InstallIn(SingletonComponent::class)
object ReleaseModule

@ -0,0 +1,20 @@
package fr.iut.alldev.allin.data.di
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import javax.inject.Singleton
@Module
@InstallIn(SingletonComponent::class)
internal object ReleaseNetworkModule {
@Provides
@Singleton
fun provideOkHttpClient(): OkHttpClient =
OkHttpClient.Builder()
.build()
}
Loading…
Cancel
Save