This commit is contained in:
oupson 2021-12-25 21:29:29 +01:00
parent 163ef6197b
commit 5802c52b97
15 changed files with 214 additions and 97 deletions

View File

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
</set>
</option>
</component>
</project>

View File

@ -4,13 +4,13 @@ plugins {
} }
android { android {
compileSdkVersion 30 compileSdkVersion 31
buildToolsVersion "30.0.3" buildToolsVersion "30.0.3"
defaultConfig { defaultConfig {
applicationId "fr.oupson.taotoolbox" applicationId "fr.oupson.taotoolbox"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 31
versionCode 1 versionCode 1
versionName "0.0.3" versionName "0.0.3"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
@ -43,19 +43,17 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.7.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.2'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference-ktx:1.1.1' implementation 'androidx.preference:preference-ktx:1.1.1'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.3' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'
implementation project(":common") implementation project(":common")
implementation 'org.osmdroid:osmdroid-android:6.1.10' implementation 'org.osmdroid:osmdroid-android:6.1.10'

View File

@ -2,6 +2,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="fr.oupson.taotoolbox"> package="fr.oupson.taotoolbox">
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" /> <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
@ -15,13 +16,17 @@
android:roundIcon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/Theme.TaoToolbox"> android:theme="@style/Theme.TaoToolbox">
<activity android:name=".activities.TaoWidgetConfigurationActivity"> <activity
android:name=".activities.TaoWidgetConfigurationActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
</intent-filter> </intent-filter>
</activity> </activity>
<receiver android:name=".receivers.TaoWidget"> <receiver
android:name=".receivers.TaoWidget"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter> </intent-filter>
@ -31,7 +36,9 @@
android:resource="@xml/widget_tao_info" /> android:resource="@xml/widget_tao_info" />
</receiver> </receiver>
<activity android:name=".activities.MainActivity"> <activity
android:name=".activities.MainActivity"
android:exported="true">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@ -12,6 +12,7 @@ import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import fr.oupson.common.api.TaoRestApi import fr.oupson.common.api.TaoRestApi
import fr.oupson.common.db.TaoDatabaseHelper import fr.oupson.common.db.TaoDatabaseHelper
@ -20,6 +21,7 @@ import fr.oupson.common.db.TaoDatabaseHelper.TaoDatabase.StopEntry
import fr.oupson.taotoolbox.BuildConfig import fr.oupson.taotoolbox.BuildConfig
import fr.oupson.taotoolbox.R import fr.oupson.taotoolbox.R
import fr.oupson.taotoolbox.databinding.ActivityMainBinding import fr.oupson.taotoolbox.databinding.ActivityMainBinding
import fr.oupson.taotoolbox.utils.PolylineDecoder
import fr.oupson.taotoolbox.windows.StopInfoWindow import fr.oupson.taotoolbox.windows.StopInfoWindow
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.json.JSONArray import org.json.JSONArray
@ -98,7 +100,7 @@ class MainActivity : AppCompatActivity() {
requestPermissionsIfNecessary() requestPermissionsIfNecessary()
GlobalScope.launch(scope) { lifecycleScope.launch(scope) {
val helper = TaoDatabaseHelper(this@MainActivity).apply { val helper = TaoDatabaseHelper(this@MainActivity).apply {
try { try {
this.checkUpdate() this.checkUpdate()
@ -241,12 +243,7 @@ class MainActivity : AppCompatActivity() {
) )
} }
val geo = route.getJSONArray("geojson") // TODO DECODE ENCODED ? PolylineDecoder.decodeInto(routeLine, route.getString("geojsonEncoded"), 1)
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) binding.map.overlays.add(routeLine)
} }

View File

@ -12,6 +12,7 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.widget.AutoCompleteTextView import android.widget.AutoCompleteTextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import fr.oupson.common.api.TaoRestApi import fr.oupson.common.api.TaoRestApi
import fr.oupson.common.db.TaoDatabaseHelper import fr.oupson.common.db.TaoDatabaseHelper
import fr.oupson.common.db.TaoDatabaseHelper.TaoDatabase.* import fr.oupson.common.db.TaoDatabaseHelper.TaoDatabase.*
@ -69,7 +70,7 @@ class TaoWidgetConfigurationActivity : AppCompatActivity() {
val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(this) val appWidgetManager: AppWidgetManager = AppWidgetManager.getInstance(this)
val ids = val ids =
appWidgetManager.getAppWidgetIds(ComponentName(this, TaoWidget::class.java)) appWidgetManager.getAppWidgetIds(ComponentName(this, TaoWidget::class.java))
GlobalScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
for (id in ids) { for (id in ids) {
TaoWidget.updateAppWidget( TaoWidget.updateAppWidget(
this@TaoWidgetConfigurationActivity, this@TaoWidgetConfigurationActivity,
@ -88,7 +89,7 @@ class TaoWidgetConfigurationActivity : AppCompatActivity() {
(binding.configSelectLine.editText as? AutoCompleteTextView)?.also { autoCompleteTextView -> (binding.configSelectLine.editText as? AutoCompleteTextView)?.also { autoCompleteTextView ->
autoCompleteTextView.setAdapter(lineAdapter) autoCompleteTextView.setAdapter(lineAdapter)
autoCompleteTextView.setOnItemClickListener { _, _, position, _ -> autoCompleteTextView.setOnItemClickListener { _, _, position, _ ->
GlobalScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
linePosition = position linePosition = position
directionPosition = -1 directionPosition = -1
loadDirectionList(position) loadDirectionList(position)
@ -99,7 +100,7 @@ class TaoWidgetConfigurationActivity : AppCompatActivity() {
(binding.configSelectDirection.editText as? AutoCompleteTextView)?.also { directionAutoComplete -> (binding.configSelectDirection.editText as? AutoCompleteTextView)?.also { directionAutoComplete ->
directionAutoComplete.setAdapter(directionAdapter) directionAutoComplete.setAdapter(directionAdapter)
directionAutoComplete.setOnItemClickListener { _, _, position, _ -> directionAutoComplete.setOnItemClickListener { _, _, position, _ ->
GlobalScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
directionPosition = position directionPosition = position
stopPosition = -1 stopPosition = -1
@ -120,7 +121,7 @@ class TaoWidgetConfigurationActivity : AppCompatActivity() {
save() save()
} }
GlobalScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
db = TaoDatabaseHelper(this@TaoWidgetConfigurationActivity).apply { db = TaoDatabaseHelper(this@TaoWidgetConfigurationActivity).apply {
checkUpdate() checkUpdate()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View File

@ -7,18 +7,19 @@ import android.view.ViewGroup
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.oupson.taotoolbox.R
import fr.oupson.common.api.LineColors import fr.oupson.common.api.LineColors
import fr.oupson.common.api.Schedule import fr.oupson.common.api.RealtimeStopArea
import fr.oupson.taotoolbox.R
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
class ScheduleAdapter( // TODO ADAPT
private val scheduleList: SimplePtr<Array<Schedule>> class RealTimeAdapter(
private val realTimeList: SimplePtr<Array<RealtimeStopArea>>
) : ) :
RecyclerView.Adapter<ScheduleAdapter.ScheduleViewHolder>() { RecyclerView.Adapter<RealTimeAdapter.RealTimeViewHolder>() {
class SimplePtr<T>(private var value: T? = null) { class SimplePtr<T>(private var value: T? = null) {
fun get(): T? = value fun get(): T? = value
fun set(value: T?) { fun set(value: T?) {
@ -26,21 +27,23 @@ class ScheduleAdapter(
} }
} }
class ScheduleViewHolder( class RealTimeViewHolder(
private val view: View private val view: View
) : RecyclerView.ViewHolder(view) { ) : RecyclerView.ViewHolder(view) {
private val scheduleDirectionTextView = private val scheduleDirectionTextView =
view.findViewById<TextView>(R.id.item_schedule_direction_text_view) 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 scheduleImageView =
private val scheduleNextTextView = view.findViewById<TextView>(R.id.item_schedule_next_text_view) 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 = private val scheduleAfterTextView =
view.findViewById<TextView>(R.id.item_schedule_after_text_view) view.findViewById<TextView>(R.id.item_schedule_after_text_view)
fun bind(schedule: Schedule) { fun bind(schedule: RealtimeStopArea) {
scheduleDirectionTextView.text = view.context.getString( scheduleDirectionTextView.text = view.context.getString(
R.string.line_and_direction_name, R.string.line_and_direction_name,
schedule.lineColors.lineCode, schedule.lineColors.lineCode,
schedule.lineDirectionName schedule.routeName
) )
scheduleNextTextView.text = scheduleNextTextView.text =
@ -50,7 +53,6 @@ class ScheduleAdapter(
schedule.timeRemainingAfter() ?: "" schedule.timeRemainingAfter() ?: ""
) )
GlobalScope.launch(Dispatchers.Default) { GlobalScope.launch(Dispatchers.Default) {
val btm = LineColors.getLineBtm( val btm = LineColors.getLineBtm(
view.context, view.context,
@ -67,16 +69,16 @@ class ScheduleAdapter(
} }
} }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ScheduleViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RealTimeViewHolder {
val inflater = LayoutInflater.from(parent.context) val inflater = LayoutInflater.from(parent.context)
return ScheduleViewHolder(inflater.inflate(R.layout.item_schedule, parent, false)) return RealTimeViewHolder(inflater.inflate(R.layout.item_schedule, parent, false))
} }
override fun onBindViewHolder(holder: ScheduleViewHolder, position: Int) { override fun onBindViewHolder(holder: RealTimeViewHolder, position: Int) {
val ref = scheduleList.get() val ref = realTimeList.get()
if (ref != null) if (ref != null)
holder.bind(ref[position]) holder.bind(ref[position])
} }
override fun getItemCount(): Int = scheduleList.get()?.size ?: 0 override fun getItemCount(): Int = realTimeList.get()?.size ?: 0
} }

View File

@ -7,6 +7,7 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.* import android.graphics.*
import android.net.Uri import android.net.Uri
import android.os.Build
import android.util.Log import android.util.Log
import android.widget.RemoteViews import android.widget.RemoteViews
import fr.oupson.common.api.LineColors import fr.oupson.common.api.LineColors
@ -37,12 +38,18 @@ class TaoWidget : AppWidgetProvider() {
serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId)) serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId))
serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)) serviceIntent.data = Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME))
val pending = PendingIntent.getBroadcast( val pending =
PendingIntent.getBroadcast(
context, context,
appWidgetId, appWidgetId,
serviceIntent, serviceIntent,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
} else {
PendingIntent.FLAG_UPDATE_CURRENT PendingIntent.FLAG_UPDATE_CURRENT
}
) )
views.setOnClickPendingIntent(R.id.tao_widget_root, pending) views.setOnClickPendingIntent(R.id.tao_widget_root, pending)
try { try {

View File

@ -0,0 +1,37 @@
package fr.oupson.taotoolbox.utils;
import org.osmdroid.util.GeoPoint;
import org.osmdroid.views.overlay.Polyline;
public class PolylineDecoder {
public static void decodeInto(Polyline line, String encodedString, int precision) {
int index = 0;
int len = encodedString.length();
int lat = 0, lng = 0;
while (index < len) {
int b, shift, result;
shift = result = 0;
do {
b = encodedString.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlat = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lat += dlat;
shift = result = 0;
do {
b = encodedString.charAt(index++) - 63;
result |= (b & 0x1f) << shift;
shift += 5;
} while (b >= 0x20);
int dlng = ((result & 1) != 0 ? ~(result >> 1) : (result >> 1));
lng += dlng;
GeoPoint p = new GeoPoint(((double) (lng * precision)) / 1.0e6, ((double) (lat * precision)) / 1.0e6, 0);
line.addPoint(p);
}
}
}

View File

@ -3,10 +3,10 @@ package fr.oupson.taotoolbox.windows
import android.widget.TextView import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import fr.oupson.taotoolbox.R import fr.oupson.common.api.RealtimeStopArea
import fr.oupson.taotoolbox.adapters.ScheduleAdapter
import fr.oupson.common.api.Schedule
import fr.oupson.common.api.TaoRestApi import fr.oupson.common.api.TaoRestApi
import fr.oupson.taotoolbox.R
import fr.oupson.taotoolbox.adapters.RealTimeAdapter
import kotlinx.coroutines.* import kotlinx.coroutines.*
import org.osmdroid.views.MapView import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.Marker import org.osmdroid.views.overlay.Marker
@ -21,13 +21,13 @@ class StopInfoWindow(
private val titleTextView: TextView by lazy { view.findViewById(R.id.window_stop_info_title_text_view) } 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 { private val realtimeList: RealTimeAdapter.SimplePtr<Array<RealtimeStopArea>> by lazy {
ScheduleAdapter.SimplePtr( RealTimeAdapter.SimplePtr(
null null
) )
} }
private val adapter: ScheduleAdapter by lazy { ScheduleAdapter(scheduleList) } private val adapter: RealTimeAdapter by lazy { RealTimeAdapter(realtimeList) }
private val recyclerView: RecyclerView by lazy { private val recyclerView: RecyclerView by lazy {
view.findViewById<RecyclerView>(R.id.window_stop_info_schedule_recycler_view).also { view.findViewById<RecyclerView>(R.id.window_stop_info_schedule_recycler_view).also {
it.adapter = adapter it.adapter = adapter
@ -36,7 +36,7 @@ class StopInfoWindow(
} }
override fun onOpen(item: Any?) { override fun onOpen(item: Any?) {
scheduleList.set(null) realtimeList.set(null)
recyclerView.adapter?.notifyDataSetChanged() recyclerView.adapter?.notifyDataSetChanged()
if (item is Marker) { if (item is Marker) {
@ -44,10 +44,10 @@ class StopInfoWindow(
titleTextView.text = item.title titleTextView.text = item.title
GlobalScope.launch(windowsContext) { GlobalScope.launch(windowsContext) {
val schedule = taoRestApi.getSchedule(id) val realtime = taoRestApi.getRealtimeByStopArea(id)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
scheduleList.set(schedule) realtimeList.set(realtime)
adapter.notifyDataSetChanged() adapter.notifyDataSetChanged()
} }
} }

View File

@ -1,13 +1,13 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript { buildscript {
ext.kotlin_version = "1.5.0" ext.kotlin_version = "1.6.10"
ext.ktor_version = "1.5.3" ext.ktor_version = "1.6.3"
repositories { repositories {
google() google()
mavenCentral() mavenCentral()
} }
dependencies { dependencies {
classpath 'com.android.tools.build:gradle:4.2.0' classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong // NOTE: Do not place your application dependencies here; they belong

View File

@ -1,18 +1,16 @@
plugins { plugins {
id 'com.android.library' id 'com.android.library'
id 'kotlin-android' id 'kotlin-android'
id 'org.jetbrains.kotlin.plugin.serialization' version '1.4.30' id 'org.jetbrains.kotlin.plugin.serialization' version '1.5.30'
} }
android { android {
compileSdkVersion 30 compileSdkVersion 31
buildToolsVersion "30.0.3" buildToolsVersion "30.0.3"
defaultConfig { defaultConfig {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 30 targetSdkVersion 31
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro" consumerProguardFiles "consumer-rules.pro"
@ -34,14 +32,13 @@ android {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.appcompat:appcompat:1.4.0'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'com.google.android.material:material:1.4.0'
implementation 'com.google.android.material:material:1.3.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.1.0" implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2"
implementation "io.ktor:ktor-client-serialization:$ktor_version" implementation "io.ktor:ktor-client-serialization:$ktor_version"
implementation "io.ktor:ktor-client-core:$ktor_version" implementation "io.ktor:ktor-client-core:$ktor_version"

View File

@ -130,7 +130,10 @@ object DateAsStringSerializer : KSerializer<Date> {
SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.getDefault()).parse(string)!! SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSX", Locale.getDefault()).parse(string)!!
} else { // Z format is not supported :/ } else { // Z format is not supported :/
val newDateStr = if (string.endsWith("Z")) { val newDateStr = if (string.endsWith("Z")) {
string.substring(0, string.length - 1) + "+0000" // Little trick to get time in correct time zone string.substring(
0,
string.length - 1
) + "+0000" // Little trick to get time in correct time zone
} else { } else {
throw Exception("$string is not supported") // TODO ? throw Exception("$string is not supported") // TODO ?
} }
@ -165,3 +168,64 @@ data class Schedule(
(it.time - Date().time) / (1000 * 60) (it.time - Date().time) / (1000 * 60)
} }
} }
@Serializable
data class RealtimeStopArea(
val lineId: String,
val lineCode: String,
val lineColors: LineColors,
val routeId: String,
val routeName: String,
val lineIsTAD: Boolean,
val nextPassages: Array<NextPassage>,
val summaryStatus: String?
) {
fun timeRemaining(): Long? =
nextPassages.getOrNull(0)?.let {
(it.date!!.time - Date().time) / (1000 * 60)
}
fun timeRemainingAfter(): Long? =
nextPassages.getOrNull(1)?.let {
(it.date!!.time - Date().time) / (1000 * 60)
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as RealtimeStopArea
if (lineId != other.lineId) return false
if (lineCode != other.lineCode) return false
if (lineColors != other.lineColors) return false
if (routeId != other.routeId) return false
if (routeName != other.routeName) return false
if (lineIsTAD != other.lineIsTAD) return false
if (!nextPassages.contentEquals(other.nextPassages)) return false
if (summaryStatus != other.summaryStatus) return false
return true
}
override fun hashCode(): Int {
var result = lineId.hashCode()
result = 31 * result + lineCode.hashCode()
result = 31 * result + lineColors.hashCode()
result = 31 * result + routeId.hashCode()
result = 31 * result + routeName.hashCode()
result = 31 * result + lineIsTAD.hashCode()
result = 31 * result + nextPassages.contentHashCode()
result = 31 * result + (summaryStatus?.hashCode() ?: 0)
return result
}
}
@Serializable
data class NextPassage(
@Serializable(with = DateAsStringSerializer::class)
val date: Date?,
val vehicleId: String,
val loadPrediction: String?, // TODO
val isLiveData: Boolean
)

View File

@ -3,6 +3,7 @@ package fr.oupson.common.api
import io.ktor.client.* import io.ktor.client.*
import io.ktor.client.engine.android.* import io.ktor.client.engine.android.*
import io.ktor.client.features.json.* import io.ktor.client.features.json.*
import io.ktor.client.features.json.serializer.*
import io.ktor.client.request.* import io.ktor.client.request.*
import io.ktor.client.request.forms.* import io.ktor.client.request.forms.*
import io.ktor.http.* import io.ktor.http.*
@ -12,7 +13,9 @@ import kotlin.coroutines.CoroutineContext
class TaoRestApi(private val httpClient: HttpClient) { class TaoRestApi(private val httpClient: HttpClient) {
constructor() : this(HttpClient(Android) { constructor() : this(HttpClient(Android) {
install(JsonFeature) install(JsonFeature) {
serializer = KotlinxSerializer(kotlinx.serialization.json.Json { ignoreUnknownKeys = true })
}
engine { engine {
connectTimeout = 10_1000 connectTimeout = 10_1000
socketTimeout = 10_000 socketTimeout = 10_000
@ -69,9 +72,24 @@ class TaoRestApi(private val httpClient: HttpClient) {
}, true) {} }, true) {}
} }
suspend fun getRealtimeByStopArea(stopAreaId: String, limit: Int? = null, includeLoadPrediction: Boolean? = null) : Array<RealtimeStopArea> = withContext(Dispatchers.IO) {
httpClient.submitForm("https://navigorleans.c-t.io/api/3.0/realtime/byStopArea", Parameters.build {
append(
"stopAreaId", stopAreaId
)
if (limit != null) {
append("limit", limit.toString())
}
if (includeLoadPrediction != null) {
append("includeLoadPrediction", includeLoadPrediction.toString())
}
}, true) {}
}
suspend fun getTaoGeoJson( suspend fun getTaoGeoJson(
lineId: String lineId: String
): String = withContext(requestContext) { ): String = withContext(requestContext) {
httpClient.get("$baseUrl/2.0/lines/$lineId/geojson") httpClient.get("$baseUrl/2.0/lines/$lineId/geojson/encoded")
} }
} }

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip

View File

@ -33,16 +33,15 @@ android {
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.4.3'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.4.2' implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:1.4.2'
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.6.0'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.3.1'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.0'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation 'androidx.wear:wear:1.1.0' implementation 'androidx.wear:wear:1.1.0'
implementation "androidx.wear:wear-tiles:1.0.0-alpha01" implementation "androidx.wear:wear-tiles:1.0.0-alpha01"
debugImplementation "androidx.wear:wear-tiles-renderer:1.0.0-alpha01" debugImplementation "androidx.wear:wear-tiles-renderer:1.0.0-alpha01"