Added updates check

This commit is contained in:
Abdelilah El Aissaoui 2022-04-04 02:48:40 +02:00
parent d3fd889e02
commit 8f92b6d195
11 changed files with 233 additions and 123 deletions

View File

@ -11,6 +11,7 @@ plugins {
id("org.jetbrains.compose") version "1.1.1" id("org.jetbrains.compose") version "1.1.1"
} }
// Remember to update Constants.APP_VERSION when changing this version
val projectVersion = "0.1.0" val projectVersion = "0.1.0"
val projectName = "Gitnuro" val projectName = "Gitnuro"
@ -23,8 +24,6 @@ repositories {
maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") } maven { url = uri("https://maven.pkg.jetbrains.space/public/p/compose/dev") }
} }
dependencies { dependencies {
implementation(compose.desktop.currentOs) implementation(compose.desktop.currentOs)
@OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class) @OptIn(org.jetbrains.compose.ExperimentalComposeLibrary::class)
@ -37,6 +36,8 @@ dependencies {
testImplementation(platform("org.junit:junit-bom:5.8.2")) testImplementation(platform("org.junit:junit-bom:5.8.2"))
testImplementation("org.junit.jupiter:junit-jupiter") testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("io.mockk:mockk:1.12.3") testImplementation("io.mockk:mockk:1.12.3")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-scalars:2.9.0")
} }
tasks.test { tasks.test {

View File

@ -48,7 +48,6 @@ class App {
init { init {
appComponent.inject(this) appComponent.inject(this)
println("AppStateManagerReference $appStateManager")
} }
private val tabsFlow = MutableStateFlow<List<TabInformation>>(emptyList()) private val tabsFlow = MutableStateFlow<List<TabInformation>>(emptyList())

View File

@ -0,0 +1,8 @@
package app
object AppConstants {
// Remember to update build.gradle when changing this
const val APP_VERSION = "0.1.0"
const val APP_VERSION_CODE = 1
const val VERSION_CHECK_URL = "https://raw.githubusercontent.com/JetpackDuba/Gitnuro/main/latest.json"
}

View File

@ -1,10 +1,18 @@
package app.di package app.di
import app.di.modules.NetworkModule
import app.ui.components.TabInformation import app.ui.components.TabInformation
import dagger.Component import dagger.Component
@TabScope @TabScope
@Component(dependencies = [AppComponent::class]) @Component(
modules = [
NetworkModule::class,
],
dependencies = [
AppComponent::class
],
)
interface TabComponent { interface TabComponent {
fun inject(tabInformation: TabInformation) fun inject(tabInformation: TabInformation)
} }

View File

@ -0,0 +1,20 @@
package app.di.modules
import app.updates.UpdatesService
import dagger.Provides
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.scalars.ScalarsConverterFactory
import javax.inject.Singleton
@dagger.Module
class NetworkModule {
@Provides
fun provideWebService(): UpdatesService {
return Retrofit.Builder()
.baseUrl("https://github.com")
.addConverterFactory(ScalarsConverterFactory.create())
.build()
.create(UpdatesService::class.java)
}
}

View File

@ -13,16 +13,19 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.BiasAlignment import androidx.compose.ui.BiasAlignment
import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.ExperimentalComposeUiApi
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp import androidx.compose.ui.unit.sp
import app.AppConstants
import app.extensions.dirName import app.extensions.dirName
import app.extensions.dirPath import app.extensions.dirPath
import app.theme.primaryTextColor import app.theme.primaryTextColor
import app.theme.secondaryTextColor import app.theme.secondaryTextColor
import app.ui.dialogs.CloneDialog import app.ui.dialogs.CloneDialog
import app.updates.Update
import app.viewmodels.TabViewModel import app.viewmodels.TabViewModel
import openDirectoryDialog import openDirectoryDialog
import openRepositoryDialog import openRepositoryDialog
@ -37,148 +40,165 @@ fun WelcomePage(
) { ) {
val appStateManager = tabViewModel.appStateManager val appStateManager = tabViewModel.appStateManager
var showCloneView by remember { mutableStateOf(false) } var showCloneView by remember { mutableStateOf(false) }
var newUpdate by remember { mutableStateOf<Update?>(null) }
// Crossfade(showCloneView) { LaunchedEffect(Unit) {
// if(it) { val latestRelease = tabViewModel.latestRelease()
if(latestRelease != null && latestRelease.appCode > AppConstants.APP_VERSION_CODE) {
newUpdate = latestRelease
}
}
// } else { Box(
Row(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background(MaterialTheme.colors.background), .background(MaterialTheme.colors.background),
horizontalArrangement = Arrangement.Center, ) {
verticalAlignment = BiasAlignment.Vertical(-0.5f), Row(
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.Top,
modifier = Modifier.align(BiasAlignment(0f, -0.5f))
) { ) {
Column( Column(
modifier = Modifier modifier = Modifier.padding(end = 32.dp),
.padding(end = 32.dp), ) {
verticalArrangement = Arrangement.Center, Text(
) { text = "Gitnuro",
Text( fontSize = 32.sp,
text = "Gitnuro", color = MaterialTheme.colors.primaryTextColor,
fontSize = 32.sp, modifier = Modifier.padding(bottom = 16.dp),
color = MaterialTheme.colors.primaryTextColor, )
modifier = Modifier
.padding(bottom = 16.dp),
)
ButtonTile( ButtonTile(
modifier = Modifier modifier = Modifier.padding(bottom = 8.dp),
.padding(bottom = 8.dp), title = "Open a repository",
title = "Open a repository", painter = painterResource("open.svg"),
painter = painterResource("open.svg"), onClick = { openRepositoryDialog(tabViewModel) })
onClick = { openRepositoryDialog(tabViewModel) }
)
ButtonTile( ButtonTile(
modifier = Modifier modifier = Modifier.padding(bottom = 8.dp),
.padding(bottom = 8.dp), title = "Clone a repository",
title = "Clone a repository", painter = painterResource("download.svg"),
painter = painterResource("download.svg"), onClick = {
onClick = { showCloneView = true
showCloneView = true }
)
ButtonTile(
modifier = Modifier.padding(bottom = 8.dp),
title = "Start a local repository",
painter = painterResource("open.svg"),
onClick = {
val dir = openDirectoryDialog()
if (dir != null) tabViewModel.initLocalRepository(dir)
}
)
Text(
text = "About Gitnuro",
fontSize = 18.sp,
color = MaterialTheme.colors.primaryTextColor,
modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
)
IconTextButton(
title = "Source code",
painter = painterResource("code.svg"),
onClick = {
Desktop.getDesktop().browse(URI("https://github.com/JetpackDuba/Gitnuro"))
}
)
IconTextButton(
title = "Report a bug",
painter = painterResource("bug.svg"),
onClick = {
Desktop.getDesktop().browse(URI("https://github.com/JetpackDuba/Gitnuro/issues"))
}
)
if(newUpdate != null) {
IconTextButton(
title = "New update ${newUpdate?.appVersion} available ",
painter = painterResource("grade.svg"),
iconColor = MaterialTheme.colors.secondary,
onClick = {
newUpdate?.downloadUrl?.let {
Desktop.getDesktop().browse(URI(it))
}
}
)
} }
) }
ButtonTile( Column(
modifier = Modifier modifier = Modifier
.padding(bottom = 8.dp), .padding(start = 32.dp),
title = "Start a local repository", ) {
painter = painterResource("open.svg"), Text(
onClick = { text = "Recent",
val dir = openDirectoryDialog() fontSize = 18.sp,
if (dir != null) modifier = Modifier.padding(top = 48.dp, bottom = 8.dp),
tabViewModel.initLocalRepository(dir) color = MaterialTheme.colors.primaryTextColor,
} )
) LazyColumn {
items(items = appStateManager.latestOpenedRepositoriesPaths) { repo ->
val repoDirName = repo.dirName
val repoDirPath = repo.dirPath
Text( Row(
text = "About Gitnuro", verticalAlignment = Alignment.CenterVertically,
fontSize = 18.sp, ) {
color = MaterialTheme.colors.primaryTextColor, TextButton(
modifier = Modifier onClick = {
.padding(top = 16.dp, bottom = 8.dp),
)
IconTextButton(
title = "Source code",
painter = painterResource("code.svg"),
onClick = {
Desktop.getDesktop().browse(URI("https://github.com/JetpackDuba/Gitnuro"))
}
)
IconTextButton(
title = "Report a bug",
painter = painterResource("bug.svg"),
onClick = {
Desktop.getDesktop().browse(URI("https://github.com/JetpackDuba/Gitnuro/issues"))
}
)
}
Column(
modifier = Modifier
.padding(start = 32.dp),
) {
Text(
text = "Recent",
fontSize = 18.sp,
modifier = Modifier
.padding(top = 16.dp, bottom = 8.dp),
color = MaterialTheme.colors.primaryTextColor,
)
LazyColumn {
items(items = appStateManager.latestOpenedRepositoriesPaths) { repo ->
val repoDirName = repo.dirName
val repoDirPath = repo.dirPath
Row(
verticalAlignment = Alignment.CenterVertically,
) {
TextButton(
onClick = {
tabViewModel.openRepository(repo) tabViewModel.openRepository(repo)
} }
) { ) {
Text(
text = repoDirName,
fontSize = 14.sp,
color = MaterialTheme.colors.primary,
)
}
Text( Text(
text = repoDirName, text = repoDirPath,
fontSize = 14.sp, fontSize = 14.sp,
color = MaterialTheme.colors.primary, modifier = Modifier.padding(start = 4.dp),
color = MaterialTheme.colors.secondaryTextColor
) )
} }
Text(
text = repoDirPath,
fontSize = 14.sp,
modifier = Modifier.padding(start = 4.dp),
color = MaterialTheme.colors.secondaryTextColor
)
} }
} }
} }
} }
Text(
"Version ${AppConstants.APP_VERSION}",
color = MaterialTheme.colors.primaryTextColor,
fontSize = 12.sp,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(bottom = 16.dp, end = 16.dp)
)
} }
LaunchedEffect(showCloneView) { LaunchedEffect(showCloneView) {
if (showCloneView) if (showCloneView) tabViewModel.cloneViewModel.reset() // Reset dialog before showing it
tabViewModel.cloneViewModel.reset() // Reset dialog before showing it
} }
if (showCloneView) if (showCloneView) CloneDialog(
CloneDialog( tabViewModel.cloneViewModel,
tabViewModel.cloneViewModel, onClose = {
onClose = { showCloneView = false
showCloneView = false tabViewModel.cloneViewModel.reset()
tabViewModel.cloneViewModel.reset() },
}, onOpenRepository = { dir ->
onOpenRepository = { dir -> tabViewModel.openRepository(dir)
tabViewModel.openRepository(dir) },
}, )
)
} }
@Composable @Composable
@ -217,6 +237,7 @@ fun IconTextButton(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
title: String, title: String,
painter: Painter, painter: Painter,
iconColor: Color = MaterialTheme.colors.primary,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
TextButton( TextButton(
@ -224,17 +245,14 @@ fun IconTextButton(
modifier = modifier.size(width = 280.dp, height = 40.dp) modifier = modifier.size(width = 280.dp, height = 40.dp)
) { ) {
Row( Row(
modifier = Modifier modifier = Modifier.fillMaxSize(),
.fillMaxSize(),
verticalAlignment = Alignment.CenterVertically, verticalAlignment = Alignment.CenterVertically,
) { ) {
Image( Image(
modifier = Modifier modifier = Modifier.padding(end = 8.dp).size(24.dp),
.padding(end = 8.dp)
.size(24.dp),
painter = painter, painter = painter,
contentDescription = null, contentDescription = null,
colorFilter = ColorFilter.tint(MaterialTheme.colors.primary), colorFilter = ColorFilter.tint(iconColor),
) )
Text( Text(

View File

@ -0,0 +1,10 @@
package app.updates
import kotlinx.serialization.Serializable
@Serializable
data class Update(
val appVersion: String,
val appCode: Int,
val downloadUrl: String,
)

View File

@ -0,0 +1,23 @@
package app.updates
import app.AppConstants
import app.AppPreferences
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import javax.inject.Inject
private val updateJson = Json {
this.ignoreUnknownKeys = true
}
class UpdatesRepository @Inject constructor(
private val updatesWebService: UpdatesService,
) {
suspend fun latestRelease(): Update? = withContext(Dispatchers.IO) {
val latestReleaseJson = updatesWebService.release(AppConstants.VERSION_CHECK_URL)
updateJson.decodeFromString(latestReleaseJson)
}
}

View File

@ -0,0 +1,9 @@
package app.updates
import retrofit2.http.GET
import retrofit2.http.Url
interface UpdatesService {
@GET
suspend fun release(@Url url: String): String
}

View File

@ -1,5 +1,6 @@
package app.viewmodels package app.viewmodels
import app.AppPreferences
import app.AppStateManager import app.AppStateManager
import app.ErrorsManager import app.ErrorsManager
import app.credentials.CredentialsState import app.credentials.CredentialsState
@ -7,6 +8,8 @@ import app.credentials.CredentialsStateManager
import app.git.* import app.git.*
import app.newErrorNow import app.newErrorNow
import app.ui.SelectedItem import app.ui.SelectedItem
import app.updates.Update
import app.updates.UpdatesRepository
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
@ -39,6 +42,7 @@ class TabViewModel @Inject constructor(
private val tabState: TabState, private val tabState: TabState,
val appStateManager: AppStateManager, val appStateManager: AppStateManager,
private val fileChangesWatcher: FileChangesWatcher, private val fileChangesWatcher: FileChangesWatcher,
private val updatesRepository: UpdatesRepository,
) { ) {
val errorsManager: ErrorsManager = tabState.errorsManager val errorsManager: ErrorsManager = tabState.errorsManager
val selectedItem: StateFlow<SelectedItem> = tabState.selectedItem val selectedItem: StateFlow<SelectedItem> = tabState.selectedItem
@ -262,6 +266,15 @@ class TabViewModel @Inject constructor(
repositoryManager.initLocalRepo(repoDir) repositoryManager.initLocalRepo(repoDir)
openRepository(repoDir) openRepository(repoDir)
} }
suspend fun latestRelease(): Update? = withContext(Dispatchers.IO) {
try {
updatesRepository.latestRelease()
} catch (ex: Exception) {
ex.printStackTrace()
null
}
}
} }

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#000000"><path d="M0 0h24v24H0z" fill="none"/><path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z"/></svg>

After

Width:  |  Height:  |  Size: 245 B