-
Notifications
You must be signed in to change notification settings - Fork 0
Draft: implement home screen #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f41b600
e9661f2
831aa8d
0572309
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,6 +1,7 @@ | ||||||||||||||
| plugins { | ||||||||||||||
| alias(libs.plugins.android.application) | ||||||||||||||
| alias(libs.plugins.kotlin.compose) | ||||||||||||||
| id("com.google.devtools.ksp") | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| android { | ||||||||||||||
|
|
@@ -28,6 +29,11 @@ android { | |||||||||||||
| getDefaultProguardFile("proguard-android-optimize.txt"), | ||||||||||||||
| "proguard-rules.pro" | ||||||||||||||
| ) | ||||||||||||||
| buildConfigField("String", "BASE_URL", "\"https://api.smartjam.com/\"") | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| getByName("debug") { | ||||||||||||||
| buildConfigField("String", "BASE_URL", "\"http://10.0.2.2:8000/\"") | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
| compileOptions { | ||||||||||||||
|
|
@@ -36,10 +42,39 @@ android { | |||||||||||||
| } | ||||||||||||||
| buildFeatures { | ||||||||||||||
| compose = true | ||||||||||||||
| buildConfig = true | ||||||||||||||
| } | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| dependencies { | ||||||||||||||
|
|
||||||||||||||
| val nav_version = "2.9.7" | ||||||||||||||
| // Jetpack Compose integration | ||||||||||||||
| implementation("androidx.navigation:navigation-compose:$nav_version+") | ||||||||||||||
|
|
||||||||||||||
| //network | ||||||||||||||
| implementation("com.squareup.retrofit2:retrofit:2.11.+") | ||||||||||||||
| implementation("com.squareup.okhttp3:okhttp:4.12.+") | ||||||||||||||
|
|
||||||||||||||
| //serialization | ||||||||||||||
| implementation("com.squareup.retrofit2:converter-gson:2.11.+") | ||||||||||||||
|
|
||||||||||||||
| //logging | ||||||||||||||
| implementation("com.squareup.okhttp3:logging-interceptor:4.12.+") | ||||||||||||||
|
|
||||||||||||||
| //database | ||||||||||||||
| implementation("androidx.datastore:datastore-preferences:1.1.+") | ||||||||||||||
|
|
||||||||||||||
| //coroutines | ||||||||||||||
| implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.9.+") | ||||||||||||||
|
|
||||||||||||||
| //ne pon | ||||||||||||||
| implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.+") | ||||||||||||||
|
Comment on lines
+56
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🌐 Web query:
💡 Result: As of March 10, 2026, the latest stable versions are:
Sources Citations:
Avoid dynamic/floating versions ( Using 🔒 Proposed fix: pin versions to exact releasesReplace each dynamic version with an exact version number. For example: - implementation("com.squareup.retrofit2:retrofit:2.11.+")
+ implementation("com.squareup.retrofit2:retrofit:2.11.0")Consult the latest stable releases on mvnrepository.com to ensure you're not pinning to outdated versions, especially for major version upgrades (Retrofit 3.x, OkHttp 5.x) that may offer significant improvements. 🤖 Prompt for AI Agents
Comment on lines
+71
to
+72
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Clarify or remove unclear comment. The comment ✏️ Suggested clarification- //ne pon
- implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.+")
+ // ViewModel integration for Jetpack Compose
+ implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| implementation("androidx.room:room-runtime:2.6.1") | ||||||||||||||
| implementation("androidx.room:room-ktx:2.6.1") | ||||||||||||||
| ksp("androidx.room:room-compiler:2.5.0") | ||||||||||||||
|
Comment on lines
+74
to
+76
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n mobile/app/build.gradle.kts | sed -n '70,80p'Repository: Satlykovs/SmartJam Length of output: 530 🌐 Web query:
💡 Result: Android Room versions 2.6.1 and 2.5.0 are both compatible with standard Android compiler (KAPT/annotationProcessor) and runtime setups. Room 2.6.1 (released Nov 29, 2023) introduces Kotlin code generation option via KSP (room.generateKotlin, available from 2.6.0), but supports both KAPT and KSP processors. No explicit incompatibilities between 2.6.1 and 2.5.0 for compiler/runtime. Key compatibility notes: - Both versions work with kapt("androidx.room:room-compiler:VERSION") or ksp("androidx.room:room-compiler:VERSION") for Kotlin projects. - For Room 2.6.1 with KSP, requires compatible KSP version matching Kotlin (e.g., Kotlin 1.9.21 with KSP 1.9.21-1.0.15; mismatches cause NoSuchMethodError as in KSP issue Citations:
Align Room compiler version with runtime. Line 76 uses Suggested fix- ksp("androidx.room:room-compiler:2.5.0")
+ ksp("androidx.room:room-compiler:2.6.1")📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||
|
|
||||||||||||||
| implementation(libs.androidx.core.ktx) | ||||||||||||||
| implementation(libs.androidx.lifecycle.runtime.ktx) | ||||||||||||||
| implementation(libs.androidx.activity.compose) | ||||||||||||||
|
|
@@ -55,4 +90,5 @@ dependencies { | |||||||||||||
| androidTestImplementation(libs.androidx.compose.ui.test.junit4) | ||||||||||||||
| debugImplementation(libs.androidx.compose.ui.tooling) | ||||||||||||||
| debugImplementation(libs.androidx.compose.ui.test.manifest) | ||||||||||||||
|
|
||||||||||||||
| } | ||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,43 +5,57 @@ import androidx.activity.ComponentActivity | |
| import androidx.activity.compose.setContent | ||
| import androidx.activity.enableEdgeToEdge | ||
| import androidx.compose.foundation.layout.fillMaxSize | ||
| import androidx.compose.foundation.layout.padding | ||
| import androidx.compose.material3.Scaffold | ||
| import androidx.compose.material3.Text | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.material3.Surface | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.tooling.preview.Preview | ||
| import com.smartjam.app.ui.theme.SmartJamTheme | ||
| import androidx.compose.ui.graphics.Color | ||
| import androidx.navigation.compose.rememberNavController | ||
| import androidx.room.Room | ||
| import com.smartjam.app.data.api.AuthApi | ||
| import com.smartjam.app.data.api.NetworkModule | ||
| import com.smartjam.app.data.api.SmartJamApi | ||
| import com.smartjam.app.data.local.SmartJamDatabase | ||
| import com.smartjam.app.data.local.TokenStorage | ||
| import com.smartjam.app.domain.repository.AuthRepository | ||
| import com.smartjam.app.domain.repository.ConnectionRepository | ||
| import com.smartjam.app.ui.navigation.SmartJamNavGraph | ||
| import kotlin.jvm.java | ||
|
|
||
| class MainActivity : ComponentActivity() { | ||
| override fun onCreate(savedInstanceState: Bundle?) { | ||
| super.onCreate(savedInstanceState) | ||
| enableEdgeToEdge() | ||
|
|
||
| val tokenStorage = TokenStorage(context = this) | ||
|
|
||
| val appDatabase = Room.databaseBuilder( | ||
| applicationContext, | ||
| SmartJamDatabase::class.java, | ||
| "smartjam_database" | ||
| ).build() | ||
|
|
||
|
|
||
| val retrofit = NetworkModule.createRetrofit(tokenStorage) | ||
| val smartJamApi = retrofit.create(SmartJamApi::class.java) | ||
| val authApi = retrofit.create(AuthApi::class.java) | ||
|
|
||
| val authRepository = AuthRepository(tokenStorage, authApi) | ||
| val connectionRepository = ConnectionRepository(smartJamApi, appDatabase.connectionDao()) | ||
|
|
||
| setContent { | ||
| SmartJamTheme { | ||
| Scaffold(modifier = Modifier.fillMaxSize()) { innerPadding -> | ||
| Greeting( | ||
| name = "Android", | ||
| modifier = Modifier.padding(innerPadding) | ||
| ) | ||
| } | ||
| val navController = rememberNavController() | ||
|
|
||
| Surface( | ||
| modifier = Modifier.fillMaxSize(), | ||
| color = Color(0xFF05050A) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Extract hardcoded color to theme. The background color 🤖 Prompt for AI Agents |
||
| ) { | ||
|
|
||
| SmartJamNavGraph( | ||
| navController = navController, | ||
| authRepository = authRepository, | ||
| connectionRepository = connectionRepository, | ||
| tokenStorage = tokenStorage | ||
| ) | ||
|
Comment on lines
+28
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. App starts at Home screen without checking authentication state. The navigation graph has Consider checking token presence at startup and navigating to Login if no valid session exists: val startDestination = if (tokenStorage.getAccessToken() != null) {
Screen.Home.route
} else {
Screen.Login.route
}Then pass 🤖 Prompt for AI Agents |
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| @Composable | ||
| fun Greeting(name: String, modifier: Modifier = Modifier) { | ||
| Text( | ||
| text = "Hello $name!", | ||
| modifier = modifier | ||
| ) | ||
| } | ||
|
|
||
| @Preview(showBackground = true) | ||
| @Composable | ||
| fun GreetingPreview() { | ||
| SmartJamTheme { | ||
| Greeting("Android") | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| package com.smartjam.app.data.api | ||
|
|
||
| import com.smartjam.app.data.model.LoginRequest | ||
| import com.smartjam.app.data.model.LoginResponse | ||
| import com.smartjam.app.data.model.RefreshRequest | ||
| import com.smartjam.app.data.model.RegisterRequest | ||
| import retrofit2.http.Body | ||
| import retrofit2.http.POST | ||
|
|
||
| interface AuthApi { | ||
|
|
||
| @POST("/api/auth/register") | ||
| suspend fun register(@Body request: RegisterRequest): LoginResponse | ||
|
|
||
| @POST("/api/auth/login") | ||
| suspend fun login(@Body request: LoginRequest): LoginResponse | ||
|
|
||
| @POST("/api/auth/refresh") | ||
| suspend fun refresh(@Body request: RefreshRequest): LoginResponse | ||
ParovozikThomas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,26 @@ | ||
| package com.smartjam.app.data.api | ||
|
|
||
| import com.smartjam.app.data.local.TokenStorage | ||
| import kotlinx.coroutines.flow.first | ||
| import kotlinx.coroutines.runBlocking | ||
| import okhttp3.Interceptor | ||
| import okhttp3.Response | ||
|
|
||
| class AuthInterceptor ( | ||
| private val tokenStorage: TokenStorage | ||
| ): Interceptor{ | ||
| override fun intercept(chain: Interceptor.Chain): Response { | ||
| val token = runBlocking { | ||
| tokenStorage.accessToken.first() | ||
| } | ||
| val originalRequest = chain.request() | ||
|
|
||
| val requestBuilder = originalRequest.newBuilder() | ||
| if (token != null){ | ||
| requestBuilder.addHeader("Authorization",token) | ||
| } | ||
|
|
||
| val newRequest = requestBuilder.build() | ||
| return chain.proceed(newRequest) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,39 @@ | ||
| package com.smartjam.app.data.api | ||
|
|
||
| import com.smartjam.app.BuildConfig | ||
| import com.smartjam.app.data.local.TokenStorage | ||
| import okhttp3.OkHttpClient | ||
| import okhttp3.logging.HttpLoggingInterceptor | ||
| import retrofit2.Retrofit | ||
| import retrofit2.converter.gson.GsonConverterFactory | ||
| import java.util.concurrent.TimeUnit | ||
|
|
||
|
|
||
| object NetworkModule { | ||
|
|
||
| fun createRetrofit(tokenStorage: TokenStorage): Retrofit { | ||
| val authInterceptor = AuthInterceptor(tokenStorage) | ||
|
|
||
| val okHttpClient = OkHttpClient.Builder() | ||
| .addInterceptor(authInterceptor) | ||
| .addInterceptor(loggingInterceptor) | ||
| .build() | ||
|
|
||
| return Retrofit.Builder() | ||
| .baseUrl(BuildConfig.BASE_URL) | ||
| .client(okHttpClient) | ||
| .addConverterFactory(GsonConverterFactory.create()) | ||
| .build() | ||
| } | ||
|
|
||
| private val loggingInterceptor by lazy { | ||
| HttpLoggingInterceptor().apply { | ||
| level = if (BuildConfig.DEBUG) { | ||
| HttpLoggingInterceptor.Level.BODY | ||
| } else{ | ||
| HttpLoggingInterceptor.Level.NONE | ||
| } | ||
| } | ||
| } | ||
|
Comment on lines
+14
to
+37
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's explore the repository structure
git ls-files | head -20Repository: Satlykovs/SmartJam Length of output: 938 🏁 Script executed: # Find MainActivity to verify AuthApi creation
fd -t f "MainActivity.kt" mobile/Repository: Satlykovs/SmartJam Length of output: 119 🏁 Script executed: # Find AuthApi interface to see what endpoints exist
fd -t f "AuthApi.kt" mobile/Repository: Satlykovs/SmartJam Length of output: 123 🏁 Script executed: # Look for NetworkModule and related setup
fd -t f "NetworkModule.kt" mobile/Repository: Satlykovs/SmartJam Length of output: 129 🏁 Script executed: cat -n mobile/app/src/main/java/com/smartjam/app/MainActivity.ktRepository: Satlykovs/SmartJam Length of output: 2628 🏁 Script executed: cat -n mobile/app/src/main/java/com/smartjam/app/data/api/AuthApi.ktRepository: Satlykovs/SmartJam Length of output: 814 🏁 Script executed: cat -n mobile/app/src/main/java/com/smartjam/app/data/api/NetworkModule.ktRepository: Satlykovs/SmartJam Length of output: 1445 Don't use BODY logging on the shared auth client.
🤖 Prompt for AI Agents |
||
|
|
||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| package com.smartjam.app.data.api | ||
|
|
||
| import com.smartjam.app.data.model.* | ||
| import retrofit2.Response | ||
| import retrofit2.http.Body | ||
| import retrofit2.http.GET | ||
| import retrofit2.http.POST | ||
| import retrofit2.http.Path | ||
|
|
||
| interface SmartJamApi { | ||
| @POST("/api/connections/invite-code") | ||
| suspend fun generateInviteCode(): Response<InviteCodeResponse> | ||
|
|
||
| @POST("/api/connections/join") | ||
| suspend fun joinByCode(@Body request: JoinRequest): Response<Unit> | ||
|
|
||
| @GET("/api/connections/pending") | ||
| suspend fun getPendingConnections(): Response<List<ConnectionDto>> | ||
|
|
||
| @GET("/api/connections/active") | ||
| suspend fun getActiveConnections(): Response<List<ConnectionDto>> | ||
|
|
||
| @POST("/api/connections/{connectionId}/respond") | ||
| suspend fun respondToConnection( | ||
| @Path("connectionId") connectionId: String, | ||
| @Body request: RespondConnectionRequest | ||
| ): Response<Unit> | ||
|
|
||
| @POST("/api/assignments") | ||
| suspend fun createAssignment( | ||
| @Body request: CreateAssignmentRequest | ||
| ): Response<PresignedUrlResponse> | ||
|
|
||
| @POST("/api/submissions") | ||
| suspend fun createSubmission( | ||
| @Body request: CreateSubmissionRequest | ||
| ): Response<PresignedUrlResponse> | ||
|
|
||
| @GET("/api/submissions/{submissionId}/status") | ||
| suspend fun getSubmissionStatus( | ||
| @Path("submissionId") submissionId: String | ||
| ): Response<SubmissionStatusResponse> | ||
|
|
||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| package com.smartjam.app.data.local | ||
|
|
||
| import androidx.room.Database | ||
| import androidx.room.RoomDatabase | ||
| import com.smartjam.app.data.local.dao.ConnectionDao | ||
| import com.smartjam.app.data.local.entity.ConnectionEntity | ||
|
|
||
| @Database(entities = [ConnectionEntity::class], version = 1, exportSchema = false) | ||
| abstract class SmartJamDatabase : RoomDatabase() { | ||
| abstract fun connectionDao(): ConnectionDao | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package com.smartjam.app.data.local | ||
|
|
||
| import android.content.Context | ||
| import androidx.datastore.core.DataStore | ||
| import androidx.datastore.preferences.core.Preferences | ||
| import androidx.datastore.preferences.core.edit | ||
| import androidx.datastore.preferences.core.longPreferencesKey | ||
| import androidx.datastore.preferences.core.stringPreferencesKey | ||
| import androidx.datastore.preferences.preferencesDataStore | ||
| import kotlinx.coroutines.flow.Flow | ||
| import kotlinx.coroutines.flow.first | ||
| import kotlinx.coroutines.flow.map | ||
|
|
||
| private val Context.dataStore : DataStore<Preferences> by preferencesDataStore( //TODO: make encrypted storage | ||
| name = "auth_preferences" | ||
| ) | ||
| class TokenStorage(private val context: Context) { | ||
| private companion object Keys{ | ||
| val ACCESS_TOKEN = stringPreferencesKey("access_token") | ||
| val REFRESH_TOKEN = stringPreferencesKey("refresh_token") | ||
|
|
||
| val ACCESS_EXPIRED_AT = longPreferencesKey("access_expires_at") | ||
|
|
||
| val REFRESH_EXPIRED_AT = longPreferencesKey("refresh_expired_at") | ||
| } | ||
|
|
||
| suspend fun saveToken(accessToken: String, refreshToken: String, accessExpiredAt: Long, refreshExpiredAt: Long){ | ||
| context.dataStore.edit { preferences -> | ||
| preferences[ACCESS_TOKEN] = accessToken | ||
| preferences[REFRESH_TOKEN] = refreshToken | ||
| preferences[ACCESS_EXPIRED_AT] = accessExpiredAt | ||
| preferences[REFRESH_EXPIRED_AT] = refreshExpiredAt | ||
| } | ||
ParovozikThomas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| val accessToken : Flow<String?> = context.dataStore.data | ||
| .map { preferences -> preferences[ACCESS_TOKEN] } | ||
|
|
||
| val refreshToken : Flow<String?> = context.dataStore.data | ||
| .map { preferences -> preferences[REFRESH_TOKEN] } | ||
|
|
||
| val accessExpiredAt : Flow<Long?> = context.dataStore.data | ||
| .map {preferences -> preferences[ACCESS_EXPIRED_AT]} | ||
|
|
||
| val refreshExpiredAt : Flow<Long?> = context.dataStore.data | ||
| .map {preferences -> preferences[REFRESH_EXPIRED_AT]} | ||
|
|
||
| suspend fun clearTokens(){ | ||
| context.dataStore.edit { preferences -> | ||
| preferences.remove(ACCESS_TOKEN) | ||
| preferences.remove(REFRESH_TOKEN) | ||
| preferences.remove(ACCESS_EXPIRED_AT) | ||
| preferences.remove(REFRESH_EXPIRED_AT) | ||
| } | ||
| } | ||
|
|
||
| suspend fun isAccessTokenExpired(): Boolean { | ||
| val expires = context.dataStore.data | ||
| .map { preferences -> preferences[ACCESS_EXPIRED_AT] } | ||
| .first() | ||
| val currentTime = System.currentTimeMillis() / 1000 | ||
| return (expires == null) || (currentTime > expires) | ||
|
|
||
| } | ||
|
|
||
| suspend fun isRefreshTokenExpired(): Boolean { | ||
| val expires = context.dataStore.data | ||
| .map { preferences -> preferences[REFRESH_EXPIRED_AT] } | ||
| .first() | ||
| val currentTime = System.currentTimeMillis() / 1000 | ||
| return (expires == null) || (currentTime > expires) | ||
|
|
||
| } | ||
|
|
||
| suspend fun isAuthenticated(): Boolean { | ||
| return !isRefreshTokenExpired() | ||
| } | ||
ParovozikThomas marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Invalid version format:
"$nav_version+"produces"2.9.7+".Appending
+to a complete version string (2.9.7+) is not a valid Maven version specifier. The+wildcard should replace a version segment (e.g.,2.9.+), not be appended to an already-specified version.Either use the exact version or restructure the pattern:
🐛 Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents