Skip to content

Commit

Permalink
Merge pull request #47 from stanwood/develop
Browse files Browse the repository at this point in the history
v1.3
  • Loading branch information
mmoczkowski authored Jun 18, 2018
2 parents bb50f37 + 6e9fef7 commit f128074
Show file tree
Hide file tree
Showing 17 changed files with 189 additions and 32 deletions.
5 changes: 3 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,15 @@ android {
minSdkVersion 21
targetSdkVersion sdk_version
versionCode 1
versionName "1.2"
versionName "1.3"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true

buildConfigField "String", "BITRISE_API_BASE_URL", "\"https://api.bitrise.io/\""
buildConfigField "int", "DEFAULT_PAGE_SIZE", "10"
buildConfigField "int", "PAGE_LOAD_THRESHOLD", "2"
buildConfigField "String", "BITRISE_API_TOKEN", "\"$bitriseApiToken\""
buildConfigField "String", "BITRISE_API_TOKEN", properties.containsKey('bitrise.api.token')? "\"$bitriseApiToken\"" : "null"
buildConfigField "String", "API_DATE_TIME_FORMAT", "\"yyyy-MM-dd'T'HH:mm:ss'Z'\""
buildConfigField "long", "DOWNLOAD_STATUS_REFRESH_DELAY", "500L"
manifestPlaceholders.appName = "@string/app_name"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ interface BitriseService {
@GET("v0.1/me")
fun login(@Header("Authorization") token: String): Deferred<Response<Me>>

@GET("v0.1/apps/{APP-SLUG}")
fun getApp(
@Header("Authorization") token: String,
@Path("APP-SLUG") appSlug: String): Deferred<Response<App>>

@GET("v0.1/me/apps")
fun getApps(
@Header("Authorization") token: String,
Expand Down
5 changes: 3 additions & 2 deletions app/src/main/java/io/stanwood/bitrise/di/ApplicationModule.kt
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package io.stanwood.bitrise.di

import android.content.SharedPreferences
import android.preference.PreferenceManager
import com.google.gson.GsonBuilder
import com.jakewharton.retrofit2.adapter.kotlin.coroutines.experimental.CoroutineCallAdapterFactory
import io.stanwood.bitrise.BuildConfig
import io.stanwood.bitrise.R
import io.stanwood.bitrise.data.net.BitriseService
import io.stanwood.bitrise.navigation.Navigator
import io.stanwood.bitrise.util.gson.GsonDateFormatAdapter
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import org.koin.android.ext.koin.androidApplication
Expand All @@ -16,6 +16,7 @@ import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import ru.terrakok.cicerone.Cicerone
import ru.terrakok.cicerone.Router
import java.util.*


val applicationModule = applicationContext {
Expand All @@ -25,7 +26,7 @@ val applicationModule = applicationContext {
*/
provide {
GsonBuilder()
.setDateFormat("yyyy-MM-dd'T'HH:mm:ss")
.registerTypeAdapter(Date::class.java, GsonDateFormatAdapter(BuildConfig.API_DATE_TIME_FORMAT))
.create()
}

Expand Down
1 change: 1 addition & 0 deletions app/src/main/java/io/stanwood/bitrise/di/Properties.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ object Properties {
const val MESSAGE = "message"
const val BRANCH = "branch"
const val WORKFLOW = "workflow"
const val FAVORITE_APPS = "fav_apps"
}
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ class ArtifactItemViewModel(
}

private fun getDownloadedApkUri(downloadId: Long): Uri {
return if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
downloadManager.getUriForDownloadedFile(downloadId)
} else {
val query = DownloadManager.Query()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,9 @@ class BuildItemViewModel(
"${build.branch}>${build.pullRequestTargetBranch}"
}

val isBuildCompleted: Boolean
get() = build.status != BuildStatus.IN_PROGRESS

fun onClick() {
router.navigateTo(SCREEN_BUILD, build)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.stanwood.bitrise.ui.dashboard.vm

import android.content.SharedPreferences
import android.content.res.Resources
import android.databinding.BaseObservable
import android.databinding.Bindable
Expand All @@ -9,6 +10,7 @@ import io.stanwood.bitrise.data.model.App
import io.stanwood.bitrise.data.model.Build
import io.stanwood.bitrise.data.model.BuildStatus
import io.stanwood.bitrise.data.net.BitriseService
import io.stanwood.bitrise.di.Properties
import io.stanwood.bitrise.navigation.SCREEN_BUILDS
import io.stanwood.bitrise.navigation.SCREEN_ERROR
import kotlinx.coroutines.experimental.Deferred
Expand All @@ -19,11 +21,12 @@ import ru.terrakok.cicerone.Router
import timber.log.Timber


class AppViewModel(
class AppItemViewModel(
private val service: BitriseService,
private val token: String,
private val resources: Resources,
private val router: Router,
private val sharedPreferences: SharedPreferences,
private val app: App) : BaseObservable() {

val title: String
Expand Down Expand Up @@ -53,6 +56,41 @@ class AppViewModel(
val buildStatusIcon: Drawable?
get() = lastBuild?.status?.getIcon(resources)

@get:Bindable
var isFavorite: Boolean
get() =
sharedPreferences
.getStringSet(Properties.FAVORITE_APPS, null)
?.contains(app.slug)
?: false
set(value) {
val favoriteAppsSlugs =
sharedPreferences
.getStringSet(Properties.FAVORITE_APPS, null)
/**
* Please note, that we cannot modify a set returned from SharedPreferences::getStringSet, thus we
* have to make a copy.
* See <a href="https://developer.android.com/reference/android/content/SharedPreferences.html#getStringSet(java.lang.String,%20java.util.Set<java.lang.String>)">SharedPreferences::getStringSet documentation</a>
*/
?.toMutableSet()
?: mutableSetOf()

if(value == favoriteAppsSlugs.contains(app.slug)) {
return
}

if(value) {
favoriteAppsSlugs.add(app.slug)
} else {
favoriteAppsSlugs.remove(app.slug)
}

sharedPreferences
.edit()
.putStringSet(Properties.FAVORITE_APPS, favoriteAppsSlugs)
.apply()
}

private var deferred: Deferred<Any?>? = null
private var lastBuild: Build? = null

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.content.SharedPreferences
import android.content.res.Resources
import android.databinding.ObservableArrayList
import android.databinding.ObservableBoolean
import io.stanwood.bitrise.data.model.App
import io.stanwood.bitrise.data.net.BitriseService
import io.stanwood.bitrise.di.Properties
import io.stanwood.bitrise.navigation.SCREEN_ERROR
Expand All @@ -26,12 +27,14 @@ class DashboardViewModel(private val router: Router,
private val resources: Resources): LifecycleObserver {

val isLoading = ObservableBoolean(false)
val items = ObservableArrayList<AppViewModel>()
val items = ObservableArrayList<AppItemViewModel>()

private var deferred: Deferred<Any>? = null
private var nextCursor: String? = null
private val shouldLoadMoreItems: Boolean
get() = !isLoading.get() && nextCursor != null
private val favoriteAppsSlugs: Set<String>?
get() = sharedPreferences.getStringSet(Properties.FAVORITE_APPS, null)

@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
fun start() {
Expand All @@ -51,6 +54,7 @@ class DashboardViewModel(private val router: Router,
loadMoreItems()
}

@Suppress("UNUSED_PARAMETER")
fun onEndOfListReached(itemCount: Int) {
if(shouldLoadMoreItems) {
loadMoreItems()
Expand All @@ -70,7 +74,8 @@ class DashboardViewModel(private val router: Router,
deferred = async(UI) {
try {
isLoading.set(true)
fetchItems()

fetchAllApps()
.forEach { viewModel ->
viewModel.start()
items.add(viewModel)
Expand All @@ -84,11 +89,27 @@ class DashboardViewModel(private val router: Router,
}
}

private suspend fun fetchItems() =
private suspend fun fetchFavoriteApps(): List<App> =
favoriteAppsSlugs
?.map {
service
.getApp(token, it)
.await()
.data
}
?: emptyList()

private suspend fun fetchNonFavoriteApps(): List<App> =
service
.getApps(token, nextCursor)
.await()
.apply { nextCursor = paging.nextCursor }
.data
.map { app -> AppViewModel(service, token, resources, router, app) }
.filter { !(favoriteAppsSlugs?.contains(it.slug) ?: false) }


private suspend fun fetchAllApps() =
listOf(if(items.isEmpty()) fetchFavoriteApps() else emptyList(), fetchNonFavoriteApps())
.flatten()
.map { app -> AppItemViewModel(service, token, resources, router, sharedPreferences, app) }
}
44 changes: 26 additions & 18 deletions app/src/main/java/io/stanwood/bitrise/ui/login/vm/LoginViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,17 @@ class LoginViewModel(
@get:Bindable
var token: String?
set(value) {
if (value?.isBlank() == true) {
/**
* Sanity check. We don't want to store an empty or null token.
*/
return
}
setProperty(Properties.TOKEN, value)
sharedPreferences
.edit()
.putString(Properties.TOKEN, value)
.apply()
.edit()
.putString(Properties.TOKEN, value)
.apply()
}
get() = sharedPreferences.getString(Properties.TOKEN, BuildConfig.BITRISE_API_TOKEN)

Expand All @@ -60,22 +66,24 @@ class LoginViewModel(
}
}

private suspend fun tryLogin(newToken: String) {
try {
isLoading.set(true)
service
.login(newToken)
.await()
token = newToken
router.newRootScreen(SCREEN_DASHBOARD)
} catch (exception: Exception) {
if(exception is HttpException && exception.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
isError.set(true)
private suspend fun tryLogin(newToken: String?) {
newToken?.let {
try {
isLoading.set(true)
service
.login(it)
.await()
token = it
router.newRootScreen(SCREEN_DASHBOARD)
} catch (exception: Exception) {
if (exception is HttpException && exception.code() == HttpURLConnection.HTTP_UNAUTHORIZED) {
isError.set(true)
}
router.navigateTo(SCREEN_ERROR, exception.message)
Timber.e(exception)
} finally {
isLoading.set(false)
}
router.navigateTo(SCREEN_ERROR, exception.message)
Timber.e(exception)
} finally {
isLoading.set(false)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package io.stanwood.bitrise.util.gson

import com.google.gson.JsonDeserializationContext
import com.google.gson.JsonDeserializer
import com.google.gson.JsonElement
import com.google.gson.JsonParseException
import com.google.gson.JsonPrimitive
import com.google.gson.JsonSerializationContext
import com.google.gson.JsonSerializer

import java.lang.reflect.Type
import java.text.DateFormat
import java.text.ParseException
import java.text.SimpleDateFormat
import java.util.*

class GsonDateFormatAdapter(private val format: String) : JsonSerializer<Date>, JsonDeserializer<Date> {

private val dateFormat: DateFormat by lazy {
SimpleDateFormat(format, Locale.getDefault()).apply {
timeZone = TimeZone.getTimeZone("UTC")
}
}

@Synchronized
override fun serialize(date: Date, type: Type, jsonSerializationContext: JsonSerializationContext): JsonElement {
return JsonPrimitive(dateFormat.format(date))
}

@Synchronized
override fun deserialize(jsonElement: JsonElement, type: Type, jsonDeserializationContext: JsonDeserializationContext): Date {
try {
return dateFormat.parse(jsonElement.asString)
} catch (e: ParseException) {
throw JsonParseException(e)
}

}
}
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_favorite_checked.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/favorite_checkbox"
android:pathData="M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z" />
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/drawable/ic_favorite_unchecked.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="@color/favorite_checkbox"
android:pathData="M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z" />
</vector>
6 changes: 6 additions & 0 deletions app/src/main/res/drawable/selector_favorite.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>

<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/ic_favorite_unchecked" android:state_checked="false" />
<item android:drawable="@drawable/ic_favorite_checked" android:state_checked="true" />
</selector>
4 changes: 2 additions & 2 deletions app/src/main/res/layout/fragment_login.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,15 +51,15 @@
app:layout_constraintTop_toBottomOf="@+id/logo">

<android.support.v7.widget.AppCompatEditText
android:id="@+id/test"
onTextSubmitted="@{(text) -> vm.onTokenEntered(text)}"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="textPassword"
android:text="@={vm.token}" />
android:text="@{vm.token}" />

</android.support.design.widget.TextInputLayout>


<android.support.v7.widget.AppCompatTextView
android:id="@+id/token_instructions"
enableLinks="@{true}"
Expand Down
Loading

0 comments on commit f128074

Please sign in to comment.