├── .gitignore
├── CHANGELOG.md
├── LICENSE
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
└── src
│ ├── debug
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-web.png
│ ├── java
│ └── de
│ │ └── codefor
│ │ └── karlsruhe
│ │ └── opensense
│ │ ├── OpenSenseApplication.kt
│ │ ├── data
│ │ ├── DateTimeAdapter.kt
│ │ ├── OpenSenseMapService.kt
│ │ └── boxes
│ │ │ ├── BoxesApi.kt
│ │ │ └── model
│ │ │ ├── Geometry.kt
│ │ │ ├── LastMeasurement.kt
│ │ │ ├── LocItem.kt
│ │ │ ├── SenseBox.kt
│ │ │ ├── Sensor.kt
│ │ │ └── SensorHistory.kt
│ │ └── widget
│ │ ├── WidgetHelper.kt
│ │ ├── base
│ │ ├── BaseWidget.kt
│ │ ├── BaseWidgetConfigurationActivity.kt
│ │ └── SensorListAdapter.kt
│ │ ├── onevalue
│ │ ├── OneValueConfigurationActivity.kt
│ │ └── OneValueWidget.kt
│ │ └── plot
│ │ ├── PlotWidget.kt
│ │ └── PlotWidgetConfigurationActivity.kt
│ └── res
│ ├── drawable-hdpi
│ ├── configuration_action_save.png
│ ├── configuration_list_adapter_cloud.png
│ ├── widget_configuration.png
│ └── widget_refresh.png
│ ├── drawable-mdpi
│ ├── configuration_action_save.png
│ ├── configuration_list_adapter_cloud.png
│ ├── widget_configuration.png
│ └── widget_refresh.png
│ ├── drawable-nodpi
│ ├── one_value_widget_example.png
│ └── plot_widget_example.png
│ ├── drawable-xhdpi
│ ├── configuration_action_save.png
│ ├── configuration_list_adapter_cloud.png
│ ├── widget_configuration.png
│ └── widget_refresh.png
│ ├── drawable-xxhdpi
│ ├── configuration_action_save.png
│ ├── configuration_list_adapter_cloud.png
│ ├── widget_configuration.png
│ └── widget_refresh.png
│ ├── drawable
│ └── sensor_list_item_selector.xml
│ ├── layout
│ ├── activity_base_widget_configuration.xml
│ ├── one_value_widget.xml
│ ├── plot_widget.xml
│ └── recycler_view_sensor_list_item.xml
│ ├── menu
│ └── base_widget_configuration.xml
│ ├── mipmap-hdpi
│ └── ic_launcher.png
│ ├── mipmap-mdpi
│ └── ic_launcher.png
│ ├── mipmap-xhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxhdpi
│ └── ic_launcher.png
│ ├── mipmap-xxxhdpi
│ └── ic_launcher.png
│ ├── values-de
│ └── strings.xml
│ ├── values
│ ├── colors.xml
│ ├── dimens.xml
│ ├── strings.xml
│ └── styles.xml
│ └── xml
│ ├── one_value_widget_info.xml
│ └── plot_widget_info.xml
├── build.gradle.kts
├── buildSrc
├── build.gradle.kts
├── settings.gradle.kts
└── src
│ └── main
│ └── kotlin
│ └── de
│ └── codefor
│ └── karlsruhe
│ └── opensense
│ └── build
│ └── Config.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle.kts
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea
5 | .DS_Store
6 | /build
7 | /captures
8 | .externalNativeBuild
9 | /buildSrc/build
10 | /buildSrc/.gradle
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 | An app with widgets for the great [openSenseMap](https://opensensemap.org) project.
3 |
4 | ## 0.6.0 (2011-06-13)
5 | Thanks to [Tobias Preuss](https://github.com/johnjohndoe) for contributing the below changes.
6 | - Update of several dependencies
7 | - Build: Use gradle-versions-plugin to detect version updates
8 | - Build: Debug build variant with different package name to enable parallel install
9 |
10 | ## 0.4.1 (2019-04-10)
11 | #### Fixed
12 | - Update mapbox dependency to fix a crash with the map view
13 |
14 | ## 0.4.0 (2019-04-10)
15 | Update build script, API calls and migrate to Gradle Kotlin DSL
16 |
17 | ## 0.3.0 (2018-01-29)
18 | Expand the features
19 |
20 | #### Added
21 | - The `Plot Widget`, which shows a plot of one sensor value
22 |
23 |
24 | ## 0.2.0 (2017-10-08)
25 | Improve widget design and configuration
26 |
27 | #### Added
28 | - Two additional buttons for the widget. They make it possible to refresh and change the configuration
29 |
30 | #### Changed
31 | - The senseBox is now selected in the configuration due a map and not by id
32 |
33 |
34 | ## 0.1.0 (2017-10-02)
35 | Initial release with an app widget.
36 |
37 | #### Added
38 | - The `One Value Widget`, which shows one sensor value of a senseBox
39 | - A configuration screen to select the senseBox and a sensor for the widget
40 |
41 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) Open Knowledge Lab Karlsruhe
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | 1. Redistributions of source code must retain the above copyright notice, this
8 | list of conditions and the following disclaimer.
9 |
10 | 2. Redistributions in binary form must reproduce the above copyright notice,
11 | this list of conditions and the following disclaimer in the documentation
12 | and/or other materials provided with the distribution.
13 |
14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Open Sense
2 | An app with widgets for the great [openSenseMap](https://opensensemap.org) project.
3 | It's still work in progress and in an early stage of development.
4 |
5 | The current version `0.6.0` is available in the [Play Store](https://play.google.com/store/apps/details?id=de.codefor.karlsruhe.opensense).
6 |
7 |
8 | ## Features
9 | - `One Value Widget`, which shows one sensor value of a senseBox
10 | - `Plot Widget`, which shows a plot of one sensor value
11 |
12 |
13 | ## Development
14 | The app is work in progress and in an early development stage.
15 | Any suggestions, feature requests, bug reports or pull requests are very much appreciated.
16 |
17 |
18 | Open Sense uses the [Gitflow](https://www.atlassian.com/git/tutorials/comparing-workflows#gitflow-workflow) workflow:
19 | - All pull requests should be branched from develop
20 | - The pull request is merged into develop
21 | - The develop branch is merged into master for the next release
22 |
23 | To work correctly, Open Sense requires an API token from [mapbox](https://www.mapbox.com/). Setup steps:
24 | - Register for the free mapbox account
25 | - Go to [API access token](https://www.mapbox.com/studio/account/tokens/) and get your API access token
26 | - Define the `mapboxApiToken` variable with the acquired token in the gradle user home `gradle.properties`.
27 | For Linux and Mac this is usually `~/.gradle/gradle.properties`
28 |
29 | Example of the `gradle.properties`:
30 | ```
31 | mapboxApiToken="API TOKEN"
32 | ```
33 |
34 |
35 | ## License
36 | Copyright (c) Open Knowledge Lab Karlsruhe
37 | All rights reserved.
38 |
39 | Redistribution and use in source and binary forms, with or without
40 | modification, are permitted provided that the following conditions are met:
41 |
42 | 1. Redistributions of source code must retain the above copyright notice, this
43 | list of conditions and the following disclaimer.
44 |
45 | 2. Redistributions in binary form must reproduce the above copyright notice,
46 | this list of conditions and the following disclaimer in the documentation
47 | and/or other materials provided with the distribution.
48 |
49 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
50 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
51 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
52 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
53 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
54 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
55 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
56 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
57 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
58 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import de.codefor.karlsruhe.opensense.build.BuildConfig
2 |
3 | plugins {
4 | id("com.android.application")
5 | id("kotlin-android")
6 | id("kotlin-android-extensions")
7 | }
8 |
9 | val mapboxApiToken: String by project
10 |
11 | android {
12 | compileSdkVersion(BuildConfig.compileSdkVersion)
13 | defaultConfig {
14 | applicationId = "de.codefor.karlsruhe.opensense"
15 | minSdkVersion(BuildConfig.minSdkVersion)
16 | targetSdkVersion(BuildConfig.targetSdkVersion)
17 | versionCode = BuildConfig.versionCode
18 | versionName = BuildConfig.versionName
19 |
20 | multiDexEnabled = true
21 | testInstrumentationRunner = "android.support.test.runner.AndroidJUnitRunner"
22 |
23 | buildConfigField("String", "MAPBOX_API_TOKEN", mapboxApiToken)
24 | }
25 | buildTypes {
26 | getByName("debug") {
27 | applicationIdSuffix = ".debug"
28 | }
29 | getByName("release") {
30 | isMinifyEnabled = false
31 | proguardFiles(getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro")
32 | }
33 | }
34 | }
35 |
36 | dependencies {
37 | implementation("com.android.support:multidex:${BuildConfig.supportLibMultiDexVersion}")
38 | implementation("com.android.support:appcompat-v7:${BuildConfig.supportLibVersion}")
39 | implementation("com.android.support:design:${BuildConfig.supportLibVersion}")
40 | implementation("com.android.support:recyclerview-v7:${BuildConfig.supportLibVersion}")
41 |
42 | implementation("net.danlew:android.joda:${BuildConfig.jodaVersion}")
43 |
44 | implementation("io.reactivex.rxjava2:rxandroid:${BuildConfig.rxAndroidVersion}")
45 | implementation("io.reactivex.rxjava2:rxjava:${BuildConfig.rxJavaVersion}")
46 |
47 | implementation("com.squareup.retrofit2:retrofit:${BuildConfig.retrofitVersion}")
48 | implementation("com.squareup.retrofit2:converter-moshi:${BuildConfig.retrofitVersion}")
49 | implementation("com.squareup.moshi:moshi-kotlin:${BuildConfig.moshiVersion}")
50 | implementation("com.squareup.retrofit2:adapter-rxjava2:${BuildConfig.retrofitVersion}")
51 |
52 | implementation("com.mapbox.mapboxsdk:mapbox-android-sdk:${BuildConfig.mapboxVersion}")
53 |
54 | implementation("com.androidplot:androidplot-core:${BuildConfig.androidPlotVersion}")
55 |
56 | testImplementation("junit:junit:${BuildConfig.junitVersion}")
57 |
58 | androidTestImplementation("com.android.support.test.espresso:espresso-core:${BuildConfig.espressoVersion}") {
59 | exclude(group = "com.android.support", module = "support-annotations")
60 | }
61 | }
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
23 | -keep class com.androidplot.** { *; }
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Open Sense DEBUG
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
52 |
53 |
54 |
55 |
56 |
57 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-web.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/ic_launcher-web.png
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/OpenSenseApplication.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense
2 |
3 | import android.support.multidex.MultiDexApplication
4 | import com.mapbox.mapboxsdk.Mapbox
5 | import net.danlew.android.joda.JodaTimeAndroid
6 |
7 | class OpenSenseApplication: MultiDexApplication() {
8 | override fun onCreate() {
9 | super.onCreate()
10 |
11 | JodaTimeAndroid.init(this)
12 | Mapbox.getInstance(applicationContext, BuildConfig.MAPBOX_API_TOKEN)
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/DateTimeAdapter.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data
2 |
3 | import com.squareup.moshi.JsonAdapter
4 | import com.squareup.moshi.JsonReader
5 | import com.squareup.moshi.JsonWriter
6 | import org.joda.time.DateTime
7 | import org.joda.time.format.ISODateTimeFormat
8 | import java.io.IOException
9 |
10 |
11 | class DateTimeAdapter : JsonAdapter() {
12 | private val fmt = ISODateTimeFormat.dateTime()
13 |
14 | @Synchronized
15 | @Throws(IOException::class)
16 | override fun fromJson(reader: JsonReader): DateTime {
17 | return DateTime(reader.nextString())
18 | }
19 |
20 | @Synchronized
21 | @Throws(IOException::class)
22 | override fun toJson(writer: JsonWriter, value: DateTime?) {
23 | writer.value(fmt.print(value))
24 | }
25 | }
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/OpenSenseMapService.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data
2 |
3 | import com.squareup.moshi.Moshi
4 | import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
5 | import de.codefor.karlsruhe.opensense.data.boxes.BoxesApi
6 | import de.codefor.karlsruhe.opensense.data.boxes.model.SenseBox
7 | import de.codefor.karlsruhe.opensense.data.boxes.model.Sensor
8 | import de.codefor.karlsruhe.opensense.data.boxes.model.SensorHistory
9 | import io.reactivex.Single
10 | import io.reactivex.functions.BiFunction
11 | import org.joda.time.DateTime
12 | import retrofit2.Retrofit
13 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
14 | import retrofit2.converter.moshi.MoshiConverterFactory
15 |
16 | typealias SensorData = Pair>
17 |
18 | object OpenSenseMapService {
19 | private val boxesApi: BoxesApi
20 |
21 | init {
22 | val moshi = Moshi.Builder()
23 | .add(KotlinJsonAdapterFactory())
24 | .add(DateTime::class.java, DateTimeAdapter()).build()
25 |
26 | val retrofit = Retrofit.Builder()
27 | .baseUrl("https://api.opensensemap.org/")
28 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
29 | .addConverterFactory(MoshiConverterFactory.create(moshi))
30 | .build()
31 |
32 | boxesApi = retrofit.create(BoxesApi::class.java)
33 | }
34 |
35 | fun getBox(boxId: String): Single {
36 | return boxesApi.getBox(boxId)
37 | }
38 |
39 | fun getAllBoxes(): Single> {
40 | return boxesApi.getAllBoxes()
41 | }
42 |
43 | fun getSenseBoxAndSensorData(boxId: String, sensorId: String): Single> {
44 | return boxesApi.getBox(boxId).zipWith(boxesApi.getSensorHistory(boxId, sensorId),
45 | BiFunction, Pair> { senseBox, sensorHistory ->
46 | val sensor = senseBox.sensors?.first { it.id == sensorId }
47 | ?: throw IllegalStateException("The box $boxId doesn't contain the sensor id $sensorId")
48 | Pair(senseBox, SensorData(sensor, sensorHistory))
49 | })
50 | }
51 | }
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/boxes/BoxesApi.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data.boxes
2 |
3 | import de.codefor.karlsruhe.opensense.data.boxes.model.SenseBox
4 | import de.codefor.karlsruhe.opensense.data.boxes.model.SensorHistory
5 | import io.reactivex.Single
6 | import retrofit2.http.GET
7 | import retrofit2.http.Path
8 | import retrofit2.http.Query
9 |
10 | interface BoxesApi {
11 | @GET("boxes/{boxId}")
12 | fun getBox(@Path("boxId") boxId: String) : Single
13 |
14 | @GET("boxes")
15 | fun getAllBoxes(@Query("minimal") minimal: Boolean = true): Single>
16 |
17 | @GET("boxes/{boxId}/data/{sensorId}")
18 | fun getSensorHistory(@Path("boxId") boxId: String, @Path("sensorId") sensorId: String): Single>
19 | }
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/boxes/model/Geometry.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data.boxes.model
2 |
3 | data class Geometry(
4 | val coordinates: List?,
5 | val type: String?
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/boxes/model/LastMeasurement.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data.boxes.model
2 |
3 | import org.joda.time.DateTime
4 |
5 | data class LastMeasurement(
6 | val createdAt: DateTime?,
7 | val value: String?
8 | )
9 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/boxes/model/LocItem.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data.boxes.model
2 |
3 | data class LocItem(
4 | val geometry: Geometry?,
5 | val type: String?
6 | )
7 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/boxes/model/SenseBox.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data.boxes.model
2 |
3 | import com.squareup.moshi.Json
4 | import org.joda.time.DateTime
5 |
6 | data class SenseBox(
7 | @Json(name = "_id") val id: String?,
8 | val createdAt: DateTime?,
9 | val updatedAt: DateTime?,
10 | val name: String?,
11 | val boxType: String?,
12 | val model: String?,
13 | val grouptag: String?,
14 | val exposure: String?,
15 | val weblink: String?,
16 | val description: String?,
17 | val currentLocation: Geometry?,
18 | val loc: List?,
19 | val sensors: List?
20 | )
21 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/boxes/model/Sensor.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data.boxes.model
2 |
3 | import com.squareup.moshi.Json
4 |
5 | data class Sensor(
6 | @Json(name = "_id") val id: String?,
7 | val lastMeasurement: LastMeasurement?,
8 | val sensorType: String?,
9 | val title: String?,
10 | val unit: String?
11 | )
12 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/data/boxes/model/SensorHistory.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.data.boxes.model
2 |
3 | import org.joda.time.DateTime
4 |
5 | data class SensorHistory(
6 | val value: Double?,
7 | val location: List?,
8 | val createdAt: DateTime?
9 | )
10 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/WidgetHelper.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget
2 |
3 | import android.app.PendingIntent
4 | import android.appwidget.AppWidgetManager
5 | import android.content.Context
6 | import android.content.Intent
7 | import de.codefor.karlsruhe.opensense.data.OpenSenseMapService
8 | import de.codefor.karlsruhe.opensense.data.SensorData
9 | import de.codefor.karlsruhe.opensense.data.boxes.model.SenseBox
10 | import de.codefor.karlsruhe.opensense.data.boxes.model.Sensor
11 | import de.codefor.karlsruhe.opensense.widget.base.BaseWidget
12 | import de.codefor.karlsruhe.opensense.widget.base.BaseWidgetConfigurationActivity
13 | import io.reactivex.Single
14 | import io.reactivex.android.schedulers.AndroidSchedulers
15 | import io.reactivex.schedulers.Schedulers
16 | import kotlin.reflect.KClass
17 |
18 | object WidgetHelper {
19 | private val PREFS_NAME = "de.codefor.karlsruhe.opensense.widget"
20 | private val PREF_BOX_ID = "box_id_"
21 | private val PREF_SENSOR_IDS = "sensor_ids_"
22 |
23 | internal fun saveConfiguration(context: Context, appWidgetId: Int, boxId: String, sensors: List) {
24 | val sensorIds = mutableListOf()
25 | sensors.forEach { it.id?.let { it1 -> sensorIds.add(it1) } }
26 |
27 | context.getSharedPreferences(PREFS_NAME, 0)
28 | .edit()
29 | .putString(PREF_BOX_ID + appWidgetId, boxId)
30 | .putStringSet(PREF_SENSOR_IDS + appWidgetId, sensorIds.toSet())
31 | .apply()
32 | }
33 |
34 | internal fun deleteConfiguration(context: Context, appWidgetId: Int) {
35 | context.getSharedPreferences(PREFS_NAME, 0)
36 | .edit()
37 | .remove(PREF_BOX_ID + appWidgetId)
38 | .remove(PREF_SENSOR_IDS + appWidgetId)
39 | .apply()
40 | }
41 |
42 | internal fun loadBoxId(context: Context, appWidgetId: Int): String {
43 | val prefs = context.getSharedPreferences(PREFS_NAME, 0)
44 | return prefs.getString(PREF_BOX_ID + appWidgetId, "")!!
45 | }
46 |
47 | internal fun loadSensorIds(context: Context, appWidgetId: Int): List {
48 | val prefs = context.getSharedPreferences(PREFS_NAME, 0)
49 | return prefs.getStringSet(PREF_SENSOR_IDS + appWidgetId, emptySet())!!.toList()
50 | }
51 |
52 | internal fun getSenseBox(context: Context, appWidgetId: Int): Single {
53 | val boxId = loadBoxId(context, appWidgetId)
54 | return getSenseBox(boxId)
55 | }
56 |
57 | internal fun getSenseBox(boxId: String): Single {
58 | if (boxId.isEmpty()) return Single.error { Exception() }
59 |
60 | return OpenSenseMapService.getBox(boxId)
61 | .subscribeOn(Schedulers.io())
62 | .observeOn(AndroidSchedulers.mainThread())
63 | }
64 |
65 | internal fun getAllBoxes(): Single> {
66 | return OpenSenseMapService.getAllBoxes()
67 | .subscribeOn(Schedulers.io())
68 | .observeOn(AndroidSchedulers.mainThread())
69 | }
70 |
71 | internal fun getSenseBoxAndSensorData(context: Context, appWidgetId: Int): Single> {
72 | val boxId = loadBoxId(context, appWidgetId)
73 | // We keep it really simple here and just use the first selected sensor of this widget
74 | val sensorId = loadSensorIds(context, appWidgetId).firstOrNull() ?: return Single.error(IllegalStateException("No sensor id stored"))
75 |
76 | return OpenSenseMapService.getSenseBoxAndSensorData(boxId, sensorId)
77 | .subscribeOn(Schedulers.io())
78 | .observeOn(AndroidSchedulers.mainThread())
79 | }
80 |
81 | internal fun createConfigurationPendingIntent(context: Context,
82 | appWidgetId: Int,
83 | configActivity: KClass): PendingIntent {
84 | val intent = Intent(context, configActivity.java)
85 | intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId)
86 | return PendingIntent.getActivity(context, appWidgetId, intent, 0)
87 | }
88 |
89 | internal fun createRefreshPendingIntent(context: Context,
90 | appWidgetId: Int,
91 | appWidgetProvider: KClass): PendingIntent {
92 | val intent = Intent(context, appWidgetProvider.java)
93 | intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, IntArray(1, { appWidgetId }))
94 | intent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
95 | return PendingIntent.getBroadcast(context, appWidgetId, intent, 0)
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/base/BaseWidget.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget.base
2 |
3 | import android.appwidget.AppWidgetManager
4 | import android.appwidget.AppWidgetProvider
5 | import android.content.Context
6 | import de.codefor.karlsruhe.opensense.widget.WidgetHelper
7 |
8 | /**
9 | * Implementation of default widget containing a maximum of five sensor data.
10 | * The configuration is implemented in [BaseWidgetConfigurationActivity] and its child classes.
11 | */
12 | abstract class BaseWidget : AppWidgetProvider() {
13 | override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray) {
14 | for (appWidgetId in appWidgetIds) {
15 | onUpdateWidget(context, appWidgetId, appWidgetManager)
16 | }
17 | }
18 |
19 | override fun onDeleted(context: Context, appWidgetIds: IntArray) {
20 | for (appWidgetId in appWidgetIds) {
21 | WidgetHelper.deleteConfiguration(context, appWidgetId)
22 | }
23 | }
24 |
25 | abstract fun onUpdateWidget(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager)
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/base/BaseWidgetConfigurationActivity.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget.base
2 |
3 | import android.app.Activity
4 | import android.appwidget.AppWidgetManager
5 | import android.content.Intent
6 | import android.os.Bundle
7 | import android.support.design.widget.Snackbar
8 | import android.support.v7.app.AppCompatActivity
9 | import android.support.v7.widget.LinearLayoutManager
10 | import android.view.Menu
11 | import android.view.MenuItem
12 | import android.view.View
13 | import com.mapbox.mapboxsdk.annotations.MarkerOptions
14 | import com.mapbox.mapboxsdk.camera.CameraUpdateFactory
15 | import com.mapbox.mapboxsdk.geometry.LatLng
16 | import com.mapbox.mapboxsdk.maps.MapboxMap
17 | import com.mapbox.mapboxsdk.maps.Style
18 | import de.codefor.karlsruhe.opensense.R
19 | import de.codefor.karlsruhe.opensense.data.boxes.model.SenseBox
20 | import de.codefor.karlsruhe.opensense.widget.WidgetHelper
21 | import kotlinx.android.synthetic.main.activity_base_widget_configuration.*
22 |
23 |
24 | /**
25 | * The configuration screen for the widgets.
26 | */
27 | abstract class BaseWidgetConfigurationActivity : AppCompatActivity() {
28 | // The maximum items that can be selected. Set in the child class.
29 | protected var maxSensorItems = 0
30 |
31 | private var widgetId = AppWidgetManager.INVALID_APPWIDGET_ID
32 | private var boxId = ""
33 |
34 | public override fun onCreate(icicle: Bundle?) {
35 | super.onCreate(icicle)
36 |
37 | setResult(Activity.RESULT_CANCELED)
38 |
39 | setContentView(R.layout.activity_base_widget_configuration)
40 | default_widget_configure_box_sensors_recycler_view.layoutManager = LinearLayoutManager(this)
41 |
42 | val extras = intent.extras
43 | if (extras != null) {
44 | widgetId = extras.getInt(
45 | AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID)
46 | }
47 |
48 | when (widgetId) {
49 | AppWidgetManager.INVALID_APPWIDGET_ID -> finish()
50 | }
51 |
52 | default_widget_configure_mapView.onCreate(icicle)
53 | default_widget_configure_mapView.getMapAsync { mapboxMap ->
54 | mapboxMap.setStyle(Style.LIGHT) {
55 | WidgetHelper.getAllBoxes().subscribe { boxes -> displayBoxesOnMap(mapboxMap, boxes) }
56 |
57 | // handle marker clicks
58 | mapboxMap.setOnMarkerClickListener { marker ->
59 | run {
60 | WidgetHelper.getSenseBox(marker.snippet)
61 | .subscribe(this::showBoxInformation) {
62 | Snackbar.make(coordinator_layout, R.string.widget_configuration_snackbar_error_loading, Snackbar.LENGTH_SHORT)
63 | .show()
64 | }
65 | return@setOnMarkerClickListener true
66 | }
67 | }
68 | }
69 | }
70 | }
71 |
72 | override fun onCreateOptionsMenu(menu: Menu): Boolean {
73 | menuInflater.inflate(R.menu.base_widget_configuration, menu)
74 | return true
75 | }
76 |
77 | override fun onOptionsItemSelected(item: MenuItem): Boolean {
78 | if (item.itemId == R.id.action_save) {
79 | saveAndShowWidget()
80 | return true
81 | }
82 | return super.onOptionsItemSelected(item)
83 | }
84 |
85 | private fun displayBoxesOnMap(mapboxMap: MapboxMap, boxes: List) {
86 | if (boxes.isEmpty()) {
87 | Snackbar.make(coordinator_layout, R.string.widget_configuration_snackbar_error_loading, Snackbar.LENGTH_SHORT)
88 | .show()
89 | return
90 | }
91 |
92 | val currentBoxId = WidgetHelper.loadBoxId(this@BaseWidgetConfigurationActivity, widgetId)
93 | for (box in boxes) {
94 | val coordinates = box.currentLocation?.coordinates ?: continue
95 | val markerPosition = LatLng(coordinates[1], coordinates[0])
96 | mapboxMap.addMarker(MarkerOptions()
97 | .position(markerPosition)
98 | .title(box.name)
99 | .snippet(box.id)
100 | )
101 |
102 | if (box.id == currentBoxId) {
103 | mapboxMap.moveCamera(CameraUpdateFactory.newLatLngZoom(markerPosition, 11.0))
104 | showBoxInformation(box)
105 | }
106 | }
107 | }
108 |
109 | private fun showBoxInformation(senseBox: SenseBox) {
110 | if (senseBox.id == null || senseBox.sensors == null) {
111 | Snackbar.make(coordinator_layout, R.string.widget_configuration_snackbar_error_invalid_data, Snackbar.LENGTH_SHORT)
112 | .show()
113 | return
114 | }
115 |
116 | boxId = senseBox.id
117 | default_widget_configure_box.visibility = View.VISIBLE
118 | default_widget_configure_box_name.text = senseBox.name
119 | default_widget_configure_box_description.text = senseBox.description
120 | default_widget_configure_box_sensors_recycler_view.adapter = SensorListAdapter(senseBox.sensors)
121 | }
122 |
123 | private fun saveAndShowWidget() {
124 | val adapter = default_widget_configure_box_sensors_recycler_view.adapter
125 | if (adapter == null || adapter !is SensorListAdapter) return
126 |
127 | when {
128 | adapter.getSelectedItems().isEmpty() -> {
129 | Snackbar.make(coordinator_layout,
130 | R.string.widget_configuration_snackbar_empty_list, Snackbar.LENGTH_SHORT).show()
131 | }
132 |
133 | adapter.getSelectedItems().size <= maxSensorItems -> {
134 | WidgetHelper.saveConfiguration(this, widgetId, boxId, adapter.getSelectedItems())
135 | update(widgetId)
136 | closeConfigurationActivity()
137 | }
138 |
139 | else -> {
140 | val text = resources.getQuantityString(R.plurals.maximumNumberOfSensors, maxSensorItems, maxSensorItems)
141 | Snackbar.make(coordinator_layout, text, Snackbar.LENGTH_SHORT).show()
142 | }
143 | }
144 | }
145 |
146 | private fun closeConfigurationActivity() {
147 | val resultValue = Intent()
148 | resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
149 | setResult(Activity.RESULT_OK, resultValue)
150 | finish()
151 | }
152 |
153 | protected abstract fun update(widgetId: Int)
154 | }
155 |
156 |
157 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/base/SensorListAdapter.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget.base
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import de.codefor.karlsruhe.opensense.R
8 | import de.codefor.karlsruhe.opensense.data.boxes.model.Sensor
9 | import kotlinx.android.synthetic.main.recycler_view_sensor_list_item.view.*
10 |
11 | class SensorListAdapter(private val sensors: List) : RecyclerView.Adapter() {
12 | private val selectedSensors: MutableList = mutableListOf()
13 |
14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
15 | val view = LayoutInflater.from(parent.context)
16 | .inflate(R.layout.recycler_view_sensor_list_item, parent, false)
17 | return ViewHolder(view, {
18 | when {
19 | selectedSensors.contains(it) -> selectedSensors.remove(it)
20 | else -> selectedSensors.add(it)
21 | }
22 | })
23 | }
24 |
25 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
26 | holder.bind(sensors[position])
27 | }
28 |
29 | override fun getItemCount() = sensors.size
30 |
31 | fun getSelectedItems(): List {
32 | return selectedSensors
33 | }
34 |
35 |
36 | class ViewHolder(view: View, private val itemClick: (Sensor) -> Unit) : RecyclerView.ViewHolder(view) {
37 | fun bind(sensor: Sensor) {
38 | with(sensor) {
39 | itemView.sensor_item_title.text = sensor.title
40 | itemView.sensor_item_value.text = itemView.context.getString(R.string.widget_configuration_current_value,
41 | sensor.lastMeasurement?.value, sensor.unit)
42 | itemView.setOnClickListener({
43 | itemView.isSelected = !itemView.isSelected
44 | itemClick(this)
45 | })
46 | }
47 | }
48 | }
49 | }
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/onevalue/OneValueConfigurationActivity.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget.onevalue
2 |
3 | import android.appwidget.AppWidgetManager
4 | import de.codefor.karlsruhe.opensense.widget.base.BaseWidgetConfigurationActivity
5 |
6 | class OneValueConfigurationActivity : BaseWidgetConfigurationActivity() {
7 | init {
8 | maxSensorItems = 1
9 | }
10 |
11 | override fun update(widgetId: Int) {
12 | OneValueWidget.update(this, widgetId, AppWidgetManager.getInstance(this))
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/onevalue/OneValueWidget.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget.onevalue
2 |
3 | import android.appwidget.AppWidgetManager
4 | import android.content.Context
5 | import android.view.View
6 | import android.widget.RemoteViews
7 | import de.codefor.karlsruhe.opensense.R
8 | import de.codefor.karlsruhe.opensense.widget.WidgetHelper
9 | import de.codefor.karlsruhe.opensense.widget.base.BaseWidget
10 |
11 |
12 | class OneValueWidget : BaseWidget() {
13 | override fun onUpdateWidget(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager) {
14 | update(context, appWidgetId, appWidgetManager)
15 | }
16 |
17 | companion object {
18 | fun update(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager) {
19 | val views = RemoteViews(context.packageName, R.layout.one_value_widget)
20 | //Show progress bar, hide refresh button
21 | views.apply {
22 | setViewVisibility(R.id.one_value_widget_refresh_button, View.INVISIBLE)
23 | setViewVisibility(R.id.one_value_widget_progress_bar, View.VISIBLE)
24 | setProgressBar(R.id.one_value_widget_progress_bar, 100, 0, true)
25 | }
26 | appWidgetManager.partiallyUpdateAppWidget(appWidgetId, views)
27 |
28 | WidgetHelper.getSenseBox(context, appWidgetId).subscribe({ senseBox ->
29 | val sensorIds = WidgetHelper.loadSensorIds(context, appWidgetId)
30 | val sensor = senseBox.sensors?.first { (id) -> id == sensorIds.first() }
31 | views.apply {
32 | //Show refresh button, hide progress bar
33 | setViewVisibility(R.id.one_value_widget_refresh_button, View.VISIBLE)
34 | setViewVisibility(R.id.one_value_widget_progress_bar, View.GONE)
35 | //Update values
36 | setTextViewText(R.id.one_value_widget_box_name, senseBox.name)
37 | setTextViewText(R.id.one_value_widget_sensor_data, "${sensor?.lastMeasurement?.value} ${sensor?.unit}")
38 | setTextViewText(R.id.one_value_widget_sensor_title, sensor?.title)
39 | }
40 |
41 | setOnClickPendingIntents(context, appWidgetId, views)
42 | appWidgetManager.updateAppWidget(appWidgetId, views)
43 | }, {
44 | views.apply {
45 | //Show refresh button, hide progress bar
46 | setViewVisibility(R.id.one_value_widget_refresh_button, View.VISIBLE)
47 | setViewVisibility(R.id.one_value_widget_progress_bar, View.GONE)
48 | //Remove values, set error text
49 | setTextViewText(R.id.one_value_widget_box_name, "")
50 | setTextViewText(R.id.one_value_widget_sensor_data, context.getString(R.string.loading_error_text_generic))
51 | setTextViewText(R.id.one_value_widget_sensor_title, "")
52 | }
53 |
54 | setOnClickPendingIntents(context, appWidgetId, views)
55 | appWidgetManager.updateAppWidget(appWidgetId, views)
56 | })
57 | }
58 |
59 | private fun setOnClickPendingIntents(context: Context, appWidgetId: Int, views: RemoteViews) {
60 | views.setOnClickPendingIntent(
61 | R.id.one_value_widget_configuration_button,
62 | WidgetHelper.createConfigurationPendingIntent(context, appWidgetId, OneValueConfigurationActivity::class)
63 | )
64 |
65 | views.setOnClickPendingIntent(
66 | R.id.one_value_widget_refresh_button,
67 | WidgetHelper.createRefreshPendingIntent(context, appWidgetId, OneValueWidget::class)
68 | )
69 | }
70 | }
71 | }
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/plot/PlotWidget.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget.plot
2 |
3 | import android.appwidget.AppWidgetManager
4 | import android.content.Context
5 | import android.graphics.Bitmap
6 | import android.graphics.Canvas
7 | import android.graphics.Color
8 | import android.graphics.Paint
9 | import android.os.Bundle
10 | import android.view.View
11 | import android.widget.RemoteViews
12 | import com.androidplot.ui.HorizontalPositioning
13 | import com.androidplot.ui.Size
14 | import com.androidplot.ui.VerticalPositioning
15 | import com.androidplot.util.PixelUtils
16 | import com.androidplot.xy.*
17 | import de.codefor.karlsruhe.opensense.R
18 | import de.codefor.karlsruhe.opensense.data.boxes.model.Sensor
19 | import de.codefor.karlsruhe.opensense.data.boxes.model.SensorHistory
20 | import de.codefor.karlsruhe.opensense.widget.WidgetHelper
21 | import de.codefor.karlsruhe.opensense.widget.base.BaseWidget
22 | import org.joda.time.DateTime
23 | import org.joda.time.format.DateTimeFormat
24 | import java.text.FieldPosition
25 | import java.text.Format
26 | import java.text.ParsePosition
27 |
28 |
29 | class PlotWidget : BaseWidget() {
30 | override fun onUpdateWidget(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager) {
31 | update(context, appWidgetId, appWidgetManager)
32 | }
33 |
34 | override fun onAppWidgetOptionsChanged(context: Context, appWidgetManager: AppWidgetManager,
35 | appWidgetId: Int, newOptions: Bundle) {
36 | onUpdateWidget(context, appWidgetId, appWidgetManager)
37 | }
38 |
39 | companion object {
40 | private val dateTimeFormatterStartEnd = DateTimeFormat.forPattern("dd.MM.")
41 | private val dateTimeFormatter = DateTimeFormat.forPattern("dd.MM. HH:mm")
42 |
43 |
44 | fun update(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager) {
45 | val views = RemoteViews(context.packageName, R.layout.plot_widget)
46 |
47 | //Show progress bar, hide refresh button
48 | views.apply {
49 | setViewVisibility(R.id.plot_widget_refresh_button, View.INVISIBLE)
50 | setViewVisibility(R.id.plot_widget_progress_bar, View.VISIBLE)
51 | setProgressBar(R.id.plot_widget_progress_bar, 100, 0, true)
52 | }
53 | appWidgetManager.partiallyUpdateAppWidget(appWidgetId, views)
54 |
55 | WidgetHelper.getSenseBoxAndSensorData(context, appWidgetId).subscribe(
56 | // onSuccess
57 | { (senseBox, sensorData) ->
58 | // Kotlin doesn't support nested destructuring, so we do it here
59 | val (sensor, sensorHist) = sensorData
60 | views.apply {
61 | //Show refresh button, hide progress bar
62 | setViewVisibility(R.id.plot_widget_refresh_button, View.VISIBLE)
63 | setViewVisibility(R.id.plot_widget_progress_bar, View.GONE)
64 | setTextViewText(R.id.plot_widget_box_name, senseBox.name)
65 | setViewVisibility(R.id.plot_widget_sensor_title, View.VISIBLE)
66 | setTextViewText(R.id.plot_widget_sensor_title, sensor.title)
67 | }
68 | appWidgetManager.partiallyUpdateAppWidget(appWidgetId, views)
69 | drawPlot(context, appWidgetId, appWidgetManager, sensor, sensorHist)
70 | }, {
71 | showErrorScreen(context, appWidgetId, appWidgetManager, views)
72 | }
73 | )
74 | }
75 |
76 | private fun showErrorScreen(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager,
77 | views: RemoteViews, errorId: Int = R.string.loading_error_text_generic) {
78 | views.apply {
79 | // Show refresh button, hide progress bar
80 | setViewVisibility(R.id.plot_widget_refresh_button, View.VISIBLE)
81 | setViewVisibility(R.id.plot_widget_progress_bar, View.GONE)
82 | setTextViewText(R.id.plot_widget_error_text, context.getString(errorId))
83 | setViewVisibility(R.id.plot_widget_error_text, View.VISIBLE)
84 | // Hide widget image and sensor title
85 | setViewVisibility(R.id.plot_widget_img, View.GONE)
86 | setViewVisibility(R.id.plot_widget_sensor_title, View.GONE)
87 | }
88 | setOnClickPendingIntents(context, appWidgetId, views)
89 | appWidgetManager.updateAppWidget(appWidgetId, views)
90 | }
91 |
92 | private fun drawPlot(context: Context, appWidgetId: Int, appWidgetManager: AppWidgetManager,
93 | sensor: Sensor, sensorHist: List) {
94 | val views = RemoteViews(context.packageName, R.layout.plot_widget)
95 |
96 | if (sensorHist.isEmpty()) {
97 | showErrorScreen(context, appWidgetId, appWidgetManager, views, R.string.loading_error_text_no_data)
98 | return
99 | }
100 |
101 | val dates = mutableListOf()
102 | val values = mutableListOf()
103 |
104 | // The time in a plot usually increases from left to right.
105 | // The provided data starts with the newest first and we have to reverse it.
106 | sensorHist.reversed().forEach { (value, _, createdAt) ->
107 | dates.add(createdAt ?: DateTime.now())
108 | values.add(value ?: Double.NaN)
109 | }
110 |
111 | if (dates.isEmpty() || values.isEmpty()) {
112 | showErrorScreen(context, appWidgetId, appWidgetManager, views, R.string.loading_error_text_no_data)
113 | return
114 | }
115 |
116 | val plot = XYPlot(context, "") // no title for the plot, it should be self-evident
117 | plot.apply {
118 | setRangeLabel(sensor.unit)
119 | setRangeStep(StepMode.SUBDIVIDE, 4.0)
120 | setDomainLabel(context.getString(R.string.plot_graph_time))
121 | setDomainStep(StepMode.SUBDIVIDE, 3.0)
122 | setBackgroundColor(Color.TRANSPARENT)
123 | }
124 |
125 |
126 | val textSize = context.resources.getDimension(R.dimen.widget_text_size_small)
127 |
128 | // Configure the graph
129 | plot.graph.apply {
130 | // show the tick labels
131 | setLineLabelEdges(XYGraphWidget.Edge.RIGHT, XYGraphWidget.Edge.BOTTOM)
132 |
133 | // add space for the labels
134 | size = Size.FILL
135 | marginLeft = PixelUtils.dpToPix(8f)
136 | marginTop = PixelUtils.dpToPix(8f)
137 | marginRight = PixelUtils.dpToPix(40f)
138 | marginBottom = PixelUtils.dpToPix(24f)
139 |
140 | lineLabelInsets.right = PixelUtils.dpToPix(-15f)
141 | lineLabelInsets.bottom = PixelUtils.dpToPix(-10f)
142 |
143 | // format the labels
144 | getLineLabelStyle(XYGraphWidget.Edge.RIGHT).paint.color = Color.WHITE
145 | getLineLabelStyle(XYGraphWidget.Edge.RIGHT).paint.textSize = textSize
146 | getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).paint.color = Color.WHITE
147 | getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).paint.textSize = textSize
148 | getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).paint.textAlign = Paint.Align.LEFT
149 |
150 | // format the DateTime
151 | getLineLabelStyle(XYGraphWidget.Edge.BOTTOM).format = object : Format() {
152 | override fun format(obj: Any, toAppendTo: StringBuffer, pos: FieldPosition): StringBuffer {
153 | val index = (obj as Number).toInt()
154 | if (index < 0 || index >= dates.size) return toAppendTo
155 |
156 | val date = dates[index]
157 |
158 | return when (index) {
159 | 0, dates.lastIndex -> toAppendTo.append(date.toString(dateTimeFormatterStartEnd))
160 | else -> toAppendTo.append(date.toString(dateTimeFormatter))
161 | }
162 | }
163 |
164 | override fun parseObject(source: String, pos: ParsePosition): Any? {
165 | return null
166 | }
167 | }
168 | }
169 |
170 | // Format the title (time and unit)
171 | plot.rangeTitle.labelPaint.textSize = textSize
172 | plot.rangeTitle.position(
173 | 25f, HorizontalPositioning.ABSOLUTE_FROM_RIGHT,
174 | 0f, VerticalPositioning.ABSOLUTE_FROM_CENTER)
175 | plot.domainTitle.labelPaint.textSize = textSize
176 | plot.domainTitle.position(
177 | 0f, HorizontalPositioning.ABSOLUTE_FROM_CENTER,
178 | 20f, VerticalPositioning.ABSOLUTE_FROM_BOTTOM)
179 | plot.legend.isVisible = false
180 |
181 | val widgetWidth = appWidgetManager.getAppWidgetOptions(appWidgetId).getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_WIDTH)
182 | val widgetHeight = appWidgetManager.getAppWidgetOptions(appWidgetId).getInt(AppWidgetManager.OPTION_APPWIDGET_MAX_HEIGHT)
183 | plot.measure(widgetWidth, widgetHeight)
184 | plot.layout(0, 0, widgetWidth, widgetHeight)
185 |
186 | val series = SimpleXYSeries(values, SimpleXYSeries.ArrayFormat.Y_VALS_ONLY, "")
187 | val seriesFormat = LineAndPointFormatter(Color.TRANSPARENT, Color.BLACK, Color.TRANSPARENT, null)
188 |
189 | // add the series to the xyplot:
190 | plot.addSeries(series, seriesFormat)
191 |
192 | val bitmap = Bitmap.createBitmap(widgetWidth, widgetHeight, Bitmap.Config.ARGB_8888)
193 | plot.draw(Canvas(bitmap))
194 | views.setImageViewBitmap(R.id.plot_widget_img, bitmap)
195 |
196 | views.apply {
197 | setViewVisibility(R.id.plot_widget_refresh_button, View.VISIBLE)
198 | setViewVisibility(R.id.plot_widget_progress_bar, View.GONE)
199 | setViewVisibility(R.id.plot_widget_error_text, View.GONE)
200 | setViewVisibility(R.id.plot_widget_img, View.VISIBLE)
201 | }
202 |
203 | setOnClickPendingIntents(context, appWidgetId, views)
204 | appWidgetManager.updateAppWidget(appWidgetId, views)
205 | }
206 |
207 | private fun setOnClickPendingIntents(context: Context, appWidgetId: Int, views: RemoteViews) {
208 | views.setOnClickPendingIntent(
209 | R.id.plot_widget_configuration_button,
210 | WidgetHelper.createConfigurationPendingIntent(context, appWidgetId, PlotWidgetConfigurationActivity::class)
211 | )
212 |
213 | views.setOnClickPendingIntent(
214 | R.id.plot_widget_refresh_button,
215 | WidgetHelper.createRefreshPendingIntent(context, appWidgetId, PlotWidget::class)
216 | )
217 | }
218 | }
219 | }
220 |
--------------------------------------------------------------------------------
/app/src/main/java/de/codefor/karlsruhe/opensense/widget/plot/PlotWidgetConfigurationActivity.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.widget.plot
2 |
3 | import android.appwidget.AppWidgetManager
4 | import de.codefor.karlsruhe.opensense.widget.base.BaseWidgetConfigurationActivity
5 |
6 | class PlotWidgetConfigurationActivity : BaseWidgetConfigurationActivity() {
7 | init {
8 | maxSensorItems = 1
9 | }
10 |
11 | override fun update(widgetId: Int) {
12 | PlotWidget.update(this, widgetId, AppWidgetManager.getInstance(this))
13 | }
14 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/configuration_action_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-hdpi/configuration_action_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/configuration_list_adapter_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-hdpi/configuration_list_adapter_cloud.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/widget_configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-hdpi/widget_configuration.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/widget_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-hdpi/widget_refresh.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/configuration_action_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-mdpi/configuration_action_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/configuration_list_adapter_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-mdpi/configuration_list_adapter_cloud.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/widget_configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-mdpi/widget_configuration.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/widget_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-mdpi/widget_refresh.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/one_value_widget_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-nodpi/one_value_widget_example.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-nodpi/plot_widget_example.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-nodpi/plot_widget_example.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/configuration_action_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xhdpi/configuration_action_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/configuration_list_adapter_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xhdpi/configuration_list_adapter_cloud.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/widget_configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xhdpi/widget_configuration.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/widget_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xhdpi/widget_refresh.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/configuration_action_save.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xxhdpi/configuration_action_save.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/configuration_list_adapter_cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xxhdpi/configuration_list_adapter_cloud.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/widget_configuration.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xxhdpi/widget_configuration.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/widget_refresh.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/drawable-xxhdpi/widget_refresh.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/sensor_list_item_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_base_widget_configuration.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
18 |
19 |
27 |
28 |
36 |
37 |
43 |
44 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/one_value_widget.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
17 |
18 |
32 |
33 |
40 |
41 |
49 |
50 |
63 |
64 |
76 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/plot_widget.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
10 |
18 |
19 |
33 |
34 |
41 |
42 |
50 |
51 |
60 |
61 |
76 |
77 |
89 |
90 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/recycler_view_sensor_list_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
22 |
23 |
28 |
29 |
34 |
35 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/base_widget_configuration.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-de/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | %1$s %2$s
5 | Speichern
6 | Bitte mindestens einen Sensor auswählen
7 | Fehler beim Laden der senseBox-Daten
8 | Ungültige senseBox-Daten
9 |
10 |
11 | Ein Fehler ist aufgetreten
12 | Keine Daten verfügbar
13 |
14 |
15 | - Bitte wähle maximal %1$d Sensor
16 | - Bitte wähle maximal %1$d Sensoren
17 |
18 |
19 | Aktualisieren
20 | Konfiguration
21 |
22 |
23 | Plot der Sensorhistorie
24 | Zeit
25 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4EAF47
4 | #388E3C
5 | #5F7E9D
6 | #A7B8C9
7 |
8 | #30000000
9 | #FFFFFF
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 4dp
4 | 18dp
5 | 10sp
6 | 18sp
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Open Sense
3 |
4 |
5 |
6 | %1$s %2$s
7 | Save
8 | Please select at least one sensor
9 | Error fetching the senseBox data
10 | Invalid senseBox data
11 |
12 |
13 | An error occurred
14 | No data available
15 |
16 |
17 | - Please select a maximum of %1$d sensor
18 | - Please select a maximum of %1$d sensors
19 |
20 |
21 | Refresh
22 | Configuration
23 |
24 |
25 | Sensor history plot
26 | Time
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/one_value_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/plot_widget_info.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | apply(plugin = "com.github.ben-manes.versions")
4 |
5 | buildscript {
6 | repositories {
7 | google()
8 | jcenter()
9 | }
10 | dependencies {
11 | classpath("com.android.tools.build:gradle:4.1.2")
12 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${de.codefor.karlsruhe.opensense.build.BuildConfig.kotlinVersion}")
13 | classpath("com.github.ben-manes:gradle-versions-plugin:0.36.0")
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | jcenter()
24 | }
25 | }
26 |
27 | task("clean") {
28 | delete(rootProject.buildDir)
29 | }
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | `kotlin-dsl`
3 | }
4 |
5 | repositories {
6 | jcenter()
7 | }
--------------------------------------------------------------------------------
/buildSrc/settings.gradle.kts:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/buildSrc/settings.gradle.kts
--------------------------------------------------------------------------------
/buildSrc/src/main/kotlin/de/codefor/karlsruhe/opensense/build/Config.kt:
--------------------------------------------------------------------------------
1 | package de.codefor.karlsruhe.opensense.build
2 |
3 | object BuildConfig {
4 | val minSdkVersion = 19
5 | val targetSdkVersion = 30
6 | val compileSdkVersion = targetSdkVersion
7 | val versionCode = 6
8 | val versionName = "0.6.0"
9 |
10 | val kotlinVersion = "1.4.31"
11 |
12 | val supportLibVersion = "28.0.0"
13 | val supportLibMultiDexVersion = "1.0.3"
14 | val jodaVersion = "2.10.1.1"
15 | val rxAndroidVersion = "2.1.1"
16 | val rxJavaVersion = "2.2.21"
17 | val retrofitVersion = "2.6.4"
18 | val moshiVersion = "1.11.0"
19 | val mapboxVersion = "7.3.0"
20 | val androidPlotVersion = "1.5.7"
21 | val junitVersion = "4.13.2"
22 | val espressoVersion = "3.0.2"
23 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/CodeforKarlsruhe/opensense/a464a33c732cd6bc43a7659cce4db482f001d8b1/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | include(":app")
--------------------------------------------------------------------------------