Skip to content
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

feat(pluto): version upgrade nudge added #266

Merged
merged 7 commits into from
Sep 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 10 additions & 0 deletions pluto/lib/build.gradle
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'com.google.devtools.ksp' version '1.8.21-1.0.11'
}

apply from: "$rootDir/scripts/build/utils.gradle"
Expand Down Expand Up @@ -58,4 +59,13 @@ android {
dependencies {
api project(path: ':pluto-plugins:base:lib')
// implementation project(path: ':pluto-tools')

implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.9.1'
implementation 'com.squareup.okhttp3:okhttp:4.9.1'
implementation 'com.squareup.okio:okio:2.10.0'

implementation "com.squareup.moshi:moshi:$moshiVersion"
ksp "com.squareup.moshi:moshi-kotlin-codegen:$moshiVersion"
}
12 changes: 12 additions & 0 deletions pluto/lib/src/main/java/com/pluto/core/network/DataModels.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.pluto.core.network

import com.squareup.moshi.JsonClass

internal sealed class ResponseWrapper<out T> {
data class Success<out T>(val body: T) : ResponseWrapper<T>()
data class Failure(val error: ErrorResponse, val errorString: String? = null) :
ResponseWrapper<Nothing>()
}

@JsonClass(generateAdapter = true)
internal data class ErrorResponse(val reason: String?, val error: String)
39 changes: 39 additions & 0 deletions pluto/lib/src/main/java/com/pluto/core/network/Network.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package com.pluto.core.network

import java.util.concurrent.TimeUnit
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory

internal object Network {

private const val READ_TIMEOUT = 30L

private val retrofit: Retrofit by lazy {
Retrofit.Builder()
.baseUrl("https://api.pluto.com")
.addConverterFactory(MoshiConverterFactory.create())
.client(okHttpClient)
.build()
}

private val okHttpClient: OkHttpClient = OkHttpClient.Builder()
.readTimeout(READ_TIMEOUT, TimeUnit.SECONDS)
.addInterceptors()
.build()

fun <T> getService(cls: Class<T>): T {
return retrofit.create(cls)
}

inline fun <reified T> getService(): Lazy<T> {
return lazy {
getService(T::class.java)
}
}
}

private fun OkHttpClient.Builder.addInterceptors(): OkHttpClient.Builder {
// addInterceptor(HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY))
return this
}
72 changes: 72 additions & 0 deletions pluto/lib/src/main/java/com/pluto/core/network/NetworkCalls.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.pluto.core.network

import android.util.Log
import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.Moshi
import java.io.IOException
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import retrofit2.HttpException

@Suppress("TooGenericExceptionCaught")
internal suspend fun <T> enqueue(
dispatcher: CoroutineDispatcher = Dispatchers.IO,
apiCall: suspend () -> T
): ResponseWrapper<T> {

return withContext(dispatcher) {
try {
ResponseWrapper.Success(apiCall.invoke())
} catch (throwable: Throwable) {
Log.e("network_error", "network failure", throwable)
when (throwable) {
is IOException -> ResponseWrapper.Failure(
ErrorResponse(
"IO_Exception", throwable.message ?: DEFAULT_ERROR_MESSAGE
)
)
is HttpException -> ResponseWrapper.Failure(convertErrorBody(throwable))
else -> ResponseWrapper.Failure(
ErrorResponse(CONVERSION_FAILURE, DEFAULT_ERROR_MESSAGE)
)
}
}
}
}

@Suppress("TooGenericExceptionCaught")
private fun convertErrorBody(throwable: HttpException): ErrorResponse {
val moshiAdapter: JsonAdapter<ErrorResponse> = Moshi.Builder().build().adapter(ErrorResponse::class.java)
val errorString = throwable.response()?.errorBody()?.string()
return if (!errorString.isNullOrEmpty()) {
try {
run {
val error = moshiAdapter.fromJson(errorString)
validateError(error)
error ?: ErrorResponse(VALIDATION_ERROR_MESSAGE, DEFAULT_ERROR_MESSAGE)
}
} catch (exception: Exception) {
Log.e(
"network_error",
exception.message.toString(),
exception
)
ErrorResponse(CONVERSION_FAILURE, DEFAULT_ERROR_MESSAGE)
}
} else {
ErrorResponse(UPSTREAM_FAILURE, EMPTY_ERROR_MESSAGE)
}
}

private fun validateError(error: ErrorResponse?) {
if (error?.error == null) { // TODO handle deserialization issue
throw KotlinNullPointerException("response.error value null")
}
}

private const val DEFAULT_ERROR_MESSAGE = "Something went wrong!"
private const val EMPTY_ERROR_MESSAGE = "empty error response"
private const val VALIDATION_ERROR_MESSAGE = "validation_error_message"
private const val UPSTREAM_FAILURE = "upstream_failure"
private const val CONVERSION_FAILURE = "response_conversion_failure"
20 changes: 20 additions & 0 deletions pluto/lib/src/main/java/com/pluto/maven/DataModels.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.pluto.maven

import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
internal data class MavenData(
val response: MavenResponse
)

@JsonClass(generateAdapter = true)
internal data class MavenResponse(
val numFound: Int,
val docs: List<MavenArtifactDetails>
)

@JsonClass(generateAdapter = true)
internal data class MavenArtifactDetails(
val id: String,
val latestVersion: String
)
9 changes: 9 additions & 0 deletions pluto/lib/src/main/java/com/pluto/maven/MavenApiService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.pluto.maven

import retrofit2.http.GET

internal interface MavenApiService {

@GET("https://search.maven.org/solrsearch/select?q=g:com.plutolib+AND+a:pluto")
suspend fun getLatestVersion(): MavenData
}
8 changes: 8 additions & 0 deletions pluto/lib/src/main/java/com/pluto/maven/MavenSession.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.pluto.maven

internal object MavenSession {
var alreadySynced: Boolean = false
var latestVersion: String? = null
val releaseUrl: String?
get() = latestVersion?.let { "https://github.com/androidPluto/pluto/releases/tag/v$it" } ?: run { null }
}
42 changes: 42 additions & 0 deletions pluto/lib/src/main/java/com/pluto/maven/MavenViewModel.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.pluto.maven

import androidx.lifecycle.LiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.pluto.BuildConfig
import com.pluto.core.network.Network
import com.pluto.core.network.ResponseWrapper
import com.pluto.core.network.enqueue
import com.pluto.utilities.SingleLiveEvent
import kotlinx.coroutines.launch

class MavenViewModel : ViewModel() {

private val apiService: MavenApiService by Network.getService()
val latestVersion: LiveData<String>
get() = _latestVersion
private val _latestVersion = SingleLiveEvent<String>()

fun getLatestVersion() {
viewModelScope.launch {
if (!MavenSession.alreadySynced) {
MavenSession.latestVersion = when (val auth = enqueue { apiService.getLatestVersion() }) {
is ResponseWrapper.Success -> retrieveLatestResponse(auth.body.response)
is ResponseWrapper.Failure -> null
}
MavenSession.alreadySynced = true
}
MavenSession.latestVersion?.let {
_latestVersion.postValue(it)
}
}
}

private fun retrieveLatestResponse(response: MavenResponse): String? {
return if (response.docs.isNotEmpty() && BuildConfig.VERSION_NAME != response.docs[0].latestVersion) {
response.docs[0].latestVersion
} else {
null
}
}
}
18 changes: 18 additions & 0 deletions pluto/lib/src/main/java/com/pluto/ui/selector/SelectorActivity.kt
Original file line number Diff line number Diff line change
@@ -1,20 +1,25 @@
package com.pluto.ui.selector

import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View.GONE
import android.view.View.VISIBLE
import android.view.animation.Animation
import android.view.animation.AnimationUtils
import androidx.activity.viewModels
import androidx.annotation.AnimRes
import androidx.core.view.isVisible
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager
import com.pluto.Pluto
import com.pluto.R
import com.pluto.core.applifecycle.AppStateCallback
import com.pluto.databinding.PlutoActivityPluginSelectorBinding
import com.pluto.maven.MavenSession
import com.pluto.maven.MavenViewModel
import com.pluto.plugin.Plugin
import com.pluto.plugin.PluginEntity
import com.pluto.plugin.PluginGroup
Expand Down Expand Up @@ -43,6 +48,7 @@ class SelectorActivity : FragmentActivity() {
private val toolsViewModel by viewModels<ToolsViewModel>()
private val toolAdapter: BaseAdapter by lazy { ToolAdapter(onActionListener) }
private val settingsViewModel by viewModels<SettingsViewModel>()
private val mavenViewModel by viewModels<MavenViewModel>()
private lateinit var binding: PlutoActivityPluginSelectorBinding

override fun onCreate(savedInstanceState: Bundle?) {
Expand Down Expand Up @@ -78,6 +84,9 @@ class SelectorActivity : FragmentActivity() {
binding.overlaySetting.setOnDebounceClickListener {
openOverlaySettings()
}
binding.mavenVersion.setOnDebounceClickListener {
startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(MavenSession.releaseUrl)))
}

Pluto.appStateCallback.state.removeObserver(appStateListener)
Pluto.appStateCallback.state.observe(this, appStateListener)
Expand All @@ -86,6 +95,8 @@ class SelectorActivity : FragmentActivity() {
pluginsViewModel.plugins.observe(this, pluginListObserver)
toolsViewModel.tools.removeObserver(toolListObserver)
toolsViewModel.tools.observe(this, toolListObserver)
mavenViewModel.latestVersion.removeObserver(mavenVersionObserver)
mavenViewModel.latestVersion.observe(this, mavenVersionObserver)

settingsViewModel.resetAll.observe(this) {
Pluto.pluginManager.installedPlugins.forEach {
Expand All @@ -96,6 +107,8 @@ class SelectorActivity : FragmentActivity() {
}
Pluto.resetDataCallback.state.postValue(true)
}

mavenViewModel.getLatestVersion()
}

private val pluginListObserver = Observer<List<PluginEntity>> {
Expand All @@ -109,6 +122,11 @@ class SelectorActivity : FragmentActivity() {
toolAdapter.list = it
}

private val mavenVersionObserver = Observer<String> {
binding.mavenVersion.isVisible = !it.isNullOrEmpty()
binding.mavenVersion.text = String.format(getString(R.string.pluto___new_version_available_text), it)
}

private val appStateListener = Observer<AppStateCallback.State> {
if (it is AppStateCallback.State.Background) {
finish()
Expand Down
29 changes: 24 additions & 5 deletions pluto/lib/src/main/res/layout/pluto___activity_plugin_selector.xml
Original file line number Diff line number Diff line change
Expand Up @@ -157,22 +157,21 @@
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pluto___dark_60"
android:paddingHorizontal="@dimen/pluto___margin_medium"
android:paddingVertical="@dimen/pluto___margin_medium">
android:background="@color/pluto___dark_60">

<TextView
android:id="@+id/version"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginVertical="@dimen/pluto___margin_medium"
android:layout_marginStart="@dimen/pluto___margin_xsmall"
android:drawablePadding="@dimen/pluto___margin_mini"
android:layout_marginBottom="@dimen/pluto___margin_medium"
android:fontFamily="@font/muli"
android:gravity="center"
android:paddingHorizontal="@dimen/pluto___margin_xsmall"
android:textColor="@color/pluto___white_80"
android:textSize="@dimen/pluto___text_small"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintBottom_toTopOf="@+id/mavenVersion"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="version" />
Expand All @@ -194,6 +193,26 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/version" />

<TextView
android:id="@+id/mavenVersion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#FFC107"
android:visibility="gone"
android:drawableEnd="@drawable/pluto___ic_chevron_right"
android:drawableRight="@drawable/pluto___ic_chevron_right"
android:ellipsize="end"
android:fontFamily="@font/muli_bold"
android:foreground="?android:attr/selectableItemBackground"
android:gravity="center_vertical"
android:maxLines="1"
android:padding="@dimen/pluto___margin_small"
android:singleLine="true"
android:text="@string/pluto___new_version_available_text"
android:textColor="@color/pluto___dark_80"
android:textSize="@dimen/pluto___text_small"
app:layout_constraintBottom_toBottomOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

</com.pluto.ui.RoundedFrameLayout>
Expand Down
1 change: 1 addition & 0 deletions pluto/lib/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,5 @@
<string name="pluto___setting_theme_updated">Pluto theme updated</string>
<string name="pluto___settings_grid_size_title">Modify Grid dimensions</string>
<string name="pluto___settings_grid_size_description">Manage the size of grid blocks of Ruler, GridView &amp; other layout plugins.</string>
<string name="pluto___new_version_available_text">New version v%s available</string>
</resources>
Loading