Merge branch 'parametrage' into 'master'

Parametrage

See merge request diamant-kennel/mememorisation_mobile_groupe11!1
This commit is contained in:
DIAMANT alexandre 2024-01-07 16:17:23 +01:00
commit 4f25c2d266
13 changed files with 509 additions and 149 deletions

View file

@ -51,14 +51,14 @@ android {
}
dependencies {
implementation("androidx.core:core-ktx:1.9.0")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")
implementation("androidx.activity:activity-compose:1.8.0")
implementation("androidx.activity:activity-compose:1.8.1")
implementation(platform("androidx.compose:compose-bom:2023.03.00"))
implementation("androidx.compose.ui:ui")
implementation("androidx.compose.ui:ui-graphics")
implementation("androidx.compose.ui:ui-tooling-preview")
implementation("androidx.compose.material3:material3")
implementation("androidx.compose.material3:material3:1.1.2")
implementation("androidx.core:core-ktx:1.12.0")
implementation("androidx.work:work-runtime-ktx:2.8.1")

View file

@ -23,3 +23,8 @@ const val STATS_TOTAL_GOOD = "total_good"
/** DataStore clef lié au nombre de questions mal répondu */
const val STATS_TOTAL_BAD = "total_bad"
const val DELAY = "delay"
const val HOUR = "hour"
const val MINUTE = "minute"

View file

@ -68,7 +68,7 @@ fun HomeScreen(
val errorEntry by model.error
if (errorEntry != null) {
ShowDialog(errorEntry != null) {
ErrorDialog(
when (errorEntry!!) {
ErrorsAjout.BAD_ENTRY -> context.getString(R.string.error_bad_entry)
@ -76,25 +76,30 @@ fun HomeScreen(
}, model::cleanErrors
)
}
if (creationRequest) {
CreationDialog(
dismiss = model::dismissCreation, model = model
)
ShowDialog(creationRequest) { CreationDialog(model::dismissCreation, model) }
ShowDialog(importationRequest) { ImportDialog(model::dismissImportation, model) }
ShowDialog(deletionRequest) {
DeletionDialog(model::dismissDeleteOne, model::deleteSelected)
}
if (importationRequest) {
ImportDialog(
dismiss = model::dismissImportation, model = model
)
}
Home(
padding,
navController,
model,
setOfQuestions,
currentSelection
)
}
if (deletionRequest) {
DeletionDialog(
model::dismissDeleteOne,
model::deleteSelected
)
}
@Composable
private fun Home(
padding: PaddingValues,
navController: NavController,
model: HomeViewModel,
setOfQuestions: List<SetOfQuestions> = listOf(),
currentSelection: SetQuestions? = null,
) {
val context = LocalContext.current
Column(
modifier = Modifier.padding(padding), horizontalAlignment = Alignment.CenterHorizontally
@ -146,7 +151,6 @@ private fun ActionRow(context: Context, model: HomeViewModel, navController: Nav
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun CreationDialog(
dismiss: () -> Unit, model: HomeViewModel = viewModel()
@ -172,7 +176,7 @@ fun CreationDialog(
})
}
@OptIn(ExperimentalMaterial3Api::class, DelicateCoroutinesApi::class)
@OptIn(DelicateCoroutinesApi::class)
@Composable
fun ImportDialog(
dismiss: () -> Unit, model: HomeViewModel

View file

@ -1,9 +1,11 @@
package fr.uparis.diamantkennel.memorisationapplication
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.BottomNavigation
@ -39,6 +41,7 @@ import fr.uparis.diamantkennel.memorisationapplication.ui.theme.MemorisationAppl
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = STATS)
class MainActivity : ComponentActivity() {
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
@ -54,18 +57,20 @@ class MainActivity : ComponentActivity() {
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun MainScreenMainActivity() {
MainScreen()
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Preview(showBackground = true)
@Composable
fun MainScreenPreview() {
MainScreen()
}
@OptIn(ExperimentalMaterial3Api::class)
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun MainScreen() {
val navController = rememberNavController()
@ -97,7 +102,7 @@ fun MainScreen() {
)
}
}
composable(SETTINGS) { SettingsScreen(padding) }
composable(SETTINGS) { SettingsScreen() }
}
}
}
@ -135,3 +140,14 @@ fun BottomBar(navController: NavHostController) =
)
})
}
@Composable
fun <T: Any> ShowDialog(
condition: Boolean,
dialog: @Composable () -> T
) {
if (condition) {
dialog()
}
}

View file

@ -4,15 +4,10 @@ import android.app.Application
import android.app.NotificationChannel
import android.app.NotificationManager
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import fr.uparis.diamantkennel.memorisationapplication.data.QuestionsDB
import java.util.Calendar
import java.util.concurrent.TimeUnit
const val CHANNEL_ID = "MY_CHANNEL_ID"
const val CHANNEL_ID = "REMINDERS"
class MemoApplication : Application() {
val database: QuestionsDB by lazy { QuestionsDB.getDataBase(this) }
@ -20,10 +15,6 @@ class MemoApplication : Application() {
override fun onCreate() {
super.onCreate()
createChannel(this)
if (this.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED) {
schedule()
}
}
private fun createChannel(c: Context) {
@ -39,25 +30,4 @@ class MemoApplication : Application() {
notificationManager.createNotificationChannel(channel)
}
}
private fun schedule() {
val wm = WorkManager.getInstance(this)
wm.cancelAllWork()
wm.enqueue(request(10, 45))
}
private fun request(h: Int, m: Int): PeriodicWorkRequest {
val now = Calendar.getInstance()
val target = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, h)
set(Calendar.MINUTE, m)
}
if (target.before(now))
target.add(Calendar.DAY_OF_YEAR, 1)
val delta = target.timeInMillis - now.timeInMillis
return PeriodicWorkRequest.Builder(RappelWorker::class.java, 1, TimeUnit.DAYS)
.setInitialDelay(delta, TimeUnit.MILLISECONDS)
.build()
}
}

View file

@ -36,7 +36,6 @@ import fr.uparis.diamantkennel.memorisationapplication.ui.ActionModifySet
import fr.uparis.diamantkennel.memorisationapplication.ui.ModifySetViewModel
import kotlin.text.Typography.ellipsis
@Composable
fun ModifySetScreen(
padding: PaddingValues,
@ -47,20 +46,33 @@ fun ModifySetScreen(
model.setId.value = idSet
model.updateQuestionList(idSet)
val context = LocalContext.current
val currentSelection by model.selection
val questions by model.questions.collectAsState(listOf())
var action by model.action
val action by model.action
if (action == ActionModifySet.AJOUT || action == ActionModifySet.MODIFICATION) {
AjoutModifDialog(action, currentSelection, model::ajoutQuestion)
{ action = ActionModifySet.AUCUN }
ShowDialog(action == ActionModifySet.AJOUT || action == ActionModifySet.MODIFICATION) {
AjoutModifDialog(action, currentSelection, model::ajoutQuestion, model::dismissAction)
}
ShowDialog(action == ActionModifySet.SUPPRIMER) {
RemoveDialog(model::removeQuestion, model::dismissAction)
}
if (action == ActionModifySet.SUPPRIMER) {
RemoveDialog(model::removeQuestion)
{ action = ActionModifySet.AUCUN }
}
Modify(
padding = padding,
model = model,
questions = questions,
currentSelection = currentSelection
)
}
@Composable
fun Modify(
padding: PaddingValues,
model: ModifySetViewModel,
questions: List<Question>,
currentSelection: Question?
) {
val context = LocalContext.current
Column(
modifier = Modifier.padding(padding),
@ -71,13 +83,13 @@ fun ModifySetScreen(
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
Button(
enabled = currentSelection != null,
onClick = { action = ActionModifySet.MODIFICATION }) {
onClick = model::modifAction) {
Text(text = context.getString(R.string.modify))
}
Spacer(modifier = Modifier.padding(2.dp))
Button(onClick = { action = ActionModifySet.AJOUT }) {
Button(onClick = model::ajoutAction) {
Text(text = context.getString(R.string.add))
}
@ -85,7 +97,7 @@ fun ModifySetScreen(
Button(
enabled = currentSelection != null,
onClick = { action = ActionModifySet.SUPPRIMER }) {
onClick = model::supprAction) {
Text(text = context.getString(R.string.modify_button_delete))
}
}

View file

@ -9,15 +9,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.SnackbarDuration
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
@ -26,10 +25,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.navigation.NavController
import fr.uparis.diamantkennel.memorisationapplication.data.Question
import fr.uparis.diamantkennel.memorisationapplication.ui.AnswerType
import fr.uparis.diamantkennel.memorisationapplication.ui.PlayViewModel
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PlayScreen(
padding: PaddingValues,
@ -41,40 +40,54 @@ fun PlayScreen(
// First update the list of questions
model.updateQuestionList(idSet)
val context = LocalContext.current
val question by model.currentQuestion
val reponse by model.proposedAnswer
val correction by model.evaluatedAnswer
var giveup by model.showAnswer
val giveup by model.showAnswer
val gameEnded by model.end
val delay by model.delay.collectAsState(initial = 5000)
val cpt by model.compteurSb
if (correction != null) {
LaunchedEffect(cpt) {
model.sbUpdate()
snackbarHostState.showSnackbar(
when (correction!!) {
AnswerType.GOOD -> context.getString(R.string.good_answer)
AnswerType.BAD -> context.getString(R.string.bad_answer)
}, duration = SnackbarDuration.Short
)
model.resetAfterSb()
}
}
if (gameEnded) {
EndDialog { navController.navigate(HOME) }
}
SnackbarAnswer(
model,
snackbarHostState,
cpt,
correction
)
if (giveup && question != null) {
ShowDialog(gameEnded) { EndDialog { navController.navigate(HOME) } }
ShowDialog(giveup && question != null) {
SolutionDialog(question!!.reponse, model::newQuestion)
}
// Update timer if needed
if (!model.isDelayElapsed() && question != null) {
model.updateTime(System.currentTimeMillis())
}
model.updateTimer(delay)
Play(
padding,
navController,
idSet,
model,
question,
reponse,
correction,
delay
)
}
@Composable
fun Play(
padding: PaddingValues,
navController: NavController,
idSet: Int,
model: PlayViewModel,
question: Question?,
reponse: String,
correction: AnswerType?,
delay: Int
) {
val context = LocalContext.current
Column(
modifier = Modifier.padding(padding),
@ -85,7 +98,7 @@ fun PlayScreen(
Text(context.getString(R.string.no_question), fontSize = 30.sp)
}
} else {
Text(text = question!!.enonce, fontSize = 30.sp, textAlign = TextAlign.Center)
Text(text = question.enonce, fontSize = 30.sp, textAlign = TextAlign.Center)
Spacer(modifier = Modifier.padding(top = 20.dp))
@ -109,8 +122,9 @@ fun PlayScreen(
}
Button(
enabled = model.isDelayElapsed(),
onClick = { giveup = true }) {
enabled = model.isDelayElapsed(delay),
onClick = model::giveUp
) {
Text(text = context.getString(R.string.see_answer))
}
}
@ -143,3 +157,25 @@ fun EndDialog(next: () -> Unit) =
confirmButton = {
Button(onClick = next) { Text(text = LocalContext.current.getString(R.string.ok)) }
})
@Composable
fun SnackbarAnswer(
model: PlayViewModel,
snackbarHostState: SnackbarHostState,
cpt: Int,
correction: AnswerType?
) {
val context = LocalContext.current
if (correction != null) {
LaunchedEffect(cpt) {
model.sbUpdate()
snackbarHostState.showSnackbar(
when (correction) {
AnswerType.GOOD -> context.getString(R.string.good_answer)
AnswerType.BAD -> context.getString(R.string.bad_answer)
}, duration = SnackbarDuration.Short
)
model.resetAfterSb()
}
}
}

View file

@ -1,45 +1,113 @@
package fr.uparis.diamantkennel.memorisationapplication
import android.content.Context
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.Divider
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
import androidx.compose.material3.TimePicker
import androidx.compose.material3.TimePickerLayoutType
import androidx.compose.material3.TimePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.Dialog
import androidx.lifecycle.viewmodel.compose.viewModel
import fr.uparis.diamantkennel.memorisationapplication.ui.SettingsViewModel
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.runBlocking
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun SettingsScreen(padding: PaddingValues, model: SettingsViewModel = viewModel()) {
fun SettingsScreen(model: SettingsViewModel = viewModel()) {
val context = LocalContext.current
var deletionDBRequest by model.deletionDB
var cleanStatRequest by model.deletionStat
var permissionNotif by model.gavePermissionNow
var choiceTimeNotifRequest by model.notif
var choiceDelayRequest by model.delayRequest
val prefConfigTime = runBlocking { model.prefConfigTime.first() }
val stateTime = rememberTimePickerState(
initialHour = prefConfigTime.hour,
initialMinute = prefConfigTime.minute,
is24Hour = true
)
model.checkPermission(context)
ShowDialog(deletionDBRequest) { DeletionDBDialog(model::deleteDb, model::dismissDeletionDB) }
ShowDialog(cleanStatRequest) { CleanStatDialog(model::cleanStats, model::dismissDeletionStat) }
ShowDialog(choiceTimeNotifRequest) {
ChoiceTimeNotifDialog(
{ model.choiceTimeNotif(stateTime, context) },
model::dismissNotif,
stateTime
)
}
ShowDialog(choiceDelayRequest) {
ChoiceDelayDialog(
{ model.choiceDelay(it) },
model::dismissDelayRequest
)
}
Settings(
model,
{ choiceTimeNotifRequest = true },
{ deletionDBRequest = true },
{ cleanStatRequest = true },
{ choiceDelayRequest = true },
model::resetDelay
)
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun Settings(
model: SettingsViewModel = viewModel(),
onTimeNotifButtonClick: () -> Unit,
deletionDBRequest: () -> Unit,
cleanStatRequest: () -> Unit,
onDelayButtonClick: () -> Unit,
onResetButtonClick: () -> Unit,
) {
val context = LocalContext.current
var permissionNotif by model.gavePermissionNow
val permissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
) {
@ -48,44 +116,34 @@ fun SettingsScreen(padding: PaddingValues, model: SettingsViewModel = viewModel(
}
}
if (deletionDBRequest) {
DeletionDBDialog(model::deleteDb) { deletionDBRequest = false }
}
if (cleanStatRequest) {
CleanStatDialog(model::cleanStats) { cleanStatRequest = false }
}
Column(
modifier = Modifier.padding(padding),
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Stats(model)
Spacer(modifier = Modifier.padding(top = 10.dp))
Divider(color = Color.Gray)
Spacer(modifier = Modifier.padding(top = 10.dp))
Text(text = "Gestion", fontSize = 30.sp)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
onClick = { deletionDBRequest = true },
colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red))
) {
Text(text = context.getString(R.string.main_button_deletebase))
}
Button(
onClick = { cleanStatRequest = true },
colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red))
) {
Text(text = context.getString(R.string.clean_stat_button))
}
}
Stats(model = model)
AddSpacedDivider()
NotificationSettings(context, model, permissionNotif, permissionLauncher, onTimeNotifButtonClick)
AddSpacedDivider()
GestionSettings(context, deletionDBRequest, cleanStatRequest)
AddSpacedDivider()
GameSettings(context, onDelayButtonClick, onResetButtonClick)
}
}
@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
fun NotificationSettings(
context: Context,
model: SettingsViewModel,
permissionNotif: Boolean,
permissionLauncher: ActivityResultLauncher<String>,
onTimeNotifButtonClick: () -> Unit,
) {
Text(text = context.getString(R.string.notification), fontSize = 30.sp)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
enabled = !permissionNotif,
onClick = {
@ -94,6 +152,59 @@ fun SettingsScreen(padding: PaddingValues, model: SettingsViewModel = viewModel(
) {
Text(text = context.getString(R.string.permission_button))
}
Button(
enabled = permissionNotif,
onClick = onTimeNotifButtonClick
) {
Text(text = context.getString(R.string.time_notif_button))
}
}
}
@Composable
fun GestionSettings(context: Context, deletionDBRequest: () -> Unit, cleanStatRequest: () -> Unit) {
Text(text = context.getString(R.string.gestion), fontSize = 30.sp)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Button(
onClick = deletionDBRequest,
colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red))
) {
Text(text = context.getString(R.string.main_button_deletebase))
}
Button(
onClick = cleanStatRequest,
colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red))
) {
Text(text = context.getString(R.string.clean_stat_button))
}
}
}
@Composable
fun GameSettings(context: Context, onDelayButtonClick: () -> Unit, onResetButtonClick: () -> Unit) {
Text(text = context.getString(R.string.game), fontSize = 30.sp)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly
) {
Column (
modifier = Modifier.fillMaxWidth(),
horizontalAlignment = Alignment.CenterHorizontally
){
Button(onClick = onDelayButtonClick) {
Text(text = context.getString(R.string.choice_delay_button))
}
Button(
onClick = onResetButtonClick,
colors = ButtonDefaults.buttonColors(containerColor = colorResource(id = R.color.red))
) {
Text(text = context.getString(R.string.reset_button))
}
}
}
}
@ -144,3 +255,71 @@ fun CleanStatDialog(confirm: () -> Unit, dismiss: () -> Unit) =
Button(onClick = confirm) { Text(text = LocalContext.current.getString(R.string.yes)) }
},
dismissButton = { Button(onClick = dismiss) { Text(text = LocalContext.current.getString(R.string.no)) } })
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ChoiceTimeNotifDialog(confirm: () -> Unit, dismiss: () -> Unit, state: TimePickerState) {
Dialog(onDismissRequest = dismiss) {
Card(
modifier = Modifier
.padding(16.dp)
.fillMaxWidth()
.height(550.dp),
shape = RoundedCornerShape(16.dp),
) {
Column(
modifier = Modifier.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = LocalContext.current.getString(R.string.time_notif),
fontSize = 30.sp,
textAlign = TextAlign.Center
)
TimePicker(state = state, layoutType = TimePickerLayoutType.Vertical)
Button(onClick = confirm) {
Text(text = LocalContext.current.getString(R.string.confirm))
}
}
}
}
}
@Composable
fun ChoiceDelayDialog(confirm: (Int) -> Unit, dismiss: () -> Unit) {
var value by remember { mutableStateOf("") }
AlertDialog(onDismissRequest = dismiss,
title = { Text(text = LocalContext.current.getString(R.string.choice_delay)) },
text = {
OutlinedTextField(
value = value,
onValueChange = {
value = if (it.toIntOrNull() != null) { it } else ""
},
label = { Text(text = LocalContext.current.getString(R.string.enter_integer)) },
keyboardOptions = KeyboardOptions.Default.copy(
keyboardType = KeyboardType.Number
),
singleLine = true
)
},
confirmButton = {
Button(onClick = {
if (value.isNotBlank()) { confirm(value.toInt()) } else dismiss()
}) {
Text(text = LocalContext.current.getString(R.string.confirm))
}
}
)
}
@Composable
fun AddSpacedDivider() {
Spacer(modifier = Modifier.padding(top = 20.dp))
Divider(color = Color.Gray)
Spacer(modifier = Modifier.padding(top = 20.dp))
}

View file

@ -0,0 +1,6 @@
package fr.uparis.diamantkennel.memorisationapplication
data class TimeConfig(
val hour: Int = 8,
val minute: Int = 0
)

View file

@ -63,4 +63,18 @@ class ModifySetViewModel(application: Application) : AndroidViewModel(applicatio
dao.deleteQuestion(selection.value!!)
}
}
fun modifAction() {
action.value = ActionModifySet.MODIFICATION
}
fun ajoutAction() {
action.value = ActionModifySet.AJOUT
}
fun supprAction() {
action.value = ActionModifySet.SUPPRIMER
}
fun dismissAction() {
action.value = ActionModifySet.AUCUN
}
}

View file

@ -11,21 +11,26 @@ import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_BAD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_DONE
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_GOOD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_TRIED
import fr.uparis.diamantkennel.memorisationapplication.DELAY
import fr.uparis.diamantkennel.memorisationapplication.data.Question
import fr.uparis.diamantkennel.memorisationapplication.dataStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
class PlayViewModel(application: Application) : AndroidViewModel(application) {
private val dao = (application as MemoApplication).database.memoDao()
private var questions = mutableStateOf<List<Question>>(listOf())
private val stats = application.dataStore
private val datastore = application.dataStore
private val statsKeyTotal = intPreferencesKey(STATS_TOTAL_TRIED)
private val statsKeyTotalDone = intPreferencesKey(STATS_TOTAL_DONE)
private val statsKeyTotalGood = intPreferencesKey(STATS_TOTAL_GOOD)
private val statsKeyTotalBad = intPreferencesKey(STATS_TOTAL_BAD)
private val delayKey= intPreferencesKey(DELAY)
val delay = datastore.data.map { it[delayKey] ?: 5000 }
var currentQuestion = mutableStateOf<Question?>(null)
private var index = mutableStateOf(0)
var proposedAnswer = mutableStateOf("")
@ -42,7 +47,7 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
dao.loadQuestions(setId).collect { questionList ->
questions.value = questionList.shuffled()
if (questions.value.isNotEmpty()) {
stats.edit { it[statsKeyTotal] = (it[statsKeyTotal] ?: 0) + 1 }
datastore.edit { it[statsKeyTotal] = (it[statsKeyTotal] ?: 0) + 1 }
}
updateQuestion()
}
@ -58,7 +63,7 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
/* Fin des questions */
end.value = true
viewModelScope.launch {
stats.edit {
datastore.edit {
it[statsKeyTotalDone] = (it[statsKeyTotalDone] ?: 0) + 1
}
}
@ -114,7 +119,7 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
when (evaluatedAnswer.value!!) {
AnswerType.GOOD -> {
viewModelScope.launch {
stats.edit {
datastore.edit {
it[statsKeyTotalGood] = (it[statsKeyTotalGood] ?: 0) + 1
}
}
@ -123,7 +128,7 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
AnswerType.BAD -> {
viewModelScope.launch {
stats.edit {
datastore.edit {
it[statsKeyTotalBad] = (it[statsKeyTotalBad] ?: 0) + 1
}
}
@ -132,10 +137,20 @@ class PlayViewModel(application: Application) : AndroidViewModel(application) {
}
}
fun isDelayElapsed() = currentTime.value - timestampQuestion.value >= 3000
fun updateTimer(delay: Int) {
if (!isDelayElapsed(delay) && currentQuestion.value != null) {
updateTime(System.currentTimeMillis())
}
}
fun isDelayElapsed(delay: Int) = currentTime.value - timestampQuestion.value >= delay
fun updateTime(time: Long) {
currentTime.value = time
}
}
fun giveUp() {
showAnswer.value = true
}
}

View file

@ -6,37 +6,57 @@ import android.content.pm.PackageManager
import android.os.Build
import androidx.activity.result.ActivityResultLauncher
import androidx.annotation.RequiresApi
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.TimePickerState
import androidx.compose.runtime.mutableStateOf
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.intPreferencesKey
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import androidx.work.PeriodicWorkRequest
import androidx.work.WorkManager
import fr.uparis.diamantkennel.memorisationapplication.HOUR
import fr.uparis.diamantkennel.memorisationapplication.MINUTE
import fr.uparis.diamantkennel.memorisationapplication.DELAY
import fr.uparis.diamantkennel.memorisationapplication.MemoApplication
import fr.uparis.diamantkennel.memorisationapplication.RappelWorker
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_BAD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_DONE
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_GOOD
import fr.uparis.diamantkennel.memorisationapplication.STATS_TOTAL_TRIED
import fr.uparis.diamantkennel.memorisationapplication.TimeConfig
import fr.uparis.diamantkennel.memorisationapplication.dataStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch
import java.util.Calendar
import java.util.concurrent.TimeUnit
class SettingsViewModel(application: Application) : AndroidViewModel(application) {
private val dao = (application as MemoApplication).database.memoDao()
private val stats = application.dataStore
private val datastore = application.dataStore
private val statsKeyTotal = intPreferencesKey(STATS_TOTAL_TRIED)
private val statsKeyTotalDone = intPreferencesKey(STATS_TOTAL_DONE)
private val statsKeyTotalGood = intPreferencesKey(STATS_TOTAL_GOOD)
private val statsKeyTotalBad = intPreferencesKey(STATS_TOTAL_BAD)
val statTotal = stats.data.map { it[statsKeyTotal] ?: 0 }
val statTotalDone = stats.data.map { it[statsKeyTotalDone] ?: 0 }
val statTotalGood = stats.data.map { it[statsKeyTotalGood] ?: 0 }
val statTotalBad = stats.data.map { it[statsKeyTotalBad] ?: 0 }
private val notifH = intPreferencesKey(HOUR)
private val notifM = intPreferencesKey(MINUTE)
val deletionDB = mutableStateOf(false)
val deletionStat = mutableStateOf(false)
private val delay = intPreferencesKey(DELAY)
val statTotal = datastore.data.map { it[statsKeyTotal] ?: 0 }
val statTotalDone = datastore.data.map { it[statsKeyTotalDone] ?: 0 }
val statTotalGood = datastore.data.map { it[statsKeyTotalGood] ?: 0 }
val statTotalBad = datastore.data.map { it[statsKeyTotalBad] ?: 0 }
var prefConfigTime = datastore.data.map { TimeConfig(it[notifH] ?: 8, it[notifM] ?: 0) }
var deletionDB = mutableStateOf(false)
var deletionStat = mutableStateOf(false)
var notif = mutableStateOf(false)
var delayRequest = mutableStateOf(false)
val gavePermissionNow = mutableStateOf(false)
@ -50,7 +70,7 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
fun cleanStats() {
deletionStat.value = false
viewModelScope.launch {
stats.edit {
datastore.edit {
it[statsKeyTotal] = 0
it[statsKeyTotalDone] = 0
it[statsKeyTotalGood] = 0
@ -59,6 +79,34 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
}
}
@OptIn(ExperimentalMaterial3Api::class)
fun choiceTimeNotif(state: TimePickerState, context: Context) {
notif.value = false
val newConfig = TimeConfig(
state.hour,
state.minute
)
save(newConfig)
schedule(newConfig, context)
}
fun choiceDelay(value: Int) {
delayRequest.value = false
viewModelScope.launch {
datastore.edit {
it[delay] = value
}
}
}
fun resetDelay() {
viewModelScope.launch {
datastore.edit {
it[delay] = 5000
}
}
}
fun winrate(good: Int, bad: Int): Int {
val total = good + bad
if (total == 0) {
@ -77,4 +125,47 @@ class SettingsViewModel(application: Application) : AndroidViewModel(application
gavePermissionNow.value =
context.checkSelfPermission(android.Manifest.permission.POST_NOTIFICATIONS) == PackageManager.PERMISSION_GRANTED
}
private fun save(config: TimeConfig) {
viewModelScope.launch {
datastore.edit {
it[notifH] = config.hour
it[notifM] = config.minute
}
}
}
private fun schedule(config: TimeConfig, context: Context) {
val wm = WorkManager.getInstance(context)
wm.cancelAllWork()
wm.enqueue(request(config.hour, config.minute))
}
private fun request(h: Int, m: Int): PeriodicWorkRequest {
val now = Calendar.getInstance()
val target = Calendar.getInstance().apply {
set(Calendar.HOUR_OF_DAY, h)
set(Calendar.MINUTE, m)
}
if (target.before(now))
target.add(Calendar.DAY_OF_YEAR, 1)
val delta = target.timeInMillis - now.timeInMillis
return PeriodicWorkRequest.Builder(RappelWorker::class.java, 1, TimeUnit.DAYS)
.setInitialDelay(delta, TimeUnit.MILLISECONDS)
.build()
}
fun dismissDeletionDB() {
deletionDB.value = false
}
fun dismissDeletionStat() {
deletionStat.value = false
}
fun dismissNotif() {
notif.value = false
}
fun dismissDelayRequest() {
delayRequest.value = false
}
}

View file

@ -21,7 +21,7 @@
<string name="error">Erreur</string>
<string name="delete_set_questions">Supprimer un jeu de question</string>
<string name="delete_set_qestions_desc">Voulez-vous supprimer ce jeu de question ?</string>
<string name="ok">Ok</string>
<string name="ok">OK</string>
<string name="update_question">Mettre à jour la question</string>
<string name="add_question">Ajouter une question</string>
<string name="question">Question</string>
@ -39,6 +39,7 @@
<string name="delete_db_desc">Voulez-vous supprimer la base de données ?</string>
<string name="yes">Oui</string>
<string name="no">Non</string>
<string name="validate">Valider</string>
<string name="bravo">Bravo</string>
<string name="set_ended">Le set de questions est terminé !</string>
<string name="stats">Statistiques</string>
@ -55,4 +56,15 @@
<string name="notif_channel_desc">Notifications de rappel</string>
<string name="notif_reminder_title">Rappel</string>
<string name="notif_reminder_content">Il est temps de réviser !</string>
<string name="time_notif_button">Modifier heure de rappel</string>
<string name="time_notif">Choix heure de rappel</string>
<string name="gestion">Gestion</string>
<string name="notification">Notifications</string>
<string name="confirm">Confirmer</string>
<string name="game">Parametre du jeu</string>
<string name="choice_delay_button">Délai avant solution</string>
<string name="reset_button">Réinitialiser par défaut</string>
<string name="reset_game_settings_button">Réinitialiser les paramètres du jeu</string>
<string name="choice_delay">Choix du délai avant solution</string>
<string name="enter_integer">Entrez un entier (en ms)</string>
</resources>