mirror of
https://github.com/sudoxnym/habitica-android.git
synced 2026-05-21 13:19:02 +00:00
ZonedDateTime Implemented
This commit is contained in:
parent
441dfa5498
commit
018991e467
9 changed files with 198 additions and 71 deletions
|
|
@ -55,6 +55,8 @@ dependencies {
|
|||
implementation 'androidx.recyclerview:recyclerview:1.2.1'
|
||||
implementation "androidx.preference:preference-ktx:1.2.0"
|
||||
|
||||
//Desugaring
|
||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.0.9'
|
||||
|
||||
// Markdown
|
||||
implementation "io.noties.markwon:core:4.6.2"
|
||||
|
|
@ -267,6 +269,9 @@ android {
|
|||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
|
||||
// Flag to enable support for the new language APIs
|
||||
coreLibraryDesugaringEnabled true
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
|
|
|
|||
|
|
@ -17,8 +17,14 @@ import com.habitrpg.android.habitica.receivers.TaskReceiver
|
|||
import com.habitrpg.shared.habitica.HLogger
|
||||
import com.habitrpg.shared.habitica.LogLevel
|
||||
import io.reactivex.rxjava3.core.Flowable
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.temporal.TemporalAccessor
|
||||
import java.util.*
|
||||
|
||||
class TaskAlarmManager(
|
||||
private var context: Context,
|
||||
|
|
@ -76,30 +82,42 @@ class TaskAlarmManager(
|
|||
|
||||
private fun setTimeForDailyReminder(remindersItem: RemindersItem?, task: Task): RemindersItem? {
|
||||
val oldTime = remindersItem?.time
|
||||
val newTime = task.getNextReminderOccurence(oldTime) ?: return null
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.time = newTime
|
||||
@Suppress("DEPRECATION")
|
||||
calendar.set(
|
||||
calendar.get(Calendar.YEAR),
|
||||
calendar.get(Calendar.MONTH),
|
||||
calendar.get(Calendar.DATE),
|
||||
oldTime?.hours ?: 0,
|
||||
oldTime?.minutes ?: 0,
|
||||
0
|
||||
)
|
||||
remindersItem?.time = calendar.time
|
||||
val newTime = (task.getNextReminderOccurence(oldTime) ?: return null)
|
||||
|
||||
remindersItem?.time = newTime.withZoneSameLocal(ZoneId.systemDefault()).format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
|
||||
return remindersItem
|
||||
}
|
||||
|
||||
fun formatter(): DateTimeFormatter =
|
||||
DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.appendPattern("['T'][' ']")
|
||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||
.appendPattern("[XX]")
|
||||
.toFormatter()
|
||||
|
||||
fun parse(dateTime: String?): ZonedDateTime? {
|
||||
val parsed: TemporalAccessor = formatter().parseBest(
|
||||
dateTime,
|
||||
ZonedDateTime::from, LocalDateTime::from
|
||||
)
|
||||
return if (parsed is ZonedDateTime) {
|
||||
parsed
|
||||
} else {
|
||||
val defaultZone: ZoneId = ZoneId.of("UTC")
|
||||
(parsed as LocalDateTime).atZone(defaultZone)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setAlarmForRemindersItem(reminderItemTask: Task, remindersItem: RemindersItem?) {
|
||||
val now = Date()
|
||||
if (remindersItem == null || remindersItem.time?.before(now) == true) {
|
||||
val now = ZonedDateTime.now().withZoneSameLocal(ZoneId.systemDefault())?.toInstant()
|
||||
if (remindersItem == null || parse(remindersItem.time)?.withZoneSameLocal(ZoneId.systemDefault())?.toInstant()?.isBefore(now) == true) {
|
||||
return
|
||||
}
|
||||
|
||||
val time = Date.from(parse(remindersItem.time)?.withZoneSameLocal(ZoneId.systemDefault())?.toInstant())
|
||||
val cal = Calendar.getInstance()
|
||||
cal.time = remindersItem.time
|
||||
cal.time = time
|
||||
|
||||
val intent = Intent(context, TaskReceiver::class.java)
|
||||
intent.action = remindersItem.id
|
||||
|
|
@ -127,6 +145,7 @@ class TaskAlarmManager(
|
|||
)
|
||||
|
||||
setAlarm(context, cal.timeInMillis, sender)
|
||||
|
||||
}
|
||||
|
||||
private fun removeAlarmForRemindersItem(remindersItem: RemindersItem) {
|
||||
|
|
|
|||
|
|
@ -4,13 +4,19 @@ import android.os.Parcel
|
|||
import android.os.Parcelable
|
||||
import io.realm.RealmObject
|
||||
import io.realm.annotations.PrimaryKey
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.temporal.TemporalAccessor
|
||||
import java.util.Date
|
||||
|
||||
open class RemindersItem : RealmObject, Parcelable {
|
||||
@PrimaryKey
|
||||
var id: String? = null
|
||||
var startDate: Date? = null
|
||||
var time: Date? = null
|
||||
var startDate: String? = null
|
||||
var time: String? = null
|
||||
|
||||
// Use to store task type before a task is created
|
||||
var type: String? = null
|
||||
|
|
@ -21,8 +27,8 @@ open class RemindersItem : RealmObject, Parcelable {
|
|||
|
||||
override fun writeToParcel(dest: Parcel, flags: Int) {
|
||||
dest.writeString(id)
|
||||
dest.writeLong(this.startDate?.time ?: -1)
|
||||
dest.writeLong(this.time?.time ?: -1)
|
||||
dest.writeString(startDate)
|
||||
dest.writeString(time)
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<RemindersItem> {
|
||||
|
|
@ -33,8 +39,8 @@ open class RemindersItem : RealmObject, Parcelable {
|
|||
|
||||
constructor(source: Parcel) {
|
||||
id = source.readString()
|
||||
startDate = Date(source.readLong())
|
||||
time = Date(source.readLong())
|
||||
startDate = source.readString()
|
||||
time = source.readString()
|
||||
}
|
||||
|
||||
constructor()
|
||||
|
|
@ -48,4 +54,24 @@ open class RemindersItem : RealmObject, Parcelable {
|
|||
override fun hashCode(): Int {
|
||||
return id?.hashCode() ?: 0
|
||||
}
|
||||
|
||||
fun formatter(): DateTimeFormatter =
|
||||
DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.appendPattern("['T'][' ']")
|
||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||
.appendPattern("[XX]")
|
||||
.toFormatter()
|
||||
|
||||
fun parse(dateTime: String): ZonedDateTime? {
|
||||
val parsed: TemporalAccessor = formatter().parseBest(
|
||||
dateTime,
|
||||
ZonedDateTime::from, LocalDateTime::from
|
||||
)
|
||||
return if (parsed is ZonedDateTime) {
|
||||
parsed
|
||||
} else {
|
||||
val defaultZone: ZoneId = ZoneId.of("UTC")
|
||||
(parsed as LocalDateTime).atZone(defaultZone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,6 +18,12 @@ import java.util.Date
|
|||
import java.util.GregorianCalendar
|
||||
import org.json.JSONArray
|
||||
import org.json.JSONException
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.temporal.TemporalAccessor
|
||||
|
||||
open class Task : RealmObject, BaseMainObject, Parcelable {
|
||||
|
||||
|
|
@ -181,32 +187,46 @@ open class Task : RealmObject, BaseMainObject, Parcelable {
|
|||
|
||||
fun checkIfDue(): Boolean = isDue == true
|
||||
|
||||
fun getNextReminderOccurence(oldTime: Date?): Date? {
|
||||
fun getNextReminderOccurence(oldTime: String?): ZonedDateTime? {
|
||||
if (oldTime == null) {
|
||||
return null
|
||||
}
|
||||
val today = Calendar.getInstance()
|
||||
|
||||
val newTime = GregorianCalendar()
|
||||
newTime.time = oldTime
|
||||
newTime.set(today.get(Calendar.YEAR), today.get(Calendar.MONTH), today.get(Calendar.DAY_OF_MONTH))
|
||||
if (today.before(newTime)) {
|
||||
today.add(Calendar.DAY_OF_MONTH, -1)
|
||||
}
|
||||
|
||||
val nextDate = nextDue?.firstOrNull()
|
||||
|
||||
return if (nextDate != null && !isDisplayedActive) {
|
||||
val nextDueCalendar = GregorianCalendar()
|
||||
nextDueCalendar.time = nextDate
|
||||
newTime.set(nextDueCalendar.get(Calendar.YEAR), nextDueCalendar.get(Calendar.MONTH), nextDueCalendar.get(Calendar.DAY_OF_MONTH))
|
||||
newTime.time
|
||||
parse(oldTime)
|
||||
?.withYear(nextDueCalendar.get(Calendar.YEAR))
|
||||
?.withMonth(nextDueCalendar.get(Calendar.MONTH))
|
||||
?.withDayOfMonth(nextDueCalendar.get(Calendar.DAY_OF_MONTH))
|
||||
} else if (isDisplayedActive) {
|
||||
newTime.time
|
||||
parse(oldTime)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun formatter(): DateTimeFormatter =
|
||||
DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.appendPattern("['T'][' ']")
|
||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||
.appendPattern("[XX]")
|
||||
.toFormatter()
|
||||
|
||||
fun parse(dateTime: String): ZonedDateTime? {
|
||||
val parsed: TemporalAccessor = formatter().parseBest(
|
||||
dateTime,
|
||||
ZonedDateTime::from, LocalDateTime::from
|
||||
)
|
||||
return if (parsed is ZonedDateTime) {
|
||||
parsed
|
||||
} else {
|
||||
val defaultZone: ZoneId = ZoneId.of("UTC")
|
||||
(parsed as LocalDateTime).atZone(defaultZone)
|
||||
}
|
||||
}
|
||||
|
||||
fun parseMarkdown() {
|
||||
parsedText = MarkdownParser.parseMarkdown(text)
|
||||
parsedNotes = MarkdownParser.parseMarkdown(notes)
|
||||
|
|
|
|||
|
|
@ -5,6 +5,12 @@ import com.habitrpg.android.habitica.models.responses.TaskDirection
|
|||
import com.habitrpg.android.habitica.models.tasks.ChecklistItem
|
||||
import com.habitrpg.android.habitica.models.tasks.Task
|
||||
import java.text.DateFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.temporal.TemporalAccessor
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
|
||||
|
|
@ -37,13 +43,19 @@ class DailyViewHolder(
|
|||
val calendar = Calendar.getInstance()
|
||||
val nextReminder = data.reminders?.firstOrNull {
|
||||
calendar.time = now
|
||||
calendar.set(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), calendar.get(Calendar.DATE), it.time?.hours ?: 0, it.time?.minutes ?: 0, 0)
|
||||
calendar.set(calendar.get(Calendar.YEAR),
|
||||
calendar.get(Calendar.MONTH),
|
||||
calendar.get(Calendar.DATE),
|
||||
parse(it.time)?.hour ?: 0,
|
||||
parse(it.time)?.minute ?: 0,
|
||||
0)
|
||||
now < calendar.time
|
||||
} ?: data.reminders?.first()
|
||||
|
||||
var reminderString = ""
|
||||
if (nextReminder?.time != null) {
|
||||
reminderString += formatter.format(nextReminder.time)
|
||||
val time = Date.from(parse(nextReminder.time)?.withZoneSameLocal(ZoneId.systemDefault())?.toInstant())
|
||||
reminderString += formatter.format(time)
|
||||
}
|
||||
if ((data.reminders?.size ?: 0) > 1) {
|
||||
reminderString = "$reminderString (+${(data.reminders?.size ?: 0) - 1})"
|
||||
|
|
@ -54,6 +66,26 @@ class DailyViewHolder(
|
|||
super.bind(data, position, displayMode)
|
||||
}
|
||||
|
||||
fun formatter(): DateTimeFormatter =
|
||||
DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.appendPattern("['T'][' ']")
|
||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||
.appendPattern("[XX]")
|
||||
.toFormatter()
|
||||
|
||||
fun parse(dateTime: String?): ZonedDateTime? {
|
||||
val parsed: TemporalAccessor = formatter().parseBest(
|
||||
dateTime,
|
||||
ZonedDateTime::from, LocalDateTime::from
|
||||
)
|
||||
return if (parsed is ZonedDateTime) {
|
||||
parsed
|
||||
} else {
|
||||
val defaultZone: ZoneId = ZoneId.of("UTC")
|
||||
(parsed as LocalDateTime).atZone(defaultZone)
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldDisplayAsActive(newTask: Task?): Boolean {
|
||||
return newTask?.isDisplayedActive ?: false
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,13 @@ import com.habitrpg.android.habitica.extensions.layoutInflater
|
|||
import com.habitrpg.android.habitica.models.tasks.RemindersItem
|
||||
import com.habitrpg.android.habitica.models.tasks.TaskType
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.ZoneId
|
||||
import java.time.ZonedDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.time.format.DateTimeFormatterBuilder
|
||||
import java.time.temporal.TemporalAccessor
|
||||
import java.util.Calendar
|
||||
import java.util.Date
|
||||
import java.util.Locale
|
||||
|
|
@ -33,13 +40,14 @@ class ReminderItemFormView @JvmOverloads constructor(
|
|||
) : LinearLayout(context, attrs, defStyleAttr), TimePickerDialog.OnTimeSetListener, DatePickerDialog.OnDateSetListener {
|
||||
private val formattedTime: CharSequence?
|
||||
get() {
|
||||
val time = item.time
|
||||
return if (time != null) {
|
||||
return if (item.time != null) {
|
||||
val time = Date.from(parse(item.time)?.withZoneSameLocal(ZoneId.systemDefault())?.toInstant())
|
||||
formatter.format(time)
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
|
||||
private val binding = TaskFormReminderItemBinding.inflate(context.layoutInflater, this)
|
||||
|
||||
private val formatter: DateFormat
|
||||
|
|
@ -103,22 +111,20 @@ class ReminderItemFormView @JvmOverloads constructor(
|
|||
binding.button.drawable.mutate().setTint(tintColor)
|
||||
|
||||
binding.textView.setOnClickListener {
|
||||
val calendar = Calendar.getInstance()
|
||||
item.time?.let { calendar.time = it }
|
||||
if (taskType == TaskType.DAILY) {
|
||||
val timePickerDialog = TimePickerDialog(
|
||||
context, this,
|
||||
calendar.get(Calendar.HOUR_OF_DAY),
|
||||
calendar.get(Calendar.MINUTE),
|
||||
ZonedDateTime.now().hour,
|
||||
ZonedDateTime.now().minute,
|
||||
android.text.format.DateFormat.is24HourFormat(context)
|
||||
)
|
||||
timePickerDialog.show()
|
||||
} else {
|
||||
val timePickerDialog = DatePickerDialog(
|
||||
context, this,
|
||||
calendar.get(Calendar.YEAR),
|
||||
calendar.get(Calendar.MONTH),
|
||||
calendar.get(Calendar.DAY_OF_MONTH)
|
||||
ZonedDateTime.now().year,
|
||||
ZonedDateTime.now().monthValue,
|
||||
ZonedDateTime.now().dayOfMonth
|
||||
)
|
||||
if ((firstDayOfWeek ?: -1) >= 0) {
|
||||
timePickerDialog.datePicker.firstDayOfWeek = firstDayOfWeek ?: 0
|
||||
|
|
@ -130,34 +136,53 @@ class ReminderItemFormView @JvmOverloads constructor(
|
|||
}
|
||||
override fun onTimeSet(view: TimePicker?, hourOfDay: Int, minute: Int) {
|
||||
valueChangedListener?.let {
|
||||
val calendar = Calendar.getInstance()
|
||||
item.time?.let { calendar.time = it }
|
||||
calendar.set(Calendar.HOUR_OF_DAY, hourOfDay)
|
||||
calendar.set(Calendar.MINUTE, minute)
|
||||
item.time = calendar.time
|
||||
val calendar = ZonedDateTime.now()
|
||||
.withHour(hourOfDay)
|
||||
.withMinute(minute)
|
||||
item.time = calendar.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
binding.textView.text = formattedTime
|
||||
item.time?.let { date -> it(date) }
|
||||
item.time?.let { date -> it(Date.from(parse(date)?.withZoneSameLocal(ZoneId.systemDefault())?.toInstant())) }
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDateSet(view: DatePicker?, year: Int, month: Int, dayOfMonth: Int) {
|
||||
valueChangedListener?.let {
|
||||
val calendar = Calendar.getInstance()
|
||||
item.time?.let { calendar.time = it }
|
||||
calendar.set(Calendar.YEAR, year)
|
||||
calendar.set(Calendar.MONTH, month)
|
||||
calendar.set(Calendar.DAY_OF_MONTH, dayOfMonth)
|
||||
item.time = calendar.time
|
||||
// val calendar = Calendar.getInstance()
|
||||
val calendar = ZonedDateTime.now()
|
||||
.withYear(year)
|
||||
.withMonth(month)
|
||||
.withDayOfMonth(dayOfMonth)
|
||||
item.time = calendar.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
|
||||
binding.textView.text = formattedTime
|
||||
item.time?.let { date -> it(date) }
|
||||
item.time?.let { date -> Date.from(parse(date)?.withZoneSameLocal(ZoneId.systemDefault())?.toInstant()) }
|
||||
|
||||
val timePickerDialog = TimePickerDialog(
|
||||
context, this,
|
||||
calendar.get(Calendar.HOUR_OF_DAY),
|
||||
calendar.get(Calendar.MINUTE),
|
||||
ZonedDateTime.now().hour,
|
||||
ZonedDateTime.now().minute,
|
||||
android.text.format.DateFormat.is24HourFormat(context)
|
||||
)
|
||||
timePickerDialog.show()
|
||||
}
|
||||
}
|
||||
|
||||
fun formatter(): DateTimeFormatter =
|
||||
DateTimeFormatterBuilder().append(DateTimeFormatter.ISO_LOCAL_DATE)
|
||||
.appendPattern("['T'][' ']")
|
||||
.append(DateTimeFormatter.ISO_LOCAL_TIME)
|
||||
.appendPattern("[XX]")
|
||||
.toFormatter()
|
||||
|
||||
fun parse(dateTime: String?): ZonedDateTime? {
|
||||
val parsed: TemporalAccessor = formatter().parseBest(
|
||||
dateTime,
|
||||
ZonedDateTime::from, LocalDateTime::from
|
||||
)
|
||||
return if (parsed is ZonedDateTime) {
|
||||
parsed
|
||||
} else {
|
||||
val defaultZone: ZoneId = ZoneId.of("UTC")
|
||||
(parsed as LocalDateTime).atZone(defaultZone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -104,8 +104,8 @@ class TaskSerializer : JsonSerializer<Task>, JsonDeserializer<Task> {
|
|||
val remindersObject = reminderElement.asJsonObject
|
||||
val reminder = RemindersItem()
|
||||
reminder.id = remindersObject.getAsString("id")
|
||||
reminder.startDate = context.deserialize(remindersObject.get("startDate"), Date::class.java)
|
||||
reminder.time = context.deserialize(remindersObject.get("time"), Date::class.java)
|
||||
reminder.startDate = remindersObject.getAsString("startDate")
|
||||
reminder.time = remindersObject.getAsString("time")
|
||||
task.reminders?.add(reminder)
|
||||
}
|
||||
}
|
||||
|
|
@ -207,9 +207,9 @@ class TaskSerializer : JsonSerializer<Task>, JsonDeserializer<Task> {
|
|||
val jsonObject = JsonObject()
|
||||
jsonObject.addProperty("id", item.id)
|
||||
if (item.startDate != null) {
|
||||
jsonObject.addProperty("startDate", item.startDate?.time)
|
||||
jsonObject.addProperty("startDate", item.startDate)
|
||||
}
|
||||
jsonObject.addProperty("time", item.time?.time ?: Date().time)
|
||||
jsonObject.addProperty("time", item.time)
|
||||
jsonArray.add(jsonObject)
|
||||
}
|
||||
return jsonArray
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ buildscript {
|
|||
maven { url "https://plugins.gradle.org/m2/" }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.1.2'
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
|
||||
classpath 'com.google.gms:google-services:4.3.10'
|
||||
classpath 'com.google.firebase:firebase-crashlytics-gradle:2.8.1'
|
||||
|
|
|
|||
6
gradle/wrapper/gradle-wrapper.properties
vendored
6
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
|||
#Tue Oct 27 17:32:09 CET 2020
|
||||
#Sun Apr 17 16:10:31 EDT 2022
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
|
|
|
|||
Loading…
Reference in a new issue