Initial commit
|
@ -0,0 +1,90 @@
|
|||
# Built application files
|
||||
*.apk
|
||||
*.aar
|
||||
*.ap_
|
||||
*.aab
|
||||
|
||||
output-metadata.json
|
||||
|
||||
# Files for the ART/Dalvik VM
|
||||
*.dex
|
||||
|
||||
# Java class files
|
||||
*.class
|
||||
|
||||
# Generated files
|
||||
bin/
|
||||
gen/
|
||||
out/
|
||||
# Uncomment the following line in case you need and you don't have the release build type files in your app
|
||||
# release/
|
||||
|
||||
# Gradle files
|
||||
.gradle/
|
||||
build/
|
||||
|
||||
# Local configuration file (sdk path, etc)
|
||||
local.properties
|
||||
|
||||
# Proguard folder generated by Eclipse
|
||||
proguard/
|
||||
|
||||
# Log Files
|
||||
*.log
|
||||
|
||||
# Android Studio Navigation editor temp files
|
||||
.navigation/
|
||||
|
||||
# Android Studio captures folder
|
||||
captures/
|
||||
|
||||
# IntelliJ
|
||||
*.iml
|
||||
.idea/workspace.xml
|
||||
.idea/tasks.xml
|
||||
.idea/gradle.xml
|
||||
.idea/assetWizardSettings.xml
|
||||
.idea/dictionaries
|
||||
.idea/libraries
|
||||
# Android Studio 3 in .gitignore file.
|
||||
.idea/caches
|
||||
.idea/modules.xml
|
||||
# Comment next line if keeping position of elements in Navigation Editor is relevant for you
|
||||
.idea/navEditor.xml
|
||||
|
||||
# Keystore files
|
||||
# Uncomment the following lines if you do not want to check your keystore files in.
|
||||
#*.jks
|
||||
#*.keystore
|
||||
|
||||
# External native build folder generated in Android Studio 2.2 and later
|
||||
.externalNativeBuild
|
||||
.cxx/
|
||||
|
||||
# Google Services (e.g. APIs or Firebase)
|
||||
# google-services.json
|
||||
|
||||
# Freeline
|
||||
freeline.py
|
||||
freeline/
|
||||
freeline_project_description.json
|
||||
|
||||
# fastlane
|
||||
fastlane/report.xml
|
||||
fastlane/Preview.html
|
||||
fastlane/screenshots
|
||||
fastlane/test_output
|
||||
fastlane/readme.md
|
||||
|
||||
# Version control
|
||||
vcs.xml
|
||||
|
||||
# lint
|
||||
lint/intermediates/
|
||||
lint/generated/
|
||||
lint/outputs/
|
||||
lint/tmp/
|
||||
# lint/reports/
|
||||
|
||||
# Android Profiling
|
||||
*.hprof
|
|
@ -0,0 +1,3 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
|
@ -0,0 +1 @@
|
|||
Tao Toolbox
|
|
@ -0,0 +1,139 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<code_scheme name="Project" version="173">
|
||||
<JetCodeStyleSettings>
|
||||
<option name="PACKAGES_TO_USE_STAR_IMPORTS">
|
||||
<value>
|
||||
<package name="java.util" alias="false" withSubpackages="false" />
|
||||
<package name="kotlinx.android.synthetic" alias="false" withSubpackages="true" />
|
||||
<package name="io.ktor" alias="false" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="PACKAGES_IMPORT_LAYOUT">
|
||||
<value>
|
||||
<package name="" alias="false" withSubpackages="true" />
|
||||
<package name="java" alias="false" withSubpackages="true" />
|
||||
<package name="javax" alias="false" withSubpackages="true" />
|
||||
<package name="kotlin" alias="false" withSubpackages="true" />
|
||||
<package name="" alias="true" withSubpackages="true" />
|
||||
</value>
|
||||
</option>
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</JetCodeStyleSettings>
|
||||
<codeStyleSettings language="XML">
|
||||
<option name="FORCE_REARRANGE_MODE" value="1" />
|
||||
<indentOptions>
|
||||
<option name="CONTINUATION_INDENT_SIZE" value="4" />
|
||||
</indentOptions>
|
||||
<arrangement>
|
||||
<rules>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:android</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>xmlns:.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:id</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*:name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>name</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>style</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>^$</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>ANDROID_ATTRIBUTE_ORDER</order>
|
||||
</rule>
|
||||
</section>
|
||||
<section>
|
||||
<rule>
|
||||
<match>
|
||||
<AND>
|
||||
<NAME>.*</NAME>
|
||||
<XML_ATTRIBUTE />
|
||||
<XML_NAMESPACE>.*</XML_NAMESPACE>
|
||||
</AND>
|
||||
</match>
|
||||
<order>BY_NAME</order>
|
||||
</rule>
|
||||
</section>
|
||||
</rules>
|
||||
</arrangement>
|
||||
</codeStyleSettings>
|
||||
<codeStyleSettings language="kotlin">
|
||||
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
|
||||
</codeStyleSettings>
|
||||
</code_scheme>
|
||||
</component>
|
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="CompilerConfiguration">
|
||||
<bytecodeTargetLevel target="1.8" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="RemoteRepositoriesConfiguration">
|
||||
<remote-repository>
|
||||
<option name="id" value="central" />
|
||||
<option name="name" value="Maven Central repository" />
|
||||
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="jboss.community" />
|
||||
<option name="name" value="JBoss Community repository" />
|
||||
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="BintrayJCenter" />
|
||||
<option name="name" value="BintrayJCenter" />
|
||||
<option name="url" value="https://jcenter.bintray.com/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="Google" />
|
||||
<option name="name" value="Google" />
|
||||
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||
</remote-repository>
|
||||
<remote-repository>
|
||||
<option name="id" value="maven" />
|
||||
<option name="name" value="maven" />
|
||||
<option name="url" value="https://jitpack.io" />
|
||||
</remote-repository>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_8" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||
</component>
|
||||
<component name="ProjectType">
|
||||
<option name="id" value="Android" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,63 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "29.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "fr.oupson.taotoolbox"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "0.0.2"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
// Enables code shrinking, obfuscation, and optimization for only
|
||||
// your project's release build type.
|
||||
minifyEnabled true
|
||||
|
||||
// Enables resource shrinking, which is performed by the
|
||||
// Android Gradle plugin.
|
||||
shrinkResources true
|
||||
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
viewBinding true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
implementation 'androidx.preference:preference-ktx:1.1.1'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
|
||||
|
||||
implementation project(":common")
|
||||
implementation 'org.osmdroid:osmdroid-android:6.1.10'
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,24 @@
|
|||
package fr.oupson.taotoolbox
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("fr.oupson.taotoolbox", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="fr.oupson.taotoolbox">
|
||||
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:fullBackupContent="@xml/backup_descriptor"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.TaoToolbox">
|
||||
<activity android:name=".activities.TaoWidgetConfigurationActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<receiver android:name=".receivers.TaoWidget">
|
||||
<intent-filter>
|
||||
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="android.appwidget.provider"
|
||||
android:resource="@xml/widget_tao_info" />
|
||||
</receiver>
|
||||
|
||||
<activity android:name=".activities.MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
|
||||
</manifest>
|
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,324 @@
|
|||
package fr.oupson.taotoolbox.activities
|
||||
|
||||
import android.Manifest
|
||||
import android.content.pm.PackageManager
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.graphics.*
|
||||
import android.opengl.Visibility
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.ActivityCompat
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import fr.oupson.common.api.TaoRestApi
|
||||
import fr.oupson.common.db.TaoDatabaseHelper
|
||||
import fr.oupson.common.db.TaoDatabaseHelper.TaoDatabase.LineEntry
|
||||
import fr.oupson.common.db.TaoDatabaseHelper.TaoDatabase.StopEntry
|
||||
import fr.oupson.taotoolbox.BuildConfig
|
||||
import fr.oupson.taotoolbox.R
|
||||
import fr.oupson.taotoolbox.databinding.ActivityMainBinding
|
||||
import fr.oupson.taotoolbox.windows.StopInfoWindow
|
||||
import kotlinx.coroutines.*
|
||||
import org.json.JSONArray
|
||||
import org.osmdroid.config.Configuration
|
||||
import org.osmdroid.tileprovider.tilesource.XYTileSource
|
||||
import org.osmdroid.util.GeoPoint
|
||||
import org.osmdroid.views.CustomZoomButtonsController
|
||||
import org.osmdroid.views.overlay.CopyrightOverlay
|
||||
import org.osmdroid.views.overlay.Marker
|
||||
import org.osmdroid.views.overlay.Polyline
|
||||
import org.osmdroid.views.overlay.compass.CompassOverlay
|
||||
import org.osmdroid.views.overlay.compass.InternalCompassOrientationProvider
|
||||
import org.osmdroid.views.overlay.mylocation.GpsMyLocationProvider
|
||||
import org.osmdroid.views.overlay.mylocation.MyLocationNewOverlay
|
||||
import kotlin.coroutines.coroutineContext
|
||||
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val TAG = "MainActivity"
|
||||
|
||||
private const val REQUEST_PERMISSIONS_REQUEST_CODE = 1
|
||||
}
|
||||
|
||||
private val taoRestApi = TaoRestApi()
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
|
||||
private val job = Job()
|
||||
private val scope = Dispatchers.IO + job
|
||||
|
||||
private val locationOverlay: MyLocationNewOverlay by lazy {
|
||||
MyLocationNewOverlay(GpsMyLocationProvider(this), binding.map)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.bottomAppBar)
|
||||
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
window.apply {
|
||||
clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
|
||||
addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
|
||||
decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
|
||||
decorView.systemUiVisibility =
|
||||
decorView.systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR
|
||||
}
|
||||
statusBarColor = Color.TRANSPARENT
|
||||
}
|
||||
|
||||
Configuration.getInstance().also {
|
||||
it.load(this, PreferenceManager.getDefaultSharedPreferences(this))
|
||||
it.userAgentValue = BuildConfig.APPLICATION_ID
|
||||
}
|
||||
|
||||
binding.loadingProgressBar.visibility = View.VISIBLE
|
||||
|
||||
|
||||
binding.map.setTileSource(
|
||||
XYTileSource(
|
||||
"osm fr", 1, 19, 256, ".png",
|
||||
arrayOf(
|
||||
"https://a.tile.openstreetmap.fr/osmfr/",
|
||||
"https://b.tile.openstreetmap.fr/osmfr/",
|
||||
"https://c.tile.openstreetmap.fr/osmfr/"
|
||||
), "© OpenStreetMap contributors"
|
||||
)
|
||||
)
|
||||
|
||||
buildOtherOverlays()
|
||||
|
||||
requestPermissionsIfNecessary()
|
||||
|
||||
GlobalScope.launch(scope) {
|
||||
val helper = TaoDatabaseHelper(this@MainActivity).apply {
|
||||
try {
|
||||
this.checkUpdate()
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.loadingProgressBar.visibility = View.GONE
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.d(TAG, "while checking update", e)
|
||||
}
|
||||
}
|
||||
|
||||
val db = helper.readableDatabase
|
||||
|
||||
val copyright = CopyrightOverlay(this@MainActivity).apply {
|
||||
this.setAlignRight(true)
|
||||
}
|
||||
binding.map.overlays.add(copyright)
|
||||
|
||||
buildLineOverlays(db, helper)
|
||||
|
||||
buildMarkerOverlays(db)
|
||||
|
||||
binding.map.overlays.remove(copyright)
|
||||
binding.map.overlays.add(copyright)
|
||||
|
||||
db.close()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun buildOtherOverlays() {
|
||||
binding.map.overlays.add(locationOverlay)
|
||||
|
||||
val compassOverlay =
|
||||
CompassOverlay(this, InternalCompassOrientationProvider(this), binding.map)
|
||||
|
||||
compassOverlay.enableCompass()
|
||||
|
||||
compassOverlay.setCompassCenter(35.0f, 70f) // TODO
|
||||
|
||||
binding.map.overlays.add(compassOverlay)
|
||||
|
||||
|
||||
binding.map.apply {
|
||||
controller.setZoom(15.0)
|
||||
controller.animateTo(GeoPoint(47.90250000, 1.90888889))
|
||||
zoomController.setVisibility(CustomZoomButtonsController.Visibility.SHOW_AND_FADEOUT)
|
||||
setMultiTouchControls(true)
|
||||
isFlingEnabled = true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private suspend fun buildMarkerOverlays(db: SQLiteDatabase) {
|
||||
val infoWindow = StopInfoWindow(binding.map, taoRestApi)
|
||||
|
||||
val cursor = db.query(
|
||||
StopEntry.TABLE_NAME,
|
||||
arrayOf(
|
||||
StopEntry.COLUMN_NAME_STOP_ID,
|
||||
StopEntry.COLUMN_NAME_STOP_NAME,
|
||||
StopEntry.COLUMN_NAME_COORD_LONG,
|
||||
StopEntry.COLUMN_NAME_COORD_LAT
|
||||
),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null
|
||||
)
|
||||
|
||||
with(cursor) {
|
||||
val stopIdIndex = cursor.getColumnIndexOrThrow(StopEntry.COLUMN_NAME_STOP_ID)
|
||||
val stopNameIndex = cursor.getColumnIndexOrThrow(StopEntry.COLUMN_NAME_STOP_NAME)
|
||||
val stopCoordLongIndex =
|
||||
cursor.getColumnIndexOrThrow(StopEntry.COLUMN_NAME_COORD_LONG)
|
||||
val stopCoordLatIndex =
|
||||
cursor.getColumnIndexOrThrow(StopEntry.COLUMN_NAME_COORD_LAT)
|
||||
|
||||
val drawable =
|
||||
ContextCompat.getDrawable(this@MainActivity, R.drawable.ic_baseline_place_48)
|
||||
drawable?.colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(this@MainActivity, R.color.tao_blue),
|
||||
PorterDuff.Mode.MULTIPLY
|
||||
)
|
||||
|
||||
while (moveToNext() && coroutineContext.isActive) {
|
||||
binding.map.overlays.add(Marker(binding.map).apply {
|
||||
position = GeoPoint(
|
||||
getDouble(stopCoordLatIndex), getDouble(stopCoordLongIndex),
|
||||
)
|
||||
icon = drawable
|
||||
|
||||
title = getString(stopNameIndex)
|
||||
id = getString(stopIdIndex)
|
||||
setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
|
||||
this.infoWindow = infoWindow
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
private suspend fun buildLineOverlays(db: SQLiteDatabase, helper: TaoDatabaseHelper) {
|
||||
val lineCursor = db.query(
|
||||
LineEntry.TABLE_NAME,
|
||||
arrayOf(
|
||||
LineEntry.COLUMN_NAME_LINE_ID,
|
||||
LineEntry.COLUMN_NAME_LINE_NAME,
|
||||
LineEntry.COLUMN_NAME_BACKGROUND_COLOR
|
||||
),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null, null
|
||||
)
|
||||
|
||||
with(lineCursor) {
|
||||
val lineIdIndex = getColumnIndexOrThrow(LineEntry.COLUMN_NAME_LINE_ID)
|
||||
val lineNameIndex = getColumnIndexOrThrow(
|
||||
LineEntry.COLUMN_NAME_LINE_NAME
|
||||
)
|
||||
val lineBackgroundColorIndex =
|
||||
getColumnIndexOrThrow(LineEntry.COLUMN_NAME_BACKGROUND_COLOR)
|
||||
|
||||
while (lineCursor.moveToNext() && coroutineContext.isActive) {
|
||||
val jsonResponse = JSONArray(taoRestApi.getTaoGeoJson(getString(lineIdIndex)))
|
||||
val color = Color.parseColor(getString(lineBackgroundColorIndex))
|
||||
|
||||
for (routeIndex in 0 until jsonResponse.length()) {
|
||||
val route = jsonResponse.getJSONObject(routeIndex)
|
||||
val routeLine = Polyline(binding.map).apply {
|
||||
outlinePaint.color = color
|
||||
id = route.getString("routeId")
|
||||
title = getString(lineNameIndex)
|
||||
subDescription = this@MainActivity.getString(
|
||||
R.string.line_popup_description,
|
||||
helper.getRouteName(db, route.getString("routeId"))
|
||||
)
|
||||
}
|
||||
|
||||
val geo = route.getJSONArray("geojson") // TODO DECODE ENCODED ?
|
||||
|
||||
for (i in 0 until geo.length()) {
|
||||
val value = geo.getJSONArray(i)
|
||||
routeLine.addPoint(GeoPoint(value.getDouble(1), value.getDouble(0)))
|
||||
}
|
||||
|
||||
binding.map.overlays.add(routeLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lineCursor.close()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
binding.map.onResume()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
binding.map.onResume()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
override fun onRequestPermissionsResult(
|
||||
requestCode: Int,
|
||||
permissions: Array<String?>,
|
||||
grantResults: IntArray
|
||||
) {
|
||||
when (requestCode) {
|
||||
REQUEST_PERMISSIONS_REQUEST_CODE -> {
|
||||
val res = grantResults.getOrNull(0)
|
||||
if (res == PackageManager.PERMISSION_GRANTED) {
|
||||
onAccessLocationPermissionGranted()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onAccessLocationPermissionGranted() {
|
||||
Log.d(TAG, "onAccessLocationPermissionGranted")
|
||||
|
||||
locationOverlay.enableMyLocation()
|
||||
locationOverlay.isOptionsMenuEnabled = true
|
||||
locationOverlay.enableFollowLocation()
|
||||
}
|
||||
|
||||
private fun requestPermissionsIfNecessary() {
|
||||
if (ActivityCompat.checkSelfPermission(
|
||||
this,
|
||||
Manifest.permission.ACCESS_FINE_LOCATION
|
||||
) != PackageManager.PERMISSION_GRANTED
|
||||
) {
|
||||
ActivityCompat.requestPermissions(
|
||||
this,
|
||||
arrayOf(Manifest.permission.ACCESS_FINE_LOCATION),
|
||||
REQUEST_PERMISSIONS_REQUEST_CODE
|
||||
)
|
||||
} else {
|
||||
onAccessLocationPermissionGranted()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
false
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,366 @@
|
|||
package fr.oupson.taotoolbox.activities
|
||||
|
||||
import android.app.Activity
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.widget.AutoCompleteTextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.edit
|
||||
import fr.oupson.common.api.TaoRestApi
|
||||
import fr.oupson.common.db.TaoDatabaseHelper
|
||||
import fr.oupson.common.db.TaoDatabaseHelper.TaoDatabase.*
|
||||
import fr.oupson.taotoolbox.BuildConfig
|
||||
import fr.oupson.taotoolbox.R
|
||||
import fr.oupson.taotoolbox.adapters.DirectionAdapter
|
||||
import fr.oupson.taotoolbox.adapters.LineAdapter
|
||||
import fr.oupson.taotoolbox.adapters.StopAdapter
|
||||
import fr.oupson.taotoolbox.databinding.ActivityTaoWidgetConfigurationBinding
|
||||
import fr.oupson.taotoolbox.receivers.TaoWidget
|
||||
import kotlinx.coroutines.*
|
||||
|
||||
// TODO FIND A WAY TO AUTO SELECT
|
||||
class TaoWidgetConfigurationActivity : AppCompatActivity() {
|
||||
companion object {
|
||||
private const val LINE_ID = "WIDGET_LINE_ID"
|
||||
private const val LINE_CODE = "WIDGET_LINE_CODE"
|
||||
private const val ROUTE_ID = "WIDGET_LINE_ID"
|
||||
private const val ROUTE_NAME = "ROUTE_NAME"
|
||||
private const val STOP_ID = "WIDGET_STOP_ID"
|
||||
private const val STOP_NAME = "WIDGET_STOP_NAME"
|
||||
private const val BG_COLOR = "WIDGET_LINE_BACKGROUND_COLOR"
|
||||
private const val TEXT_COLOR = "WIDGET_LINE_TEXT_COLOR"
|
||||
|
||||
private const val PREFERENCE_NAME = "WIDGET"
|
||||
|
||||
private const val TAG = "TaoWidgetConfiguration"
|
||||
|
||||
private fun savePrefs(
|
||||
context: Context,
|
||||
widgetId: Int,
|
||||
lineId: String,
|
||||
lineCode: String,
|
||||
routeId: String,
|
||||
routeName: String,
|
||||
stopId: String,
|
||||
stopName: String,
|
||||
bgColor: Int,
|
||||
textColor: Int
|
||||
) {
|
||||
Log.d(TAG, "savePrefs, widget id : $widgetId")
|
||||
context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE).edit(true) {
|
||||
putString("${LINE_ID}_$widgetId", lineId)
|
||||
putString("${LINE_CODE}_$widgetId", lineCode)
|
||||
putString("${ROUTE_ID}_$widgetId", routeId)
|
||||
putString("${ROUTE_NAME}_$widgetId", routeName)
|
||||
putString("${STOP_ID}_$widgetId", stopId)
|
||||
putString("${STOP_NAME}_$widgetId", stopName)
|
||||
putInt("${BG_COLOR}_$widgetId", bgColor)
|
||||
putInt("${TEXT_COLOR}_$widgetId", textColor)
|
||||
}
|
||||
}
|
||||
|
||||
data class WidgetPrefs(
|
||||
val lineId: String,
|
||||
val lineCode: String,
|
||||
val routeId: String,
|
||||
val routeName: String,
|
||||
val stopId: String,
|
||||
val stopName: String,
|
||||
val bgColor: Int,
|
||||
val textColor: Int
|
||||
)
|
||||
|
||||
fun getPrefs(context: Context, widgetId: Int): WidgetPrefs? =
|
||||
with(context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)) {
|
||||
Log.d(TAG, "getPrefs, widget id : $widgetId")
|
||||
WidgetPrefs(
|
||||
getString("${LINE_ID}_$widgetId", null) ?: return null,
|
||||
getString("${LINE_CODE}_$widgetId", null) ?: return null,
|
||||
getString("${ROUTE_ID}_$widgetId", null) ?: return null,
|
||||
getString("${ROUTE_NAME}_$widgetId", null) ?: return null,
|
||||
getString("${STOP_ID}_$widgetId", null) ?: return null,
|
||||
getString("${STOP_NAME}_$widgetId", null) ?: return null,
|
||||
getInt("${BG_COLOR}_$widgetId", Color.BLACK),
|
||||
getInt("${TEXT_COLOR}_$widgetId", Color.WHITE),
|
||||
)
|
||||
}
|
||||
|
||||
fun destroyPrefs(context: Context, widgetId: Int) {
|
||||
Log.d(TAG, "destroyPrefs, widget id : $widgetId")
|
||||
val prefs = context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE)
|
||||
prefs.edit(commit = true) {
|
||||
remove("${LINE_ID}_$widgetId")
|
||||
remove("${LINE_CODE}_$widgetId")
|
||||
remove("${ROUTE_ID}_$widgetId")
|
||||
remove("${ROUTE_NAME}_$widgetId")
|
||||
remove("${STOP_ID}_$widgetId")
|
||||
remove("${STOP_NAME}_$widgetId")
|
||||
remove("${BG_COLOR}_$widgetId")
|
||||
remove("${TEXT_COLOR}_$widgetId")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var widgetId: Int? = null
|
||||
private lateinit var binding: ActivityTaoWidgetConfigurationBinding
|
||||
|
||||
private var db: SQLiteDatabase? = null
|
||||
|
||||
private val lineList = arrayListOf<LineAdapter.Line>()
|
||||
private val directionList = arrayListOf<DirectionAdapter.Direction>()
|
||||
private val stopList = arrayListOf<StopAdapter.Stop>()
|
||||
|
||||
private val lineAdapter: LineAdapter by lazy { LineAdapter(this, lineList) }
|
||||
private val directionAdapter: DirectionAdapter by lazy { DirectionAdapter(this, directionList) }
|
||||
private val stopAdapter: StopAdapter by lazy { StopAdapter(this, stopList) }
|
||||
|
||||
private var linePosition: Int = -1
|
||||
private var directionPosition: Int = -1
|
||||
private var stopPosition: Int = -1
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityTaoWidgetConfigurationBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
setSupportActionBar(binding.bottomAppBar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
|
||||
widgetId = intent.extras?.getInt(
|
||||
AppWidgetManager.EXTRA_APPWIDGET_ID,
|
||||
AppWidgetManager.INVALID_APPWIDGET_ID
|
||||
)
|
||||
|
||||
if ((widgetId == AppWidgetManager.INVALID_APPWIDGET_ID || widgetId == null) && !BuildConfig.DEBUG) {
|
||||
setResult(Activity.RESULT_CANCELED, null)
|
||||
finish()
|
||||
}
|
||||
|
||||
binding.loadingProgressBar.visibility = View.VISIBLE
|
||||
|
||||
(binding.configSelectLine.editText as? AutoCompleteTextView)?.also { autoCompleteTextView ->
|
||||
autoCompleteTextView.setAdapter(lineAdapter)
|
||||
autoCompleteTextView.setOnItemClickListener { _, _, position, _ ->
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
linePosition = position
|
||||
directionPosition = -1
|
||||
loadDirectionList(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(binding.configSelectDirection.editText as? AutoCompleteTextView)?.also { directionAutoComplete ->
|
||||
directionAutoComplete.setAdapter(directionAdapter)
|
||||
directionAutoComplete.setOnItemClickListener { _, _, position, _ ->
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
directionPosition = position
|
||||
stopPosition = -1
|
||||
|
||||
loadStopList(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
(binding.configSelectStop.editText as? AutoCompleteTextView)?.also {
|
||||
it.setAdapter(stopAdapter)
|
||||
it.setOnItemClickListener { _, _, position, _ ->
|
||||
binding.configSelectStop.isErrorEnabled = false
|
||||
stopPosition = position
|
||||
}
|
||||
}
|
||||
|
||||
binding.fabConfirm.setOnClickListener {
|
||||
save()
|
||||
}
|
||||
|
||||
GlobalScope.launch(Dispatchers.IO) {
|
||||
db = TaoDatabaseHelper(this@TaoWidgetConfigurationActivity).apply {
|
||||
checkUpdate()
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.loadingProgressBar.visibility = View.GONE
|
||||
}
|
||||
}.readableDatabase
|
||||
|
||||
loadLineList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun checkEntryValid(): Boolean {
|
||||
var res = true
|
||||
if (linePosition == -1) {
|
||||
res = false
|
||||
binding.configSelectLine.error = getString(R.string.activity_tao_config_missing_value)
|
||||
}
|
||||
|
||||
if (directionPosition == -1) {
|
||||
res = false
|
||||
binding.configSelectDirection.error =
|
||||
getString(R.string.activity_tao_config_missing_value)
|
||||
}
|
||||
|
||||
if (stopPosition == -1) {
|
||||
res = false
|
||||
binding.configSelectStop.error = getString(R.string.activity_tao_config_missing_value)
|
||||
}
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private fun save() {
|
||||
if (!checkEntryValid())
|
||||
return
|
||||
|
||||
if (widgetId == AppWidgetManager.INVALID_APPWIDGET_ID || widgetId == null) {
|
||||
setResult(Activity.RESULT_CANCELED, null)
|
||||
} else {
|
||||
savePrefs(
|
||||
this,
|
||||
widgetId!!,
|
||||
lineList[linePosition].lineId,
|
||||
lineList[linePosition].lineCode,
|
||||
directionList[directionPosition].routeId,
|
||||
directionList[directionPosition].directionName,
|
||||
stopList[stopPosition].stopId,
|
||||
stopList[stopPosition].stopName,
|
||||
lineList[linePosition].bgColor,
|
||||
lineList[linePosition].textColor,
|
||||
)
|
||||
|
||||
val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(this)
|
||||
runBlocking {
|
||||
TaoWidget.updateAppWidget(
|
||||
this@TaoWidgetConfigurationActivity,
|
||||
appWidgetManager,
|
||||
widgetId!!,
|
||||
TaoRestApi()
|
||||
)
|
||||
}
|
||||
|
||||
val resultValue = Intent().apply {
|
||||
putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId)
|
||||
}
|
||||
setResult(Activity.RESULT_OK, resultValue)
|
||||
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
|
||||
private suspend fun loadLineList() {
|
||||
if (db != null) {
|
||||
lineList.clear()
|
||||
|
||||
val cursor = db!!.query(
|
||||
true,
|
||||
LineEntry.TABLE_NAME,
|
||||
arrayOf(
|
||||
LineEntry.COLUMN_NAME_LINE_ID,
|
||||
LineEntry.COLUMN_NAME_LINE_CODE,
|
||||
LineEntry.COLUMN_NAME_LINE_NAME,
|
||||
LineEntry.COLUMN_NAME_BACKGROUND_COLOR,
|
||||
LineEntry.COLUMN_NAME_TEXT_COLOR
|
||||
),
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
LineEntry.COLUMN_NAME_LINE_ORDER,
|
||||
null
|
||||
)
|
||||
|
||||
while (cursor.moveToNext()) {
|
||||
lineList.add(
|
||||
LineAdapter.Line(
|
||||
cursor.getString(0),
|
||||
cursor.getString(1),
|
||||
cursor.getString(2),
|
||||
Color.parseColor(cursor.getString(3)),
|
||||
Color.parseColor(cursor.getString(4))
|
||||
)
|
||||
)
|
||||
}
|
||||
cursor.close()
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
lineAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadStopList(position: Int) {
|
||||
stopList.clear()
|
||||
if (db != null) {
|
||||
val cursor = db!!.rawQuery(
|
||||
"SELECT ${StopEntry.COLUMN_NAME_STOP_ID}, ${StopEntry.COLUMN_NAME_STOP_NAME} FROM ${StopEntry.TABLE_NAME} NATURAL JOIN ${IsStopEntry.TABLE_NAME} NATURAL JOIN ${DirectionEntry.TABLE_NAME} WHERE ${DirectionEntry.COLUMN_NAME_ROUTE_ID} = ? ORDER BY ${IsStopEntry.COLUMN_NAME_STOP_INDEX}",
|
||||
arrayOf(directionList[position].routeId)
|
||||
)
|
||||
while (cursor.moveToNext()) {
|
||||
stopList.add(
|
||||
StopAdapter.Stop(
|
||||
cursor.getString(0),
|
||||
cursor.getString(1)
|
||||
)
|
||||
)
|
||||
}
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.configSelectDirection.isErrorEnabled = false
|
||||
stopAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun loadDirectionList(position: Int) {
|
||||
directionList.clear()
|
||||
|
||||
if (db != null) {
|
||||
val cursor = db!!.rawQuery(
|
||||
"SELECT ${LineEntry.COLUMN_NAME_LINE_ID}, ${LineEntry.COLUMN_NAME_LINE_CODE}, ${DirectionEntry.COLUMN_NAME_ROUTE_NAME}, ${DirectionEntry.COLUMN_NAME_ROUTE_ID}, ${LineEntry.COLUMN_NAME_BACKGROUND_COLOR}, ${LineEntry.COLUMN_NAME_TEXT_COLOR} FROM ${LineEntry.TABLE_NAME} NATURAL JOIN ${DirectionEntry.TABLE_NAME} WHERE ${LineEntry.COLUMN_NAME_LINE_ID} = ?",
|
||||
arrayOf(lineList[position].lineId)
|
||||
)
|
||||
while (cursor.moveToNext()) {
|
||||
directionList.add(
|
||||
DirectionAdapter.Direction(
|
||||
cursor.getString(0),
|
||||
cursor.getString(1),
|
||||
cursor.getString(2),
|
||||
cursor.getString(3),
|
||||
Color.parseColor(cursor.getString(4)),
|
||||
Color.parseColor(cursor.getString(5))
|
||||
)
|
||||
)
|
||||
}
|
||||
cursor.close()
|
||||
}
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
binding.configSelectLine.isErrorEnabled = false
|
||||
directionAdapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.v(TAG, "Closing database")
|
||||
db?.close()
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
onBackPressed()
|
||||
false
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
package fr.oupson.taotoolbox.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import fr.oupson.taotoolbox.R
|
||||
import fr.oupson.common.api.LineColors
|
||||
|
||||
class DirectionAdapter(context: Context, private val lines: List<Direction>) :
|
||||
ArrayAdapter<DirectionAdapter.Direction>(context, 0, lines) {
|
||||
data class Direction(
|
||||
val lineId: String,
|
||||
val lineCode: String,
|
||||
val directionName: String,
|
||||
val routeId: String,
|
||||
val bgColor: Int,
|
||||
val textColor: Int
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "${this.lineCode} : ${this.directionName}"
|
||||
}
|
||||
}
|
||||
|
||||
override fun getView(position: Int, c: View?, parent: ViewGroup): View {
|
||||
var convertView = c
|
||||
val holder = if (convertView == null) {
|
||||
convertView =
|
||||
LayoutInflater.from(context).inflate(R.layout.item_line_direction, parent, false)
|
||||
|
||||
LineViewHolder(convertView).also {
|
||||
convertView.tag = it
|
||||
}
|
||||
} else {
|
||||
convertView.tag as LineViewHolder
|
||||
}
|
||||
|
||||
holder.bind(context, lines[position])
|
||||
|
||||
return convertView!!
|
||||
}
|
||||
|
||||
class LineViewHolder(view: View) {
|
||||
private val directionNameTextView: TextView =
|
||||
view.findViewById(R.id.item_line_direction_name_text_view)
|
||||
private val directionImageView: ImageView =
|
||||
view.findViewById(R.id.item_line_direction_image_view)
|
||||
|
||||
fun bind(context: Context, line: Direction) {
|
||||
this.directionNameTextView.text = line.directionName
|
||||
this.directionImageView.setImageBitmap(
|
||||
LineColors.getLineBtm(
|
||||
context,
|
||||
line.lineCode,
|
||||
line.bgColor,
|
||||
line.textColor,
|
||||
256,
|
||||
256
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package fr.oupson.taotoolbox.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import fr.oupson.common.api.LineColors
|
||||
import fr.oupson.taotoolbox.R
|
||||
|
||||
class LineAdapter(context: Context, private val lines: List<Line>) :
|
||||
ArrayAdapter<LineAdapter.Line>(context, 0, lines) {
|
||||
data class Line(
|
||||
val lineId: String,
|
||||
val lineCode: String,
|
||||
val lineName: String,
|
||||
val bgColor: Int,
|
||||
val textColor: Int
|
||||
) {
|
||||
override fun toString(): String {
|
||||
return "${this.lineCode} : ${this.lineName}"
|
||||
}
|
||||
}
|
||||
|
||||
override fun getView(position: Int, c: View?, parent: ViewGroup): View {
|
||||
var convertView = c
|
||||
val holder = if (convertView == null) {
|
||||
convertView = LayoutInflater.from(context).inflate(R.layout.item_line, parent, false)
|
||||
LineViewHolder(convertView).also {
|
||||
convertView.tag = it
|
||||
}
|
||||
} else {
|
||||
convertView.tag as LineViewHolder
|
||||
}
|
||||
|
||||
holder.bind(context, lines[position])
|
||||
|
||||
return convertView!!
|
||||
}
|
||||
|
||||
class LineViewHolder(view: View) {
|
||||
private val lineNameTextView: TextView = view.findViewById(R.id.item_line_name_text_view)
|
||||
private val lineImageView: ImageView = view.findViewById(R.id.item_line_image_view)
|
||||
|
||||
fun bind(context: Context, line: Line) {
|
||||
this.lineNameTextView.text = line.lineName
|
||||
this.lineImageView.setImageBitmap(
|
||||
LineColors.getLineBtm(
|
||||
context,
|
||||
line.lineCode,
|
||||
line.bgColor,
|
||||
line.textColor,
|
||||
256,
|
||||
256
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
package fr.oupson.taotoolbox.adapters
|
||||
|
||||
import android.graphics.Color
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.oupson.taotoolbox.R
|
||||
import fr.oupson.common.api.LineColors
|
||||
import fr.oupson.common.api.Schedule
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ScheduleAdapter(
|
||||
private val scheduleList: SimplePtr<Array<Schedule>>
|
||||
) :
|
||||
RecyclerView.Adapter<ScheduleAdapter.ScheduleViewHolder>() {
|
||||
class SimplePtr<T>(private var value: T? = null) {
|
||||
fun get(): T? = value
|
||||
fun set(value: T?) {
|
||||
this.value = value
|
||||
}
|
||||
}
|
||||
|
||||
class ScheduleViewHolder(
|
||||
private val view: View
|
||||
) : RecyclerView.ViewHolder(view) {
|
||||
private val scheduleDirectionTextView =
|
||||
view.findViewById<TextView>(R.id.item_schedule_direction_text_view)
|
||||
private val scheduleImageView = view.findViewById<ImageView>(R.id.item_schedule_line_image_view)
|
||||
private val scheduleNextTextView = view.findViewById<TextView>(R.id.item_schedule_next_text_view)
|
||||
private val scheduleAfterTextView =
|
||||
view.findViewById<TextView>(R.id.item_schedule_after_text_view)
|
||||
|
||||
fun bind(schedule: Schedule) {
|
||||
scheduleDirectionTextView.text = view.context.getString(
|
||||
R.string.line_and_direction_name,
|
||||
schedule.lineColors.lineCode,
|
||||
schedule.lineDirectionName
|
||||
)
|
||||
|
||||
scheduleNextTextView.text =
|
||||
view.context.getString(R.string.time_remaining, schedule.timeRemaining() ?: "∅")
|
||||
scheduleAfterTextView.text = view.context.getString(
|
||||
R.string.time_remaining,
|
||||
schedule.timeRemainingAfter() ?: "∅"
|
||||
)
|
||||
|
||||
|
||||
GlobalScope.launch(Dispatchers.Default) {
|
||||
val btm = LineColors.getLineBtm(
|
||||
view.context,
|
||||
schedule.lineColors.lineCode,
|
||||
Color.parseColor(schedule.lineColors.background),
|
||||
Color.parseColor(schedule.lineColors.text),
|
||||
48,
|
||||
48
|
||||
)
|
||||
withContext(Dispatchers.Main) {
|
||||
scheduleImageView.setImageBitmap(btm)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduleViewHolder {
|
||||
val inflater = LayoutInflater.from(parent.context)
|
||||
return ScheduleViewHolder(inflater.inflate(R.layout.item_schedule, parent, false))
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: ScheduleViewHolder, position: Int) {
|
||||
val ref = scheduleList.get()
|
||||
if (ref != null)
|
||||
holder.bind(ref[position])
|
||||
}
|
||||
|
||||
override fun getItemCount(): Int = scheduleList.get()?.size ?: 0
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package fr.oupson.taotoolbox.adapters
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.TextView
|
||||
import fr.oupson.taotoolbox.R
|
||||
|
||||
class StopAdapter(context: Context, private val lines: List<Stop>) :
|
||||
ArrayAdapter<StopAdapter.Stop>(context, 0, lines) {
|
||||
data class Stop(val stopId: String, val stopName: String) {
|
||||
override fun toString(): String {
|
||||
return this.stopName
|
||||
}
|
||||
}
|
||||
|
||||
override fun getView(position: Int, c: View?, parent: ViewGroup): View {
|
||||
var convertView = c
|
||||
val holder = if (convertView == null) {
|
||||
convertView = LayoutInflater.from(context).inflate(R.layout.item_stop, parent, false)
|
||||
LineViewHolder(convertView).also {
|
||||
convertView.tag = it
|
||||
}
|
||||
} else {
|
||||
convertView.tag as LineViewHolder
|
||||
}
|
||||
|
||||
holder.bind(lines[position])
|
||||
|
||||
return convertView!!
|
||||
}
|
||||
|
||||
class LineViewHolder(view: View) {
|
||||
private val stopNameTextView: TextView = view.findViewById(R.id.item_stop_name)
|
||||
|
||||
fun bind(line: Stop) {
|
||||
this.stopNameTextView.text = line.stopName
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,238 @@
|
|||
package fr.oupson.taotoolbox.receivers
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.appwidget.AppWidgetManager
|
||||
import android.appwidget.AppWidgetProvider
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.*
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.widget.RemoteViews
|
||||
import fr.oupson.common.api.LineColors
|
||||
import fr.oupson.common.api.RealTimes
|
||||
import fr.oupson.common.api.TaoRestApi
|
||||
import fr.oupson.taotoolbox.R
|
||||
import fr.oupson.taotoolbox.activities.TaoWidgetConfigurationActivity
|
||||
import kotlinx.coroutines.*
|
||||
import java.util.*
|
||||
|
||||
class TaoWidget : AppWidgetProvider() {
|
||||
companion object {
|
||||
private const val TAG = "TaoWidget"
|
||||
|
||||
suspend fun updateAppWidget(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetId: Int,
|
||||
taoRestApi: TaoRestApi
|
||||
) {
|
||||
Log.d(TAG, "updateAppWidget, appWidgetId : $appWidgetId")
|
||||
|
||||
// Construct the RemoteViews object
|
||||
val views = RemoteViews(context.packageName, R.layout.widget_tao)
|
||||
|
||||
val serviceIntent = Intent(context, TaoWidget::class.java)
|
||||
serviceIntent.action = AppWidgetManager.ACTION_APPWIDGET_UPDATE
|
||||
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId))
|
||||
serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME))
|
||||
|
||||
val pending = PendingIntent.getBroadcast(
|
||||
context,
|
||||
appWidgetId,
|
||||
serviceIntent,
|
||||
PendingIntent.FLAG_UPDATE_CURRENT
|
||||
)
|
||||
views.setOnClickPendingIntent(R.id.tao_widget_root, pending)
|
||||
|
||||
try {
|
||||
val prefs = TaoWidgetConfigurationActivity.getPrefs(context, appWidgetId)
|
||||
if (prefs != null) {
|
||||
val depart = taoRestApi.getNextDeparturesForLine(
|
||||
prefs.stopId,
|
||||
prefs.routeId
|
||||
)
|
||||
|
||||
views.setRealtime(
|
||||
context,
|
||||
prefs.stopName,
|
||||
prefs.routeName.toDirection(),
|
||||
LineColors.getLineBtm(
|
||||
context,
|
||||
prefs.lineCode,
|
||||
prefs.bgColor,
|
||||
prefs.textColor,
|
||||
256,
|
||||
256
|
||||
),
|
||||
depart
|
||||
)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "While updating widget", e)
|
||||
|
||||
views.setTextViewText(
|
||||
R.id.widget_tao_stop_name_text_view,
|
||||
context.getString(R.string.widget_tao_error_title)
|
||||
)
|
||||
views.setTextViewText(
|
||||
R.id.widget_tao_stop_direction_text_view,
|
||||
context.getString(R.string.widget_tao_error_description)
|
||||
)
|
||||
|
||||
views.setImageViewResource(
|
||||
R.id.widget_tao_line_icon_image_view,
|
||||
R.drawable.ic_outline_error_outline_24
|
||||
)
|
||||
|
||||
val now = GregorianCalendar()
|
||||
views.setTextViewText(
|
||||
R.id.widget_tao_refresh_date_text_view,
|
||||
context.getString(
|
||||
R.string.widget_tao_time,
|
||||
now.get(GregorianCalendar.HOUR_OF_DAY),
|
||||
now.get(GregorianCalendar.MINUTE)
|
||||
)
|
||||
)
|
||||
|
||||
views.setTextViewText(
|
||||
R.id.widget_tao_next_text_view, ""
|
||||
)
|
||||
|
||||
views.setTextViewText(
|
||||
R.id.widget_tao_after_next_text_view,
|
||||
""
|
||||
)
|
||||
|
||||
views.setTextViewText(
|
||||
R.id.widget_tao_third_text_view,
|
||||
""
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// Instruct the widget manager to update the widget
|
||||
appWidgetManager.updateAppWidget(appWidgetId, views)
|
||||
}
|
||||
|
||||
|
||||
private fun String.toDirection(): String {
|
||||
return if (indexOf(" - ") > 0)
|
||||
split(" - ").last()
|
||||
else
|
||||
this
|
||||
}
|
||||
|
||||
private fun RemoteViews.setRealtime(
|
||||
context: Context,
|
||||
stopName: String,
|
||||
lineName: String,
|
||||
lineBtm: Bitmap,
|
||||
realTimes: RealTimes
|
||||
) {
|
||||
setImageViewBitmap(
|
||||
R.id.widget_tao_line_icon_image_view,
|
||||
lineBtm.roundCorner(context.resources.getDimension(R.dimen.widget_tao_corner))
|
||||
)
|
||||
setTextViewText(R.id.widget_tao_stop_name_text_view, stopName)
|
||||
setTextViewText(R.id.widget_tao_stop_direction_text_view, lineName)
|
||||
|
||||
val now = GregorianCalendar()
|
||||
setTextViewText(
|
||||
R.id.widget_tao_refresh_date_text_view,
|
||||
context.getString(
|
||||
R.string.widget_tao_time,
|
||||
now.get(GregorianCalendar.HOUR_OF_DAY),
|
||||
now.get(GregorianCalendar.MINUTE)
|
||||
)
|
||||
)
|
||||
|
||||
setTextViewText(
|
||||
R.id.widget_tao_next_text_view, context.getString(
|
||||
R.string.time_remaining,
|
||||
realTimes.timeRemaining(0) ?: "∅"
|
||||
)
|
||||
)
|
||||
|
||||
setTextViewText(
|
||||
R.id.widget_tao_after_next_text_view,
|
||||
context.getString(
|
||||
R.string.time_remaining,
|
||||
realTimes.timeRemainingAfter(0) ?: "∅"
|
||||
)
|
||||
)
|
||||
|
||||
setTextViewText(
|
||||
R.id.widget_tao_third_text_view,
|
||||
context.getString(
|
||||
R.string.time_remaining,
|
||||
realTimes.timeRemainingThird(0) ?: "∅"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun Bitmap.roundCorner(radius: Float): Bitmap {
|
||||
val res = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(res)
|
||||
|
||||
canvas.drawColor(Color.TRANSPARENT)
|
||||
|
||||
val shader = BitmapShader(
|
||||
this,
|
||||
Shader.TileMode.CLAMP,
|
||||
Shader.TileMode.CLAMP
|
||||
)
|
||||
|
||||
val paint = Paint()
|
||||
paint.isAntiAlias = true
|
||||
paint.shader = shader
|
||||
|
||||
|
||||
val rect = RectF(0.0f, 0.0f, width.toFloat(), height.toFloat())
|
||||
|
||||
canvas.drawRoundRect(rect, radius, radius, paint)
|
||||
|
||||
return res
|
||||
}
|
||||
}
|
||||
|
||||
private val job = Job()
|
||||
private val coroutineContext = Dispatchers.IO + job
|
||||
|
||||
override fun onUpdate(
|
||||
context: Context,
|
||||
appWidgetManager: AppWidgetManager,
|
||||
appWidgetIds: IntArray
|
||||
) {
|
||||
GlobalScope.launch(coroutineContext) {
|
||||
val taoRestApi = TaoRestApi()
|
||||
// There may be multiple widgets active, so update all of them
|
||||
for (appWidgetId in appWidgetIds) {
|
||||
updateAppWidget(context, appWidgetManager, appWidgetId, taoRestApi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onEnabled(context: Context) {
|
||||
// Enter relevant functionality for when the first widget is created
|
||||
}
|
||||
|
||||
override fun onDisabled(context: Context) {
|
||||
// Enter relevant functionality for when the last widget is disabled
|
||||
}
|
||||
|
||||
override fun onDeleted(context: Context?, appWidgetIds: IntArray?) {
|
||||
super.onDeleted(context, appWidgetIds)
|
||||
Log.d(TAG, "onDeleted : ${appWidgetIds?.contentToString()}")
|
||||
|
||||
runBlocking {
|
||||
job.cancelAndJoin()
|
||||
}
|
||||
|
||||
if (context != null && appWidgetIds != null) {
|
||||
for (widgetId in appWidgetIds) {
|
||||
TaoWidgetConfigurationActivity.destroyPrefs(context, widgetId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package fr.oupson.taotoolbox.windows
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import fr.oupson.taotoolbox.R
|
||||
import fr.oupson.taotoolbox.adapters.ScheduleAdapter
|
||||
import fr.oupson.common.api.Schedule
|
||||
import fr.oupson.common.api.TaoRestApi
|
||||
import kotlinx.coroutines.*
|
||||
import org.osmdroid.views.MapView
|
||||
import org.osmdroid.views.overlay.Marker
|
||||
import org.osmdroid.views.overlay.infowindow.InfoWindow
|
||||
|
||||
class StopInfoWindow(
|
||||
mapView: MapView,
|
||||
private val taoRestApi: TaoRestApi = TaoRestApi()
|
||||
) : InfoWindow(R.layout.window_stop_info, mapView) {
|
||||
private val job = Job()
|
||||
private val windowsContext = Dispatchers.IO + job
|
||||
|
||||
private val titleTextView: TextView by lazy { view.findViewById(R.id.window_stop_info_title_text_view) }
|
||||
|
||||
private val scheduleList: ScheduleAdapter.SimplePtr<Array<Schedule>> by lazy {
|
||||
ScheduleAdapter.SimplePtr(
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
private val adapter: ScheduleAdapter by lazy { ScheduleAdapter(scheduleList) }
|
||||
private val recyclerView: RecyclerView by lazy {
|
||||
view.findViewById<RecyclerView>(R.id.window_stop_info_schedule_recycler_view).also {
|
||||
it.adapter = adapter
|
||||
it.layoutManager = LinearLayoutManager(view.context)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onOpen(item: Any?) {
|
||||
scheduleList.set(null)
|
||||
recyclerView.adapter?.notifyDataSetChanged()
|
||||
|
||||
if (item is Marker) {
|
||||
val id = item.id
|
||||
titleTextView.text = item.title
|
||||
|
||||
GlobalScope.launch(windowsContext) {
|
||||
val schedule = taoRestApi.getSchedule(id)
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
scheduleList.set(schedule)
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onClose() {
|
||||
windowsContext.cancelChildren()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<solid android:color="@color/widget_background_color" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/widget_border_color" />
|
||||
<corners android:radius="@dimen/widget_tao_corner" />
|
||||
<padding
|
||||
android:bottom="0dp"
|
||||
android:left="0dp"
|
||||
android:right="0dp"
|
||||
android:top="0dp" />
|
||||
</shape>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M12,2C8.13,2 5,5.13 5,9c0,5.25 7,13 7,13s7,-7.75 7,-13c0,-3.87 -3.13,-7 -7,-7zM12,11.5c-1.38,0 -2.5,-1.12 -2.5,-2.5s1.12,-2.5 2.5,-2.5 2.5,1.12 2.5,2.5 -1.12,2.5 -2.5,2.5z"/>
|
||||
</vector>
|
|
@ -0,0 +1,22 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<group
|
||||
android:scaleX="2.1141"
|
||||
android:scaleY="2.1141"
|
||||
android:translateX="28.6308"
|
||||
android:translateY="28.6308">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M12,2c-4.42,0 -8,0.5 -8,4v10c0,0.88 0.39,1.67 1,2.22L5,20c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1h8v1c0,0.55 0.45,1 1,1h1c0.55,0 1,-0.45 1,-1v-1.78c0.61,-0.55 1,-1.34 1,-2.22L20,6c0,-3.5 -3.58,-4 -8,-4zM17.66,4.99L6.34,4.99C6.89,4.46 8.31,4 12,4s5.11,0.46 5.66,0.99zM18,6.99L18,10L6,10L6,6.99h12zM17.66,16.73l-0.29,0.27L6.63,17l-0.29,-0.27C6.21,16.62 6,16.37 6,16v-4h12v4c0,0.37 -0.21,0.62 -0.34,0.73z" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M8.5,14.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0" />
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M15.5,14.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0" />
|
||||
</group>
|
||||
</vector>
|
|
@ -0,0 +1,5 @@
|
|||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M11,15h2v2h-2v-2zM11,7h2v6h-2L11,7zM11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
|
||||
</vector>
|
|
@ -0,0 +1,10 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="#FFFFFF"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM19,19L5,19L5,5h11.17L19,7.83L19,19zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3zM6,6h9v4L6,10z" />
|
||||
</vector>
|
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,33 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="false"
|
||||
tools:context=".activities.MainActivity">
|
||||
|
||||
<org.osmdroid.views.MapView
|
||||
android:id="@+id/map"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="?actionBarSize"
|
||||
android:fitsSystemWindows="false" />
|
||||
|
||||
<com.google.android.material.progressindicator.LinearProgressIndicator
|
||||
android:id="@+id/loading_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:indicatorColor="?attr/colorAccent"
|
||||
app:layout_anchor="@id/bottomAppBar" />
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
style="@style/Widget.MaterialComponents.BottomAppBar.Colored"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
app:fabAlignmentMode="end" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,107 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".activities.TaoWidgetConfigurationActivity">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="?actionBarSize">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/config_select_line"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:hint="@string/activity_tao_config_line"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/activity_tao_config_line"
|
||||
android:inputType="none"
|
||||
android:labelFor="@id/config_select_line" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/config_select_direction"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:hint="@string/activity_tao_config_direction"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/config_select_line">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/activity_tao_config_direction"
|
||||
android:inputType="none"
|
||||
android:labelFor="@id/config_select_direction" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/config_select_stop"
|
||||
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:hint="@string/activity_tao_config_stop"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/config_select_direction">
|
||||
|
||||
<AutoCompleteTextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/activity_tao_config_stop"
|
||||
android:inputType="none"
|
||||
android:labelFor="@id/config_select_stop" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
android:id="@+id/fabConfirm"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/activity_tao_config_save_button_description"
|
||||
android:focusable="true"
|
||||
android:src="@drawable/ic_outline_save_24"
|
||||
app:layout_anchor="@id/bottomAppBar" />
|
||||
|
||||
<com.google.android.material.progressindicator.CircularProgressIndicator
|
||||
android:id="@+id/loading_progress_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:indeterminate="true"
|
||||
android:visibility="gone"
|
||||
app:indicatorColor="?attr/colorAccent" />
|
||||
|
||||
<com.google.android.material.bottomappbar.BottomAppBar
|
||||
android:id="@+id/bottomAppBar"
|
||||
style="@style/Widget.MaterialComponents.BottomAppBar.Colored"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
app:fabAlignmentMode="end" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
|
@ -0,0 +1,19 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_line_image_view"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:contentDescription="@string/widget_tao_line_image_view_description"
|
||||
android:padding="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_line_name_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="8dp" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,33 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_line_direction_image_view"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:contentDescription="@string/widget_tao_line_image_view_description"
|
||||
android:padding="8dp" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="2"
|
||||
android:text="@string/activity_tao_config_direction"
|
||||
android:textStyle="italic" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_line_direction_name_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="2"
|
||||
android:gravity="center_vertical" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,64 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_schedule_direction_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="4dp"
|
||||
android:layout_marginEnd="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold|italic"
|
||||
tools:text="L" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="4dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/item_schedule_line_image_view"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/widget_tao_line_image_view_description"
|
||||
android:maxWidth="48dp"
|
||||
android:maxHeight="48dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:textColor="@color/widget_text_color"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_schedule_next_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold|italic"
|
||||
tools:text="10mn" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/item_schedule_after_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold|italic"
|
||||
tools:text="20mn" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,6 @@
|
|||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/item_stop_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="8dp" />
|
|
@ -0,0 +1,115 @@
|
|||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/tao_widget_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="@dimen/widget_tao_margin"
|
||||
android:background="@drawable/bg_widget"
|
||||
android:orientation="vertical"
|
||||
android:padding="@dimen/widget_tao_margin"
|
||||
android:theme="@style/ThemeOverlay.TaoToolbox.AppWidgetContainer">
|
||||
<!-- TODO ALL dp/sp IN DIMEN -->
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="4dp">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="3">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_tao_stop_name_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="3"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="Gascogne" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_tao_refresh_date_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center_vertical|end"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold"
|
||||
tools:text="12:58" />
|
||||
</LinearLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_tao_stop_direction_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="2"
|
||||
android:gravity="center_vertical"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold|italic"
|
||||
tools:text="L" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="4dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/widget_tao_line_icon_image_view"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@android:color/transparent"
|
||||
android:contentDescription="@string/widget_tao_line_image_view_description"
|
||||
android:maxWidth="48dp"
|
||||
android:maxHeight="48dp"
|
||||
android:scaleType="fitCenter"
|
||||
android:textColor="@color/widget_text_color"
|
||||
tools:src="@mipmap/ic_launcher" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_tao_next_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold|italic"
|
||||
tools:text="10mn" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_tao_after_next_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold|italic"
|
||||
tools:text="20mn" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/widget_tao_third_text_view"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:textColor="@color/widget_text_color"
|
||||
android:textSize="12sp"
|
||||
android:textStyle="bold|italic"
|
||||
tools:text="30mn" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/bg_widget"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/window_stop_info_title_text_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:textSize="18sp"
|
||||
android:textColor="@color/widget_text_color"
|
||||
tools:text="Gascogne" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/window_stop_info_schedule_recycler_view"
|
||||
android:layout_width="@dimen/window_stop_info_dimen"
|
||||
android:layout_height="@dimen/window_stop_info_dimen" />
|
||||
</LinearLayout>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/tao_blue" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 4.6 KiB |
After Width: | Height: | Size: 7.2 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Boite à outils TAO</string>
|
||||
<string name="line_popup_description">Direction : %s</string>
|
||||
<string name="activity_tao_config_missing_value">Il manque une valeur</string>
|
||||
<string name="activity_tao_config_line">Ligne</string>
|
||||
<string name="activity_tao_config_direction">Direction</string>
|
||||
<string name="activity_tao_config_stop">Arret</string>
|
||||
<string name="activity_tao_config_save_button_description">Sauvegarder</string>
|
||||
<string name="widget_tao_line_image_view_description">Le logo de la ligne</string>
|
||||
<string name="widget_tao_description">Un widget pour savoir le temps restant avant un départ</string>
|
||||
<string name="widget_tao_error_title">Erreur</string>
|
||||
<string name="widget_tao_error_description">Quelque chose s\'est mal passé</string>
|
||||
<string name="time_remaining">%smn</string>
|
||||
</resources>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="is_theme_light">false</bool>
|
||||
<bool name="nav_bar_light">true</bool>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="widget_background_color">#99000000</color>
|
||||
<color name="widget_border_color">#000</color>
|
||||
<color name="widget_text_color">#fff</color>
|
||||
</resources>
|
|
@ -0,0 +1,27 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.TaoToolbox" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/tao_blue</item>
|
||||
<item name="colorPrimaryVariant">@color/tao_blue_variant</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/tao_pink</item>
|
||||
<item name="colorSecondaryVariant">@color/tao_pink_variant</item>
|
||||
<item name="colorOnSecondary">@color/white</item>
|
||||
<!-- Status bar color. -->
|
||||
<!-- <item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item> -->
|
||||
<!-- Customize your theme here. -->
|
||||
|
||||
|
||||
<item name="android:windowLightStatusBar" tools:targetApi="m">@bool/is_theme_light</item>
|
||||
<item name="android:navigationBarColor" tools:targetApi="lollipop">?attr/colorPrimary</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">@bool/nav_bar_light
|
||||
</item>
|
||||
<item name="colorControlHighlight">?attr/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.TaoToolbox.AppWidgetContainer" parent="">
|
||||
<item name="appWidgetTextColor">@color/widget_text_color</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,6 @@
|
|||
<resources>
|
||||
<declare-styleable name="AppWidgetAttrs">
|
||||
<attr name="appWidgetBackgroundColor" format="color" />
|
||||
<attr name="appWidgetTextColor" format="color" />
|
||||
</declare-styleable>
|
||||
</resources>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<bool name="is_theme_light">true</bool>
|
||||
<bool name="nav_bar_light">false</bool>
|
||||
</resources>
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="tao_blue">#4baed5</color>
|
||||
<color name="tao_blue_variant">#16527c</color>
|
||||
|
||||
<color name="tao_night_blue">#26285b</color>
|
||||
<color name="tao_night_blue_variant">#7c86b2</color>
|
||||
|
||||
<color name="tao_pink">#e0217e</color>
|
||||
<color name="tao_pink_variant">#a10058</color>
|
||||
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
|
||||
<color name="widget_background_color">#99ffffff</color>
|
||||
<color name="widget_border_color">#fff</color>
|
||||
<color name="widget_text_color">#000</color>
|
||||
</resources>
|
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!--
|
||||
Refer to App Widget Documentation for margin information
|
||||
http://developer.android.com/guide/topics/appwidgets/index.html#CreatingLayout
|
||||
TODO DOC
|
||||
-->
|
||||
|
||||
<!-- Tao Widget -->
|
||||
<dimen name="widget_tao_margin">2dp</dimen>
|
||||
<dimen name="widget_tao_corner">10dp</dimen>
|
||||
|
||||
<!-- Stop Info Window -->
|
||||
<dimen name="window_stop_info_dimen">200dp</dimen>
|
||||
</resources>
|
|
@ -0,0 +1,25 @@
|
|||
<resources>
|
||||
<string name="app_name">TAO ToolBox</string>
|
||||
|
||||
<!-- MainActivity -->
|
||||
<string name="line_popup_description">Direction : %s</string>
|
||||
|
||||
<!-- TaoWidgetConfigurationActivity -->
|
||||
<string name="activity_tao_config_missing_value">Missing value</string>
|
||||
<string name="activity_tao_config_line">Line</string>
|
||||
<string name="activity_tao_config_direction">Direction</string>
|
||||
<string name="activity_tao_config_stop">Stop</string>
|
||||
<string name="activity_tao_config_save_button_description">Save</string>
|
||||
|
||||
|
||||
<!-- Widget -->
|
||||
<string name="widget_tao_line_image_view_description">The logo of the line</string>
|
||||
<string name="widget_tao_description">A widget to know the remaining time</string>
|
||||
<string name="widget_tao_time" translatable="false">%02d:%02d</string>
|
||||
<string name="widget_tao_error_title">Error</string>
|
||||
<string name="widget_tao_error_description">Something went wrong</string>
|
||||
|
||||
<string name="time_remaining">%smn</string>
|
||||
|
||||
<string name="line_and_direction_name" translatable="false">%s : %s</string>
|
||||
</resources>
|
|
@ -0,0 +1,26 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.TaoToolbox" parent="Theme.MaterialComponents.DayNight.NoActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/tao_night_blue</item>
|
||||
<item name="colorPrimaryVariant">@color/tao_night_blue_variant</item>
|
||||
<item name="colorOnPrimary">@color/white</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/tao_pink</item>
|
||||
<item name="colorSecondaryVariant">@color/tao_pink_variant</item>
|
||||
<item name="colorOnSecondary">@color/white</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">@android:color/transparent</item>
|
||||
<!-- Customize your theme here. -->
|
||||
|
||||
<item name="android:windowLightStatusBar" tools:targetApi="m">@bool/is_theme_light</item>
|
||||
<item name="android:navigationBarColor" tools:targetApi="lollipop">?attr/colorPrimary</item>
|
||||
<item name="android:windowLightNavigationBar" tools:targetApi="o_mr1">@bool/nav_bar_light
|
||||
</item>
|
||||
<item name="colorControlHighlight">?attr/colorPrimary</item>
|
||||
</style>
|
||||
|
||||
<style name="ThemeOverlay.TaoToolbox.AppWidgetContainer" parent="">
|
||||
<item name="appWidgetTextColor">@color/widget_text_color</item>
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<full-backup-content>
|
||||
<!-- Exclude specific shared preferences that contain GCM registration Id -->
|
||||
<!-- TODO -->
|
||||
</full-backup-content>
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:configure="fr.oupson.taotoolbox.activities.TaoWidgetConfigurationActivity"
|
||||
android:initialKeyguardLayout="@layout/widget_tao"
|
||||
android:initialLayout="@layout/widget_tao"
|
||||
android:minWidth="120dp"
|
||||
android:minHeight="20dp"
|
||||
android:minResizeWidth="40dp"
|
||||
android:minResizeHeight="40dp"
|
||||
android:previewImage="@drawable/tao_widget_preview"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:updatePeriodMillis="600000"
|
||||
android:widgetCategory="home_screen"
|
||||
android:description="@string/widget_tao_description" />
|
|
@ -0,0 +1,17 @@
|
|||
package fr.oupson.taotoolbox
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
buildscript {
|
||||
ext.kotlin_version = "1.4.32"
|
||||
ext.ktor_version = "1.5.3"
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:4.1.3'
|
||||
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
jcenter()
|
||||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,49 @@
|
|||
plugins {
|
||||
id 'com.android.library'
|
||||
id 'kotlin-android'
|
||||
id 'org.jetbrains.kotlin.plugin.serialization' version '1.4.30'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "29.0.3"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
consumerProguardFiles "consumer-rules.pro"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
//minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0"
|
||||
|
||||
implementation "io.ktor:ktor-client-serialization:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-core:$ktor_version"
|
||||
implementation "io.ktor:ktor-client-cio:$ktor_version"
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
|
||||
-keepattributes *Annotation*, InnerClasses
|
||||
-dontnote kotlinx.serialization.AnnotationsKt # core serialization annotations
|
||||
|
||||
# kotlinx-serialization-json specific. Add this if you have java.lang.NoClassDefFoundError kotlinx.serialization.json.JsonObjectSerializer
|
||||
-keepclassmembers class kotlinx.serialization.json.** {
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class kotlinx.serialization.json.** {
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
||||
|
||||
# Change here com.yourcompany.yourpackage
|
||||
-keep,includedescriptorclasses class fr.oupson.common.**$$serializer { *; } # <-- change package name to your app's
|
||||
-keepclassmembers class fr.oupson.common.** { # <-- change package name to your app's
|
||||
*** Companion;
|
||||
}
|
||||
-keepclasseswithmembers class fr.oupson.common.** { # <-- change package name to your app's
|
||||
kotlinx.serialization.KSerializer serializer(...);
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,24 @@
|
|||
package fr.oupson.common
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("fr.oupson.common.test", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="fr.oupson.common">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
</manifest>
|
|
@ -0,0 +1,89 @@
|
|||
package fr.oupson.common.api
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Paint
|
||||
import android.graphics.Rect
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import fr.oupson.common.R
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
@Serializable
|
||||
data class LineColors(
|
||||
val lineCode: String,
|
||||
val background: String,
|
||||
val text: String,
|
||||
val commuteMode: String,
|
||||
val lineOrder: Int,
|
||||
val _id: String,
|
||||
val lineShortCode: String
|
||||
) {
|
||||
companion object {
|
||||
fun getLineBtm(
|
||||
context: Context,
|
||||
lineCode: String,
|
||||
lineColor: Int,
|
||||
textColor: Int,
|
||||
width: Int = 24,
|
||||
height: Int = 24
|
||||
): Bitmap {
|
||||
val res = Bitmap.createBitmap(width, height, Bitmap.Config.RGB_565)
|
||||
val canvas = Canvas(res)
|
||||
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), Paint().apply {
|
||||
color = lineColor
|
||||
})
|
||||
|
||||
val textPaint = Paint().apply {
|
||||
typeface = ResourcesCompat.getFont(context, R.font.noto_sans_bold)
|
||||
textAlign = Paint.Align.CENTER
|
||||
color = textColor
|
||||
}
|
||||
|
||||
setTextSizeForWidth(
|
||||
textPaint,
|
||||
(4f * width.toFloat()) / 5,
|
||||
(4f * height.toFloat()) / 5f,
|
||||
lineCode
|
||||
)
|
||||
|
||||
val rect = Rect()
|
||||
canvas.getClipBounds(rect)
|
||||
val cHeight: Int = rect.height()
|
||||
|
||||
textPaint.getTextBounds(lineCode, 0, lineCode.length, rect)
|
||||
val xPos =
|
||||
width.toFloat() / 2f // cWidth / 2f - rect.width().toFloat() / 2f - rect.left.toFloat()
|
||||
val yPos = cHeight / 2f + rect.height() / 2f - rect.bottom
|
||||
|
||||
canvas.drawText(lineCode, xPos, yPos, textPaint)
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
private fun setTextSizeForWidth(
|
||||
paint: Paint, desiredWidth: Float, desiredHeight: Float,
|
||||
text: String
|
||||
) {
|
||||
|
||||
// Pick a reasonably large value for the test. Larger values produce
|
||||
// more accurate results, but may cause problems with hardware
|
||||
// acceleration. But there are workarounds for that, too; refer to
|
||||
// http://stackoverflow.com/questions/6253528/font-size-too-large-to-fit-in-cache
|
||||
val testTextSize = 48f
|
||||
|
||||
// Get the bounds of the text, using our testTextSize.
|
||||
paint.textSize = testTextSize
|
||||
val bounds = Rect()
|
||||
paint.getTextBounds(text, 0, text.length, bounds)
|
||||
|
||||
// Calculate the desired size as a proportion of our testTextSize.
|
||||
val desiredTextSize: Float =
|
||||
(testTextSize * desiredWidth / bounds.width()).coerceAtMost(testTextSize * desiredHeight / bounds.height())
|
||||
|
||||
// Set the paint for that size.
|
||||
paint.textSize = desiredTextSize - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package fr.oupson.common.api
|
||||
|
||||
import android.util.Log
|
||||
import fr.oupson.common.BuildConfig
|
||||
import kotlinx.serialization.KSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.descriptors.PrimitiveKind
|
||||
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
|
||||
import kotlinx.serialization.descriptors.SerialDescriptor
|
||||
import kotlinx.serialization.encoding.Decoder
|
||||
import kotlinx.serialization.encoding.Encoder
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
@Serializable
|
||||
data class Stop(
|
||||
val type: String,
|
||||
val stopName: String,
|
||||
val city: String,
|
||||
val _id: String,
|
||||
// LONG THEN LAT
|
||||
val coord: FloatArray
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Stop
|
||||
|
||||
if (stopName != other.stopName) return false
|
||||
if (city != other.city) return false
|
||||
if (_id != other._id) return false
|
||||
if (!coord.contentEquals(other.coord)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = stopName.hashCode()
|
||||
result = 31 * result + city.hashCode()
|
||||
result = 31 * result + _id.hashCode()
|
||||
result = 31 * result + coord.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class RealTimes(
|
||||
val realTimes: Array<Departure>,
|
||||
val disruptions: String?,
|
||||
val summaryMessage: String?,
|
||||
val summaryStatus: String?
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as RealTimes
|
||||
|
||||
if (!realTimes.contentEquals(other.realTimes)) return false
|
||||
if (disruptions != other.disruptions) return false
|
||||
if (summaryMessage != other.summaryMessage) return false
|
||||
if (summaryStatus != other.summaryStatus) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = realTimes.contentHashCode()
|
||||
result = 31 * result + (disruptions?.hashCode() ?: 0)
|
||||
result = 31 * result + (summaryMessage?.hashCode() ?: 0)
|
||||
result = 31 * result + (summaryStatus?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
|
||||
fun timeRemaining(index: Int): Long? =
|
||||
this.realTimes.getOrNull(index)?.nextAsDate?.let {
|
||||
(it.time - Date().time) / (1000 * 60)
|
||||
}
|
||||
|
||||
fun timeRemainingAfter(index: Int): Long? =
|
||||
this.realTimes.getOrNull(index)?.nextAfterAsDate?.let {
|
||||
(it.time - Date().time) / (1000 * 60)
|
||||
}
|
||||
|
||||
fun timeRemainingThird(index: Int): Long? =
|
||||
this.realTimes.getOrNull(index)?.nextThirdAsDate?.let {
|
||||
(it.time - Date().time) / (1000 * 60)
|
||||
}
|
||||
}
|
||||
|
||||
@Serializable
|
||||
data class Departure(
|
||||
val lineId: String,
|
||||
val lineCode: String,
|
||||
val routeId: String,
|
||||
val lineColors: LineColors,
|
||||
@Serializable(with = DateAsStringSerializer::class)
|
||||
val nextAsDate: Date?,
|
||||
@Serializable(with = DateAsStringSerializer::class)
|
||||
val nextAfterAsDate: Date?,
|
||||
@Serializable(with = DateAsStringSerializer::class)
|
||||
val nextThirdAsDate: Date?,
|
||||
val nextVehicleId: String?,
|
||||
val nextAfterVehicleId: String?,
|
||||
val nextThirdVehicleId: String?,
|
||||
val lineDirectionName: String
|
||||
)
|
||||
|
||||
object DateAsStringSerializer : KSerializer<Date> {
|
||||
private const val TAG = "DateAsStringSerializer"
|
||||
override val descriptor: SerialDescriptor =
|
||||
PrimitiveSerialDescriptor("GregorianCalendar", PrimitiveKind.STRING)
|
||||
|
||||
override fun serialize(encoder: Encoder, value: Date) {
|
||||
encoder.encodeString(
|
||||
SimpleDateFormat(
|
||||
"yyyy-MM-dd'T'HH:mm:ss.SSSX",
|
||||
Locale.getDefault()
|
||||
).format(value)
|
||||
)
|
||||
}
|
||||
|
||||
override fun deserialize(decoder: Decoder): Date {
|
||||
val string = decoder.decodeString()
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.v(TAG, "Deserialize : $string")
|
||||
return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.getDefault()).parse(string)!!
|
||||
} else { // Z format is not supported :/
|
||||
val newDateStr = if (string.endsWith("Z")) {
|
||||
string.substring(0, string.length - 1) + "+0000" // Little trick to get time in correct time zone
|
||||
} else {
|
||||
throw Exception("$string is not supported") // TODO ?
|
||||
}
|
||||
|
||||
if (BuildConfig.DEBUG)
|
||||
Log.v(TAG, "New date str : $newDateStr")
|
||||
|
||||
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ", Locale.getDefault()).parse(newDateStr)!!
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Serializable
|
||||
data class Schedule(
|
||||
val lineId: String,
|
||||
val routeId: String,
|
||||
val lineColors: LineColors,
|
||||
val lineDirectionName: String,
|
||||
@Serializable(with = DateAsStringSerializer::class)
|
||||
val nextSchedule: Date?,
|
||||
@Serializable(with = DateAsStringSerializer::class)
|
||||
val nextScheduleAfter: Date?
|
||||
) {
|
||||
fun timeRemaining(): Long? =
|
||||
nextSchedule?.let {
|
||||
(it.time - Date().time) / (1000 * 60)
|
||||
}
|
||||
|
||||
fun timeRemainingAfter(): Long? =
|
||||
nextScheduleAfter?.let {
|
||||
(it.time - Date().time) / (1000 * 60)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package fr.oupson.common.api
|
||||
|
||||
import io.ktor.client.*
|
||||
import io.ktor.client.engine.cio.*
|
||||
import io.ktor.client.features.json.*
|
||||
import io.ktor.client.request.*
|
||||
import io.ktor.client.request.forms.*
|
||||
import io.ktor.http.*
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class TaoRestApi(private val httpClient: HttpClient) {
|
||||
constructor() : this(HttpClient(CIO) {
|
||||
install(JsonFeature)
|
||||
})
|
||||
|
||||
var requestContext: CoroutineContext = Dispatchers.IO
|
||||
|
||||
private var baseUrl = "https://navigorleans.c-t.io/api"
|
||||
|
||||
suspend fun getNextDeparturesForLine(
|
||||
stopId: String,
|
||||
routeId: String,
|
||||
limit: Int = 100
|
||||
): RealTimes = withContext(
|
||||
requestContext
|
||||
) {
|
||||
httpClient.submitForm(
|
||||
"$baseUrl/2.0/realtime/byStopAreaAndRoute",
|
||||
Parameters.build {
|
||||
append(
|
||||
"stopAreaId", stopId
|
||||
)
|
||||
append(
|
||||
"routeId", routeId
|
||||
)
|
||||
append("limit", "$limit")
|
||||
},
|
||||
true
|
||||
) {}
|
||||
}
|
||||
|
||||
suspend fun getTaoLineJson(
|
||||
clientLineVersion: Int,
|
||||
clientStopVersion: Int,
|
||||
clientLastUpdate: String?
|
||||
): String = withContext(requestContext) {
|
||||
httpClient.submitForm(
|
||||
"$baseUrl/2.0/version/withDate",
|
||||
Parameters.build {
|
||||
append("clientLineVersion", "$clientLineVersion")
|
||||
append("clientStopVersion", "$clientStopVersion")
|
||||
append("clientLastUpdate", clientLastUpdate ?: "1970-01-01T00:00:00.000Z")
|
||||
},
|
||||
true
|
||||
) {}
|
||||
}
|
||||
|
||||
suspend fun getSchedule(stopId: String): Array<Schedule> = withContext(Dispatchers.IO) {
|
||||
httpClient.submitForm("https://navigorleans.c-t.io/api/2.0/schedule", Parameters.build {
|
||||
append(
|
||||
"stopId", stopId
|
||||
)
|
||||
}, true) {}
|
||||
}
|
||||
|
||||
suspend fun getTaoGeoJson(
|
||||
lineId: String
|
||||
): String = withContext(requestContext) {
|
||||
httpClient.get("$baseUrl/2.0/lines/$lineId/geojson")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,333 @@
|
|||
package fr.oupson.common.db
|
||||
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.util.Log
|
||||
import fr.oupson.common.api.TaoRestApi
|
||||
import org.json.JSONObject
|
||||
|
||||
class TaoDatabaseHelper(
|
||||
context: Context,
|
||||
private val taoRestApi: TaoRestApi = TaoRestApi()
|
||||
) :
|
||||
SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
|
||||
class TaoDatabase {
|
||||
object LineEntry {
|
||||
const val TABLE_NAME = "LINE"
|
||||
const val COLUMN_NAME_LINE_ID = "lineID"
|
||||
const val COLUMN_NAME_LINE_NAME = "lineName"
|
||||
const val COLUMN_NAME_LINE_CODE = "lineCode"
|
||||
const val COLUMN_NAME_LINE_SHORT_CODE = "lineShortCode"
|
||||
const val COLUMN_NAME_BACKGROUND_COLOR = "backgroundColor"
|
||||
const val COLUMN_NAME_TEXT_COLOR = "textColor"
|
||||
const val COLUMN_NAME_COMMUTE_MODE = "commuteMode"
|
||||
const val COLUMN_NAME_LINE_ORDER = "lineOrder"
|
||||
|
||||
const val SQL_CREATE_ENTRIES =
|
||||
"CREATE TABLE $TABLE_NAME (" +
|
||||
"$COLUMN_NAME_LINE_ID TEXT PRIMARY KEY," +
|
||||
"$COLUMN_NAME_LINE_NAME TEXT," +
|
||||
"$COLUMN_NAME_LINE_CODE TEXT," +
|
||||
"$COLUMN_NAME_LINE_SHORT_CODE TEXT," +
|
||||
"$COLUMN_NAME_BACKGROUND_COLOR TEXT," +
|
||||
"$COLUMN_NAME_TEXT_COLOR TEXT," +
|
||||
"$COLUMN_NAME_COMMUTE_MODE TEXT," +
|
||||
"$COLUMN_NAME_LINE_ORDER INTEGER" +
|
||||
")"
|
||||
const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS $TABLE_NAME"
|
||||
}
|
||||
|
||||
object StopEntry {
|
||||
const val TABLE_NAME = "STOP"
|
||||
const val COLUMN_NAME_STOP_ID = "stopID"
|
||||
const val COLUMN_NAME_STOP_NAME = "stopName"
|
||||
const val COLUMN_NAME_STOP_NAME_NORMALIZED = "stopNameNormalized"
|
||||
const val COLUMN_NAME_CITY_NAME = "cityName"
|
||||
const val COLUMN_NAME_COORD_LONG = "coordLong"
|
||||
const val COLUMN_NAME_COORD_LAT = "coordLat"
|
||||
|
||||
const val SQL_CREATE_ENTRIES =
|
||||
"CREATE TABLE $TABLE_NAME(" +
|
||||
"$COLUMN_NAME_STOP_ID TEXT PRIMARY KEY," +
|
||||
"$COLUMN_NAME_STOP_NAME TEXT," +
|
||||
"$COLUMN_NAME_STOP_NAME_NORMALIZED TEXT," +
|
||||
"$COLUMN_NAME_CITY_NAME TEXT," +
|
||||
"$COLUMN_NAME_COORD_LONG REAL," +
|
||||
"$COLUMN_NAME_COORD_LAT REAL" +
|
||||
")"
|
||||
|
||||
const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS $TABLE_NAME"
|
||||
}
|
||||
|
||||
object DirectionEntry {
|
||||
const val TABLE_NAME = "DIRECTION"
|
||||
const val COLUMN_NAME_ROUTE_ID = "routeID"
|
||||
const val COLUMN_NAME_ROUTE_NAME = "routeName"
|
||||
|
||||
const val SQL_CREATE_ENTRIES = "CREATE TABLE $TABLE_NAME(" +
|
||||
"$COLUMN_NAME_ROUTE_ID TEXT PRIMARY KEY," +
|
||||
"${LineEntry.COLUMN_NAME_LINE_ID} TEXT," +
|
||||
"$COLUMN_NAME_ROUTE_NAME TEXT" +
|
||||
")"
|
||||
|
||||
const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS $TABLE_NAME"
|
||||
}
|
||||
|
||||
object IsStopEntry {
|
||||
const val TABLE_NAME = "IS_STOP"
|
||||
const val COLUMN_NAME_STOP_INDEX = "stopIndex"
|
||||
const val COLUMN_NAME_STOP_POINT_ID = "stopPointID"
|
||||
|
||||
const val SQL_CREATE_ENTRIES = "CREATE TABLE $TABLE_NAME(" +
|
||||
"${DirectionEntry.COLUMN_NAME_ROUTE_ID} TEXT," +
|
||||
"${StopEntry.COLUMN_NAME_STOP_ID} TEXT," +
|
||||
"$COLUMN_NAME_STOP_INDEX INTEGER," +
|
||||
"$COLUMN_NAME_STOP_POINT_ID TEXT," +
|
||||
"PRIMARY KEY(${DirectionEntry.COLUMN_NAME_ROUTE_ID}, ${StopEntry.COLUMN_NAME_STOP_ID}, ${IsStopEntry.COLUMN_NAME_STOP_INDEX})" +
|
||||
")"
|
||||
|
||||
const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS $TABLE_NAME"
|
||||
}
|
||||
|
||||
object VersionEntry {
|
||||
const val TABLE_NAME = "VERSION"
|
||||
const val PRIMARY_KEY = "versionPrimaryKey"
|
||||
const val COLUMN_NAME_STOP_VERSION = "stopVersion"
|
||||
const val COLUMN_NAME_LINE_VERSION = "lineVersion"
|
||||
const val COLUMN_NAME_LAST_UPDATE = "lastUpdate"
|
||||
|
||||
const val SQL_CREATE_ENTRIES = "CREATE TABLE $TABLE_NAME(" +
|
||||
"$PRIMARY_KEY INTEGER PRIMARY KEY," +
|
||||
"$COLUMN_NAME_STOP_VERSION INTEGER," +
|
||||
"$COLUMN_NAME_LINE_VERSION INTEGER," +
|
||||
"$COLUMN_NAME_LAST_UPDATE TEXT" +
|
||||
")"
|
||||
|
||||
const val SQL_DELETE_ENTRIES = "DROP TABLE IF EXISTS $TABLE_NAME"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun onCreate(db: SQLiteDatabase?) {
|
||||
db?.execSQL(TaoDatabase.LineEntry.SQL_CREATE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.StopEntry.SQL_CREATE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.DirectionEntry.SQL_CREATE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.IsStopEntry.SQL_CREATE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.VersionEntry.SQL_CREATE_ENTRIES)
|
||||
}
|
||||
|
||||
suspend fun checkUpdate() {
|
||||
val str = taoRestApi.getTaoLineJson(11, 11, null)
|
||||
val json = JSONObject(str)
|
||||
|
||||
val db = this.writableDatabase
|
||||
|
||||
val cursor = db.rawQuery(
|
||||
"SELECT ${TaoDatabase.VersionEntry.COLUMN_NAME_LINE_VERSION}, ${TaoDatabase.VersionEntry.COLUMN_NAME_STOP_VERSION}, ${TaoDatabase.VersionEntry.COLUMN_NAME_LAST_UPDATE} FROM ${TaoDatabase.VersionEntry.TABLE_NAME}",
|
||||
null
|
||||
)
|
||||
val updateVersion = cursor.moveToNext()
|
||||
val updateDb = if (updateVersion) {
|
||||
val currentDBLineVersion = cursor.getInt(0)
|
||||
val currentDBStopVersion = cursor.getInt(1)
|
||||
val currentDBLastUpdate = cursor.getString(2)
|
||||
|
||||
val jsonLineVersion = json.getInt("actualLineVersion")
|
||||
val jsonStopVersion = json.getInt("actualStopVersion")
|
||||
val jsonLastUpdate = json.getString("lastUpdate")
|
||||
|
||||
currentDBLineVersion != jsonLineVersion || currentDBStopVersion != jsonStopVersion || currentDBLastUpdate != jsonLastUpdate
|
||||
} else {
|
||||
true
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
|
||||
Log.d(TAG, "updating database : $updateDb")
|
||||
|
||||
if (updateDb) {
|
||||
db?.execSQL(TaoDatabase.LineEntry.SQL_DELETE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.StopEntry.SQL_DELETE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.DirectionEntry.SQL_DELETE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.IsStopEntry.SQL_DELETE_ENTRIES)
|
||||
|
||||
db?.execSQL(TaoDatabase.LineEntry.SQL_CREATE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.StopEntry.SQL_CREATE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.DirectionEntry.SQL_CREATE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.IsStopEntry.SQL_CREATE_ENTRIES)
|
||||
|
||||
val stops = json.getJSONArray("stops")
|
||||
for (i in 0 until stops.length()) {
|
||||
val stop = stops.getJSONObject(i)
|
||||
val stopEntryValues = ContentValues().apply {
|
||||
put(TaoDatabase.StopEntry.COLUMN_NAME_STOP_ID, stop.getString("_id"))
|
||||
put(TaoDatabase.StopEntry.COLUMN_NAME_STOP_NAME, stop.getString("stopName"))
|
||||
put(
|
||||
TaoDatabase.StopEntry.COLUMN_NAME_STOP_NAME_NORMALIZED,
|
||||
stop.getString("stopNameNormalized")
|
||||
)
|
||||
put(TaoDatabase.StopEntry.COLUMN_NAME_CITY_NAME, stop.getString("city"))
|
||||
val coordArray = stop.getJSONArray("coord")
|
||||
put(TaoDatabase.StopEntry.COLUMN_NAME_COORD_LONG, coordArray.getDouble(0))
|
||||
put(TaoDatabase.StopEntry.COLUMN_NAME_COORD_LAT, coordArray.getDouble(1))
|
||||
}
|
||||
|
||||
db?.insert(TaoDatabase.StopEntry.TABLE_NAME, null, stopEntryValues)
|
||||
|
||||
val stopAreasOnLine = stop.getJSONArray("stopAreaOnLines")
|
||||
for (stopAreaIndex in 0 until stopAreasOnLine.length()) {
|
||||
val stopArea = stopAreasOnLine.getJSONObject(stopAreaIndex)
|
||||
val isStopEntryValues = ContentValues().apply {
|
||||
put(
|
||||
TaoDatabase.DirectionEntry.COLUMN_NAME_ROUTE_ID,
|
||||
stopArea.getString("routeId")
|
||||
)
|
||||
put(TaoDatabase.StopEntry.COLUMN_NAME_STOP_ID, stopArea.getString("stopId"))
|
||||
put(
|
||||
TaoDatabase.IsStopEntry.COLUMN_NAME_STOP_INDEX,
|
||||
stopArea.getString("stopIndex")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.IsStopEntry.COLUMN_NAME_STOP_POINT_ID,
|
||||
stopArea.getString("stopPointId")
|
||||
)
|
||||
}
|
||||
|
||||
db?.insert(TaoDatabase.IsStopEntry.TABLE_NAME, null, isStopEntryValues)
|
||||
}
|
||||
}
|
||||
|
||||
val lines = json.getJSONArray("lines")
|
||||
for (i in 0 until lines.length()) {
|
||||
val line = lines.getJSONObject(i)
|
||||
|
||||
val lineEntryValues = ContentValues().apply {
|
||||
put(TaoDatabase.LineEntry.COLUMN_NAME_LINE_ID, line.getString("_id"))
|
||||
put(TaoDatabase.LineEntry.COLUMN_NAME_LINE_NAME, line.getString("lineName"))
|
||||
put(TaoDatabase.LineEntry.COLUMN_NAME_LINE_CODE, line.getString("lineCode"))
|
||||
put(
|
||||
TaoDatabase.LineEntry.COLUMN_NAME_LINE_SHORT_CODE,
|
||||
line.getString("lineShortCode")
|
||||
)
|
||||
|
||||
val lineColors = line.getJSONObject("lineColors")
|
||||
put(
|
||||
TaoDatabase.LineEntry.COLUMN_NAME_BACKGROUND_COLOR,
|
||||
lineColors.getString("background")
|
||||
)
|
||||
put(TaoDatabase.LineEntry.COLUMN_NAME_TEXT_COLOR, lineColors.getString("text"))
|
||||
put(
|
||||
TaoDatabase.LineEntry.COLUMN_NAME_COMMUTE_MODE,
|
||||
lineColors.getString("commuteMode")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.LineEntry.COLUMN_NAME_LINE_ORDER,
|
||||
lineColors.getString("lineOrder")
|
||||
)
|
||||
}
|
||||
|
||||
db?.insert(TaoDatabase.LineEntry.TABLE_NAME, null, lineEntryValues)
|
||||
|
||||
val directions = line.getJSONArray("lineDirections")
|
||||
for (directionIndex in 0 until directions.length()) {
|
||||
val direction = directions.getJSONObject(directionIndex)
|
||||
|
||||
val directionEntryValues = ContentValues().apply {
|
||||
put(
|
||||
TaoDatabase.DirectionEntry.COLUMN_NAME_ROUTE_ID,
|
||||
direction.getString("routeCode")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.LineEntry.COLUMN_NAME_LINE_ID,
|
||||
direction.getString("lineCode")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.DirectionEntry.COLUMN_NAME_ROUTE_NAME,
|
||||
direction.getString("routeName")
|
||||
)
|
||||
}
|
||||
db?.insert(TaoDatabase.DirectionEntry.TABLE_NAME, null, directionEntryValues)
|
||||
}
|
||||
}
|
||||
|
||||
if (updateVersion) {
|
||||
db?.update(
|
||||
TaoDatabase.VersionEntry.TABLE_NAME,
|
||||
ContentValues().apply {
|
||||
put(TaoDatabase.VersionEntry.PRIMARY_KEY, 0)
|
||||
put(
|
||||
TaoDatabase.VersionEntry.COLUMN_NAME_LINE_VERSION,
|
||||
json.getInt("actualLineVersion")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.VersionEntry.COLUMN_NAME_STOP_VERSION,
|
||||
json.getInt("actualStopVersion")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.VersionEntry.COLUMN_NAME_LAST_UPDATE,
|
||||
json.getString("lastUpdate")
|
||||
)
|
||||
},
|
||||
"${TaoDatabase.VersionEntry.PRIMARY_KEY} = 0",
|
||||
null
|
||||
)
|
||||
} else {
|
||||
db.insert(TaoDatabase.VersionEntry.TABLE_NAME, null, ContentValues().apply {
|
||||
put(TaoDatabase.VersionEntry.PRIMARY_KEY, 0)
|
||||
put(
|
||||
TaoDatabase.VersionEntry.COLUMN_NAME_LINE_VERSION,
|
||||
json.getInt("actualLineVersion")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.VersionEntry.COLUMN_NAME_STOP_VERSION,
|
||||
json.getInt("actualStopVersion")
|
||||
)
|
||||
put(
|
||||
TaoDatabase.VersionEntry.COLUMN_NAME_LAST_UPDATE,
|
||||
json.getString("lastUpdate")
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
db.close()
|
||||
}
|
||||
|
||||
fun getRouteName(db: SQLiteDatabase, routeId: String): String? {
|
||||
val cursor = db.rawQuery(
|
||||
"SELECT ${TaoDatabase.DirectionEntry.COLUMN_NAME_ROUTE_NAME} FROM ${TaoDatabase.DirectionEntry.TABLE_NAME} WHERE ${TaoDatabase.DirectionEntry.COLUMN_NAME_ROUTE_ID} = ?",
|
||||
arrayOf(routeId)
|
||||
)
|
||||
|
||||
val res = if (cursor.moveToNext()) {
|
||||
cursor.getString(0)
|
||||
} else {
|
||||
|
||||
null
|
||||
}
|
||||
|
||||
cursor.close()
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
|
||||
db?.execSQL(TaoDatabase.LineEntry.SQL_DELETE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.StopEntry.SQL_DELETE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.DirectionEntry.SQL_DELETE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.IsStopEntry.SQL_DELETE_ENTRIES)
|
||||
db?.execSQL(TaoDatabase.VersionEntry.SQL_DELETE_ENTRIES)
|
||||
onCreate(db)
|
||||
}
|
||||
|
||||
override fun onDowngrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
|
||||
onUpgrade(db, oldVersion, newVersion)
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val DATABASE_VERSION = 3
|
||||
const val DATABASE_NAME = "TaoDb.sql"
|
||||
private const val TAG = "TaoDatabaseHelper"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package fr.oupson.common
|
||||
|
||||
import org.junit.Test
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
class ExampleUnitTest {
|
||||
@Test
|
||||
fun addition_isCorrect() {
|
||||
assertEquals(4, 2 + 2)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Project-wide Gradle settings.
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||
# Android operating system, and which are packaged with your app"s APK
|
||||
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||
android.useAndroidX=true
|
||||
# Automatically convert third-party libraries to use AndroidX
|
||||
android.enableJetifier=true
|
||||
# Kotlin code style for this project: "official" or "obsolete":
|
||||
kotlin.code.style=official
|
|
@ -0,0 +1,6 @@
|
|||
#Thu Apr 01 20:55:44 CEST 2021
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip
|
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
NONSTOP* )
|
||||
nonstop=true
|
||||
;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Escape application args
|
||||
save () {
|
||||
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||
echo " "
|
||||
}
|
||||
APP_ARGS=$(save "$@")
|
||||
|
||||
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||
|
||||
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||
cd "$(dirname "$0")"
|
||||
fi
|
||||
|
||||
exec "$JAVACMD" "$@"
|
|
@ -0,0 +1,84 @@
|
|||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windows variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
|
@ -0,0 +1,4 @@
|
|||
include ':wear_tao'
|
||||
include ':common'
|
||||
include ':app'
|
||||
rootProject.name = "Tao Toolbox"
|
|
@ -0,0 +1 @@
|
|||
/build
|
|
@ -0,0 +1,51 @@
|
|||
plugins {
|
||||
id 'com.android.application'
|
||||
id 'kotlin-android'
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdkVersion 30
|
||||
buildToolsVersion "29.0.3"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "fr.oupson.wear_tao"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 30
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
kotlinOptions {
|
||||
jvmTarget = '1.8'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.4.2'
|
||||
implementation 'androidx.core:core-ktx:1.3.2'
|
||||
implementation 'androidx.appcompat:appcompat:1.2.0'
|
||||
implementation 'com.google.android.material:material:1.3.0'
|
||||
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
|
||||
testImplementation 'junit:junit:4.13.2'
|
||||
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
|
||||
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
|
||||
implementation 'androidx.wear:wear:1.1.0'
|
||||
implementation "androidx.wear:wear-tiles:1.0.0-alpha01"
|
||||
debugImplementation "androidx.wear:wear-tiles-renderer:1.0.0-alpha01"
|
||||
|
||||
implementation project(":common")
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,24 @@
|
|||
package fr.oupson.wear_tao
|
||||
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
import org.junit.Assert.*
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* See [testing documentation](http://d.android.com/tools/testing).
|
||||
*/
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ExampleInstrumentedTest {
|
||||
@Test
|
||||
fun useAppContext() {
|
||||
// Context of the app under test.
|
||||
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||
assertEquals("fr.oupson.wear_tao", appContext.packageName)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="fr.oupson.wear_tao">
|
||||
|
||||
<uses-feature android:name="android.hardware.type.watch" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/Theme.TaoToolbox">
|
||||
<activity android:name=".MainActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<meta-data
|
||||
android:name="com.google.android.wearable.standalone"
|
||||
android:value="true" />
|
||||
|
||||
<service
|
||||
android:name=".TaoTile"
|
||||
android:description="@string/tile_description"
|
||||
android:exported="true"
|
||||
android:label="Tuile TAO"
|
||||
android:permission="com.google.android.wearable.permission.BIND_TILE_PROVIDER">
|
||||
<intent-filter>
|
||||
<action android:name="androidx.wear.tiles.action.BIND_TILE_PROVIDER" />
|
||||
</intent-filter>
|
||||
<intent-filter>
|
||||
<action android:name="com.google.android.clockwork.ACTION_TILE_UPDATE_REQUEST" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.wear.tiles.PREVIEW"
|
||||
android:resource="@drawable/ic_launcher_background" />
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -0,0 +1,30 @@
|
|||
package fr.oupson.wear_tao
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.os.Bundle
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.wear.tiles.manager.TileManager
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
private lateinit var tileManager: TileManager
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_main)
|
||||
val rootLayout = findViewById<FrameLayout>(R.id.tile_container)
|
||||
tileManager = TileManager(
|
||||
context = this,
|
||||
component = ComponentName(this, TaoTile::class.java),
|
||||
parentView = rootLayout
|
||||
)
|
||||
tileManager.create()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
tileManager.close()
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
package fr.oupson.wear_tao
|
||||
|
||||
import android.graphics.Color
|
||||
import android.util.Log
|
||||
import androidx.wear.tiles.TileProviderService
|
||||
import androidx.wear.tiles.builders.*
|
||||
import androidx.wear.tiles.builders.DimensionBuilders.*
|
||||
import androidx.wear.tiles.builders.LayoutElementBuilders.*
|
||||
import androidx.wear.tiles.readers.EventReaders
|
||||
import androidx.wear.tiles.readers.RequestReaders
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import fr.oupson.common.api.LineColors.Companion.getLineBtm
|
||||
import fr.oupson.common.api.RealTimes
|
||||
import fr.oupson.common.api.TaoRestApi
|
||||
import fr.oupson.common.db.TaoDatabaseHelper
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.guava.future
|
||||
import java.nio.ByteBuffer
|
||||
|
||||
// TODO
|
||||
class TaoTile : TileProviderService() {
|
||||
companion object {
|
||||
private const val TAG = "TaoTile"
|
||||
private const val RESOURCE_VERSION = "1"
|
||||
}
|
||||
|
||||
private val serviceJob = Job()
|
||||
private val serviceScope = CoroutineScope(Dispatchers.IO + serviceJob)
|
||||
|
||||
private val taoRestApi : TaoRestApi by lazy {
|
||||
TaoRestApi().apply {
|
||||
this.requestContext = Dispatchers.IO + serviceJob
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTileRequest(requestParams: RequestReaders.TileRequest) = serviceScope.future {
|
||||
val gascogne = async {
|
||||
taoRestApi.getNextDeparturesForLine(
|
||||
"a",
|
||||
"route:OLS:1_R_388"
|
||||
)
|
||||
}
|
||||
|
||||
val coligny = async {
|
||||
taoRestApi.getNextDeparturesForLine(
|
||||
"b",
|
||||
"route:OLS:1_R_388"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
val iut = async {
|
||||
taoRestApi.getNextDeparturesForLine(
|
||||
"c",
|
||||
"route:OLS:1_A_419"
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
TileBuilders.Tile.builder()
|
||||
.setResourcesVersion(RESOURCE_VERSION)
|
||||
.setTimeline(
|
||||
TimelineBuilders.Timeline.builder().addTimelineEntry(
|
||||
TimelineBuilders.TimelineEntry.builder().setLayout(
|
||||
Layout.builder().setRoot(
|
||||
|
||||
Box.builder()
|
||||
.setWidth(expand())
|
||||
.setHeight(expand())
|
||||
.setModifiers(
|
||||
ModifiersBuilders.Modifiers.builder()
|
||||
.setClickable(
|
||||
ModifiersBuilders.Clickable.builder()
|
||||
.setId("reLoad")
|
||||
.setOnClick(ActionBuilders.LoadAction.builder())
|
||||
)
|
||||
)
|
||||
.setVerticalAlignment(VALIGN_CENTER)
|
||||
.addContent(
|
||||
Column.builder()
|
||||
.setWidth(expand())
|
||||
.setHeight(wrap())
|
||||
.setHorizontalAlignment(HALIGN_CENTER)
|
||||
.addContent(generateLayout("a", gascogne.await()))
|
||||
.addContent(generateLayout("b", coligny.await()))
|
||||
.addContent(generateLayout("c", iut.await()))
|
||||
.build()
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
).build()
|
||||
}
|
||||
|
||||
private fun generateLayout(stopName: String, realTimes: RealTimes): LayoutElement =
|
||||
Column.builder()
|
||||
.setWidth(expand())
|
||||
.setHeight(wrap()).setModifiers(
|
||||
ModifiersBuilders.Modifiers.builder()
|
||||
.setPadding(
|
||||
ModifiersBuilders.Padding.builder()
|
||||
.setTop(dp(2f))
|
||||
.setBottom(dp(2f))
|
||||
.setStart(dp(2f))
|
||||
.setEnd(dp(2f))
|
||||
)
|
||||
)
|
||||
.addContent(
|
||||
Text.builder().setText(stopName).setFontStyle(FontStyle.builder().setSize(sp(16f)))
|
||||
.setLineHeight(sp(0f))
|
||||
.build()
|
||||
).also {
|
||||
val r = realTimes.realTimes.getOrNull(0)
|
||||
realTimes.realTimes.getOrNull(0)?.lineDirectionName?.let { dir ->
|
||||
it.addContent(
|
||||
line(dir)
|
||||
|
||||
|
||||
)
|
||||
}
|
||||
}
|
||||
.addContent(
|
||||
layout(realTimes)
|
||||
)
|
||||
.build()
|
||||
|
||||
private fun layout(realTimes: RealTimes): LayoutElement = Row.builder()
|
||||
.setHeight(wrap())
|
||||
.addContent(
|
||||
Text.builder()
|
||||
.setText("${realTimes.timeRemaining(0) ?: "∅"}")
|
||||
.setFontStyle(FontStyle.builder().setSize(sp(14f)))
|
||||
)
|
||||
.addContent(Spacer.builder().setWidth(dp(16f)))
|
||||
.addContent(
|
||||
Text.builder()
|
||||
.setText("${realTimes.timeRemainingAfter(0) ?: "∅"}")
|
||||
.setFontStyle(FontStyle.builder().setSize(sp(14f)))
|
||||
)
|
||||
.addContent(Spacer.builder().setWidth(dp(16f)))
|
||||
.addContent(
|
||||
Text.builder()
|
||||
.setText("${realTimes.timeRemainingThird(0) ?: "∅"}")
|
||||
.setFontStyle(FontStyle.builder().setSize(sp(14f)))
|
||||
)
|
||||
.build()
|
||||
|
||||
|
||||
private fun line2(dir: String): LayoutElement = Spannable.builder()
|
||||
.addSpan(
|
||||
SpanImage.builder()
|
||||
.setHeight(dp(18f))
|
||||
.setWidth(dp(18f))
|
||||
.setResourceId("line1")
|
||||
).addSpan(
|
||||
SpanText.builder().setText(dir)
|
||||
.setFontStyle(FontStyle.builder().setSize(sp(16f)))
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
|
||||
private fun line(dir: String): LayoutElement = Row.builder()
|
||||
.setVerticalAlignment(VALIGN_CENTER)
|
||||
.addContent(
|
||||
Image.builder()
|
||||
.setHeight(dp(18f))
|
||||
.setWidth(dp(18f))
|
||||
.setResourceId("line1")
|
||||
)
|
||||
.addContent(Spacer.builder().setWidth(dp(2f)))
|
||||
.addContent(
|
||||
Text.builder().setText(dir)
|
||||
.setFontStyle(FontStyle.builder().setSize(sp(16f)))
|
||||
|
||||
.build()
|
||||
).build()
|
||||
|
||||
override fun onTileAddEvent(requestParams: EventReaders.TileAddEvent) {
|
||||
super.onTileAddEvent(requestParams)
|
||||
Log.d(TAG, "onTileAddEvent")
|
||||
}
|
||||
|
||||
override fun onResourcesRequest(requestParams: RequestReaders.ResourcesRequest): ListenableFuture<ResourceBuilders.Resources> =
|
||||
serviceScope.future {
|
||||
val db = TaoDatabaseHelper(this@TaoTile).readableDatabase
|
||||
val c = db.rawQuery(
|
||||
"SELECT backgroundColor, textColor FROM LINE WHERE lineCode = ?",
|
||||
arrayOf("1")
|
||||
)
|
||||
c.moveToNext()
|
||||
val bg = c.getString(0)
|
||||
val tc = c.getString(1)
|
||||
c.close()
|
||||
|
||||
db.close()
|
||||
|
||||
val btm = getLineBtm(
|
||||
this@TaoTile,
|
||||
"1",
|
||||
Color.parseColor(bg),
|
||||
Color.parseColor(tc),
|
||||
width = 24,
|
||||
height = 24
|
||||
)
|
||||
|
||||
val bitmapData = ByteBuffer.allocate(btm.byteCount).apply {
|
||||
btm.copyPixelsToBuffer(this)
|
||||
}.array()
|
||||
|
||||
ResourceBuilders.Resources.builder()
|
||||
.setVersion(RESOURCE_VERSION)
|
||||
.addIdToImageMapping(
|
||||
"line1", ResourceBuilders.ImageResource.builder()
|
||||
.setInlineResource(
|
||||
ResourceBuilders.InlineImageResource.builder()
|
||||
.setData(bitmapData)
|
||||
.setWidthPx(24)
|
||||
.setHeightPx(24)
|
||||
.setFormat(ResourceBuilders.IMAGE_FORMAT_RGB_565)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
).build()
|
||||
}
|
||||
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
// Cleans up the coroutine
|
||||
serviceJob.cancel()
|
||||
}
|
||||
}
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path
|
||||
android:fillColor="#3DDC84"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeWidth="0.8"
|
||||
android:strokeColor="#33FFFFFF" />
|
||||
</vector>
|
|
@ -0,0 +1,30 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportWidth="108"
|
||||
android:viewportHeight="108">
|
||||
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="85.84757"
|
||||
android:endY="92.4963"
|
||||
android:startX="42.9492"
|
||||
android:startY="49.59793"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||
android:strokeWidth="1"
|
||||
android:strokeColor="#00000000" />
|
||||
</vector>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/tile_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/black"/>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@drawable/ic_launcher_background" />
|
||||
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||
</adaptive-icon>
|
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 5.2 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 4.8 KiB |
After Width: | Height: | Size: 7.3 KiB |
After Width: | Height: | Size: 7.7 KiB |
After Width: | Height: | Size: 12 KiB |
After Width: | Height: | Size: 10 KiB |
After Width: | Height: | Size: 16 KiB |
|
@ -0,0 +1,16 @@
|
|||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.TaoToolbox" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
<item name="colorPrimary">@color/purple_200</item>
|
||||
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||
<item name="colorOnPrimary">@color/black</item>
|
||||
<!-- Secondary brand color. -->
|
||||
<item name="colorSecondary">@color/teal_200</item>
|
||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="purple_200">#FFBB86FC</color>
|
||||
<color name="purple_500">#FF6200EE</color>
|
||||
<color name="purple_700">#FF3700B3</color>
|
||||
<color name="teal_200">#FF03DAC5</color>
|
||||
<color name="teal_700">#FF018786</color>
|
||||
<color name="black">#FF000000</color>
|
||||
<color name="white">#FFFFFFFF</color>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<resources>
|
||||
<string name="app_name">WearTAO</string>
|
||||
<string name="tile_description">Sample tiles</string>
|
||||
</resources>
|