Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
d6752ab
Prepare client to work with back-end api
Jun 15, 2021
8525fa2
Fix broken CI
mik629 Aug 29, 2021
ad0aa61
Remove fragments
mik629 Oct 29, 2021
05548a3
Extracted dependencies declaration into buildSrc/Config
mik629 Oct 29, 2021
65f88b2
Added gradlew
mik629 Oct 29, 2021
e963a03
NewsScreen re-written in compose
mik629 Oct 29, 2021
3e7233a
[NewsScreen] Fixed tabs behaviour
mik629 Oct 31, 2021
78ff650
Compose Theming
mik629 Nov 11, 2021
e7073cc
Fixed up profile screen icons
mik629 Nov 11, 2021
a0fc7d7
Added Profile Screen
mik629 Nov 11, 2021
9eee289
Organise code by layers
mik629 Nov 15, 2021
6faa9fc
Fixed profile/discard changes
mik629 Nov 16, 2021
d1a904a
Fixed profile/edit
mik629 Nov 23, 2021
33da48f
Added draft implementation of VideoScreen
mik629 Dec 2, 2021
1dc3746
Added draft implementation of VideoScreen
mik629 Dec 3, 2021
64f4cf7
Added draft implementation of VideoScreen
mik629 Dec 3, 2021
84d73dd
Refactoring
mik629 Dec 3, 2021
54386b3
Clean up
mik629 Dec 14, 2021
e91c5cd
Refactoring
mik629 Dec 17, 2021
e60ac6c
LogIn screen
mik629 Dec 20, 2021
ec4cc7e
Changed statusBar, bottomBar and tabs background to comply with main …
mik629 Dec 21, 2021
02dcaec
Added splash screen and fixed up the navigation
mik629 Dec 24, 2021
4e249a4
Added Academy label
mik629 Dec 27, 2021
adcbdb7
[AuthOptionsScreen] Improve UX
mik629 Dec 29, 2021
9b95caa
[AuthOptionsScreen] Refactoring
mik629 Jan 11, 2022
d70c610
[AuthOptionsScreen] Auth via back-end
mik629 Jan 14, 2022
67ebef3
Update README.md
mik629 Jan 18, 2022
173bd93
[ProfileScreen] Clear focus on cancel/done
mik629 Jan 18, 2022
933884d
[CI] Fix CI
mik629 Jan 18, 2022
4b62655
Merge pull request #1 from mik629/compose
mik629 Jan 18, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
66 changes: 48 additions & 18 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -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"
Expand All @@ -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")

Expand All @@ -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"]}")
Expand All @@ -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"
Expand All @@ -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"
Expand All @@ -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")
Expand Down
8 changes: 5 additions & 3 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:name=".App"
Expand All @@ -13,10 +13,12 @@
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.AndroidAcademy">
android:theme="@style/Theme.AndroidAcademy"
android:usesCleartextTraffic="true"
android:windowSoftInputMode="adjustResize">
<activity
android:name=".MainActivity"
android:label="@string/app_name">
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

Expand Down
39 changes: 12 additions & 27 deletions app/src/main/java/com/academy/android/MainActivity.kt
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
}
59 changes: 59 additions & 0 deletions app/src/main/java/com/academy/android/data/PrefsStorage.kt
Original file line number Diff line number Diff line change
@@ -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<String>, value: String) {
appContext.dataStore.edit { prefs ->
prefs[key] = value
}
}

fun readString(key: Preferences.Key<String>, default: String): Flow<String> =
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<OperationResult<UserProfile, Throwable?>> =
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")
}
}
56 changes: 56 additions & 0 deletions app/src/main/java/com/academy/android/data/network/ServerApi.kt
Original file line number Diff line number Diff line change
@@ -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<CourseDTO>

@GET("courses/all")
suspend fun getAllCourses(): List<CourseDTO>

@POST("courses/update")
suspend fun updateCourse(
@Body updateCourseRequestDTO: UpdateCourseRequestDTO
)

@POST("lectures/update")
suspend fun updateLecture(
@Body updateLectureRequestDTO: UpdateLectureRequestDTO
)

@GET("lectures/all")
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use path variable, like:
{courseId}/lectures

suspend fun getAllLectures(
@Query(value = "courseId") courseId: Long
): List<LectureDTO>

@GET("lectures/by-id")
suspend fun getLectureById(
@Query(value = "lectureId") lectureId: Long
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to use a path variable, I think

): LectureDTO

@POST("user/update-fcm-token")
suspend fun updateFcmToken(
@Body fcmToken: String
)
}
Original file line number Diff line number Diff line change
@@ -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
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add a PrefsStorage having DataStore under the hood

) : 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"
}
}
Original file line number Diff line number Diff line change
@@ -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)
Loading