diff --git a/app/build.gradle b/app/build.gradle index 4add9a7..b4c9861 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,17 +4,19 @@ plugins { id 'kotlin-kapt' id 'kotlinx-serialization' id 'dagger.hilt.android.plugin' + alias(libs.plugins.com.google.devtools.ksp) + alias(libs.plugins.com.google.protobuf) + alias(libs.plugins.org.jetbrains.kotlin.plugin.compose) } android { - compileSdkVersion 34 - defaultConfig { applicationId "us.mitene.practicalexam" - minSdkVersion 26 - targetSdkVersion 34 - versionCode 1 - versionName "1.0" + compileSdk libs.versions.app.compileSdk.get().toInteger() + minSdkVersion libs.versions.app.minSdk.get().toInteger() + targetSdkVersion libs.versions.app.targetSdk.get().toInteger() + versionCode libs.versions.app.versionCode.get().toInteger() + versionName libs.versions.app.versionName.get() testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" @@ -41,7 +43,7 @@ android { targetCompatibility JavaVersion.VERSION_17 } composeOptions { - kotlinCompilerExtensionVersion = "1.5.11" + kotlinCompilerExtensionVersion = libs.versions.app.kotlinCompilerExtensionVersion.get() } kotlinOptions { jvmTarget = JavaVersion.VERSION_17.toString() @@ -54,112 +56,127 @@ android { dependencies { - implementation 'androidx.core:core-ktx:1.13.1' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.12.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - testImplementation 'junit:junit:4.13.2' - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + implementation libs.androidx.core.core.ktx + implementation libs.androidx.appcompat + implementation libs.com.google.android.material + implementation libs.androidx.constraintlayout + testImplementation libs.junit + androidTestImplementation libs.androidx.test.ext.junit + androidTestImplementation libs.androidx.test.espresso.espresso.core // jetpack compose bom - implementation platform('androidx.compose:compose-bom:2024.05.00') - implementation 'androidx.compose.ui:ui' - implementation 'androidx.compose.material:material' - implementation 'androidx.compose.material:material-icons-extended' - implementation 'androidx.compose.ui:ui-tooling-preview' - implementation 'androidx.compose.material3:material3' - debugImplementation 'androidx.compose.ui:ui-tooling' - debugImplementation 'androidx.compose.ui:ui-test-manifest' - implementation 'androidx.compose.runtime:runtime-livedata' + implementation platform(libs.androidx.compose.compose.bom) + implementation libs.androidx.ui + implementation libs.androidx.material + implementation libs.androidx.material.icons.extended + implementation libs.androidx.ui.tooling.preview + implementation libs.androidx.material3 + debugImplementation libs.androidx.ui.tooling + debugImplementation libs.androidx.ui.test.manifest + implementation libs.androidx.runtime.livedata // jetpack compose - implementation 'androidx.activity:activity-compose:1.9.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0' - implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.8.0' + implementation libs.androidx.activity.activity.compose + implementation libs.androidx.lifecycle.lifecycle.viewmodel.ktx + implementation libs.androidx.lifecycle.lifecycle.viewmodel.compose // for test - testImplementation 'org.robolectric:robolectric:4.12.1' - testImplementation "androidx.test:core:1.5.0" - testImplementation "androidx.test:core-ktx:1.5.0" - testImplementation "androidx.test.ext:junit:1.1.5" - testImplementation "androidx.test.ext:junit-ktx:1.1.5" - testImplementation("androidx.arch.core:core-testing:2.2.0") - testImplementation 'io.mockk:mockk:1.13.10' - testImplementation 'org.mockito:mockito-core:5.12.0' + testImplementation libs.org.robolectric + testImplementation libs.androidx.test.core + testImplementation libs.androidx.test.core.ktx + testImplementation libs.androidx.test.ext.junit + testImplementation libs.androidx.test.ext.junit.ktx + testImplementation(libs.androidx.arch.core.core.testing) + testImplementation libs.io.mockk + testImplementation libs.org.mockito.mockito.core // jet pack - implementation "androidx.fragment:fragment-ktx:1.7.1" - implementation "androidx.recyclerview:recyclerview:1.3.2" + implementation libs.androidx.fragment.fragment.ktx + implementation libs.androidx.recyclerview // androidx.lifecycle - def lifecycle_version = "2.8.0" - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + implementation libs.androidx.lifecycle.lifecycle.viewmodel.ktx + implementation libs.androidx.lifecycle.lifecycle.livedata.ktx //noinspection LifecycleAnnotationProcessorWithJava8 - kapt "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" - implementation "androidx.lifecycle:lifecycle-process:$lifecycle_version" + kapt libs.androidx.lifecycle.lifecycle.compiler + implementation libs.androidx.lifecycle.lifecycle.process // room - def room_version = "2.6.1" - implementation("androidx.room:room-runtime:$room_version") - kapt "androidx.room:room-compiler:$room_version" - implementation("androidx.room:room-ktx:$room_version") - implementation("androidx.room:room-rxjava2:$room_version") - implementation("androidx.room:room-rxjava3:$room_version") - testImplementation("androidx.room:room-testing:$room_version") + implementation(libs.androidx.room.room.runtime) + ksp libs.androidx.room.room.compiler + implementation(libs.androidx.room.room.ktx) + implementation(libs.androidx.room.room.rxjava2) + implementation(libs.androidx.room.room.rxjava3) + testImplementation(libs.androidx.room.room.testing) // okhttp - def okhttp_version = "4.12.0" - implementation "com.squareup.okhttp3:okhttp:$okhttp_version" - implementation "com.squareup.okhttp3:logging-interceptor:$okhttp_version" - testImplementation "com.squareup.okhttp3:mockwebserver:$okhttp_version" + implementation libs.com.squareup.okhttp3.okhttp + implementation libs.com.squareup.okhttp3.logging.interceptor + testImplementation libs.com.squareup.okhttp3.mockwebserver // glide - def glide_version = '4.16.0' - implementation "com.github.bumptech.glide:glide:$glide_version" - implementation "com.github.bumptech.glide:glide:$glide_version" - implementation "com.github.bumptech.glide:annotations:$glide_version" - kapt "com.github.bumptech.glide:compiler:$glide_version" + implementation libs.com.github.bumptech.glide + implementation libs.com.github.bumptech.glide + implementation libs.com.github.bumptech.glide.annotations + ksp libs.com.github.bumptech.glide.compiler // coroutine - def coroutine_version = "1.8.0" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine_version" - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-rx2:$coroutine_version" - testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:$coroutine_version" + implementation libs.org.jetbrains.kotlinx.kotlinx.coroutines.core + implementation libs.org.jetbrains.kotlinx.kotlinx.coroutines.android + implementation libs.org.jetbrains.kotlinx.kotlinx.coroutines.rx2 + testImplementation libs.org.jetbrains.kotlinx.kotlinx.coroutines.test // rxjava - implementation "io.reactivex.rxjava2:rxjava:2.2.21" - implementation "io.reactivex.rxjava2:rxandroid:2.1.1" - implementation "io.reactivex.rxjava2:rxkotlin:2.4.0" + implementation libs.io.reactivex.rxjava2.rxjava + implementation libs.io.reactivex.rxjava2.rxandroid + implementation libs.io.reactivex.rxjava2.rxkotlin // retrofit - def retrofit2_version = "2.10.0" - implementation "com.squareup.retrofit2:retrofit:$retrofit2_version" - implementation "com.squareup.retrofit2:adapter-rxjava2:$retrofit2_version" - implementation "com.squareup.retrofit2:converter-gson:$retrofit2_version" - implementation "com.squareup.retrofit2:converter-moshi:$retrofit2_version" - implementation 'com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0' + implementation libs.com.squareup.retrofit2.retrofit + implementation libs.com.squareup.retrofit2.adapter.rxjava2 + implementation libs.com.squareup.retrofit2.converter.gson + implementation libs.com.squareup.retrofit2.converter.moshi + implementation libs.com.jakewharton.retrofit.retrofit2.kotlinx.serialization.converter // gson - implementation "com.google.code.gson:gson:2.10.1" + implementation libs.com.google.code.gson // moshi - implementation 'com.squareup.moshi:moshi-kotlin:1.15.1' + implementation libs.com.squareup.moshi.moshi.kotlin // serialization - implementation 'org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3' + implementation libs.org.jetbrains.kotlinx.kotlinx.serialization.json // timber - implementation "com.jakewharton.timber:timber:5.0.1" + implementation libs.com.jakewharton.timber // DI : dagger - implementation 'com.google.dagger:dagger:2.51.1' - kapt 'com.google.dagger:dagger-compiler:2.51.1' + implementation libs.com.google.dagger + kapt libs.com.google.dagger.dagger.compiler // DI : hilt - implementation 'com.google.dagger:hilt-android:2.51.1' - kapt 'com.google.dagger:hilt-compiler:2.51.1' - testImplementation 'com.google.dagger:hilt-android-testing:2.51.1' - kaptTest 'com.google.dagger:hilt-compiler:2.51.1' + implementation libs.com.google.dagger.hilt.android + kapt libs.com.google.dagger.hilt.compiler + testImplementation libs.com.google.dagger.hilt.android.testing + kaptTest libs.com.google.dagger.hilt.compiler + implementation libs.androidx.hilt.hilt.navigation.compose + + // Proto DataStore + implementation libs.androidx.datastore + implementation libs.com.google.protobuf.protobuf.javalite +} + +protobuf { + protoc { + artifact = libs.com.google.protobuf.protoc.get() + } + + generateProtoTasks { + all().each { task -> + task.builtins { + java { + option 'lite' + } + } + } + } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 0a107b8..d42e5bb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -1,13 +1,15 @@ + + android:theme="@style/Theme.PracticalExam" + android:name=".MainApplication"> diff --git a/app/src/main/java/us/mitene/practicalexam/MainActivity.kt b/app/src/main/java/us/mitene/practicalexam/MainActivity.kt index 903cdae..b624b76 100644 --- a/app/src/main/java/us/mitene/practicalexam/MainActivity.kt +++ b/app/src/main/java/us/mitene/practicalexam/MainActivity.kt @@ -1,11 +1,21 @@ package us.mitene.practicalexam -import androidx.appcompat.app.AppCompatActivity import android.os.Bundle +import androidx.activity.compose.setContent +import androidx.appcompat.app.AppCompatActivity +import dagger.hilt.android.AndroidEntryPoint +import us.mitene.practicalexam.ui.screen.GithubReposScreen +import us.mitene.practicalexam.ui.theme.PracticalExamTheme +@AndroidEntryPoint class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContentView(R.layout.activity_main) + + setContent { + PracticalExamTheme { + GithubReposScreen() + } + } } } \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/MainApplication.kt b/app/src/main/java/us/mitene/practicalexam/MainApplication.kt new file mode 100644 index 0000000..5595251 --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/MainApplication.kt @@ -0,0 +1,14 @@ +package us.mitene.practicalexam + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp +import timber.log.Timber + +@HiltAndroidApp +class MainApplication : Application(){ + override fun onCreate() { + super.onCreate() + + Timber.plant(Timber.DebugTree()) + } +} \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/data/GithubRepoRepository.kt b/app/src/main/java/us/mitene/practicalexam/data/GithubRepoRepository.kt new file mode 100644 index 0000000..9cfb31c --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/data/GithubRepoRepository.kt @@ -0,0 +1,37 @@ +package us.mitene.practicalexam.data + +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.flowOf +import kotlinx.coroutines.flow.map +import us.mitene.practicalexam.datastore.GithubRepoLocalDataSource +import us.mitene.practicalexam.network.GithubRepoRemoteDataSource +import java.time.ZonedDateTime +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GithubRepoRepository @Inject constructor( + private val githubRepoLocalDataSource: GithubRepoLocalDataSource, + private val githubRepoRemoteDataSource: GithubRepoRemoteDataSource, +) { + private val reposCache = githubRepoLocalDataSource.repos + + suspend fun getNames(): Flow> { + val cache = reposCache.first() + val now = ZonedDateTime.now().toEpochSecond() - cache.fetchedAt + + if (now > CACHE_DURATION) { + val repos = githubRepoRemoteDataSource.getRepos() + githubRepoLocalDataSource.storeRepos(repos) + + return reposCache.map { it.namesList } + } + + return flowOf(cache.namesList) + } + + companion object { + private const val CACHE_DURATION = 30 // 秒で指定 + } +} \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/datastore/GithubRepoCacheSerializer.kt b/app/src/main/java/us/mitene/practicalexam/datastore/GithubRepoCacheSerializer.kt new file mode 100644 index 0000000..529cec3 --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/datastore/GithubRepoCacheSerializer.kt @@ -0,0 +1,21 @@ +package us.mitene.practicalexam.datastore + +import androidx.datastore.core.CorruptionException +import androidx.datastore.core.Serializer +import com.google.protobuf.InvalidProtocolBufferException +import us.mitene.practicalexam.datastore.proto.GithubRepoCache +import java.io.InputStream +import java.io.OutputStream + +object GithubRepoCacheSerializer : Serializer { + override val defaultValue: GithubRepoCache = GithubRepoCache.getDefaultInstance() + override suspend fun readFrom(input: InputStream): GithubRepoCache { + try { + return GithubRepoCache.parseFrom(input) + } catch (e: InvalidProtocolBufferException) { + throw CorruptionException("Cannot read proto.", e) + } + } + + override suspend fun writeTo(t: GithubRepoCache, output: OutputStream) = t.writeTo(output) +} \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/datastore/GithubRepoLocalDataSource.kt b/app/src/main/java/us/mitene/practicalexam/datastore/GithubRepoLocalDataSource.kt new file mode 100644 index 0000000..3b43aa6 --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/datastore/GithubRepoLocalDataSource.kt @@ -0,0 +1,57 @@ +package us.mitene.practicalexam.datastore + +import android.content.Context +import androidx.datastore.core.DataStore +import androidx.datastore.dataStore +import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CoroutineDispatcher +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.withContext +import timber.log.Timber +import us.mitene.practicalexam.datastore.proto.GithubRepoCache +import us.mitene.practicalexam.di.IoDispatcher +import us.mitene.practicalexam.network.GithubRepo +import java.io.IOException +import java.time.ZonedDateTime +import javax.inject.Inject +import javax.inject.Singleton + +@Singleton +class GithubRepoLocalDataSource @Inject constructor( + @ApplicationContext private val context: Context, + @IoDispatcher private val dispatcher: CoroutineDispatcher, +) { + private val Context.githubRepoStore: DataStore by dataStore( + fileName = DATA_STORE_FILE_NAME, + serializer = GithubRepoCacheSerializer + ) + + val repos = context.githubRepoStore.data + .catch { exception -> + // dataStore.data throws an IOException when an error is encountered when reading data + if (exception is IOException) { + Timber.tag(TAG).e(exception, "Error reading sort order preferences.") + emit(GithubRepoCache.getDefaultInstance()) + } else { + throw exception + } + } + + suspend fun storeRepos(repos: List) = withContext(dispatcher) { + context.githubRepoStore.updateData { cache -> + val names = repos.map { it.name } + val fetchedAt = ZonedDateTime.now().toEpochSecond() + + cache.toBuilder() + .setFetchedAt(fetchedAt) + .clearNames() + .addAllNames(names) + .build() + } + } + + companion object { + private const val DATA_STORE_FILE_NAME = "github_repo_cache.pb" + private const val TAG: String = "GithubRepoCacheRepo" + } +} \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/di/DispatcherModule.kt b/app/src/main/java/us/mitene/practicalexam/di/DispatcherModule.kt new file mode 100644 index 0000000..2ea2e5c --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/di/DispatcherModule.kt @@ -0,0 +1,15 @@ +package us.mitene.practicalexam.di + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.coroutines.Dispatchers + +@Module +@InstallIn(SingletonComponent::class) +object DispatcherModule { + @Provides + @IoDispatcher + internal fun providesIoDispatcher() = Dispatchers.IO +} \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/di/Dispatchers.kt b/app/src/main/java/us/mitene/practicalexam/di/Dispatchers.kt new file mode 100644 index 0000000..43f7f16 --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/di/Dispatchers.kt @@ -0,0 +1,6 @@ +package us.mitene.practicalexam.di + +import javax.inject.Qualifier + +@Qualifier +annotation class IoDispatcher diff --git a/app/src/main/java/us/mitene/practicalexam/network/GithubRepo.kt b/app/src/main/java/us/mitene/practicalexam/network/GithubRepo.kt new file mode 100644 index 0000000..5461dba --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/network/GithubRepo.kt @@ -0,0 +1,8 @@ +package us.mitene.practicalexam.network + +import kotlinx.serialization.Serializable + +@Serializable +data class GithubRepo( + val name: String +) \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/network/GithubRepoRemoteDataSource.kt b/app/src/main/java/us/mitene/practicalexam/network/GithubRepoRemoteDataSource.kt new file mode 100644 index 0000000..3c202fd --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/network/GithubRepoRemoteDataSource.kt @@ -0,0 +1,32 @@ +package us.mitene.practicalexam.network + +import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import retrofit2.Retrofit +import retrofit2.http.GET +import javax.inject.Inject +import javax.inject.Singleton + +interface GithubApi { + @GET("orgs/mixigroup/repos") + suspend fun getRepos(): List +} + +@Singleton +class GithubRepoRemoteDataSource @Inject constructor() { + private val json = Json { ignoreUnknownKeys = true } + private val retrofit = Retrofit.Builder() + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .baseUrl(BASE_URL) + .build() + private val retrofitService: GithubApi by lazy { + retrofit.create(GithubApi::class.java) + } + + suspend fun getRepos(): List = retrofitService.getRepos() + + companion object { + private const val BASE_URL = "https://api.github.com" + } +} \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/ui/screen/GithubReposScreen.kt b/app/src/main/java/us/mitene/practicalexam/ui/screen/GithubReposScreen.kt new file mode 100644 index 0000000..532edec --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/ui/screen/GithubReposScreen.kt @@ -0,0 +1,58 @@ +package us.mitene.practicalexam.ui.screen + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.hilt.navigation.compose.hiltViewModel + +@Composable +fun GithubReposScreen( + githubReposViewModel: GithubReposViewModel = hiltViewModel() +) { + val githubReposUiState by githubReposViewModel.uiState.collectAsState() + + Scaffold { + GithubReposLayout( + repoNames = githubReposUiState.repoNames, + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(it) + ) + } +} + +@Composable +fun GithubReposLayout( + repoNames: List, + modifier: Modifier = Modifier, +) { + LazyColumn(modifier = modifier) { + items(repoNames) { name -> + Text(text = name) + } + } +} + +@Preview +@Composable +private fun Preview() { + Scaffold { paddingValues -> + GithubReposLayout( + repoNames = (1..200).map { it.toString() }.toList(), + modifier = Modifier + .fillMaxWidth() + .wrapContentHeight() // TODO: どんな効果があるか調べる + .padding(paddingValues) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/us/mitene/practicalexam/ui/screen/GithubReposViewModel.kt b/app/src/main/java/us/mitene/practicalexam/ui/screen/GithubReposViewModel.kt new file mode 100644 index 0000000..d63e1e0 --- /dev/null +++ b/app/src/main/java/us/mitene/practicalexam/ui/screen/GithubReposViewModel.kt @@ -0,0 +1,34 @@ +package us.mitene.practicalexam.ui.screen + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import us.mitene.practicalexam.data.GithubRepoRepository +import javax.inject.Inject + +@HiltViewModel +class GithubReposViewModel @Inject constructor( + private val githubRepoRepository: GithubRepoRepository, +) : ViewModel() { + private val _uiState = MutableStateFlow(GithubReposUiState(emptyList())) + val uiState = _uiState.asStateFlow() + + init { + fetch() + } + + private fun fetch() { + viewModelScope.launch { + githubRepoRepository.getNames().collect { + _uiState.value = GithubReposUiState(it) + } + } + } +} + +data class GithubReposUiState( + val repoNames: List +) \ No newline at end of file diff --git a/app/src/main/proto/github_repo_cache.proto b/app/src/main/proto/github_repo_cache.proto new file mode 100644 index 0000000..bd70539 --- /dev/null +++ b/app/src/main/proto/github_repo_cache.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +option java_package = "us.mitene.practicalexam.datastore.proto"; +option java_multiple_files = true; + +message GithubRepoCache { + // 最後にfetchしたDatetime + int64 fetchedAt = 1; + // レポジトリ名一覧 + repeated string names = 2; +} diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index c08b7a3..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/build.gradle b/build.gradle index c253fbe..d61fc8d 100644 --- a/build.gradle +++ b/build.gradle @@ -1,21 +1,27 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.9.23' + ext.kotlin_version = libs.versions.kotlin repositories { google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:8.4.1' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath "org.jetbrains.kotlin:kotlin-serialization:$kotlin_version" - classpath 'com.google.dagger:hilt-android-gradle-plugin:2.51.1' + classpath libs.com.android.tools.build.gradle + classpath libs.com.android.tools.build.gradle + classpath libs.org.jetbrains.kotlin.kotlin.gradle.plugin + classpath libs.org.jetbrains.kotlin.kotlin.serialization + classpath libs.com.google.dagger.hilt.android.gradle.plugin // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } } +plugins { + alias(libs.plugins.com.google.devtools.ksp) apply false + alias(libs.plugins.org.jetbrains.kotlin.plugin.compose) apply false +} + allprojects { repositories { google() diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 0000000..c834b4c --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,96 @@ +[versions] +kotlin = "2.0.0" +androidx-lifecycle = "2.8.0" +androidx-room = "2.6.1" +androidx-test = "1.5.0" +androidx-test-ext = "1.1.5" +app-compileSdk = "34" +app-minSdk = "26" +app-targetSdk = "26" +app-versionCode = "1" +app-versionName = "1.0" +app-kotlinCompilerExtensionVersion = "1.5.11" +com-github-bumptech-glide = "4.16.0" +com-google-dagger = "2.51.1" +com-squareup-okhttp3 = "4.12.0" +com-squareup-retrofit2 = "2.10.0" +org-jetbrains-kotlinx = "1.8.0" + +[libraries] +androidx-activity-activity-compose = "androidx.activity:activity-compose:1.9.0" +androidx-appcompat = "androidx.appcompat:appcompat:1.6.1" +androidx-arch-core-core-testing = "androidx.arch.core:core-testing:2.2.0" +androidx-compose-compose-bom = "androidx.compose:compose-bom:2024.05.00" +androidx-constraintlayout = "androidx.constraintlayout:constraintlayout:2.1.4" +androidx-core-core-ktx = "androidx.core:core-ktx:1.13.1" +androidx-datastore = "androidx.datastore:datastore:1.0.0" +androidx-fragment-fragment-ktx = "androidx.fragment:fragment-ktx:1.7.1" +androidx-hilt-hilt-navigation-compose = "androidx.hilt:hilt-navigation-compose:1.2.0" +androidx-lifecycle-lifecycle-compiler = { module = "androidx.lifecycle:lifecycle-compiler", version.ref = "androidx-lifecycle" } +androidx-lifecycle-lifecycle-livedata-ktx = { module = "androidx.lifecycle:lifecycle-livedata-ktx", version.ref = "androidx-lifecycle" } +androidx-lifecycle-lifecycle-process = { module = "androidx.lifecycle:lifecycle-process", version.ref = "androidx-lifecycle" } +androidx-lifecycle-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" } +androidx-lifecycle-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } +androidx-material = { module = "androidx.compose.material:material" } +androidx-material-icons-extended = { module = "androidx.compose.material:material-icons-extended" } +androidx-material3 = { module = "androidx.compose.material3:material3" } +androidx-recyclerview = "androidx.recyclerview:recyclerview:1.3.2" +androidx-room-room-compiler = { module = "androidx.room:room-compiler", version.ref = "androidx-room" } +androidx-room-room-ktx = { module = "androidx.room:room-ktx", version.ref = "androidx-room" } +androidx-room-room-runtime = { module = "androidx.room:room-runtime", version.ref = "androidx-room" } +androidx-room-room-rxjava2 = { module = "androidx.room:room-rxjava2", version.ref = "androidx-room" } +androidx-room-room-rxjava3 = { module = "androidx.room:room-rxjava3", version.ref = "androidx-room" } +androidx-room-room-testing = { module = "androidx.room:room-testing", version.ref = "androidx-room" } +androidx-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata" } +androidx-test-core = { module = "androidx.test:core", version.ref = "androidx-test" } +androidx-test-core-ktx = { module = "androidx.test:core-ktx", version.ref = "androidx-test" } +androidx-test-espresso-espresso-core = "androidx.test.espresso:espresso-core:3.5.1" +androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext" } +androidx-test-ext-junit-ktx = { module = "androidx.test.ext:junit-ktx", version.ref = "androidx-test-ext" } +androidx-ui = { module = "androidx.compose.ui:ui" } +androidx-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest" } +androidx-ui-tooling = { module = "androidx.compose.ui:ui-tooling" } +androidx-ui-tooling-preview = { module = "androidx.compose.ui:ui-tooling-preview" } +com-android-tools-build-gradle = "com.android.tools.build:gradle:8.4.1" +com-github-bumptech-glide = { module = "com.github.bumptech.glide:glide", version.ref = "com-github-bumptech-glide" } +com-github-bumptech-glide-annotations = { module = "com.github.bumptech.glide:annotations", version.ref = "com-github-bumptech-glide" } +com-github-bumptech-glide-compiler = { module = "com.github.bumptech.glide:ksp", version.ref = "com-github-bumptech-glide" } +com-google-android-material = "com.google.android.material:material:1.12.0" +com-google-code-gson = "com.google.code.gson:gson:2.10.1" +com-google-dagger = { module = "com.google.dagger:dagger", version.ref = "com-google-dagger" } +com-google-dagger-dagger-compiler = { module = "com.google.dagger:dagger-compiler", version.ref = "com-google-dagger" } +com-google-dagger-hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "com-google-dagger" } +com-google-dagger-hilt-android-gradle-plugin = { module = "com.google.dagger:hilt-android-gradle-plugin", version.ref = "com-google-dagger" } +com-google-dagger-hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "com-google-dagger" } +com-google-dagger-hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "com-google-dagger" } +com-google-protobuf-protobuf-javalite = "com.google.protobuf:protobuf-javalite:3.18.0" +com-google-protobuf-protoc = "com.google.protobuf:protoc:3.21.7" +com-jakewharton-retrofit-retrofit2-kotlinx-serialization-converter = "com.jakewharton.retrofit:retrofit2-kotlinx-serialization-converter:1.0.0" +com-jakewharton-timber = "com.jakewharton.timber:timber:5.0.1" +com-squareup-moshi-moshi-kotlin = "com.squareup.moshi:moshi-kotlin:1.15.1" +com-squareup-okhttp3-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "com-squareup-okhttp3" } +com-squareup-okhttp3-mockwebserver = { module = "com.squareup.okhttp3:mockwebserver", version.ref = "com-squareup-okhttp3" } +com-squareup-okhttp3-okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "com-squareup-okhttp3" } +com-squareup-retrofit2-adapter-rxjava2 = { module = "com.squareup.retrofit2:adapter-rxjava2", version.ref = "com-squareup-retrofit2" } +com-squareup-retrofit2-converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "com-squareup-retrofit2" } +com-squareup-retrofit2-converter-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "com-squareup-retrofit2" } +com-squareup-retrofit2-retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "com-squareup-retrofit2" } +io-mockk = "io.mockk:mockk:1.13.10" +io-reactivex-rxjava2-rxandroid = "io.reactivex.rxjava2:rxandroid:2.1.1" +io-reactivex-rxjava2-rxjava = "io.reactivex.rxjava2:rxjava:2.2.21" +io-reactivex-rxjava2-rxkotlin = "io.reactivex.rxjava2:rxkotlin:2.4.0" +junit = "junit:junit:4.13.2" +org-jetbrains-kotlin-kotlin-gradle-plugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } +org-jetbrains-kotlin-kotlin-serialization = { module = "org.jetbrains.kotlin:kotlin-serialization", version.ref = "kotlin" } +org-jetbrains-kotlinx-kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "org-jetbrains-kotlinx" } +org-jetbrains-kotlinx-kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "org-jetbrains-kotlinx" } +org-jetbrains-kotlinx-kotlinx-coroutines-rx2 = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-rx2", version.ref = "org-jetbrains-kotlinx" } +org-jetbrains-kotlinx-kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "org-jetbrains-kotlinx" } +org-jetbrains-kotlinx-kotlinx-serialization-json = "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3" +org-mockito-mockito-core = "org.mockito:mockito-core:5.12.0" +org-robolectric = "org.robolectric:robolectric:4.12.1" + +[plugins] +com-google-protobuf = "com.google.protobuf:0.9.1" +com-google-devtools-ksp = "com.google.devtools.ksp:2.0.0-1.0.22" +org-jetbrains-kotlin-plugin-compose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }