diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml index 6025979..dfebd19 100644 --- a/.github/workflows/android.yml +++ b/.github/workflows/android.yml @@ -13,10 +13,10 @@ jobs: steps: - uses: actions/checkout@v2 - - name: set up JDK 1.8 + - name: set up JDK 11 uses: actions/setup-java@v1 with: - java-version: 1.8 + java-version: 11 - name: Grant execute permission for gradlew run: chmod +x gradlew - name: Build with Gradle diff --git a/README.md b/README.md index fd309d7..43daf5a 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,8 @@ The application for Android Academy made by Students Design -- [Link](https://www.figma.com/file/qoC9xPS1X7GU9VcGK8SSpA/Main?node-id=0%3A1) -Android Architecture Diagram -- [Link](https://drive.google.com/file/d/1svw28LBVJ2k2EIOyJmdzE41NW1Q-UQ3o/view?usp=sharing) +Architecture diagram can be found [here](https://drive.google.com/file/d/1EgUr_bFkBrK0Cazm2GhbnaT5Y7i9nBkp). -DataBase Entity Relation Diagram -- [Link](https://drive.google.com/file/d/1rtEUZz_3XEBnn2Ck9IwUpzrevrE9lzRU/view?usp=sharing) +DataBase Entity Relation Diagram -- TBD. + +[Here](https://github.com/mik629/AndroidAcademyClient/wiki/Tech-stack) is the tech stack we agreed to use. diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 2d8ca71..fc73162 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -1,20 +1,25 @@ +import Config.androidBuildTools +import Config.androidCompileSdk +import Config.androidMinSdk +import Config.androidTargetSdk + plugins { - id("com.android.application") - kotlin("android") - kotlin("plugin.serialization") - kotlin("kapt") - id("dagger.hilt.android.plugin") - id("androidx.navigation.safeargs.kotlin") + id(Plugins.appPlugin) + kotlin(Plugins.androidPlugin) + kotlin(Plugins.serializationPlugin) + kotlin(Plugins.kapt) + id(Plugins.hiltPlugin) + id(Plugins.navigationSafeArgsPlugin) } android { - compileSdkVersion(30) - buildToolsVersion = "30.0.3" + compileSdk = androidCompileSdk + buildToolsVersion = androidBuildTools defaultConfig { applicationId = "com.academy.android" - minSdkVersion(21) - targetSdkVersion(30) + minSdk = androidMinSdk + targetSdk = androidTargetSdk versionCode = 1 versionName = "1.0" testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner" @@ -35,16 +40,24 @@ android { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } + composeOptions { + kotlinCompilerVersion = Versions.kotlin + kotlinCompilerExtensionVersion = Versions.compose + } kotlinOptions { jvmTarget = "1.8" } buildFeatures { viewBinding = true + compose = true } } dependencies { + // auth + implementation(Libs.playServicesAuth) + // Core implementation("androidx.core:core-ktx:1.3.2") @@ -62,6 +75,7 @@ dependencies { // Serialization implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1") + implementation("com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:0.8.0") // Navigation implementation("androidx.navigation:navigation-fragment-ktx:${rootProject.extra["navigationVersion"]}") @@ -73,16 +87,21 @@ dependencies { implementation("androidx.lifecycle:lifecycle-livedata-ktx:$lifecycleVersion") implementation("androidx.lifecycle:lifecycle-common-java8:$lifecycleVersion") + // Network + implementation("com.squareup.retrofit2:retrofit:2.9.0") + implementation("com.squareup.okhttp3:logging-interceptor:4.9.1") + // DI - val daggerVersion = "2.33" - implementation("com.google.dagger:dagger:$daggerVersion") - kapt("com.google.dagger:dagger-compiler:$daggerVersion") - implementation("com.google.dagger:hilt-android:${rootProject.extra["hiltVersion"]}") - kapt("com.google.dagger:hilt-compiler:${rootProject.extra["hiltVersion"]}") + implementation(Libs.dagger) + kapt(Libs.daggerCompiler) + + implementation(Libs.hilt) + kapt(Libs.hiltCompiler) + implementation(Libs.hiltNavigationCompose) val androidxHilt = "1.0.0-alpha03" implementation("androidx.hilt:hilt-lifecycle-viewmodel:$androidxHilt") - kapt("androidx.hilt:hilt-compiler:$androidxHilt") - implementation("androidx.hilt:hilt-work:$androidxHilt") + kapt("androidx.hilt:hilt-compiler:1.0.0") + implementation("androidx.hilt:hilt-work:1.0.0") // Concurrency val coroutinesVersion = "1.4.3" @@ -96,7 +115,7 @@ dependencies { implementation("com.jakewharton.timber:timber:4.7.1") //SharedPreference - implementation ("androidx.preference:preference-ktx:1.1.1") + implementation (Libs.dataStore) // DB val roomVersion = "2.2.6" @@ -113,6 +132,17 @@ dependencies { // ViewBindingPropertyDelegate implementation("com.github.kirich1409:viewbindingpropertydelegate-noreflection:1.4.4") + // compose + implementation(Libs.activityCompose) + implementation(Libs.vmCompose) + implementation(Libs.composeCompiler) + implementation(Libs.composeFoundation) + implementation(Libs.composeMaterial) + implementation(Libs.composeUI) + implementation(Libs.composeTooling) + implementation(Libs.coil) + implementation(Libs.glideComposeVersion) + // Testing testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.2") diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d276c77..b419955 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -4,7 +4,7 @@ - + + android:theme="@style/Theme.AndroidAcademy" + android:usesCleartextTraffic="true" + android:windowSoftInputMode="adjustResize"> + android:exported="true"> diff --git a/app/src/main/java/com/academy/android/MainActivity.kt b/app/src/main/java/com/academy/android/MainActivity.kt index 62692e6..c17119b 100644 --- a/app/src/main/java/com/academy/android/MainActivity.kt +++ b/app/src/main/java/com/academy/android/MainActivity.kt @@ -1,39 +1,24 @@ package com.academy.android import android.os.Bundle -import androidx.appcompat.app.AppCompatActivity -import androidx.navigation.NavController -import androidx.navigation.findNavController -import androidx.navigation.ui.AppBarConfiguration -import androidx.navigation.ui.NavigationUI -import androidx.navigation.ui.setupActionBarWithNavController -import androidx.navigation.ui.setupWithNavController -import com.google.android.material.bottomnavigation.BottomNavigationView +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.navigation.compose.rememberNavController +import com.academy.android.ui.AcademyTheme +import com.academy.android.ui.StartNavigation import dagger.hilt.android.AndroidEntryPoint @AndroidEntryPoint -class MainActivity : AppCompatActivity() { - - private val navController: NavController by lazy { - findNavController(R.id.nav_host_fragment) - } +class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) - val navView: BottomNavigationView = findViewById(R.id.nav_view) - // Passing each menu ID as a set of Ids because each - // menu should be considered as top level destinations. - val appBarConfiguration = AppBarConfiguration( - setOf( - R.id.navigation_videos, R.id.navigation_news, R.id.navigation_profile - ) - ) - setupActionBarWithNavController(navController, appBarConfiguration) - navView.setupWithNavController(navController) - } + setContent { + val navController = rememberNavController() - override fun onSupportNavigateUp(): Boolean { - return NavigationUI.navigateUp(navController, null) + AcademyTheme { + StartNavigation(navController = navController) + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/PrefsStorage.kt b/app/src/main/java/com/academy/android/data/PrefsStorage.kt new file mode 100644 index 0000000..6d591a8 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/PrefsStorage.kt @@ -0,0 +1,59 @@ +package com.academy.android.data + +import android.content.Context +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import androidx.datastore.preferences.preferencesDataStore +import com.academy.android.domain.OperationResult +import com.academy.android.domain.models.UserProfile +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map +import kotlinx.serialization.json.Json +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class PrefsStorage @Inject constructor( + @ApplicationContext private val appContext: Context +) { + private val Context.dataStore by preferencesDataStore("settings") + + suspend fun writeString(key: Preferences.Key, value: String) { + appContext.dataStore.edit { prefs -> + prefs[key] = value + } + } + + fun readString(key: Preferences.Key, default: String): Flow = + appContext.dataStore.data + .map { prefs -> + prefs[key] ?: default + } + + suspend fun saveProfile(profile: UserProfile) { + val serializedProfile = Json.encodeToString(UserProfile.serializer(), profile) + appContext.dataStore.edit { prefs -> + prefs[PROFILE_KEY] = serializedProfile + } + } + + fun loadProfile(): Flow> = + appContext.dataStore + .data + .map { prefs -> + val profile = prefs[PROFILE_KEY] + OperationResult.Success( + data = if (profile != null) { + Json.decodeFromString(UserProfile.serializer(), profile) + } else { + UserProfile.UNKNOWN + } + ) + } + + companion object { + private val PROFILE_KEY = stringPreferencesKey("user_profile") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/ServerApi.kt b/app/src/main/java/com/academy/android/data/network/ServerApi.kt new file mode 100644 index 0000000..d8862f3 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/ServerApi.kt @@ -0,0 +1,56 @@ +package com.academy.android.data.network + +import com.academy.android.data.network.models.CourseDTO +import com.academy.android.data.network.models.LectureDTO +import com.academy.android.data.network.models.LoginRequestDTO +import com.academy.android.data.network.models.LoginResponseDTO +import com.academy.android.data.network.models.RegisterRequestDTO +import com.academy.android.data.network.models.UpdateCourseRequestDTO +import com.academy.android.data.network.models.UpdateLectureRequestDTO +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.POST +import retrofit2.http.Query + +interface ServerApi { + @POST("login") + suspend fun login( + @Body loginRequest: LoginRequestDTO + ): LoginResponseDTO + + @POST("register") + suspend fun register( + @Body registerRequestDTO: RegisterRequestDTO + ): LoginResponseDTO + + @GET("courses/favorite") + suspend fun getFavouriteCourses(): List + + @GET("courses/all") + suspend fun getAllCourses(): List + + @POST("courses/update") + suspend fun updateCourse( + @Body updateCourseRequestDTO: UpdateCourseRequestDTO + ) + + @POST("lectures/update") + suspend fun updateLecture( + @Body updateLectureRequestDTO: UpdateLectureRequestDTO + ) + + @GET("lectures/all") + suspend fun getAllLectures( + @Query(value = "courseId") courseId: Long + ): List + + @GET("lectures/by-id") + suspend fun getLectureById( + @Query(value = "lectureId") lectureId: Long + ): LectureDTO + + @POST("user/update-fcm-token") + suspend fun updateFcmToken( + @Body fcmToken: String + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/TokenAuthenticator.kt b/app/src/main/java/com/academy/android/data/network/TokenAuthenticator.kt new file mode 100644 index 0000000..4592308 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/TokenAuthenticator.kt @@ -0,0 +1,36 @@ +package com.academy.android.data.network + +import okhttp3.Authenticator +import okhttp3.Request +import okhttp3.Response +import okhttp3.Route +import javax.inject.Inject + +// fixme: fix user auth +class TokenAuthenticator @Inject constructor( +// private val prefsStorage: PrefsStorage +) : Authenticator { + override fun authenticate(route: Route?, response: Response): Request? = + if (responseCount(response) < MAX_ATTEMPTS) { + response.request + .newBuilder() +// .header(AUTH_HEADER, prefsStorage.loadUser()?.token.orEmpty()) + .build() + } else { + null + } + + private fun responseCount(response: Response): Int { + var result = 1 + var r: Response? = response + while (true) { + r = r?.priorResponse ?: return result + result++ + } + } + + companion object { + const val MAX_ATTEMPTS = 3 + const val AUTH_HEADER = "Token" + } +} diff --git a/app/src/main/java/com/academy/android/data/network/models/AdditionalMaterialDTO.kt b/app/src/main/java/com/academy/android/data/network/models/AdditionalMaterialDTO.kt new file mode 100644 index 0000000..72f5a51 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/AdditionalMaterialDTO.kt @@ -0,0 +1,13 @@ +package com.academy.android.data.network.models + +import com.academy.android.domain.models.AdditionalMaterial +import kotlinx.serialization.Serializable + +@Serializable +data class AdditionalMaterialDTO( + val topicName: String, + val url: String +) + +fun AdditionalMaterialDTO.toAdditionalMaterial(): AdditionalMaterial = + AdditionalMaterial(topicName = topicName, url = url) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/models/CourseDTO.kt b/app/src/main/java/com/academy/android/data/network/models/CourseDTO.kt new file mode 100644 index 0000000..ee82200 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/CourseDTO.kt @@ -0,0 +1,27 @@ +package com.academy.android.data.network.models + +import com.academy.android.domain.models.Course +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class CourseDTO( + val id: Long, + val title: String, + val shortDescription: String? = null, + val fullDescription: String? = null, + val imgUrl: String? = null, + val tags: List, + @SerialName("subscribed") val isSubscribed: Boolean = false +) + +fun CourseDTO.toCourse(): Course = + Course( + id = id, + title = title, + shortDescription = shortDescription, + fullDescription = fullDescription, + imgUrl = imgUrl, + tags = tags, + isSubscribed = isSubscribed + ) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/models/LectureDTO.kt b/app/src/main/java/com/academy/android/data/network/models/LectureDTO.kt new file mode 100644 index 0000000..dd71af9 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/LectureDTO.kt @@ -0,0 +1,32 @@ +package com.academy.android.data.network.models + +import com.academy.android.domain.models.Lecture +import kotlinx.serialization.Serializable + +@Serializable +data class LectureDTO( + val id: Long, + val title: String, + val youtubeUrl: String = "", + val githubRepoUrl: String = "", + val telegramChannel: String = "", + val additionalMaterials: List, + val imgUrl: String? = null, + val tags: List, + val courseId: Long, + val startTimestamp : Long +) + +fun LectureDTO.toLecture(): Lecture = + Lecture( + id = id, + title = title, + youtubeUrl = youtubeUrl, + githubRepoUrl = githubRepoUrl, + telegramChannel = telegramChannel, + additionalMaterials = additionalMaterials.map(AdditionalMaterialDTO::toAdditionalMaterial), + imgUrl = imgUrl, + tags = tags, + courseId = courseId, + startTimestamp = startTimestamp + ) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/models/LoginRequestDTO.kt b/app/src/main/java/com/academy/android/data/network/models/LoginRequestDTO.kt new file mode 100644 index 0000000..05ab248 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/LoginRequestDTO.kt @@ -0,0 +1,9 @@ +package com.academy.android.data.network.models + +import kotlinx.serialization.Serializable + +@Serializable +data class LoginRequestDTO( + val username: String, + val pwd: String +) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/models/LoginResponseDTO.kt b/app/src/main/java/com/academy/android/data/network/models/LoginResponseDTO.kt new file mode 100644 index 0000000..8a2130e --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/LoginResponseDTO.kt @@ -0,0 +1,12 @@ +package com.academy.android.data.network.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class LoginResponseDTO( + @SerialName("user_profile") + val userProfile: UserProfileDTO, + @SerialName("token") + val token: String +) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/models/RegisterRequestDTO.kt b/app/src/main/java/com/academy/android/data/network/models/RegisterRequestDTO.kt new file mode 100644 index 0000000..34a95d2 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/RegisterRequestDTO.kt @@ -0,0 +1,12 @@ +package com.academy.android.data.network.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class RegisterRequestDTO( + val username: String, + val pwd: String, + val name: String, + @SerialName("mentor") val isMentor: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/models/UpdateCourseRequestDTO.kt b/app/src/main/java/com/academy/android/data/network/models/UpdateCourseRequestDTO.kt new file mode 100644 index 0000000..406fa25 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/UpdateCourseRequestDTO.kt @@ -0,0 +1,14 @@ +package com.academy.android.data.network.models + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateCourseRequestDTO( + val id: Long? = null, + val title: String, + val shortDescription: String? = null, + val fullDescription: String? = null, + val imgUrl: String? = null, + val tags: List, + val subscribed: Boolean +) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/network/models/UpdateLectureRequestDTO.kt b/app/src/main/java/com/academy/android/data/network/models/UpdateLectureRequestDTO.kt new file mode 100644 index 0000000..2f82254 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/UpdateLectureRequestDTO.kt @@ -0,0 +1,17 @@ +package com.academy.android.data.network.models + +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateLectureRequestDTO( + val id: Long? = null, + val title: String, + val youtubeUrl: String = "", + val githubRepoUrl: String = "", + val telegramChannel: String = "", + val additionalMaterials: List, + val imgUrl: String? = null, + val tags: List, + val courseId: Long, + val startTimestamp : Long +) diff --git a/app/src/main/java/com/academy/android/data/network/models/UserProfileDTO.kt b/app/src/main/java/com/academy/android/data/network/models/UserProfileDTO.kt new file mode 100644 index 0000000..3dd35f9 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/network/models/UserProfileDTO.kt @@ -0,0 +1,36 @@ +package com.academy.android.data.network.models + +import com.academy.android.domain.models.UserProfile +import com.academy.android.domain.models.UserTitle +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UserProfileDTO( + @SerialName("username") + val username: String, + @SerialName("mentor") + val mentor: Boolean, + @SerialName("prof_pic") + var profPic: String? = null, + @SerialName("first_name") + var firstName: String? = null, + @SerialName("last_name") + var lastName: String? = null, + @SerialName("phone_number") + var phoneNumber: String? = null, + @SerialName("email") + var email: String? = null, + @SerialName("title") + var title: String? = null +) + +fun UserProfileDTO.toUserProfile(): UserProfile = + UserProfile( + profPic = profPic, + firstName = firstName, + lastName = lastName, + phoneNumber = phoneNumber, + email = email, + title = title?.let { userTitle -> UserTitle.valueOf(userTitle) } ?: UserTitle.UNKNOWN + ) \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/repositories/AuthRepositoryImpl.kt b/app/src/main/java/com/academy/android/data/repositories/AuthRepositoryImpl.kt new file mode 100644 index 0000000..218ae40 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/repositories/AuthRepositoryImpl.kt @@ -0,0 +1,96 @@ +package com.academy.android.data.repositories + +import com.academy.android.data.PrefsStorage +import com.academy.android.data.network.ServerApi +import com.academy.android.data.network.models.LoginRequestDTO +import com.academy.android.data.network.models.RegisterRequestDTO +import com.academy.android.data.network.models.UserProfileDTO +import com.academy.android.data.network.models.toUserProfile +import com.academy.android.domain.OperationResult +import com.academy.android.domain.models.UserProfile +import com.academy.android.domain.repositories.AuthRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class AuthRepositoryImpl @Inject constructor( + private val prefsStorage: PrefsStorage, + private val serverApi: ServerApi +) : AuthRepository { + + override val userProfile: Flow> = + prefsStorage.loadProfile() + + override suspend fun login( + username: String, + password: String + ): OperationResult = + try { + serverApi.login( + LoginRequestDTO( + username = username, + pwd = password + ) + ).userProfile + .also { profile -> + saveProfile(profile) + } + + sendFcmTokenToBackend() + + OperationResult.Success(Unit) + + } catch (e: Throwable) { + OperationResult.Error(e) + } + + override suspend fun register( + username: String, + password: String, + name: String, + isMentor: Boolean + ): OperationResult = + try { + serverApi.register( + RegisterRequestDTO( + username = username, + pwd = password, + name = name, + isMentor = isMentor + ) + ).userProfile + .also { profile -> + saveProfile(profile) + } + + sendFcmTokenToBackend() + + OperationResult.Success(Unit) + + } catch (e: Throwable) { + OperationResult.Error(e) + } + + private suspend fun saveProfile(profileDto: UserProfileDTO) { + profileDto.toUserProfile() + .also { profile -> + prefsStorage.saveProfile(profile = profile) + } + } + + private fun sendFcmTokenToBackend() { +// FirebaseMessaging.getInstance().token +// .addOnSuccessListener { fcmToken -> +// GlobalScope.launch { +// println("fcm_token:$fcmToken") +// serverApi.updateFcmToken(fcmToken) +// } +// } +// .addOnFailureListener { +// Timber.e(it, "Cannot get FCM token") +// } + } + + override suspend fun enterGuestMode() { + prefsStorage.saveProfile(UserProfile.GUEST) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/repositories/CourseRepositoryImpl.kt b/app/src/main/java/com/academy/android/data/repositories/CourseRepositoryImpl.kt new file mode 100644 index 0000000..766616d --- /dev/null +++ b/app/src/main/java/com/academy/android/data/repositories/CourseRepositoryImpl.kt @@ -0,0 +1,108 @@ +package com.academy.android.data.repositories + +import com.academy.android.data.network.ServerApi +import com.academy.android.data.network.models.AdditionalMaterialDTO +import com.academy.android.data.network.models.UpdateCourseRequestDTO +import com.academy.android.data.network.models.UpdateLectureRequestDTO +import com.academy.android.data.network.models.toLecture +import com.academy.android.domain.OperationResult +import com.academy.android.domain.models.Course +import com.academy.android.domain.models.Lecture +import com.academy.android.domain.repositories.CourseRepository +import javax.inject.Inject + +class CourseRepositoryImpl @Inject constructor( + private val serverApi: ServerApi +) : CourseRepository { + + override suspend fun getAllCourses(): OperationResult, String?> = + try { + val courses = serverApi.getAllCourses().map { + Course( + id = it.id, + title = it.title, + shortDescription = it.shortDescription, + fullDescription = it.fullDescription, + imgUrl = it.imgUrl, + isSubscribed = it.isSubscribed, + tags = it.tags + ) + } + OperationResult.Success(courses) + } catch (e: Throwable) { + OperationResult.Error(e.message) + } + + override suspend fun getFavouriteCourses(username: String): OperationResult, String?> = + try { + val courses = serverApi.getFavouriteCourses().map { + Course( + id = it.id, + title = it.title, + shortDescription = it.shortDescription, + fullDescription = it.fullDescription, + imgUrl = it.imgUrl, + isSubscribed = it.isSubscribed, + tags = it.tags + ) + }.filter { it.isSubscribed } // TODO: remove filter + OperationResult.Success(courses) + } catch (e: Throwable) { + OperationResult.Error(e.message) + } + + override suspend fun updateCourse(course: Course): OperationResult = + try { + serverApi.updateCourse( + UpdateCourseRequestDTO( + id = course.id, + title = course.title, + shortDescription = course.shortDescription, + fullDescription = course.fullDescription, + imgUrl = course.imgUrl, + subscribed = course.isSubscribed, + tags = course.tags + ) + ) + OperationResult.Success(Unit) + } catch (e: Throwable) { + OperationResult.Error(e.message) + } + + + override suspend fun getAllLectures(courseId: Long): OperationResult, String?> = + try { + val lectures = serverApi.getAllLectures(courseId).map { + it.toLecture() + } + OperationResult.Success(lectures) + } catch (e: Throwable) { + OperationResult.Error(e.message) + } + + override suspend fun updateLecture(lecture: Lecture): OperationResult = + try { + serverApi.updateLecture( + UpdateLectureRequestDTO( + id = lecture.id, + title = lecture.title, + youtubeUrl = lecture.youtubeUrl, + githubRepoUrl = lecture.githubRepoUrl, + telegramChannel = lecture.telegramChannel, + additionalMaterials = lecture.additionalMaterials.map { + AdditionalMaterialDTO( + topicName = it.topicName, + url = it.url + ) + }, + imgUrl = lecture.imgUrl, + tags = lecture.tags, + courseId = lecture.courseId, + startTimestamp = lecture.startTimestamp + ) + ) + OperationResult.Success(Unit) + } catch (e: Throwable) { + OperationResult.Error(e.message) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/repositories/LecturesRepositoryImpl.kt b/app/src/main/java/com/academy/android/data/repositories/LecturesRepositoryImpl.kt new file mode 100644 index 0000000..ff290e9 --- /dev/null +++ b/app/src/main/java/com/academy/android/data/repositories/LecturesRepositoryImpl.kt @@ -0,0 +1,16 @@ +package com.academy.android.data.repositories + +import com.academy.android.data.network.ServerApi +import com.academy.android.data.network.models.toLecture +import com.academy.android.domain.models.Lecture +import com.academy.android.domain.repositories.LecturesRepository +import javax.inject.Inject + +class LecturesRepositoryImpl @Inject constructor( + private val serverApi: ServerApi +) : LecturesRepository { + + override suspend fun getLecture(id: Long): Lecture = + serverApi.getLectureById(lectureId = id) + .toLecture() +} \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/repositories/ProfileRepository.kt b/app/src/main/java/com/academy/android/data/repositories/ProfileRepository.kt deleted file mode 100644 index 26ad1fe..0000000 --- a/app/src/main/java/com/academy/android/data/repositories/ProfileRepository.kt +++ /dev/null @@ -1,37 +0,0 @@ -package com.academy.android.data.repositories - -import com.academy.android.model.Profile -import com.academy.android.util.SharedPreferenceHelper -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import javax.inject.Inject - - -interface ProfileRepositorySource{ - - fun saveProfileData( - profile: Profile - ) - - fun getProfileData(): Profile -} - -class ProfileRepository @Inject constructor( - private val prefsHelper: SharedPreferenceHelper -): ProfileRepositorySource { - - val profileData: Flow = flow { - val profile = getProfileData() - emit(profile) - } - - override fun saveProfileData(profile: Profile) { - prefsHelper.saveProfData(profile) - } - - override fun getProfileData(): Profile { - return prefsHelper.getProfData() - } - - -} \ No newline at end of file diff --git a/app/src/main/java/com/academy/android/data/repositories/VideosRepository.kt b/app/src/main/java/com/academy/android/data/repositories/VideosRepository.kt deleted file mode 100644 index 27c6f48..0000000 --- a/app/src/main/java/com/academy/android/data/repositories/VideosRepository.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.academy.android.data.repositories - -import com.academy.android.model.FilterParameters -import com.academy.android.model.Video -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import javax.inject.Inject - -interface VideosRepositorySource { - val videosList: StateFlow> - fun getFilterParameters() : FilterParameters -} - -class VideosRepository @Inject constructor() : VideosRepositorySource { - private val _videosList = MutableStateFlow(provideMokkVideos()) - override val videosList: StateFlow> = _videosList - - private val filterParameters = FilterParameters(listOf("Moscow", "Minsk", "Tel-Aviv"), - listOf("Fundamentals", "Advanced"), - listOf("2019-2020", "2020-2021")) - - override fun getFilterParameters() : FilterParameters = filterParameters - - private fun provideMokkVideos(): List