fix schedule request. Add Moshi and network logger
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".App"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
|
|||||||
7
app/src/main/java/ru/fincode/tsudesk/App.kt
Normal file
7
app/src/main/java/ru/fincode/tsudesk/App.kt
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package ru.fincode.tsudesk
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class App : Application()
|
||||||
@@ -1,21 +1,33 @@
|
|||||||
package ru.fincode.tsudesk
|
package ru.fincode.tsudesk
|
||||||
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.enableEdgeToEdge
|
import android.util.Log
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.core.view.WindowInsetsCompat
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.domain.usecase.GetScheduleUseCase
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.domain.usecase.GetScheduleUseCase.ScheduleType
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
import ru.fincode.tsudesk.core.network.NetworkConstants
|
@AndroidEntryPoint
|
||||||
import ru.fincode.tsudesk.core.network.RetrofitProvider
|
class MainActivity : ComponentActivity() {
|
||||||
import ru.fincode.tsudesk.core.network.NetworkModule
|
@Inject
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleApi
|
lateinit var getScheduleUseCase: GetScheduleUseCase
|
||||||
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
enableEdgeToEdge()
|
|
||||||
setContentView(R.layout.activity_main)
|
lifecycleScope.launch {
|
||||||
|
try {
|
||||||
|
val result = getScheduleUseCase(
|
||||||
|
ScheduleType.Group("220631")
|
||||||
|
)
|
||||||
|
Log.d("TSUDesk", result.isSuccess.toString())
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("TSUDesk", "Error loading schedule", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -42,5 +42,12 @@ dependencies {
|
|||||||
api(libs.retrofit)
|
api(libs.retrofit)
|
||||||
api(libs.okhttp)
|
api(libs.okhttp)
|
||||||
|
|
||||||
implementation(libs.retrofit.simplexml)
|
implementation(libs.okhttp.logging)
|
||||||
|
implementation(libs.converter.gson)
|
||||||
|
|
||||||
|
api(libs.moshi)
|
||||||
|
api(libs.moshiKotlin)
|
||||||
|
api(libs.retrofitMoshi)
|
||||||
|
|
||||||
|
api(libs.retrofit.simplexml)
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package ru.fincode.tsudesk.core.network
|
||||||
|
|
||||||
|
import okhttp3.Interceptor
|
||||||
|
import okhttp3.Response
|
||||||
|
import android.util.Log
|
||||||
|
|
||||||
|
class DebugInterceptor : Interceptor {
|
||||||
|
|
||||||
|
override fun intercept(chain: Interceptor.Chain): Response {
|
||||||
|
val request = chain.request()
|
||||||
|
|
||||||
|
Log.d("NETWORK_DEBUG", "URL: ${request.url}")
|
||||||
|
Log.d("NETWORK_DEBUG", "Method: ${request.method}")
|
||||||
|
Log.d("NETWORK_DEBUG", "Headers: ${request.headers}")
|
||||||
|
|
||||||
|
return chain.proceed(request)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
package ru.fincode.tsudesk.core.network
|
package ru.fincode.tsudesk.core.network
|
||||||
|
|
||||||
object NetworkConstants {
|
object NetworkConstants {
|
||||||
const val BASE_URL = "https://api.tsu.tula.ru/"
|
const val BASE_URL = "https://tulsu.ru/schedule/queries/"
|
||||||
}
|
}
|
||||||
@@ -8,12 +8,16 @@ import okhttp3.OkHttpClient
|
|||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
|
import okhttp3.logging.HttpLoggingInterceptor
|
||||||
|
|
||||||
@Module
|
@Module
|
||||||
@InstallIn(SingletonComponent::class)
|
@InstallIn(SingletonComponent::class)
|
||||||
object NetworkModule {
|
object NetworkModule {
|
||||||
|
|
||||||
private const val TIMEOUT_SEC = 30L
|
private const val TIMEOUT_SEC = 30L
|
||||||
|
val logging = HttpLoggingInterceptor().apply {
|
||||||
|
level = HttpLoggingInterceptor.Level.BODY
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
@@ -23,6 +27,7 @@ object NetworkModule {
|
|||||||
.readTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
|
.readTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
|
||||||
.writeTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
|
.writeTimeout(TIMEOUT_SEC, TimeUnit.SECONDS)
|
||||||
.retryOnConnectionFailure(true)
|
.retryOnConnectionFailure(true)
|
||||||
|
.addInterceptor(DebugInterceptor())
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|||||||
@@ -1,14 +1,24 @@
|
|||||||
package ru.fincode.tsudesk.core.network
|
package ru.fincode.tsudesk.core.network
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import retrofit2.Retrofit
|
import retrofit2.Retrofit
|
||||||
import retrofit2.converter.simplexml.SimpleXmlConverterFactory
|
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||||
|
import javax.inject.Inject
|
||||||
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class RetrofitProvider @Inject constructor() {
|
||||||
|
|
||||||
|
private val moshi: Moshi = Moshi.Builder()
|
||||||
|
.add(KotlinJsonAdapterFactory())
|
||||||
|
.build()
|
||||||
|
|
||||||
class RetrofitProvider {
|
|
||||||
fun process(baseUrl: String, client: OkHttpClient): Retrofit =
|
fun process(baseUrl: String, client: OkHttpClient): Retrofit =
|
||||||
Retrofit.Builder()
|
Retrofit.Builder()
|
||||||
.baseUrl(baseUrl)
|
.baseUrl(baseUrl)
|
||||||
.client(client)
|
.client(client)
|
||||||
.addConverterFactory(SimpleXmlConverterFactory.create())
|
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ package ru.fincode.tsudesk.feature.schedule.data
|
|||||||
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleRemoteDataSource
|
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleRemoteDataSource
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.mapper.ScheduleDtoToDomainMapper
|
import ru.fincode.tsudesk.feature.schedule.data.mapper.ScheduleDtoToDomainMapper
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.model.Schedule
|
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
|
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@@ -10,12 +10,11 @@ class ScheduleRepositoryImpl @Inject constructor(
|
|||||||
private val remote: ScheduleRemoteDataSource, private val mapper: ScheduleDtoToDomainMapper
|
private val remote: ScheduleRemoteDataSource, private val mapper: ScheduleDtoToDomainMapper
|
||||||
) : ScheduleRepository {
|
) : ScheduleRepository {
|
||||||
|
|
||||||
override suspend fun loadScheduleByGroup(groupNumber: String): Schedule {
|
override suspend fun loadScheduleByGroup(
|
||||||
return mapper.map(remote.loadScheduleByGroup(groupNumber))
|
groupNumber: String
|
||||||
}
|
): Result<ScheduleEntity> = remote.loadScheduleByGroup(groupNumber).map(mapper::invoke)
|
||||||
|
|
||||||
override suspend fun loadScheduleByTeacher(teacherName: String): Schedule {
|
|
||||||
return mapper.map(remote.loadScheduleByTeacher(teacherName))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
override suspend fun loadScheduleByTeacher(
|
||||||
|
name: String
|
||||||
|
): Result<ScheduleEntity> = remote.loadScheduleByTeacher(name).map(mapper::invoke)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.datasource
|
package ru.fincode.tsudesk.feature.schedule.data.datasource
|
||||||
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.local.ScheduleEntity
|
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleDto
|
|
||||||
|
|
||||||
interface ScheduleLocalDataSource {
|
interface ScheduleLocalDataSource {
|
||||||
suspend fun getScheduleByGroup(groupNumber: String): ScheduleEntity?
|
suspend fun getScheduleByGroup(groupNumber: String): ScheduleEntity?
|
||||||
suspend fun getScheduleByTeacherName(teacherName: String): ScheduleEntity?
|
suspend fun getScheduleByTeacher(teacherName: String): ScheduleEntity?
|
||||||
suspend fun saveSchedule(entity: ScheduleEntity)
|
suspend fun saveSchedule(entity: ScheduleEntity)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.datasource
|
package ru.fincode.tsudesk.feature.schedule.data.datasource
|
||||||
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleApi
|
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleApi
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleDto
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.LessonDto.ScheduleSearchField.GROUP_P
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleXmlParser
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.LessonDto.ScheduleSearchField.PREP
|
||||||
import java.io.IOException
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.ScheduleDto
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ScheduleRemoteDataSource(
|
class ScheduleRemoteDataSource @Inject constructor(
|
||||||
private val api: ScheduleApi, private val xmlParser: ScheduleXmlParser
|
private val api: ScheduleApi
|
||||||
) {
|
) {
|
||||||
suspend fun loadScheduleByGroup(groupNumber: String): ScheduleDto {
|
suspend fun loadScheduleByGroup(groupNumber: String): Result<ScheduleDto> =
|
||||||
val response = api.getScheduleByGroup(groupNumber)
|
runCatching { api.getSchedule(GROUP_P, groupNumber) }
|
||||||
return xmlParser.parse(response.body() ?: throw IOException("Response body is null"))
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun loadScheduleByTeacher(name: String): ScheduleDto {
|
suspend fun loadScheduleByTeacher(name: String): Result<ScheduleDto> =
|
||||||
val response = api.getScheduleByTeacherName(name)
|
runCatching { api.getSchedule(PREP, name) }
|
||||||
return xmlParser.parse(response.body() ?: throw IOException("Response body is null"))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,22 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.datasource
|
package ru.fincode.tsudesk.feature.schedule.data.datasource
|
||||||
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.mapper.ScheduleDtoToDomainMapper
|
import ru.fincode.tsudesk.feature.schedule.data.mapper.ScheduleDtoToDomainMapper
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.model.Schedule
|
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
|
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ScheduleRepositoryImpl @Inject constructor(
|
class ScheduleRemoteDataSourceImpl @Inject constructor(
|
||||||
private val remote: ScheduleRemoteDataSource,
|
private val remote: ScheduleRemoteDataSource,
|
||||||
private val mapper: ScheduleDtoToDomainMapper
|
private val mapper: ScheduleDtoToDomainMapper
|
||||||
) : ScheduleRepository {
|
) : ScheduleRepository {
|
||||||
|
|
||||||
override suspend fun loadScheduleByGroup(groupNumber: String): Schedule {
|
override suspend fun loadScheduleByGroup(groupNumber: String): Result<ScheduleEntity> =
|
||||||
return mapper.map(remote.loadScheduleByGroup(groupNumber));
|
remote
|
||||||
}
|
.loadScheduleByGroup(groupNumber) // Result<ScheduleDto>
|
||||||
|
.map(mapper::invoke) // Result<ScheduleEntity>
|
||||||
|
|
||||||
override suspend fun loadScheduleByTeacher(teacherName: String): Schedule {
|
override suspend fun loadScheduleByTeacher(name: String): Result<ScheduleEntity> =
|
||||||
return mapper.map(remote.loadScheduleByTeacher(teacherName));
|
remote
|
||||||
}
|
.loadScheduleByTeacher(name) // Result<ScheduleDto>
|
||||||
|
.map(mapper::invoke) // Result<ScheduleEntity>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +0,0 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.local;
|
|
||||||
|
|
||||||
public class ScheduleEntity {
|
|
||||||
}
|
|
||||||
@@ -1,23 +1,25 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.mapper
|
package ru.fincode.tsudesk.feature.schedule.data.mapper
|
||||||
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleDto
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.LessonDto
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.ScheduleDto
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.model.Lesson
|
import ru.fincode.tsudesk.feature.schedule.domain.model.Lesson
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.model.Schedule
|
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
class ScheduleDtoToDomainMapper {
|
class ScheduleDtoToDomainMapper @Inject constructor() {
|
||||||
|
|
||||||
fun map(dto: ScheduleDto): Schedule =
|
operator fun invoke(dto: ScheduleDto): ScheduleEntity =
|
||||||
Schedule(
|
ScheduleEntity(lessons = dto.map(::mapLesson))
|
||||||
lessons = dto.lessons.map { l ->
|
|
||||||
Lesson(
|
private fun mapLesson(item: LessonDto): Lesson =
|
||||||
dayOfWeek = l.dayOfWeek,
|
Lesson(
|
||||||
dayName = l.dayName,
|
date = item.date.trim(),
|
||||||
weekType = l.weekType,
|
time = item.time.trim(),
|
||||||
time = l.time,
|
subject = item.discipline.trim(),
|
||||||
room = l.room,
|
typeName = item.typeName.trim(),
|
||||||
subjectName = l.subjectName,
|
room = item.room.trim(),
|
||||||
teacherName = l.teacherName
|
teacher = item.teacher.trim(),
|
||||||
)
|
groupId = item.groups.firstOrNull()?.groupCode.orEmpty(),
|
||||||
}
|
type = item.type.trim()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,28 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.remote
|
package ru.fincode.tsudesk.feature.schedule.data.remote
|
||||||
|
|
||||||
import okhttp3.ResponseBody
|
|
||||||
import retrofit2.Response
|
|
||||||
import retrofit2.http.GET
|
import retrofit2.http.GET
|
||||||
import retrofit2.http.Query
|
import retrofit2.http.Query
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.LessonDto
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.LessonDto.*
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.ScheduleDto
|
||||||
|
|
||||||
interface ScheduleApi {
|
interface ScheduleApi {
|
||||||
@GET("schedule")
|
|
||||||
suspend fun getScheduleByGroup(
|
|
||||||
@Query("group") groupNumber: String
|
|
||||||
): Response<ResponseBody>
|
|
||||||
|
|
||||||
@GET("schedule")
|
/**
|
||||||
suspend fun getScheduleByTeacherName(
|
* Расписание по номеру группы.
|
||||||
@Query("fio") teacherName: String
|
* Пример: search_field=GROUP_P&search_value=220631
|
||||||
): Response<ResponseBody>
|
* https://tulsu.ru/schedule/queries/GetSchedule.php?search_field=GROUP_P&search_value=220631
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Расписание по ФИО преподавателя (строкой).
|
||||||
|
* Пример: search_field=PREP&search_value=Набродова Ирина Николаевна
|
||||||
|
*
|
||||||
|
* Важно: Retrofit сам URL-энкодит параметр search_value
|
||||||
|
* https://tulsu.ru/schedule/queries/GetDates.php?search_value=%D0%9D%D0%B0%D0%B1%D1%80%D0%BE%D0%B4%D0%BE%D0%B2%D0%B0%20%D0%98%D1%80%D0%B8%D0%BD%D0%B0%20%D0%9D%D0%B8%D0%BA%D0%BE%D0%BB%D0%B0%D0%B5%D0%B2%D0%BD%D0%B0
|
||||||
|
*/
|
||||||
|
@GET("GetSchedule.php")
|
||||||
|
suspend fun getSchedule(
|
||||||
|
@Query("search_field") searchField: String,
|
||||||
|
@Query("search_value") searchValue: String
|
||||||
|
): ScheduleDto
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,38 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.remote
|
package ru.fincode.tsudesk.feature.schedule.data.remote.model
|
||||||
|
|
||||||
data class ScheduleDto(
|
import com.squareup.moshi.Json
|
||||||
val lessons: List<LessonDto>
|
import com.squareup.moshi.JsonClass
|
||||||
)
|
|
||||||
|
|
||||||
|
typealias ScheduleDto = List<LessonDto>
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = false) // reflection-адаптер через moshi-kotlin
|
||||||
data class LessonDto(
|
data class LessonDto(
|
||||||
val dayOfWeek: String,
|
@Json(name = "DATE_Z")
|
||||||
val dayName: String,
|
val date: String, // "12.01.2026"
|
||||||
val time: String,
|
@Json(name = "TIME_Z")
|
||||||
val room: String,
|
val time: String, // "11:00 - 13:55"
|
||||||
val subjectName: String,
|
@Json(name = "DISCIP")
|
||||||
val teacherName: String,
|
val discipline: String, // предмет
|
||||||
val weekType: Int
|
@Json(name = "KOW")
|
||||||
)
|
val typeName: String, // "Лекции", "Лабораторные занятия", "Э", "зч", "КР", "ДЗ" и т.п.
|
||||||
|
@Json(name = "AUD")
|
||||||
|
val room: String, // можно назвать audience, но ключ в JSON "AUD"
|
||||||
|
@Json(name = "PREP")
|
||||||
|
val teacher: String,
|
||||||
|
@Json(name = "GROUPS")
|
||||||
|
val groups: List<GroupDto>,
|
||||||
|
@Json(name = "CLASS")
|
||||||
|
val type: String // "lecture" / "lab" / "practice" / "default"
|
||||||
|
) {
|
||||||
|
|
||||||
|
data class GroupDto(
|
||||||
|
@Json(name = "GROUP_P")
|
||||||
|
val groupCode: String
|
||||||
|
)
|
||||||
|
object ScheduleSearchField {
|
||||||
|
const val GROUP_P = "GROUP_P"
|
||||||
|
const val PREP = "PREP"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package ru.fincode.tsudesk.feature.schedule.data.remote
|
||||||
|
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import com.squareup.moshi.Types
|
||||||
|
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.data.remote.model.LessonDto
|
||||||
|
|
||||||
|
class ScheduleJsonParser {
|
||||||
|
|
||||||
|
private val moshi = Moshi.Builder()
|
||||||
|
.add(KotlinJsonAdapterFactory())
|
||||||
|
.build()
|
||||||
|
|
||||||
|
private val type = Types.newParameterizedType(
|
||||||
|
List::class.java,
|
||||||
|
LessonDto::class.java
|
||||||
|
)
|
||||||
|
|
||||||
|
private val adapter = moshi.adapter<List<LessonDto>>(type)
|
||||||
|
fun parse(json: String): List<LessonDto> =
|
||||||
|
adapter.fromJson(json)
|
||||||
|
?: throw IllegalStateException("Schedule JSON is null/invalid")
|
||||||
|
}
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.data.remote
|
|
||||||
|
|
||||||
import okhttp3.ResponseBody
|
|
||||||
|
|
||||||
interface ScheduleXmlParser {
|
|
||||||
fun parse(body: ResponseBody): ScheduleDto
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class ScheduleXmlParserImpl : ScheduleXmlParser {
|
|
||||||
override fun parse(body: ResponseBody): ScheduleDto {
|
|
||||||
val xml = body.string()
|
|
||||||
return ScheduleDto(lessons = emptyList())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.fincode.tsudesk.feature.schedule.di
|
||||||
|
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import retrofit2.Retrofit
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.data.remote.ScheduleApi
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object ScheduleNetworkModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideScheduleApi(retrofit: Retrofit): ScheduleApi =
|
||||||
|
retrofit.create(ScheduleApi::class.java)
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
package ru.fincode.tsudesk.feature.schedule.di
|
||||||
|
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.data.ScheduleRepositoryImpl
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
abstract class ScheduleRepositoryModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindScheduleRepository(
|
||||||
|
impl: ScheduleRepositoryImpl
|
||||||
|
): ScheduleRepository
|
||||||
|
}
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.domain.model
|
|
||||||
|
|
||||||
data class Schedule(
|
|
||||||
val lessons: List<Lesson>
|
|
||||||
)
|
|
||||||
|
|
||||||
data class Lesson(
|
|
||||||
val dayOfWeek: String,
|
|
||||||
val dayName: String,
|
|
||||||
val time: String,
|
|
||||||
val room: String,
|
|
||||||
val subjectName: String,
|
|
||||||
val teacherName: String,
|
|
||||||
val weekType: Int
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package ru.fincode.tsudesk.feature.schedule.domain.model
|
||||||
|
|
||||||
|
data class ScheduleEntity(
|
||||||
|
val lessons: List<Lesson>
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Lesson(
|
||||||
|
val date: String, // "12.01.2026"
|
||||||
|
val time: String, // "11:00 - 13:55"
|
||||||
|
val subject: String, // discipline
|
||||||
|
val typeName: String, // "Лекции", "Лабораторные занятия" и т.д.
|
||||||
|
val room: String,
|
||||||
|
val teacher: String,
|
||||||
|
val groupId: String,
|
||||||
|
val type: String // "lecture" / "lab" / "practice" / "default"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.domain.repository
|
package ru.fincode.tsudesk.feature.schedule.domain.repository
|
||||||
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.model.Schedule
|
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
|
||||||
|
|
||||||
interface ScheduleRepository {
|
interface ScheduleRepository {
|
||||||
suspend fun loadScheduleByGroup(groupNumber: String): Schedule
|
suspend fun loadScheduleByGroup(groupNumber: String): Result<ScheduleEntity>
|
||||||
suspend fun loadScheduleByTeacher(teacherName: String): Schedule
|
suspend fun loadScheduleByTeacher(name: String): Result<ScheduleEntity>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.domain.repository
|
|
||||||
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleLocalDataSource
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.datasource.ScheduleRemoteDataSource
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.data.mapper.ScheduleDtoToDomainMapper
|
|
||||||
import ru.fincode.tsudesk.feature.schedule.domain.model.Schedule
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
class ScheduleRepositoryImpl @Inject constructor(
|
|
||||||
private val remoteDataSource: ScheduleRemoteDataSource,
|
|
||||||
private val localDataSource: ScheduleLocalDataSource,
|
|
||||||
private val mapper: ScheduleDtoToDomainMapper
|
|
||||||
) : ScheduleRepository {
|
|
||||||
|
|
||||||
override suspend fun loadScheduleByGroup(groupNumber: String): Schedule {
|
|
||||||
val dto = remoteDataSource.loadScheduleByGroup(groupNumber)
|
|
||||||
return mapper.map(dto)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun loadScheduleByTeacher(teacherName: String): Schedule {
|
|
||||||
val dto = remoteDataSource.loadScheduleByTeacher(teacherName)
|
|
||||||
return mapper.map(dto)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,20 @@
|
|||||||
package ru.fincode.tsudesk.feature.schedule.domain.usecase
|
package ru.fincode.tsudesk.feature.schedule.domain.usecase
|
||||||
|
|
||||||
class GetScheduleUseCase {
|
import ru.fincode.tsudesk.feature.schedule.domain.model.ScheduleEntity
|
||||||
|
import ru.fincode.tsudesk.feature.schedule.domain.repository.ScheduleRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class GetScheduleUseCase @Inject constructor(
|
||||||
|
private val repository: ScheduleRepository
|
||||||
|
) {
|
||||||
|
sealed interface ScheduleType {
|
||||||
|
data class Group(val groupNumber: String) : ScheduleType
|
||||||
|
data class Teacher(val teacherName: String) : ScheduleType
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend operator fun invoke(type: ScheduleType): Result<ScheduleEntity> =
|
||||||
|
when (type) {
|
||||||
|
is ScheduleType.Group -> repository.loadScheduleByGroup(type.groupNumber)
|
||||||
|
is ScheduleType.Teacher -> repository.loadScheduleByTeacher(type.teacherName)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ versionName = "1.0"
|
|||||||
versionCode = "1"
|
versionCode = "1"
|
||||||
|
|
||||||
agp = "8.12.0"
|
agp = "8.12.0"
|
||||||
kotlin = "2.0.21"
|
kotlin = "1.9.24"
|
||||||
jvmTarget = "17"
|
jvmTarget = "17"
|
||||||
|
|
||||||
coreKtx = "1.10.1"
|
coreKtx = "1.10.1"
|
||||||
@@ -19,6 +19,10 @@ hilt = "2.50"
|
|||||||
retrofit = "2.11.0"
|
retrofit = "2.11.0"
|
||||||
okhttp = "4.12.0"
|
okhttp = "4.12.0"
|
||||||
|
|
||||||
|
moshi = "1.15.1"
|
||||||
|
lifecycle = "2.7.0"
|
||||||
|
coroutines = "1.8.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
|
||||||
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
|
||||||
@@ -34,6 +38,18 @@ hiltandroid = { module = "com.google.dagger:hilt-android", version.ref = "hilt"
|
|||||||
hiltcompiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
|
hiltcompiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hilt" }
|
||||||
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
|
||||||
|
|
||||||
|
androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycle" }
|
||||||
|
kotlinx-coroutines-android = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-android", version.ref = "coroutines" }
|
||||||
|
|
||||||
|
|
||||||
|
moshi = { group="com.squareup.moshi", name="moshi", version.ref="moshi" }
|
||||||
|
moshiKotlin = { group="com.squareup.moshi", name="moshi-kotlin", version.ref="moshi" }
|
||||||
|
retrofitMoshi = { group="com.squareup.retrofit2", name="converter-moshi", version.ref="retrofit" }
|
||||||
|
|
||||||
|
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
[plugins]
|
[plugins]
|
||||||
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
hilt = { id = "com.google.dagger.hilt.android", version.ref = "hilt" }
|
||||||
|
|||||||
@@ -13,12 +13,13 @@ pluginManagement {
|
|||||||
}
|
}
|
||||||
dependencyResolutionManagement {
|
dependencyResolutionManagement {
|
||||||
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
|
||||||
repositories(fun RepositoryHandler.() {
|
repositories {
|
||||||
google()
|
google()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
rootProject.name = "TSUDesk"
|
rootProject.name = "TSUDesk"
|
||||||
include(":app")
|
include(":app")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user