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>
|