diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/ZeContributorsService.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/ZeContributorsService.kt deleted file mode 100644 index 198cb168..00000000 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/ZeContributorsService.kt +++ /dev/null @@ -1,58 +0,0 @@ -package de.berlindroid.zeapp.zeservices - -import de.berlindroid.zeapp.zeui.zeabout.Contributor -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.flowOn -import kotlinx.serialization.SerialName -import kotlinx.serialization.Serializable -import kotlinx.serialization.json.Json -import okhttp3.MediaType.Companion.toMediaType -import retrofit2.Retrofit -import retrofit2.converter.kotlinx.serialization.asConverterFactory -import retrofit2.http.GET -import retrofit2.http.Query -import javax.inject.Inject - -class ZeContributorsService @Inject constructor() { - fun contributors(page: Int): Flow> = flow { - val contributors = githubApiService.getContributors(page) - - emit( - contributors.map { Contributor(it.login, it.url, it.imageUrl, it.contributions) }, - ) - }.flowOn(Dispatchers.IO) -} - -private val json = Json { - ignoreUnknownKeys = true -} - -private val retrofit = Retrofit.Builder() - .baseUrl("https://api.github.com/repos/gdg-berlin-android/zebadge/") - .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) - .build() - -private val githubApiService = retrofit.create(GithubApi::class.java) - -private interface GithubApi { - - @Serializable - data class Contributor( - @SerialName(value = "login") - val login: String, - - @SerialName(value = "contributions") - val contributions: Int, - - @SerialName(value = "html_url") - val url: String, - - @SerialName(value = "avatar_url") - val imageUrl: String, - ) - - @GET("contributors") - suspend fun getContributors(@Query("page") page: Int): List -} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/GitHubApi.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/GitHubApi.kt new file mode 100644 index 00000000..fca881e9 --- /dev/null +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/GitHubApi.kt @@ -0,0 +1,42 @@ +package de.berlindroid.zeapp.zeservices.github + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import retrofit2.http.GET +import retrofit2.http.Query + +interface GitHubApi { + + @Serializable + data class Contributor( + @SerialName(value = "login") + val login: String, + + @SerialName(value = "contributions") + val contributions: Int, + + @SerialName(value = "html_url") + val url: String, + + @SerialName(value = "avatar_url") + val imageUrl: String, + ) + + @Serializable + data class Release( + @SerialName(value = "url") + val url: String, + + @SerialName(value = "name") + val name: String, + + @SerialName(value = "tag_name") + val tagName: String, + ) + + @GET("contributors") + suspend fun getContributors(@Query("page") page: Int): List + + @GET("releases") + suspend fun getReleases(@Query("per_page") pageSize: Int = 30): List +} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeContributorsService.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeContributorsService.kt new file mode 100644 index 00000000..8bae1b9d --- /dev/null +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeContributorsService.kt @@ -0,0 +1,20 @@ +package de.berlindroid.zeapp.zeservices.github + +import de.berlindroid.zeapp.zeui.zeabout.Contributor +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.flowOn +import javax.inject.Inject + +class ZeContributorsService @Inject constructor( + private val githubApi: GitHubApi, +) { + fun contributors(page: Int): Flow> = flow { + val contributors = githubApi.getContributors(page) + + emit( + contributors.map { Contributor(it.login, it.url, it.imageUrl, it.contributions) }, + ) + }.flowOn(Dispatchers.IO) +} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeGitHubModule.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeGitHubModule.kt new file mode 100644 index 00000000..0fdf7274 --- /dev/null +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeGitHubModule.kt @@ -0,0 +1,27 @@ +package de.berlindroid.zeapp.zeservices.github + +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory + +@Module +@InstallIn(SingletonComponent::class) +object ZeGitHubModule { + + private val json = Json { + ignoreUnknownKeys = true + } + + private val retrofit = Retrofit.Builder() + .baseUrl("https://api.github.com/repos/gdg-berlin-android/zebadge/") + .addConverterFactory(json.asConverterFactory("application/json".toMediaType())) + .build() + + @Provides + fun gitHubApiService(): GitHubApi = retrofit.create(GitHubApi::class.java) +} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeReleaseService.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeReleaseService.kt new file mode 100644 index 00000000..76cec347 --- /dev/null +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeservices/github/ZeReleaseService.kt @@ -0,0 +1,31 @@ +package de.berlindroid.zeapp.zeservices.github + +import de.berlindroid.zeapp.BuildConfig +import okio.IOException +import retrofit2.HttpException +import timber.log.Timber +import javax.inject.Inject + +class ZeReleaseService @Inject constructor( + private val githubApi: GitHubApi, +) { + suspend fun getLatestRelease() = try { + // We only need the latest release + githubApi.getReleases(pageSize = 1).firstOrNull() + } catch (ioException: IOException) { + Timber.w("Failed to get latest version", ioException) + null + } catch (httpException: HttpException) { + Timber.w("Failed to get latest version", httpException) + null + } + + suspend fun getNewRelease(): Int? = + try { + getLatestRelease()?.tagName?.toInt()?.takeIf { + it > BuildConfig.VERSION_CODE + } + } catch (nfe: NumberFormatException) { + null + } +} diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zeabout/ZeAboutViewModel.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zeabout/ZeAboutViewModel.kt index 22f7d572..8a592635 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zeabout/ZeAboutViewModel.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zeabout/ZeAboutViewModel.kt @@ -3,7 +3,7 @@ package de.berlindroid.zeapp.zeui.zeabout import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel -import de.berlindroid.zeapp.zeservices.ZeContributorsService +import de.berlindroid.zeapp.zeservices.github.ZeContributorsService import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeDrawerContent.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeDrawerContent.kt index 8f25d740..f6d1693e 100644 --- a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeDrawerContent.kt +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeDrawerContent.kt @@ -18,6 +18,8 @@ import androidx.compose.material3.NavigationDrawerItem import androidx.compose.material3.NavigationDrawerItemDefaults 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.graphics.painter.Painter import androidx.compose.ui.graphics.vector.ImageVector @@ -26,6 +28,7 @@ import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import de.berlindroid.zeapp.R import de.berlindroid.zeapp.zeui.zetheme.ZeBlack import de.berlindroid.zeapp.zeui.zetheme.ZeWhite @@ -43,6 +46,9 @@ internal fun ZeDrawerContent( onCloseDrawer: () -> Unit = {}, onTitleClick: () -> Unit = {}, ) { + val viewModel: ZeDrawerViewModel = hiltViewModel() + val uiState by viewModel.uiState.collectAsState() + @Composable fun NavDrawerItem( text: String, @@ -173,6 +179,14 @@ internal fun ZeDrawerContent( ) } + uiState.newReleaseVersion?.let { version -> + item { + Text( + stringResource(id = R.string.ze_navdrawer_new_release, version), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp), + ) + } + } item { NavDrawerItem( text = stringResource(id = R.string.ze_navdrawer_open_release_page), diff --git a/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeDrawerViewModel.kt b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeDrawerViewModel.kt new file mode 100644 index 00000000..6c4c8353 --- /dev/null +++ b/zeapp/android/src/main/java/de/berlindroid/zeapp/zeui/zehome/ZeDrawerViewModel.kt @@ -0,0 +1,39 @@ +package de.berlindroid.zeapp.zeui.zehome + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import dagger.hilt.android.lifecycle.HiltViewModel +import de.berlindroid.zeapp.zeservices.github.ZeReleaseService +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.update +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class ZeDrawerViewModel @Inject constructor( + private val releaseService: ZeReleaseService, +) : ViewModel() { + + private val _uiState = MutableStateFlow(UiState()) + val uiState = _uiState.asStateFlow() + + init { + checkForNewRelease() + } + + private fun checkForNewRelease() { + viewModelScope.launch { + val newReleaseVersion = releaseService.getNewRelease() + _uiState.update { + it.copy( + newReleaseVersion = newReleaseVersion, + ) + } + } + } + + data class UiState( + val newReleaseVersion: Int? = null, // Version of a new release, in case there is one + ) +} diff --git a/zeapp/android/src/main/res/values/strings.xml b/zeapp/android/src/main/res/values/strings.xml index 9544f45a..0ea70cc6 100644 --- a/zeapp/android/src/main/res/values/strings.xml +++ b/zeapp/android/src/main/res/values/strings.xml @@ -57,6 +57,7 @@ Save all pages to badge Update config on badge Send random page to badge + ✨ New release available: %d Open release page Contributors Open Source