Skip to content

Commit

Permalink
Merge branch 'feature/shortcuts' into master
Browse files Browse the repository at this point in the history
  • Loading branch information
AmkSk committed Dec 6, 2020
2 parents 9b27212 + 34c77a3 commit 8c30ead
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 123 deletions.
17 changes: 9 additions & 8 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ android {
applicationId "sk.amk.homeberry"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
versionCode 2
versionName "1.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
Expand All @@ -31,29 +31,30 @@ android {

dependencies {
// Android
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.activity:activity-ktx:1.1.0"

// Kotlin
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.5'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.1'

// Room
def room_version = "2.2.5"
implementation "androidx.room:room-runtime:$room_version"
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"

// Network
implementation "io.ktor:ktor-client-android:1.3.2"
implementation "io.ktor:ktor-client-logging-jvm:1.3.2"

// UI
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'com.google.android.material:material:1.2.1'
implementation 'com.afollestad.material-dialogs:core:3.3.0'
implementation 'com.afollestad.material-dialogs:files:3.3.0'

// Others
implementation "com.squareup.moshi:moshi-kotlin:1.9.2"
implementation "com.squareup.moshi:moshi-kotlin:1.10.0"
}
2 changes: 1 addition & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

<application
android:allowBackup="true"
android:allowBackup="false"
android:icon="@drawable/ic_icons8_raspberry_pi"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_icons8_raspberry_pi"
Expand Down
133 changes: 96 additions & 37 deletions app/src/main/java/sk/amk/homeberry/main/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ package sk.amk.homeberry.main
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.content.pm.ShortcutInfo
import android.content.pm.ShortcutManager
import android.graphics.drawable.Icon
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.util.TypedValue
import android.view.Menu
Expand All @@ -14,9 +18,9 @@ import android.widget.LinearLayout
import android.widget.TextView
import android.widget.Toast
import androidx.activity.viewModels
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.lifecycle.Observer
import com.afollestad.materialdialogs.MaterialDialog
import com.afollestad.materialdialogs.callbacks.onCancel
import com.afollestad.materialdialogs.customview.customView
Expand All @@ -39,29 +43,18 @@ class MainActivity : AppCompatActivity() {
setSupportActionBar(toolbar)
title = ""

viewModel.requests.observe(this, Observer { requests ->
txtEmptyState.isVisible = requests.isEmpty()
buttonOpenSettings.isVisible = requests.isEmpty()
viewModel.requests.observe(this, this::observeRequests)
viewModel.state.observe(this, this::observeVmState)

if (requests.isNotEmpty()) {
generateButtons(requests)
}
})

viewModel.state.observe(this, Observer { state ->
if (state !is MainState.RequestInProgress) {
progressDialog?.dismiss()
}
buttonOpenSettings.setOnClickListener { openSettings() }
}

when (state) {
is MainState.RequestInProgress -> showProgressDialog(state.request!!)
is MainState.RequestSuccess -> handleSuccess(state.message, state.request!!)
is MainState.RequestFailure -> handleError(state.message, state.request!!)
is MainState.RequestFailureConnection -> showConnectionErrorDialog()
}
})
override fun onResume() {
super.onResume()

buttonOpenSettings.setOnClickListener { openSettings() }
intent.dataString?.toLong()?.let {
viewModel.callRequest(it)
}
}

override fun onPause() {
Expand All @@ -70,6 +63,47 @@ class MainActivity : AppCompatActivity() {
super.onPause()
}

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_main_power -> showShutDownDialog()
R.id.menu_main_restart -> showRebootDialog()
R.id.menu_main_settings -> openSettings()
}

return super.onOptionsItemSelected(item)
}

private fun observeVmState(state: MainState){
if (state !is MainState.RequestInProgress) {
progressDialog?.dismiss()
}

when (state) {
is MainState.RequestInProgress -> showProgressDialog(state.request!!)
is MainState.RequestSuccess -> handleSuccess(state.message, state.request!!)
is MainState.RequestFailure -> handleError(state.message, state.request!!)
is MainState.RequestFailureConnection -> showConnectionErrorDialog()
}
}

private fun observeRequests(requests: List<HomeberryRequest>) {
txtEmptyState.isVisible = requests.isEmpty()
buttonOpenSettings.isVisible = requests.isEmpty()

if (requests.isNotEmpty()) {
generateButtons(requests)
}

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
createShortcuts(requests)
}
}

private fun generateButtons(requests: List<HomeberryRequest>) {
for (request in requests) {
val button = Button(this)
Expand All @@ -94,21 +128,6 @@ class MainActivity : AppCompatActivity() {
).toInt()
}

override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main, menu)
return true
}

override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_main_power -> showShutDownDialog()
R.id.menu_main_restart -> showRebootDialog()
R.id.menu_main_settings -> openSettings()
}

return super.onOptionsItemSelected(item)
}

private fun showRebootDialog() {
MaterialDialog(this).show {
title(res = R.string.are_you_sure)
Expand Down Expand Up @@ -157,6 +176,11 @@ class MainActivity : AppCompatActivity() {
).show()
}
}

// close when app launched from shortcut
if (intent.dataString != null) {
finish()
}
}

private fun handleError(errorMessage: String, request: HomeberryRequest) {
Expand All @@ -180,6 +204,40 @@ class MainActivity : AppCompatActivity() {
startActivity(Intent(this, SettingsActivity::class.java))
}

@RequiresApi(Build.VERSION_CODES.N_MR1)
private fun createShortcuts(requests: List<HomeberryRequest>) {
val shortcutManager = getSystemService(ShortcutManager::class.java)
shortcutManager.removeAllDynamicShortcuts()

val shortcuts: MutableList<ShortcutInfo> = mutableListOf()

val shortcutsCount = if (requests.size >= MAX_DYNAMIC_SHORTCUTS_COUNT) {
MAX_DYNAMIC_SHORTCUTS_COUNT
} else {
requests.size
}

for (i in 0 until shortcutsCount) {
val request = requests[i]

val shortcut = ShortcutInfo.Builder(this, request.id.toString())
.setShortLabel(request.name)
.setLongLabel(request.name)
.setIcon(Icon.createWithResource(this, R.drawable.ic_icons8_raspberry_pi))
.setIntent(
Intent(this, MainActivity::class.java).apply {
flags = Intent.FLAG_ACTIVITY_CLEAR_TASK
action = Intent.ACTION_VIEW
data = Uri.parse(request.id.toString())
})
.build()

shortcuts.add(shortcut)
}

shortcutManager!!.dynamicShortcuts = shortcuts
}

/** Open another app.
* @param context current Context, like Activity, App, or Service
* @param packageName the full package name of the app to open
Expand All @@ -199,5 +257,6 @@ class MainActivity : AppCompatActivity() {

companion object {
const val BUTTON_MARGIN_DP = 16f
const val MAX_DYNAMIC_SHORTCUTS_COUNT = 4
}
}
57 changes: 40 additions & 17 deletions app/src/main/java/sk/amk/homeberry/main/MainViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import sk.amk.homeberry.HomeberryApp
import sk.amk.homeberry.model.HomeberryRequest
Expand All @@ -25,14 +26,17 @@ import java.net.ConnectException
*/
class MainViewModel(val app: Application) : AndroidViewModel(app) {

val state: MutableLiveData<MainState> = MutableLiveData()

val requests = (app as HomeberryApp).db.requestDao().getAllLiveData()
val baseUrl = (app as HomeberryApp).sharedPreferences.getString(
val baseUrl : String = (app as HomeberryApp).sharedPreferences.getString(
HomeberryApp.BASE_URL_KEY,
HomeberryApp.DEFAULT_BASE_URL
)!!
val state: MutableLiveData<MainState> = MutableLiveData()
private val db = (app as HomeberryApp).db

val requests = db.requestDao().getAllLiveData()

private var lastRunningJob: Job? = null

private val httpClient = HttpClient {
install(Logging) {
logger = Logger.ANDROID
Expand All @@ -46,23 +50,42 @@ class MainViewModel(val app: Application) : AndroidViewModel(app) {

lastRunningJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
try {
var url = ""

if (!baseUrl.contains("http") && !baseUrl.contains("https")) {
url += "http://"
}

url += "$baseUrl/${request.endpoint}"
val response = httpClient.get<String>(urlString = url)
state.postValue(MainState.RequestSuccess(response, request))
} catch (exception: Exception) {
handleEndpointError(exception, request)
}
sendRequest(request)
}
}
}

fun callRequest(requestId: Long) {
val request = runBlocking {
db.requestDao().getById(requestId)
}

state.postValue(MainState.RequestInProgress(request))
lastRunningJob?.cancel()

lastRunningJob = viewModelScope.launch {
withContext(Dispatchers.IO) {
sendRequest(request)
}
}
}

private suspend fun sendRequest(request: HomeberryRequest) {
try {
var url = ""

if (!baseUrl.contains("http") && !baseUrl.contains("https")) {
url += "http://"
}

url += "$baseUrl/${request.endpoint}"
val response = httpClient.get<String>(urlString = url)
state.postValue(MainState.RequestSuccess(response, request))
} catch (exception: Exception) {
handleEndpointError(exception, request)
}
}

fun cancelRequest() {
lastRunningJob?.cancel()
}
Expand Down
19 changes: 13 additions & 6 deletions app/src/main/java/sk/amk/homeberry/model/database/RequestDao.kt
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
package sk.amk.homeberry.model.database

import androidx.lifecycle.LiveData
import androidx.room.*
import androidx.room.Dao
import androidx.room.Delete
import androidx.room.Insert
import androidx.room.OnConflictStrategy
import androidx.room.Query
import sk.amk.homeberry.model.HomeberryRequest

/**
* @author Andrej Martinák <andrej.martinak@gmail.com>
*/
@Dao
interface RequestDao {
@Query("SELECT * FROM homeberryrequest WHERE id = :id")
suspend fun getById(id: Long): HomeberryRequest

@Query("SELECT * FROM homeberryrequest")
fun getAll(): List<HomeberryRequest>
suspend fun getAll(): List<HomeberryRequest>

@Query("SELECT * FROM homeberryrequest")
fun getAllLiveData(): LiveData<List<HomeberryRequest>>

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(request: HomeberryRequest)
suspend fun insert(request: HomeberryRequest)

@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertAll(requests: List<HomeberryRequest>)
suspend fun insertAll(requests: List<HomeberryRequest>)

@Delete
fun delete(request: HomeberryRequest)
suspend fun delete(request: HomeberryRequest)

@Query("DELETE FROM homeberryrequest")
fun deleteAll()
suspend fun deleteAll()
}
Loading

0 comments on commit 8c30ead

Please sign in to comment.