Page MenuHomeGitPull.it

D83.1729607227.diff
No OneTemporary

Size
218 KB
Referenced Files
None
Subscribers
None

D83.1729607227.diff

diff --git a/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json
new file mode 100644
--- /dev/null
+++ b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/1.json
@@ -0,0 +1,464 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 1,
+ "identityHash": "5c633aea20ff416df784e00a939d7ae5",
+ "entities": [
+ {
+ "tableName": "gtfs_calendar_dates",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `date` TEXT NOT NULL, `exception_type` INTEGER NOT NULL, PRIMARY KEY(`service_id`, `date`), FOREIGN KEY(`service_id`) REFERENCES `gtfs_calendar`(`service_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exceptionType",
+ "columnName": "exception_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id",
+ "date"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "gtfs_calendar",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "service_id"
+ ],
+ "referencedColumns": [
+ "service_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "stops_gtfs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stop_id` INTEGER NOT NULL, `stop_code` TEXT NOT NULL, `stop_name` TEXT NOT NULL, `stop_desc` TEXT NOT NULL, `stop_lat` REAL NOT NULL, `stop_lon` REAL NOT NULL, `wheelchair_boarding` TEXT NOT NULL, PRIMARY KEY(`stop_id`))",
+ "fields": [
+ {
+ "fieldPath": "internalID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttStopID",
+ "columnName": "stop_code",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopName",
+ "columnName": "stop_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttPlaceName",
+ "columnName": "stop_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "stop_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "stop_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchair",
+ "columnName": "wheelchair_boarding",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_calendar",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `monday` INTEGER NOT NULL, `tuesday` INTEGER NOT NULL, `wednesday` INTEGER NOT NULL, `thursday` INTEGER NOT NULL, `friday` INTEGER NOT NULL, `saturday` INTEGER NOT NULL, `sunday` INTEGER NOT NULL, `start_date` TEXT NOT NULL, `end_date` TEXT NOT NULL, PRIMARY KEY(`service_id`))",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onMonday",
+ "columnName": "monday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onTuesday",
+ "columnName": "tuesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onWednesday",
+ "columnName": "wednesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onThursday",
+ "columnName": "thursday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onFriday",
+ "columnName": "friday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSaturday",
+ "columnName": "saturday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSunday",
+ "columnName": "sunday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "start_date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "end_date",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "routes_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_type` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, `route_sort_order` INTEGER NOT NULL, PRIMARY KEY(`route_id`))",
+ "fields": [
+ {
+ "fieldPath": "ID",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agencyID",
+ "columnName": "agency_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "route_short_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longName",
+ "columnName": "route_long_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "route_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "type",
+ "columnName": "route_type",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "route_color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "textColor",
+ "columnName": "route_text_color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "sortOrder",
+ "columnName": "route_sort_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "route_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_stop_times",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trip_id` TEXT NOT NULL, `arrival_time` TEXT NOT NULL, `departure_time` TEXT NOT NULL, `stop_id` INTEGER NOT NULL, `stop_sequence` INTEGER NOT NULL, PRIMARY KEY(`trip_id`, `stop_id`), FOREIGN KEY(`stop_id`) REFERENCES `stops_gtfs`(`stop_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`trip_id`) REFERENCES `gtfs_trips`(`trip_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrival_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departure_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopSequence",
+ "columnName": "stop_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id",
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_stop_times_stop_id",
+ "unique": false,
+ "columnNames": [
+ "stop_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_stop_times_stop_id` ON `${TABLE_NAME}` (`stop_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "stops_gtfs",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stop_id"
+ ],
+ "referencedColumns": [
+ "stop_id"
+ ]
+ },
+ {
+ "table": "gtfs_trips",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "trip_id"
+ ],
+ "referencedColumns": [
+ "trip_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_trips",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `service_id` TEXT NOT NULL, `trip_id` TEXT NOT NULL, `trip_headsign` TEXT NOT NULL, `direction_id` INTEGER NOT NULL, `block_id` TEXT NOT NULL, `shape_id` TEXT NOT NULL, `wheelchair_accessible` INTEGER NOT NULL, `limited_route` INTEGER NOT NULL, PRIMARY KEY(`trip_id`), FOREIGN KEY(`route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "routeID",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "trip_headsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionID",
+ "columnName": "direction_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockID",
+ "columnName": "block_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isWheelchairAccess",
+ "columnName": "wheelchair_accessible",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isLimitedRoute",
+ "columnName": "limited_route",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_trips_route_id",
+ "unique": false,
+ "columnNames": [
+ "route_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_trips_route_id` ON `${TABLE_NAME}` (`route_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "routes_table",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "route_id"
+ ],
+ "referencedColumns": [
+ "route_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_shapes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`shape_id` TEXT NOT NULL, `shape_pt_lat` REAL NOT NULL, `shape_pt_lon` REAL NOT NULL, `shape_pt_sequence` INTEGER NOT NULL, PRIMARY KEY(`shape_id`, `shape_pt_sequence`))",
+ "fields": [
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLat",
+ "columnName": "shape_pt_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLon",
+ "columnName": "shape_pt_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointSequence",
+ "columnName": "shape_pt_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "shape_id",
+ "shape_pt_sequence"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5c633aea20ff416df784e00a939d7ae5')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json
new file mode 100644
--- /dev/null
+++ b/assets/schemas/it.reyboz.bustorino.data.gtfs.GtfsDatabase/2.json
@@ -0,0 +1,648 @@
+{
+ "formatVersion": 1,
+ "database": {
+ "version": 2,
+ "identityHash": "6d2aa826894d1e6b1429678e13b65433",
+ "entities": [
+ {
+ "tableName": "gtfs_feeds",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`feed_id` TEXT NOT NULL, PRIMARY KEY(`feed_id`))",
+ "fields": [
+ {
+ "fieldPath": "gtfsId",
+ "columnName": "feed_id",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "feed_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_agencies",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`gtfs_id` TEXT NOT NULL, `ag_name` TEXT NOT NULL, `ag_url` TEXT NOT NULL, `fare_url` TEXT, `phone` TEXT, `feed_id` TEXT, PRIMARY KEY(`gtfs_id`))",
+ "fields": [
+ {
+ "fieldPath": "gtfsId",
+ "columnName": "gtfs_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "name",
+ "columnName": "ag_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "url",
+ "columnName": "ag_url",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "fareUrl",
+ "columnName": "fare_url",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "phone",
+ "columnName": "phone",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "feed.gtfsId",
+ "columnName": "feed_id",
+ "affinity": "TEXT",
+ "notNull": false
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "gtfs_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_calendar_dates",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `date` TEXT NOT NULL, `exception_type` INTEGER NOT NULL, PRIMARY KEY(`service_id`, `date`), FOREIGN KEY(`service_id`) REFERENCES `gtfs_calendar`(`service_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "date",
+ "columnName": "date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "exceptionType",
+ "columnName": "exception_type",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id",
+ "date"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "gtfs_calendar",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "service_id"
+ ],
+ "referencedColumns": [
+ "service_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "stops_gtfs",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`stop_id` INTEGER NOT NULL, `stop_code` TEXT NOT NULL, `stop_name` TEXT NOT NULL, `stop_desc` TEXT NOT NULL, `stop_lat` REAL NOT NULL, `stop_lon` REAL NOT NULL, `wheelchair_boarding` TEXT NOT NULL, PRIMARY KEY(`stop_id`))",
+ "fields": [
+ {
+ "fieldPath": "internalID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttStopID",
+ "columnName": "stop_code",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopName",
+ "columnName": "stop_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "gttPlaceName",
+ "columnName": "stop_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "latitude",
+ "columnName": "stop_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longitude",
+ "columnName": "stop_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "wheelchair",
+ "columnName": "wheelchair_boarding",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_calendar",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`service_id` TEXT NOT NULL, `monday` INTEGER NOT NULL, `tuesday` INTEGER NOT NULL, `wednesday` INTEGER NOT NULL, `thursday` INTEGER NOT NULL, `friday` INTEGER NOT NULL, `saturday` INTEGER NOT NULL, `sunday` INTEGER NOT NULL, `start_date` TEXT NOT NULL, `end_date` TEXT NOT NULL, PRIMARY KEY(`service_id`))",
+ "fields": [
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onMonday",
+ "columnName": "monday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onTuesday",
+ "columnName": "tuesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onWednesday",
+ "columnName": "wednesday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onThursday",
+ "columnName": "thursday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onFriday",
+ "columnName": "friday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSaturday",
+ "columnName": "saturday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "onSunday",
+ "columnName": "sunday",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "startDate",
+ "columnName": "start_date",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "endDate",
+ "columnName": "end_date",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "service_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "routes_table",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_mode` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, PRIMARY KEY(`route_id`))",
+ "fields": [
+ {
+ "fieldPath": "gtfsId",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "agencyID",
+ "columnName": "agency_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shortName",
+ "columnName": "route_short_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "longName",
+ "columnName": "route_long_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "description",
+ "columnName": "route_desc",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "mode",
+ "columnName": "route_mode",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "color",
+ "columnName": "route_color",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "textColor",
+ "columnName": "route_text_color",
+ "affinity": "TEXT",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "route_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "gtfs_stop_times",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`trip_id` TEXT NOT NULL, `arrival_time` TEXT NOT NULL, `departure_time` TEXT NOT NULL, `stop_id` INTEGER NOT NULL, `stop_sequence` INTEGER NOT NULL, PRIMARY KEY(`trip_id`, `stop_id`), FOREIGN KEY(`stop_id`) REFERENCES `stops_gtfs`(`stop_id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`trip_id`) REFERENCES `gtfs_trips`(`trip_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "arrivalTime",
+ "columnName": "arrival_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "departureTime",
+ "columnName": "departure_time",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopID",
+ "columnName": "stop_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopSequence",
+ "columnName": "stop_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id",
+ "stop_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_stop_times_stop_id",
+ "unique": false,
+ "columnNames": [
+ "stop_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_stop_times_stop_id` ON `${TABLE_NAME}` (`stop_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "stops_gtfs",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "stop_id"
+ ],
+ "referencedColumns": [
+ "stop_id"
+ ]
+ },
+ {
+ "table": "gtfs_trips",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "trip_id"
+ ],
+ "referencedColumns": [
+ "trip_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_trips",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`route_id` TEXT NOT NULL, `service_id` TEXT NOT NULL, `trip_id` TEXT NOT NULL, `trip_headsign` TEXT NOT NULL, `direction_id` INTEGER NOT NULL, `block_id` TEXT NOT NULL, `shape_id` TEXT NOT NULL, `wheelchair_accessible` INTEGER NOT NULL, `limited_route` INTEGER NOT NULL, PRIMARY KEY(`trip_id`), FOREIGN KEY(`route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "routeID",
+ "columnName": "route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "serviceID",
+ "columnName": "service_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripID",
+ "columnName": "trip_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "tripHeadsign",
+ "columnName": "trip_headsign",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionID",
+ "columnName": "direction_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "blockID",
+ "columnName": "block_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isWheelchairAccess",
+ "columnName": "wheelchair_accessible",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "isLimitedRoute",
+ "columnName": "limited_route",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "trip_id"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [
+ {
+ "name": "index_gtfs_trips_route_id",
+ "unique": false,
+ "columnNames": [
+ "route_id"
+ ],
+ "orders": [],
+ "createSql": "CREATE INDEX IF NOT EXISTS `index_gtfs_trips_route_id` ON `${TABLE_NAME}` (`route_id`)"
+ }
+ ],
+ "foreignKeys": [
+ {
+ "table": "routes_table",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "route_id"
+ ],
+ "referencedColumns": [
+ "route_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "gtfs_shapes",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`shape_id` TEXT NOT NULL, `shape_pt_lat` REAL NOT NULL, `shape_pt_lon` REAL NOT NULL, `shape_pt_sequence` INTEGER NOT NULL, PRIMARY KEY(`shape_id`, `shape_pt_sequence`))",
+ "fields": [
+ {
+ "fieldPath": "shapeID",
+ "columnName": "shape_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLat",
+ "columnName": "shape_pt_lat",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointLon",
+ "columnName": "shape_pt_lon",
+ "affinity": "REAL",
+ "notNull": true
+ },
+ {
+ "fieldPath": "pointSequence",
+ "columnName": "shape_pt_sequence",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "shape_id",
+ "shape_pt_sequence"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": []
+ },
+ {
+ "tableName": "mato_patterns",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pattern_name` TEXT NOT NULL, `pattern_code` TEXT NOT NULL, `pattern_hash` TEXT NOT NULL, `pattern_direction_id` INTEGER NOT NULL, `pattern_route_id` TEXT NOT NULL, `pattern_headsign` TEXT, `pattern_polyline` TEXT NOT NULL, `pattern_polylength` INTEGER NOT NULL, PRIMARY KEY(`pattern_code`), FOREIGN KEY(`pattern_route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "name",
+ "columnName": "pattern_name",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "code",
+ "columnName": "pattern_code",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "semanticHash",
+ "columnName": "pattern_hash",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "directionId",
+ "columnName": "pattern_direction_id",
+ "affinity": "INTEGER",
+ "notNull": true
+ },
+ {
+ "fieldPath": "routeGtfsId",
+ "columnName": "pattern_route_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "headsign",
+ "columnName": "pattern_headsign",
+ "affinity": "TEXT",
+ "notNull": false
+ },
+ {
+ "fieldPath": "patternGeometryPoly",
+ "columnName": "pattern_polyline",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "patternGeometryLength",
+ "columnName": "pattern_polylength",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "pattern_code"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "routes_table",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "pattern_route_id"
+ ],
+ "referencedColumns": [
+ "route_id"
+ ]
+ }
+ ]
+ },
+ {
+ "tableName": "patterns_stops",
+ "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`pattern_gtfs_id` TEXT NOT NULL, `stop_gtfs_id` TEXT NOT NULL, `stop_order` INTEGER NOT NULL, PRIMARY KEY(`pattern_gtfs_id`, `stop_gtfs_id`, `stop_order`), FOREIGN KEY(`pattern_gtfs_id`) REFERENCES `mato_patterns`(`pattern_code`) ON UPDATE NO ACTION ON DELETE CASCADE )",
+ "fields": [
+ {
+ "fieldPath": "patternId",
+ "columnName": "pattern_gtfs_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "stopGtfsId",
+ "columnName": "stop_gtfs_id",
+ "affinity": "TEXT",
+ "notNull": true
+ },
+ {
+ "fieldPath": "order",
+ "columnName": "stop_order",
+ "affinity": "INTEGER",
+ "notNull": true
+ }
+ ],
+ "primaryKey": {
+ "columnNames": [
+ "pattern_gtfs_id",
+ "stop_gtfs_id",
+ "stop_order"
+ ],
+ "autoGenerate": false
+ },
+ "indices": [],
+ "foreignKeys": [
+ {
+ "table": "mato_patterns",
+ "onDelete": "CASCADE",
+ "onUpdate": "NO ACTION",
+ "columns": [
+ "pattern_gtfs_id"
+ ],
+ "referencedColumns": [
+ "pattern_code"
+ ]
+ }
+ ]
+ }
+ ],
+ "views": [],
+ "setupQueries": [
+ "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+ "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6d2aa826894d1e6b1429678e13b65433')"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
--- a/build.gradle
+++ b/build.gradle
@@ -7,6 +7,8 @@
}
ext {
+ androidXTestVersion = "1.4.0"
+
//multidex
multidex_version = "2.0.1"
//libraries versions
@@ -57,6 +59,12 @@
versionName "1.16.3"
vectorDrawables.useSupportLibrary = true
multiDexEnabled true
+ javaCompileOptions {
+ annotationProcessorOptions {
+ arguments = ["room.schemaLocation": "$projectDir/assets/schemas/".toString()]
+ }
+ }
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
compileOptions {
@@ -65,6 +73,8 @@
}
sourceSets {
+ androidTest.assets.srcDirs += files("$projectDir/assets/schemas/".toString())
+
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = ['src']
@@ -93,7 +103,7 @@
dependencies {
//new libraries
- implementation "androidx.fragment:fragment:$fragment_version"
+ implementation "androidx.fragment:fragment-ktx:$fragment_version"
implementation "androidx.activity:activity:$activity_version"
implementation "androidx.annotation:annotation:1.3.0"
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.1.0"
@@ -121,25 +131,45 @@
// ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
// LiveData
- implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
+ implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
// Lifecycles only (without ViewModel or LiveData)
- implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"
+ implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version"
+ // Legacy
+ implementation 'androidx.legacy:legacy-support-v4:1.0.0'
// Room components
implementation "androidx.room:room-ktx:$room_version"
kapt "androidx.room:room-compiler:$room_version"
- androidTestImplementation "androidx.room:room-testing:$room_version"
//multidex - we need this to build the app
implementation "androidx.multidex:multidex:$multidex_version"
implementation 'de.siegmar:fastcsv:2.0.0'
+ testImplementation 'junit:junit:4.12'
+ implementation 'junit:junit:4.12'
+
+ implementation "androidx.test.ext:junit:1.1.3"
+ implementation "androidx.test:core:$androidXTestVersion"
+ implementation "androidx.test:runner:$androidXTestVersion"
+ implementation "androidx.room:room-testing:$room_version"
+
+ androidTestImplementation "androidx.test.ext:junit:1.1.3"
+ androidTestImplementation "androidx.test:core:$androidXTestVersion"
+ androidTestImplementation "androidx.test:runner:$androidXTestVersion"
+ androidTestImplementation "androidx.test:rules:$androidXTestVersion"
+ androidTestImplementation "androidx.room:room-testing:$room_version"
+
+
+
+
}
}
dependencies {
+
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version"
api "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version"
+
}
diff --git a/res/drawable/ic_moving.xml b/res/drawable/ic_moving.xml
new file mode 100644
--- /dev/null
+++ b/res/drawable/ic_moving.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.15,36.2 L3.85,33.9 14.9,22.85Q16.55,21.2 18.9,21.2Q21.25,21.2 22.9,22.85L25.2,25.15Q25.9,25.85 26.85,25.85Q27.8,25.85 28.55,25.15L38.55,15.15H32.9V11.85H44.15V23.15H40.9V17.5L30.85,27.5Q29.2,29.15 26.85,29.15Q24.5,29.15 22.85,27.5L20.5,25.15Q19.8,24.5 18.825,24.5Q17.85,24.5 17.2,25.15Z"/>
+</vector>
diff --git a/res/drawable/ic_moving_emph.xml b/res/drawable/ic_moving_emph.xml
new file mode 100644
--- /dev/null
+++ b/res/drawable/ic_moving_emph.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="48dp"
+ android:height="48dp"
+ android:viewportWidth="48"
+ android:viewportHeight="48">
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.25,36.9 L3.25,33.9 14.65,22.5Q16.5,20.7 19.025,20.7Q21.55,20.7 23.35,22.5L25.65,24.8Q26.2,25.35 26.975,25.35Q27.75,25.35 28.3,24.8L37.45,15.65H32.5V11.35H44.8V23.65H40.5V18.7L31.3,27.85Q29.5,29.7 26.95,29.7Q24.4,29.7 22.6,27.85L20.25,25.5Q19.7,24.95 18.95,24.95Q18.2,24.95 17.65,25.5Z"/>
+</vector>
diff --git a/res/layout/arrivals_nearby_card.xml b/res/layout/arrivals_nearby_card.xml
--- a/res/layout/arrivals_nearby_card.xml
+++ b/res/layout/arrivals_nearby_card.xml
@@ -27,16 +27,23 @@
android:layout_margin="10dp"
android:textStyle="normal" android:layout_marginRight="20dp" android:layout_marginEnd="20dp"
android:layout_marginBottom="20dp"/>
- <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
- android:id="@+id/lineDirectionTextView"
- android:text="piazza statuto" android:textSize="20sp"
- android:textAppearance="?android:attr/textAppearanceLarge"
- android:layout_toLeftOf="@id/arrivalsDistanceTextView"
- android:layout_toRightOf="@id/lineNameTextView"
- android:textStyle="normal" android:layout_marginRight="6dp" android:layout_marginEnd="6dp"
- android:layout_alignBaseline="@id/lineNameTextView"
- android:layout_marginLeft="10dp" android:layout_marginTop="10dp"
- android:layout_marginStart="10dp"/>
+
+ <TextView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/lineDirectionTextView"
+ android:text="piazza statuto"
+ android:textSize="20sp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:layout_toLeftOf="@id/arrivalsDistanceTextView"
+ android:layout_toRightOf="@id/lineNameTextView"
+ android:textStyle="normal"
+ android:layout_marginRight="6dp"
+ android:layout_marginEnd="6dp"
+ android:layout_alignBaseline="@id/lineNameTextView"
+ android:layout_marginLeft="10dp"
+ android:layout_marginTop="5dp"
+ android:layout_marginStart="10dp" />
<TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
android:layout_alignBaseline="@id/arrivalsTimeTextView"
android:textAppearance="?android:attr/textAppearanceMedium"
diff --git a/res/layout/bus_stop_line_elmt.xml b/res/layout/bus_stop_line_elmt.xml
new file mode 100644
--- /dev/null
+++ b/res/layout/bus_stop_line_elmt.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout 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="wrap_content">
+
+ <TextView
+ android:id="@+id/busStopID"
+ android:layout_width="60dp"
+ android:layout_height="60dp"
+
+ android:layout_centerVertical="true"
+ android:background="@drawable/bus_stop_background"
+ android:gravity="center"
+ android:textColor="@color/grey_100"
+ android:textSize="20sp"
+ android:layout_margin="-1dp"
+ app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginLeft="14dp"
+ android:layout_marginStart="14dp"
+ app:layout_constraintTop_toBottomOf="@+id/topStub"/>
+ <View
+ android:layout_width="8dp"
+ android:layout_height="16dp" android:id="@+id/topStub"
+ android:background="@color/blue_500"
+
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintStart_toStartOf="@id/busStopID"
+ android:layout_marginLeft="25dp"
+ android:layout_marginStart="25dp"/>
+ <View
+ android:layout_width="8dp"
+ android:layout_height="0dp" android:id="@+id/bottomStub"
+ android:background="@color/blue_500"
+
+ app:layout_constraintTop_toBottomOf="@id/busStopID"
+ app:layout_constraintStart_toStartOf="@id/busStopID"
+ app:layout_constraintBottom_toBottomOf="parent"
+ android:layout_marginLeft="25dp"
+ android:layout_marginStart="25dp"/>
+
+ <TextView
+ android:id="@+id/busStopName"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/blue_500"
+ app:layout_constraintStart_toEndOf="@id/busStopID"
+ app:layout_constraintTop_toTopOf="@id/busStopID"
+ android:layout_marginStart="10dp"
+ android:layout_marginLeft="10dp"
+ android:layout_marginTop="0dp"
+ android:layout_marginEnd="10dp"
+
+ android:layout_marginRight="10dp"
+ app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ android:id="@+id/busStopLocality"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginRight="16dp"
+ android:layout_marginEnd="16dp"
+ app:layout_constraintTop_toBottomOf="@id/busStopName"
+ app:layout_constraintStart_toStartOf="@id/busStopName"
+ android:layout_marginStart="3dp"
+ android:layout_marginLeft="3dp"
+ android:layout_marginTop="4dp"
+
+ android:textColor="@color/grey_600"
+ android:textAppearance="?android:attr/textAppearanceMedium" app:layout_constraintEnd_toEndOf="parent"/>
+
+ <TextView
+ android:id="@+id/routesThatStopHere"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/busStopLocality"
+ android:layout_marginRight="25dp"
+ android:layout_marginEnd="25dp"
+ android:gravity="center_vertical"
+
+ app:layout_constraintStart_toStartOf="@id/busStopName"
+ android:layout_marginStart="10dp"
+ android:layout_marginLeft="10dp"
+
+
+ app:layout_constraintTop_toBottomOf="@id/busStopLocality"
+ android:layout_marginTop="5dp"
+
+
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/grey_600" app:layout_constraintEnd_toEndOf="parent"/>
+</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file
diff --git a/res/layout/entry_bus_stop.xml b/res/layout/entry_bus_stop.xml
--- a/res/layout/entry_bus_stop.xml
+++ b/res/layout/entry_bus_stop.xml
@@ -6,68 +6,69 @@
android:paddingBottom="8dip" >
<TextView
- android:id="@+id/busStopName"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="16sp"
- android:layout_marginStart="16sp"
- android:layout_marginRight="16sp"
- android:layout_marginEnd="16sp"
- android:layout_marginTop="2sp"
- android:layout_alignParentTop="true"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"
- android:layout_toLeftOf="@+id/busStopID"
- android:layout_toStartOf="@+id/busStopID"
- android:textAppearance="?android:attr/textAppearanceMedium"
- android:textColor="@color/blue_500"/>
+ android:id="@+id/busStopName"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16sp"
+ android:layout_marginStart="16sp"
+ android:layout_marginRight="16sp"
+ android:layout_marginEnd="16sp"
+ android:layout_marginTop="2sp"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:layout_toLeftOf="@+id/busStopID"
+ android:layout_toStartOf="@+id/busStopID"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:textColor="@color/blue_500"/>
<TextView
- android:id="@+id/busStopLocality"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginLeft="16sp"
- android:layout_marginStart="16sp"
- android:layout_marginRight="16sp"
- android:layout_marginEnd="16sp"
- android:layout_below="@id/busStopName"
- android:layout_toLeftOf="@+id/busStopID"
- android:layout_toStartOf="@+id/busStopID"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"
- android:textColor="@color/grey_600"
- android:textAppearance="?android:attr/textAppearanceMedium"/>
+ android:id="@+id/busStopLocality"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginLeft="16sp"
+ android:layout_marginStart="16sp"
+ android:layout_marginRight="16sp"
+ android:layout_marginEnd="16sp"
+ android:layout_below="@id/busStopName"
+ android:layout_toLeftOf="@+id/busStopID"
+ android:layout_toStartOf="@+id/busStopID"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:textColor="@color/grey_600"
+ android:textAppearance="?android:attr/textAppearanceMedium"/>
<TextView
- android:id="@+id/routesThatStopHere"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_below="@id/busStopLocality"
- android:layout_marginLeft="16sp"
- android:layout_marginStart="16sp"
- android:layout_marginRight="16sp"
- android:layout_marginEnd="16sp"
- android:gravity="center_vertical"
- android:layout_toLeftOf="@id/busStopID"
- android:layout_alignParentLeft="true"
- android:layout_alignParentStart="true"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="@color/grey_600"/>
+ android:id="@+id/routesThatStopHere"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/busStopLocality"
+ android:layout_marginLeft="16sp"
+ android:layout_marginStart="16sp"
+ android:layout_marginRight="16sp"
+ android:layout_marginEnd="16sp"
+ android:gravity="center_vertical"
+ android:layout_toLeftOf="@id/busStopID"
+ android:layout_toStartOf="@id/busStopID"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:textAppearance="?android:attr/textAppearanceSmall"
+ android:textColor="@color/grey_600"/>
<TextView
- android:id="@+id/busStopID"
- android:layout_width="70dip"
- android:layout_height="70dip"
- android:layout_marginRight="16sp"
- android:layout_marginEnd="16sp"
- android:layout_alignParentRight="true"
- android:layout_alignParentEnd="true"
- android:layout_alignTop="@id/busStopName"
- android:layout_centerVertical="true"
- android:background="@drawable/bus_stop_background"
- android:gravity="center"
- android:textColor="@color/grey_100"
- android:textSize="21sp"/>
+ android:id="@+id/busStopID"
+ android:layout_width="70dip"
+ android:layout_height="70dip"
+ android:layout_marginRight="16sp"
+ android:layout_marginEnd="16sp"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ android:layout_alignTop="@id/busStopName"
+ android:layout_centerVertical="true"
+ android:background="@drawable/bus_stop_background"
+ android:gravity="center"
+ android:textColor="@color/grey_100"
+ android:textSize="21sp"/>
</RelativeLayout>
\ No newline at end of file
diff --git a/res/layout/fragment_lines.xml b/res/layout/fragment_lines.xml
new file mode 100644
--- /dev/null
+++ b/res/layout/fragment_lines.xml
@@ -0,0 +1,103 @@
+<?xml version="1.0" encoding="utf-8"?>
+<FrameLayout 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=".fragments.LinesFragment">
+
+ <androidx.constraintlayout.widget.ConstraintLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <Spinner
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:id="@+id/linesSpinner"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toEndOf="@+id/linesLabel"
+ android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
+ android:layout_marginTop="8dp"
+
+ />
+
+ <Spinner
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:id="@+id/patternsSpinner"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintStart_toStartOf="@+id/linesSpinner"
+ app:layout_constraintTop_toBottomOf="@id/routeDescriptionTextView"
+ android:layout_marginTop="16dp" />
+
+ <TextView
+ android:text="@string/line"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/linesLabel"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ android:layout_marginTop="8dp"
+ android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
+ android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+ app:layout_constraintBottom_toBottomOf="@+id/linesSpinner"
+
+ android:gravity="center_vertical"
+
+ />
+
+ <TextView
+ android:text="Descr"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:id="@+id/routeDescriptionTextView"
+ app:layout_constraintStart_toStartOf="@id/linesSpinner"
+ app:layout_constraintTop_toBottomOf="@id/linesSpinner"
+ app:layout_constraintEnd_toEndOf="parent"
+
+ android:layout_marginTop="8dp"
+ android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp"
+ android:textAppearance="@style/TextAppearance.AppCompat.Medium"
+
+ android:gravity="center_vertical"
+
+ android:layout_marginRight="16dp" android:layout_marginEnd="16dp"/>
+ <View
+ android:id="@+id/divider"
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="?android:attr/listDivider"
+ app:layout_constraintTop_toBottomOf="@id/patternsSpinner"
+ android:layout_marginTop="8dp"/>
+ <androidx.recyclerview.widget.RecyclerView
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:id="@+id/patternStopsRecyclerView"
+ app:layout_constraintTop_toBottomOf="@+id/divider"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintVertical_bias="0.0"
+ android:layout_marginBottom="8dp"
+ android:layout_marginTop="0dp"
+ android:layout_margin="4dp"
+ app:layout_constraintHorizontal_bias="0.0" />
+ <!--
+ <TextView
+ android:text="TextView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/textView2"
+ app:layout_constraintTop_toBottomOf="@+id/linesSpinner"
+ android:layout_marginTop="16dp"
+ app:layout_constraintStart_toStartOf="parent"
+ android:layout_marginLeft="16dp"
+ android:layout_marginStart="16dp" />
+ -->
+ </androidx.constraintlayout.widget.ConstraintLayout>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/res/menu/drawer_main.xml b/res/menu/drawer_main.xml
--- a/res/menu/drawer_main.xml
+++ b/res/menu/drawer_main.xml
@@ -14,6 +14,10 @@
android:id="@+id/nav_favorites_item"
android:icon="@drawable/ic_star_filled_white"
android:title="@string/nav_favorites_text" />
+ <item android:id="@+id/nav_lines_item"
+ android:icon="@drawable/ic_moving_emph"
+ android:title="@string/lines"
+ />
</group>
<item android:id="@+id/drawer_action_settings"
diff --git a/res/menu/menu_line_item.xml b/res/menu/menu_line_item.xml
new file mode 100644
--- /dev/null
+++ b/res/menu/menu_line_item.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@+id/action_show_arrivals"
+ android:title="@string/show_arrivals"/>
+ <item
+ android:id="@+id/action_view_on_map"
+ android:title="@string/action_view_on_map"/>
+</menu>
\ No newline at end of file
diff --git a/res/values-it/strings.xml b/res/values-it/strings.xml
--- a/res/values-it/strings.xml
+++ b/res/values-it/strings.xml
@@ -14,7 +14,11 @@
<string name="no_arrivals_stop">Nessun passaggio trovato alla fermata</string>
<string name="parsing_error">Errore di lettura del sito 5T/GTT (dannato sito!)</string>
<string name="passages">Fermata: %1$s</string>
- <string name="lines">Linee: %1$s</string>
+ <string name="line">Linea</string>
+ <string name="lines">Linee</string>
+
+ <string name="line_fill">Linea: %1$s</string>
+ <string name="lines_fill">Linee: %1$s</string>
<string name="results">Scegli la fermata…</string>
<string name="no_passages">Nessun passaggio</string>
<string name="no_qrcode">Nessun QR code</string>
@@ -95,7 +99,10 @@
<string name="position_searching_message">Ricerca della posizione in corso&#8230;</string>
<string name="no_stops_nearby">Nessuna fermata nei dintorni</string>
<string name="main_menu_pref">Preferenze</string>
- <string name="database_update_message">Aggiornamento del database&#8230;</string>
+ <string name="database_update_msg_inapp">Aggiornamento del database&#8230;</string>
+ <string name="database_update_msg_notif">Aggiornamento del database</string>
+ <string name="database_update_req">Aggiornamento database forzato</string>
+ <string name="database_update_req_descr">Tocca per aggiornare ora il database</string>
<string name="pref_num_elements">Numero minimo di fermate</string>
<string name="num_stops_nearby_not_number">Il numero di fermate da ricercare non è valido</string>
<string name="invalid_number">Valore errato, inserisci un numero</string>
@@ -140,6 +147,10 @@
<string name="default_notification_channel_description">Canale unico delle notifiche</string>
+ <string name="database_notification_channel">Database</string>
+ <string name="database_notification_channel_desc">Informazioni sul database (aggiornamento)</string>
+
+
<string name="too_many_permission_asks">Chiesto troppe volte per il permesso %1$s</string>
<string name="permission_storage_maps_msg">Non si può usare questa funzionalità senza il permesso di archivio</string>
<string name="storage_permission">di archivio</string>
@@ -160,6 +171,10 @@
<string name="app_version">Versione app</string>
<string name="arrival_times">Orari di arrivo</string>
+ <!-- Preferences -->
+ <string name="requesting_db_update">Richiesto aggiornamento del database</string>
+
+
<string name="pref_directions_capitalize">Mostra direzioni in maiuscolo</string>
<string-array name="directions_capitalize">
@@ -167,4 +182,7 @@
<item>Tutto in maiuscolo</item>
<item>Solo la prima lettera maiuscola</item>
</string-array>
+
+ <!-- lines -->
+ <string name="long_press_for_options">Tocca a lungo per le opzioni</string>
</resources>
diff --git a/res/values/keys.xml b/res/values/keys.xml
--- a/res/values/keys.xml
+++ b/res/values/keys.xml
@@ -2,4 +2,5 @@
<resources xmlns:tools="http://schemas.android.com/tools"
tools:ignore="MissingTranslation">
<string name="pref_layout">layout_pref</string>
+ <string name="pref_update_db_now">pref_update_db_now</string>
</resources>
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -22,7 +22,10 @@
</string> <!-- TODO: carry out experiments to determine the best wording for this message and publish a paper with the findings -->
<string name="passages">Arrivals at: %1$s</string>
<string name="results">Choose the bus stop…</string>
- <string name="lines">Lines: %1$s</string>
+ <string name="line">Line</string>
+ <string name="lines">Lines</string>
+ <string name="lines_fill">Lines: %1$s</string>
+ <string name="line_fill">Line: %1$s</string>
<string name="no_passages">No timetable found</string>
<string name="no_qrcode">No QR code</string>
<string name="internal_error">Unexpected internal error, cannot extract data from GTT/5T website</string>
@@ -126,7 +129,10 @@
<string name="enable_position_message_map">Allow access to position to show it on the map</string>
<string name="enableGpsText">Please enable GPS</string>
- <string name="database_update_message">Database update in progress&#8230;</string>
+ <string name="database_update_msg_inapp">Database update in progress&#8230;</string>
+ <string name="database_update_msg_notif">Updating the database</string>
+ <string name="database_update_req">Force database update</string>
+ <string name="database_update_req_descr">Touch to update the app database now</string>
<string name="bus_arriving_at">is arriving at</string>
<string name="arrivals_card_at_the_stop">at the stop</string>
<string name="two_strings_format" translatable="false">%1$s - %2$s</string>
@@ -156,6 +162,9 @@
-->
<string name="default_notification_channel" translatable="false">Default</string>
<string name="default_notification_channel_description">Default channel for notifications</string>
+ <string name="database_notification_channel">Database</string>
+ <string name="database_notification_channel_desc">Notifications on the update of the database</string>
+
<string name="too_many_permission_asks">Asked for %1$s permission too many times</string>
<string name="permission_storage_maps_msg">Cannot use the map with the storage permission!</string>
<string name="storage_permission">storage</string>
@@ -176,6 +185,7 @@
<string name="donate_now">Buy us a coffee</string>
<string name="map">Map</string>
<string name="stop_search_view_title">Search by stop</string>
+ <string name="requesting_db_update">Launching database update</string>
<!-- preferences -->
@@ -191,4 +201,7 @@
<item>CAPITALIZE_FIRST</item>
</array>
+
+ <!-- lines -->
+ <string name="long_press_for_options">Long press for options</string>
</resources>
diff --git a/res/xml/preferences.xml b/res/xml/preferences.xml
--- a/res/xml/preferences.xml
+++ b/res/xml/preferences.xml
@@ -58,4 +58,12 @@
android:key="@string/pref_arrival_times_capit"
/>
</androidx.preference.PreferenceCategory>
+
+ <androidx.preference.PreferenceCategory android:title="Database">
+ <androidx.preference.Preference
+ android:title="@string/database_update_req"
+ android:summary="@string/database_update_req_descr"
+ android:key="pref_db_update_now"
+ />
+ </androidx.preference.PreferenceCategory>
</androidx.preference.PreferenceScreen>
diff --git a/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java b/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
new file mode 100644
--- /dev/null
+++ b/src/androidTest/java/it/reyboz/bustorino/data/gtfs/GtfsDBMigrationsTest.java
@@ -0,0 +1,53 @@
+package it.reyboz.bustorino.data.gtfs;
+
+import androidx.room.Room;
+import androidx.room.migration.Migration;
+import androidx.room.testing.MigrationTestHelper;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.IOException;
+
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
+
+//@RunWith(AndroidJUnit4.class)
+public class GtfsDBMigrationsTest {
+ private static final String TEST_DB = "migration-test";
+
+ @Rule
+ public MigrationTestHelper helper;
+
+ public GtfsDBMigrationsTest() {
+ helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
+ GtfsDatabase.class.getCanonicalName(),
+ new FrameworkSQLiteOpenHelperFactory());
+ }
+
+ @Test
+ public void migrateAll() throws IOException {
+ // Create earliest version of the database.
+ SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
+ db.close();
+
+ // Open latest version of the database. Room will validate the schema
+ // once all migrations execute.
+ GtfsDatabase appDb = Room.databaseBuilder(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ GtfsDatabase.class,
+ TEST_DB)
+ .addMigrations(ALL_MIGRATIONS).build();
+ appDb.getOpenHelper().getWritableDatabase();
+ appDb.close();
+ }
+
+ // Array of all migrations
+ private static final Migration[] ALL_MIGRATIONS = new Migration[]{
+ GtfsDatabase.Companion.getMIGRATION_1_2()};
+}
+
diff --git a/src/it/reyboz/bustorino/ActivityExperiments.java b/src/it/reyboz/bustorino/ActivityExperiments.java
--- a/src/it/reyboz/bustorino/ActivityExperiments.java
+++ b/src/it/reyboz/bustorino/ActivityExperiments.java
@@ -18,21 +18,16 @@
package it.reyboz.bustorino;
import android.content.Context;
-import android.os.AsyncTask;
-import android.os.Handler;
-import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;
-import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.gtfs.GtfsDataParser;
import it.reyboz.bustorino.backend.networkTools;
-import it.reyboz.bustorino.backend.utils;
import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
-import it.reyboz.bustorino.data.gtfs.StaticGtfsDao;
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao;
import it.reyboz.bustorino.middleware.GeneralActivity;
import java.io.*;
@@ -44,7 +39,6 @@
import java.util.concurrent.atomic.AtomicReference;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
-import java.util.zip.ZipInputStream;
public class ActivityExperiments extends GeneralActivity {
@@ -91,7 +85,7 @@
"ExperimentGTFS", "Last update date is " + updateDate//utils.joinList(files, "\n")
);
//Toast.makeText(v.getContext(), "Gtfs data already downloaded", Toast.LENGTH_SHORT).show();
- StaticGtfsDao dao = GtfsDatabase.Companion.getGtfsDatabase(appContext).gtfsDao();
+ GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(appContext).gtfsDao();
Log.d(DEBUG_TAG, String.valueOf(dao));
dao.deleteAllStopTimes();
@@ -106,10 +100,12 @@
// now iterate through each item in the stream. The get next
// entry call will return a ZipEntry for each file in the
// stream
+ /*
Enumeration<? extends ZipEntry> entries = zipFile.entries();
ZipEntry entry;
String line;
//final BufferedReader reader = new BufferedReader(new InputStreamReader(stream));
+
HashSet<ZipEntry> readLater = new HashSet<>();
while(entries.hasMoreElements()){
entry = entries.nextElement();
@@ -123,30 +119,7 @@
for(ZipEntry laterEntry: readLater){
GtfsDataParser.readGtfsZipEntry(laterEntry, zipFile, v.getContext().getApplicationContext());
}
- //Toast.makeText(appContext, "D", Toast.LENGTH_SHORT).show();
- /*
- while ((entry = stream.getNextEntry()) != null) {
- String s = String.format(Locale.ENGLISH, "Entry: %s len %d added",
- entry.getName(),
- entry.getSize()
- );
- if(entry.getName().contains("stop_times.")){
- //skip and do later
-
- }
- //Toast.makeText(v.getContext(), "File: " + entry.getName(), Toast.LENGTH_SHORT).show();
- Log.d(DEBUG_TAG, s);
- //read data in table
- final String tableName = entry.getName().split("\\.")[0].trim();
-
-
- // Once we get the entry from the stream, the stream is
- // positioned read to read the raw data, and we keep
- // reading until read returns 0 or less.
- //result.add(entry.getName());
- }
- stream.close();
*/
} catch (IOException e) {
e.printStackTrace();
@@ -165,32 +138,18 @@
}
};
- Toast.makeText(this, "Launching, no result will show", Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, "Test disabled", Toast.LENGTH_SHORT).show();
//Looper looper = new Looper(true);
//Handler handler = new Handler();
//handler.post(run);
- executorService.execute(run);
+ //executorService.execute(run);
}
public void deleteDatabase(View v){
- final Context con = getApplicationContext().getApplicationContext();
- Toast.makeText(this, "Deleting GTFS DB contents, wait a few seconds", Toast.LENGTH_SHORT).show();
- Runnable deleteDB = new Runnable() {
- @Override
- public void run() {
- StaticGtfsDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
- Log.d(DEBUG_TAG, String.valueOf(dao));
- dao.deleteAllStopTimes();
- dao.deleteAllTrips();
- dao.deleteAllRoutes();
- dao.deleteAllStops();
- dao.deleteAllServices();
- Log.d(DEBUG_TAG, "Deleted stuff");
- }
- };
+ //final Context con = getApplicationContext().getApplicationContext();
+ Toast.makeText(this, "Deleting GTFS DB contents isn't allowed anymore", Toast.LENGTH_SHORT).show();
- executorService.execute(deleteDB);
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/ActivityPrincipal.java b/src/it/reyboz/bustorino/ActivityPrincipal.java
--- a/src/it/reyboz/bustorino/ActivityPrincipal.java
+++ b/src/it/reyboz/bustorino/ActivityPrincipal.java
@@ -41,11 +41,6 @@
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction;
import androidx.preference.PreferenceManager;
-import androidx.work.BackoffPolicy;
-import androidx.work.Constraints;
-import androidx.work.ExistingPeriodicWorkPolicy;
-import androidx.work.NetworkType;
-import androidx.work.PeriodicWorkRequest;
import androidx.work.WorkInfo;
import androidx.work.WorkManager;
@@ -53,7 +48,6 @@
import com.google.android.material.snackbar.Snackbar;
import java.util.Arrays;
-import java.util.concurrent.TimeUnit;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.DBUpdateWorker;
@@ -179,7 +173,7 @@
requestArrivalsForStopID(busStopID);
}
//Try (hopefully) database update
- DatabaseUpdate.requestDBUpdateWithWork(this, false);
+ DatabaseUpdate.requestDBUpdateWithWork(this, false, false);
/*
Watch for database update
*/
@@ -239,8 +233,8 @@
//get Fragment
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
FavoritesFragment fragment = FavoritesFragment.newInstance();
- ft.replace(R.id.mainActContentFrame,fragment, TAG_FAVORITES);
- ft.addToBackStack(null);
+ ft.replace(R.id.mainActContentFrame,fragment, TAG_FAVORITES)
+ .addToBackStack("main");
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
ft.commit();
return true;
@@ -266,6 +260,22 @@
Toast.makeText(getApplicationContext(),text, Toast.LENGTH_LONG).show();
}
return true;
+ } else if (menuItem.getItemId() == R.id.nav_lines_item) {
+ closeDrawerIfOpen();
+ FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
+ Fragment f = getSupportFragmentManager().findFragmentByTag(LinesFragment.FRAGMENT_TAG);
+ if(f!=null){
+ ft.replace(R.id.mainActContentFrame, f, LinesFragment.FRAGMENT_TAG);
+ }else{
+ //use new method
+ ft.replace(R.id.mainActContentFrame,LinesFragment.class,null,LinesFragment.FRAGMENT_TAG);
+ }
+
+ ft.setReorderingAllowed(true)
+ .addToBackStack("lines")
+ .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
+ .commit();
+ return true;
}
//selectDrawerItem(menuItem);
Log.d(DEBUG_TAG, "pressed item "+menuItem);
@@ -389,7 +399,7 @@
}
if (baseView == null) baseView = findViewById(R.id.mainActContentFrame);
if (baseView == null) Log.e(DEBUG_TAG, "baseView null for default snackbar, probably exploding now");
- snackbar = Snackbar.make(baseView, R.string.database_update_message, Snackbar.LENGTH_INDEFINITE);
+ snackbar = Snackbar.make(baseView, R.string.database_update_msg_inapp, Snackbar.LENGTH_INDEFINITE);
snackbar.show();
}
@@ -500,6 +510,10 @@
titleResId=R.string.app_name_full;
mNavView.setCheckedItem(R.id.nav_arrivals);
break;
+ case LINES:
+ titleResId=R.string.lines;
+ mNavView.setCheckedItem(R.id.nav_lines_item);
+ break;
default:
titleResId = 0;
}
diff --git a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
--- a/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/ArrivalsStopAdapter.java
@@ -18,10 +18,12 @@
package it.reyboz.bustorino.adapters;
import android.content.Context;
+import android.content.SharedPreferences;
import android.location.Location;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.util.Pair;
+import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
@@ -37,15 +39,18 @@
import java.util.*;
-public class ArrivalsStopAdapter extends RecyclerView.Adapter<ArrivalsStopAdapter.ViewHolder> {
+public class ArrivalsStopAdapter extends RecyclerView.Adapter<ArrivalsStopAdapter.ViewHolder> implements SharedPreferences.OnSharedPreferenceChangeListener {
private final static int layoutRes = R.layout.arrivals_nearby_card;
//private List<Stop> stops;
private @Nullable Location userPosition;
private FragmentListenerMain listener;
- private List< Pair<Stop, Route> > routesPairList = new ArrayList<>();
+ private List< Pair<Stop, Route> > routesPairList;
private final Context context;
//Maximum number of stops to keep
private final int MAX_STOPS = 20; //TODO: make it programmable
+ private String KEY_CAPITALIZE;
+ private NameCapitalize capit;
+
public ArrivalsStopAdapter(@Nullable List< Pair<Stop, Route> > routesPairList, FragmentListenerMain fragmentListener, Context con, @Nullable Location pos) {
listener = fragmentListener;
@@ -55,10 +60,16 @@
resetListAndPosition();
// if(paline!=null)
//resetRoutesPairList(paline);
+ KEY_CAPITALIZE = context.getString(R.string.pref_arrival_times_capit);
+ SharedPreferences defSharPref = PreferenceManager.getDefaultSharedPreferences(context);
+ defSharPref.registerOnSharedPreferenceChangeListener(this);
+ String capitalizeKey = defSharPref.getString(KEY_CAPITALIZE, "");
+ this.capit = NameCapitalize.getCapitalize(capitalizeKey);
}
+ @NonNull
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final View view = LayoutInflater.from(parent.getContext()).inflate(layoutRes, parent, false);
@@ -85,7 +96,7 @@
//final String routeName = String.format(context.getResources().getString(R.string.two_strings_format),r.getNameForDisplay(),r.destinazione);
if (r!=null) {
holder.lineNameTextView.setText(r.getNameForDisplay());
- holder.lineDirectionTextView.setText(r.destinazione);
+ holder.lineDirectionTextView.setText(NameCapitalize.capitalizePass(r.destinazione, capit));
holder.arrivalsTextView.setText(r.getPassaggiToString(0,2,true));
} else {
holder.lineNameTextView.setVisibility(View.INVISIBLE);
@@ -117,6 +128,17 @@
return routesPairList.size();
}
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if(key.equals(KEY_CAPITALIZE)){
+ String k = sharedPreferences.getString(KEY_CAPITALIZE, "");
+ capit = NameCapitalize.getCapitalize(k);
+
+ notifyDataSetChanged();
+
+ }
+ }
+
class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener {
TextView lineNameTextView;
TextView lineDirectionTextView;
diff --git a/src/it/reyboz/bustorino/adapters/NameCapitalize.java b/src/it/reyboz/bustorino/adapters/NameCapitalize.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/adapters/NameCapitalize.java
@@ -0,0 +1,44 @@
+package it.reyboz.bustorino.adapters;
+
+import java.util.Locale;
+
+import it.reyboz.bustorino.backend.utils;
+
+public enum NameCapitalize {
+ DO_NOTHING, ALL, FIRST;
+ public static NameCapitalize getCapitalize(String capitalize){
+
+ switch (capitalize.trim()){
+ case "KEEP":
+ return NameCapitalize.DO_NOTHING;
+ case "CAPITALIZE_ALL":
+ return NameCapitalize.ALL;
+
+ case "CAPITALIZE_FIRST":
+ return NameCapitalize.FIRST;
+ }
+ return NameCapitalize.DO_NOTHING;
+ }
+
+ /**
+ * Parse the output
+ * @param input the input string
+ * @param capitalize the capitalize value
+ * @return parsed string
+ */
+ public static String capitalizePass(String input, NameCapitalize capitalize){
+ String dest = input;
+ switch (capitalize){
+ case ALL:
+ dest = input.toUpperCase(Locale.ROOT);
+ break;
+ case FIRST:
+ dest = utils.toTitleCase(input, true);
+ break;
+ case DO_NOTHING:
+ default:
+
+ }
+ return dest;
+ }
+}
diff --git a/src/it/reyboz/bustorino/adapters/StopAdapter.java b/src/it/reyboz/bustorino/adapters/StopAdapter.java
--- a/src/it/reyboz/bustorino/adapters/StopAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/StopAdapter.java
@@ -35,7 +35,7 @@
*/
public class StopAdapter extends ArrayAdapter<Stop> {
private LayoutInflater li;
- private static int row_layout = R.layout.entry_bus_stop;
+ private static final int row_layout = R.layout.entry_bus_stop;
private static final int busIcon = R.drawable.bus;
private static final int trainIcon = R.drawable.subway;
private static final int tramIcon = R.drawable.tram;
diff --git a/src/it/reyboz/bustorino/adapters/AdapterListener.java b/src/it/reyboz/bustorino/adapters/StopAdapterListener.java
rename from src/it/reyboz/bustorino/adapters/AdapterListener.java
rename to src/it/reyboz/bustorino/adapters/StopAdapterListener.java
--- a/src/it/reyboz/bustorino/adapters/AdapterListener.java
+++ b/src/it/reyboz/bustorino/adapters/StopAdapterListener.java
@@ -19,6 +19,8 @@
import it.reyboz.bustorino.backend.Stop;
-public interface AdapterListener {
+public interface StopAdapterListener {
void onTappedStop(Stop stop);
+
+ boolean onLongPressOnStop(Stop stop);
}
diff --git a/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java
--- a/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java
+++ b/src/it/reyboz/bustorino/adapters/StopRecyclerAdapter.java
@@ -35,13 +35,17 @@
public class StopRecyclerAdapter extends RecyclerView.Adapter<StopRecyclerAdapter.ViewHolder> {
private List<Stop> stops;
- private static final int row_layout = R.layout.entry_bus_stop;
+ private static final int ITEM_LAYOUT_FAVORITES = R.layout.entry_bus_stop;
+ private static final int ITEM_LAYOUT_LINES = R.layout.bus_stop_line_elmt;
private static final int busIcon = R.drawable.bus;
private static final int trainIcon = R.drawable.subway;
private static final int tramIcon = R.drawable.tram;
private static final int cityIcon = R.drawable.city;
- private AdapterListener listener;
+ private NameCapitalize capitalizeLocation = NameCapitalize.DO_NOTHING;
+ private final Use usedFor;
+
+ private final StopAdapterListener listener;
private int position;
@@ -52,14 +56,29 @@
//TextView busLineVehicleIcon;
TextView busStopLinesTextView;
TextView busStopLocaLityTextView;
+
+ View topStub, bottomStub;
Stop mStop;
- public ViewHolder(@NonNull View itemView, AdapterListener listener) {
+ int menuResID=R.menu.menu_favourites_entry;
+
+ public ViewHolder(@NonNull View itemView, StopAdapterListener listener, Use usedFor) {
super(itemView);
- busStopIDTextView = (TextView) itemView.findViewById(R.id.busStopID);
- busStopNameTextView = (TextView) itemView.findViewById(R.id.busStopName);
- busStopLinesTextView = (TextView) itemView.findViewById(R.id.routesThatStopHere);
- busStopLocaLityTextView = (TextView) itemView.findViewById(R.id.busStopLocality);
+ busStopIDTextView = itemView.findViewById(R.id.busStopID);
+ busStopNameTextView = itemView.findViewById(R.id.busStopName);
+ busStopLinesTextView = itemView.findViewById(R.id.routesThatStopHere);
+ busStopLocaLityTextView = itemView.findViewById(R.id.busStopLocality);
+ switch (usedFor){
+ case LINES:
+ topStub = itemView.findViewById(R.id.topStub);
+ bottomStub = itemView.findViewById(R.id.bottomStub);
+ menuResID = R.menu.menu_line_item;
+ break;
+ case FAVORITES:
+ default:
+ topStub = null;
+ bottomStub = null;
+ }
mStop = new Stop("");
@@ -71,13 +90,29 @@
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
MenuInflater inflater = new MenuInflater(view.getContext());
- inflater.inflate(R.menu.menu_favourites_entry, contextMenu);
+ inflater.inflate(menuResID, contextMenu);
}
}
- public StopRecyclerAdapter(List<Stop> stops,AdapterListener listener) {
+ public StopRecyclerAdapter(List<Stop> stops, StopAdapterListener listener, Use usedFor) {
this.stops = stops;
this.listener = listener;
+ this.usedFor = usedFor;
+ }
+ public StopRecyclerAdapter(List<Stop> stops, StopAdapterListener listener, Use usedFor, NameCapitalize locationCapit) {
+ this.stops = stops;
+ this.listener = listener;
+ this.usedFor = usedFor;
+ this.capitalizeLocation = locationCapit;
+ }
+
+ public NameCapitalize getCapitalizeLocation() {
+ return capitalizeLocation;
+ }
+
+ public void setCapitalizeLocation(NameCapitalize capitalizeLocation) {
+ this.capitalizeLocation = capitalizeLocation;
+ notifyDataSetChanged();
}
public void setStops(List<Stop> stops){
@@ -100,10 +135,20 @@
@NonNull
@Override
public StopRecyclerAdapter.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ final int layoutID;
+ switch (usedFor){
+ case LINES:
+ layoutID = ITEM_LAYOUT_LINES;
+ break;
+ case FAVORITES:
+ default:
+ layoutID = ITEM_LAYOUT_FAVORITES;
+
+ }
View view = LayoutInflater.from(parent.getContext())
- .inflate(row_layout, parent, false);
+ .inflate(layoutID, parent, false);
- return new StopRecyclerAdapter.ViewHolder(view, listener);
+ return new StopRecyclerAdapter.ViewHolder(view, listener, this.usedFor);
}
@Override
@@ -114,11 +159,11 @@
@Override
public void onBindViewHolder(@NonNull StopRecyclerAdapter.ViewHolder vh, int position) {
- Log.d("StopRecyclerAdapter", "Called for position "+position);
+ //Log.d("StopRecyclerAdapter", "Called for position "+position);
Stop stop = stops.get(position);
vh.busStopIDTextView.setText(stop.ID);
vh.mStop = stop;
- Log.d("StopRecyclerAdapter", "Stop: "+stop.ID);
+ //Log.d("StopRecyclerAdapter", "Stop: "+stop.ID);
// NOTE: intentionally ignoring stop username in search results: if it's in the favorites, why are you searching for it?
vh.busStopNameTextView.setText(stop.getStopDisplayName());
@@ -154,21 +199,34 @@
if (stop.location == null) {
vh.busStopLocaLityTextView.setVisibility(View.GONE);
} else {
- vh.busStopLocaLityTextView.setText(stop.location);
+ vh.busStopLocaLityTextView.setText(NameCapitalize.capitalizePass(stop.location, capitalizeLocation));
vh.busStopLocaLityTextView.setVisibility(View.VISIBLE); // might be GONE due to View Holder Pattern
}
//trick to set the position
- vh.itemView.setOnLongClickListener(new View.OnLongClickListener() {
- @Override
- public boolean onLongClick(View view) {
- setPosition(vh.getAdapterPosition());
- return false;
- }
+ vh.itemView.setOnLongClickListener(view -> {
+ setPosition(vh.getAdapterPosition());
+ return false;
});
+ if(this.usedFor == Use.LINES){
+
+ //vh.menuResID;
+ vh.bottomStub.setVisibility(View.VISIBLE);
+ vh.topStub.setVisibility(View.VISIBLE);
+ if(position == 0) {
+ vh.topStub.setVisibility(View.GONE);
+ }
+ else if (position == stops.size()-1) {
+ vh.bottomStub.setVisibility(View.GONE);
+ }
+ }
}
@Override
public int getItemCount() {
return stops.size();
}
+
+ public enum Use{
+ FAVORITES, LINES
+ }
}
diff --git a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
--- a/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
+++ b/src/it/reyboz/bustorino/backend/FiveTAPIFetcher.java
@@ -291,7 +291,8 @@
Stop s = new Stop(stopID, stopName,
null,location,t,Arrays.asList(lines),
Double.parseDouble(currentStop.getString("lat")),
- Double.parseDouble(currentStop.getString("lng")));
+ Double.parseDouble(currentStop.getString("lng")),
+ null);
if(placeName!=null)
s.setAbsurdGTTPlaceName(placeName);
stopslist.add(s);
diff --git a/src/it/reyboz/bustorino/backend/Notifications.java b/src/it/reyboz/bustorino/backend/Notifications.java
--- a/src/it/reyboz/bustorino/backend/Notifications.java
+++ b/src/it/reyboz/bustorino/backend/Notifications.java
@@ -8,6 +8,7 @@
public class Notifications {
public static final String DEFAULT_CHANNEL_ID ="Default";
+ public static final String DB_UPDATE_CHANNELS_ID ="Database Update";
public static void createDefaultNotificationChannel(Context context) {
// Create the NotificationChannel, but only on API 26+ because
diff --git a/src/it/reyboz/bustorino/backend/Palina.java b/src/it/reyboz/bustorino/backend/Palina.java
--- a/src/it/reyboz/bustorino/backend/Palina.java
+++ b/src/it/reyboz/bustorino/backend/Palina.java
@@ -48,13 +48,13 @@
public Palina(Stop s){
super(s.ID,s.getStopDefaultName(),s.getStopUserName(),s.location,s.type,
- s.getRoutesThatStopHere(),s.getLatitude(),s.getLongitude());
+ s.getRoutesThatStopHere(),s.getLatitude(),s.getLongitude(), null);
}
public Palina(@NonNull String ID, @Nullable String name, @Nullable String userName,
@Nullable String location,
- @Nullable Double lat, @Nullable Double lon) {
- super(ID, name, userName, location, null, null, lat, lon);
+ @Nullable Double lat, @Nullable Double lon, @Nullable String gtfsID) {
+ super(ID, name, userName, location, null, null, lat, lon, gtfsID);
}
public Palina(@Nullable String name, @NonNull String ID, @Nullable String location, @Nullable Route.Type type, @Nullable List<String> routesThatStopHere) {
diff --git a/src/it/reyboz/bustorino/backend/Result.java b/src/it/reyboz/bustorino/backend/Result.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/Result.java
@@ -0,0 +1,38 @@
+package it.reyboz.bustorino.backend;
+
+import androidx.annotation.Nullable;
+
+
+public class Result<T> {
+ @Nullable
+ public final T result;
+
+ @Nullable
+ public final Exception exception;
+ public boolean isSuccess() {
+ return exception == null;
+ }
+
+
+ public static <T> Result<T> success(@Nullable T result) {
+ return new Result<>(result);
+ }
+
+ /**
+ * Returns a failed response
+ */
+ public static <T> Result<T> failure(Exception error) {
+ return new Result<>(error);
+ }
+
+ private Result(@Nullable T result) {
+ this.result = result;
+ this.exception = null;
+ }
+
+
+ private Result(Exception error) {
+ this.result = null;
+ this.exception = error;
+ }
+}
diff --git a/src/it/reyboz/bustorino/backend/Stop.java b/src/it/reyboz/bustorino/backend/Stop.java
--- a/src/it/reyboz/bustorino/backend/Stop.java
+++ b/src/it/reyboz/bustorino/backend/Stop.java
@@ -80,7 +80,9 @@
/**
* Constructor that sets EVERYTHING.
*/
- public Stop(@NonNull String ID, @Nullable String name, @Nullable String userName, @Nullable String location, @Nullable Route.Type type, @Nullable List<String> routesThatStopHere, @Nullable Double lat, @Nullable Double lon) {
+ public Stop(@NonNull String ID, @Nullable String name, @Nullable String userName,
+ @Nullable String location, @Nullable Route.Type type, @Nullable List<String> routesThatStopHere,
+ @Nullable Double lat, @Nullable Double lon, @Nullable String gtfsID) {
this.ID = ID;
this.name = name;
this.username = userName;
@@ -89,6 +91,7 @@
this.routesThatStopHere = routesThatStopHere;
this.lat = lat;
this.lon = lon;
+ this.gtfsID = gtfsID;
}
diff --git a/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java b/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/gtfs/PolylineParser.java
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.backend.gtfs;
+
+import org.osmdroid.util.GeoPoint;
+
+import java.util.ArrayList;
+
+public final class PolylineParser {
+ /**
+ * Decode a Google polyline
+ * Thanks to https://stackoverflow.com/questions/9341020/how-to-decode-googles-polyline-algorithm
+ * @param encodedPolyline the encoded polyline in a string
+ * @param initial_capacity for the list
+ * @return the list of points correspoding to the polyline
+ */
+ public static ArrayList<GeoPoint> decodePolyline(String encodedPolyline, int initial_capacity) {
+ ArrayList<GeoPoint> points = new ArrayList<>(initial_capacity);
+ int truck = 0;
+ int carriage_q = 0;
+ int longit=0, latit=0;
+ boolean is_lat=true;
+ for (int x = 0, xx = encodedPolyline.length(); x < xx; ++x) {
+ int i = encodedPolyline.charAt(x);
+ i -= 63;
+ int _5_bits = i << (32 - 5) >>> (32 - 5);
+ truck |= _5_bits << carriage_q;
+ carriage_q += 5;
+ boolean is_last = (i & (1 << 5)) == 0;
+ if (is_last) {
+ boolean is_negative = (truck & 1) == 1;
+ truck >>>= 1;
+ if (is_negative) {
+ truck = ~truck;
+ }
+ if (is_lat){
+ latit += truck;
+ is_lat = false;
+ } else{
+ longit += truck;
+ points.add(new GeoPoint((double)latit/1e5,(double)longit/1e5));
+ is_lat=true;
+ }
+ carriage_q = 0;
+ truck = 0;
+ }
+ }
+ return points;
+ }
+}
diff --git a/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java b/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
--- a/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
+++ b/src/it/reyboz/bustorino/backend/mato/MapiArrivalRequest.java
@@ -31,10 +31,7 @@
import org.json.JSONException;
import org.json.JSONObject;
-import java.nio.charset.StandardCharsets;
import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import it.reyboz.bustorino.backend.Fetcher;
@@ -54,7 +51,7 @@
AtomicReference<Fetcher.Result> res,
Response.Listener<Palina> listener,
@Nullable Response.ErrorListener errorListener) {
- super(MatoAPIFetcher.QueryType.ARRIVALS, listener, errorListener);
+ super(MatoQueries.QueryType.ARRIVALS, listener, errorListener);
this.stopName = stopName;
this.startingTime = startingTime;
this.timeRange = timeRange;
diff --git a/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java b/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
--- a/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
+++ b/src/it/reyboz/bustorino/backend/mato/MapiVolleyRequest.java
@@ -12,9 +12,9 @@
private static final String API_URL="https://mapi.5t.torino.it/routing/v1/routers/mat/index/graphql";
protected final Response.Listener<T> listener;
- private final MatoAPIFetcher.QueryType type;
+ protected final MatoQueries.QueryType type;
public MapiVolleyRequest(
- MatoAPIFetcher.QueryType type,
+ MatoQueries.QueryType type,
Response.Listener<T> listener,
@Nullable Response.ErrorListener errorListener) {
super(Method.POST, API_URL, errorListener);
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
--- a/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
+++ b/src/it/reyboz/bustorino/backend/mato/MatoAPIFetcher.kt
@@ -19,15 +19,23 @@
import android.content.Context
import android.util.Log
+import com.android.volley.DefaultRetryPolicy
import com.android.volley.toolbox.RequestFuture
import it.reyboz.bustorino.BuildConfig
import it.reyboz.bustorino.backend.*
+import it.reyboz.bustorino.data.gtfs.GtfsAgency
+import it.reyboz.bustorino.data.gtfs.GtfsFeed
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+import org.json.JSONArray
+import org.json.JSONException
import org.json.JSONObject
import java.util.*
import java.util.concurrent.ExecutionException
import java.util.concurrent.TimeUnit
import java.util.concurrent.TimeoutException
import java.util.concurrent.atomic.AtomicReference
+import kotlin.collections.ArrayList
open class MatoAPIFetcher(val minNumPassaggi: Int) : ArrivalsFetcher {
@@ -58,7 +66,7 @@
return Palina(stopID)
}
val requestQueue = NetworkVolleyManager.getInstance(appContext).requestQueue
- request.setTag(getVolleyReqTag(QueryType.ARRIVALS))
+ request.setTag(getVolleyReqTag(MatoQueries.QueryType.ARRIVALS))
requestQueue.add(request)
try {
@@ -105,10 +113,15 @@
"DNT" to "1",
"Host" to "mapi.5t.torino.it")
- fun getVolleyReqTag(type: QueryType): String{
+ private val longRetryPolicy = DefaultRetryPolicy(10000,5,DefaultRetryPolicy.DEFAULT_BACKOFF_MULT)
+
+ fun getVolleyReqTag(type: MatoQueries.QueryType): String{
return when (type){
- QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops"
- QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals"
+ MatoQueries.QueryType.ALL_STOPS -> VOLLEY_TAG +"_AllStops"
+ MatoQueries.QueryType.ARRIVALS -> VOLLEY_TAG+"_Arrivals"
+ MatoQueries.QueryType.FEEDS -> VOLLEY_TAG +"_Feeds"
+ MatoQueries.QueryType.ROUTES -> VOLLEY_TAG +"_AllRoutes"
+ MatoQueries.QueryType.PATTERNS_FOR_ROUTES -> VOLLEY_TAG + "_PatternsForRoute"
}
}
@@ -120,14 +133,15 @@
val future = RequestFuture.newFuture<List<Palina>>()
val request = VolleyAllStopsRequest(future, future)
- request.tag = getVolleyReqTag(QueryType.ALL_STOPS)
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.ALL_STOPS)
+ request.retryPolicy = longRetryPolicy
requestQueue.add(request)
var palinaList:List<Palina> = mutableListOf()
try {
- palinaList = future.get(60, TimeUnit.SECONDS)
+ palinaList = future.get(120, TimeUnit.SECONDS)
res?.set(Fetcher.Result.OK)
}catch (e: InterruptedException) {
@@ -172,10 +186,9 @@
val palina = Palina(
jsonStop.getString("code"),
jsonStop.getString("name"),
- null, null, latitude, longitude
+ null, null, latitude, longitude,
+ jsonStop.getString("gtfsId")
)
- palina.gtfsID = jsonStop.getString("gtfsId")
-
val routesStoppingJSON = jsonStop.getJSONArray("routes")
val baseRoutes = mutableListOf<Route>()
// get all the possible routes
@@ -259,10 +272,159 @@
return data
}
- }
- enum class QueryType {
- ARRIVALS, ALL_STOPS
+ fun getFeedsAndAgencies(context: Context, res: AtomicReference<Fetcher.Result>?):
+ Pair<List<GtfsFeed>, ArrayList<GtfsAgency>> {
+ val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+ val future = RequestFuture.newFuture<JSONObject>()
+
+ val request = MatoVolleyJSONRequest(MatoQueries.QueryType.FEEDS, JSONObject(), future, future)
+ request.setRetryPolicy(longRetryPolicy)
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.FEEDS)
+
+ requestQueue.add(request)
+
+ val feeds = ArrayList<GtfsFeed>()
+ val agencies = ArrayList<GtfsAgency>()
+ var outObj = ""
+ try {
+ val resObj = future.get(120,TimeUnit.SECONDS)
+ outObj = resObj.toString(1)
+ val feedsJSON = resObj.getJSONArray("feeds")
+ for (i in 0 until feedsJSON.length()){
+ val resTup = ResponseParsing.parseFeedJSON(feedsJSON.getJSONObject(i))
+ feeds.add(resTup.first)
+
+ agencies.addAll(resTup.second)
+ }
+
+
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ } catch (e: ExecutionException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.SERVER_ERROR)
+ } catch (e: TimeoutException) {
+ res?.set(Fetcher.Result.CONNECTION_ERROR)
+ e.printStackTrace()
+ } catch (e: JSONException){
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+ }
+ return Pair(feeds,agencies)
+
+ }
+ fun getRoutes(context: Context, res: AtomicReference<Fetcher.Result>?):
+ ArrayList<GtfsRoute>{
+ val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+ val future = RequestFuture.newFuture<JSONObject>()
+
+ val params = JSONObject()
+ params.put("feeds","gtt")
+
+ val request = MatoVolleyJSONRequest(MatoQueries.QueryType.ROUTES, params, future, future)
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.ROUTES)
+ request.retryPolicy = longRetryPolicy
+
+ requestQueue.add(request)
+
+ val routes = ArrayList<GtfsRoute>()
+ var outObj = ""
+ try {
+ val resObj = future.get(120,TimeUnit.SECONDS)
+ outObj = resObj.toString(1)
+ val routesJSON = resObj.getJSONArray("routes")
+ for (i in 0 until routesJSON.length()){
+ val route = ResponseParsing.parseRouteJSON(routesJSON.getJSONObject(i))
+ routes.add(route)
+ }
+
+
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ } catch (e: ExecutionException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.SERVER_ERROR)
+ } catch (e: TimeoutException) {
+ res?.set(Fetcher.Result.CONNECTION_ERROR)
+ e.printStackTrace()
+ } catch (e: JSONException){
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+ }
+ return routes
+
+ }
+ fun getPatternsWithStops(context: Context, routesGTFSIds: ArrayList<String>, res: AtomicReference<Fetcher.Result>?): ArrayList<MatoPattern>{
+ val requestQueue = NetworkVolleyManager.getInstance(context).requestQueue
+
+ val future = RequestFuture.newFuture<JSONObject>()
+
+ val params = JSONObject()
+ for (r in routesGTFSIds){
+ if(r.isEmpty()) routesGTFSIds.remove(r)
+ }
+ val routes = JSONArray(routesGTFSIds)
+
+ params.put("routes",routes)
+
+ val request = MatoVolleyJSONRequest(MatoQueries.QueryType.PATTERNS_FOR_ROUTES, params, future, future)
+ request.retryPolicy = longRetryPolicy
+ request.tag = getVolleyReqTag(MatoQueries.QueryType.PATTERNS_FOR_ROUTES)
+
+ requestQueue.add(request)
+
+ val patterns = ArrayList<MatoPattern>()
+ //var outObj = ""
+ try {
+ val resObj = future.get(60,TimeUnit.SECONDS)
+ //outObj = resObj.toString(1)
+ val routesJSON = resObj.getJSONArray("routes")
+ for (i in 0 until routesJSON.length()){
+ val patternList = ResponseParsing.parseRoutePatternsStopsJSON(routesJSON.getJSONObject(i))
+ patterns.addAll(patternList)
+ }
+
+
+ } catch (e: InterruptedException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ } catch (e: ExecutionException) {
+ e.printStackTrace()
+ res?.set(Fetcher.Result.SERVER_ERROR)
+ } catch (e: TimeoutException) {
+ res?.set(Fetcher.Result.CONNECTION_ERROR)
+ e.printStackTrace()
+ } catch (e: JSONException){
+ e.printStackTrace()
+ res?.set(Fetcher.Result.PARSER_ERROR)
+ //Log.e(DEBUG_TAG, "Downloading feeds: $outObj")
+ }
+ /*
+ var numRequests = 0
+ for(routeName in routesGTFSIds){
+ if (!routeName.isEmpty()) numRequests++
+ }
+ val countDownForRequests = CountDownLatch(numRequests)
+ val lockSave = ReentrantLock()
+ //val countDownFor
+ for (routeName in routesGTFSIds){
+ val pars = JSONObject()
+ pars.put("")
+
+ }
+ val goodResponseListener = Response.Listener<JSONObject> { }
+ val errorResponseListener = Response.ErrorListener { }
+ */
+
+ return patterns
+ }
+
+
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt b/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
--- a/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
+++ b/src/it/reyboz/bustorino/backend/mato/MatoQueries.kt
@@ -86,5 +86,81 @@
}
}
"""
+
+ const val ALL_FEEDS="""
+ query AllFeeds{
+ feeds{
+ feedId
+ agencies{
+ gtfsId
+ name
+ url
+ fareUrl
+ phone
+ }
+ }
+ }
+ """
+
+ const val ROUTES_BY_FEED="""
+ query AllRoutes(${'$'}feeds: [String]){
+ routes(feeds: ${'$'}feeds) {
+ agency{
+ gtfsId
+ }
+ gtfsId
+ shortName
+ longName
+ type
+ desc
+ color
+ textColor
+ }
+ }
+ """
+
+ const val ROUTES_WITH_PATTERNS="""
+ query RoutesWithPatterns(${'$'}routes: [String]) {
+ routes(ids: ${'$'}routes) {
+ gtfsId
+ shortName
+ longName
+ type
+
+ patterns{
+ name
+ code
+ semanticHash
+ directionId
+ headsign
+ stops{
+ gtfsId
+ lat
+ lon
+ }
+ patternGeometry{
+ length
+ points
+ }
+
+ }
+ }
+ }
+ """
+
+ fun getNameAndRequest(type: QueryType): Pair<String, String>{
+ return when (type){
+ QueryType.FEEDS -> Pair("AllFeeds", ALL_FEEDS)
+ QueryType.ALL_STOPS -> Pair("AllStops", ALL_STOPS_BY_FEEDS)
+ QueryType.ARRIVALS -> Pair("AllStopsDirect", QUERY_ARRIVALS)
+ QueryType.ROUTES -> Pair("AllRoutes", ROUTES_BY_FEED)
+ QueryType.PATTERNS_FOR_ROUTES -> Pair("RoutesWithPatterns", ROUTES_WITH_PATTERNS)
+ }
+ }
}
+
+ enum class QueryType {
+ ARRIVALS, ALL_STOPS, FEEDS, ROUTES, PATTERNS_FOR_ROUTES
+ }
+
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt b/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/mato/MatoVolleyJSONRequest.kt
@@ -0,0 +1,48 @@
+package it.reyboz.bustorino.backend.mato
+
+import android.util.Log
+import com.android.volley.NetworkResponse
+import com.android.volley.Response
+import com.android.volley.VolleyError
+import com.android.volley.toolbox.HttpHeaderParser
+import org.json.JSONException
+import org.json.JSONObject
+
+class MatoVolleyJSONRequest(type: MatoQueries.QueryType,
+ val variables: JSONObject,
+ listener: Response.Listener<JSONObject>,
+ errorListener: Response.ErrorListener?)
+ : MapiVolleyRequest<JSONObject>(type, listener, errorListener) {
+ protected val requestName:String
+ protected val requestQuery:String
+ init {
+ val dd = MatoQueries.getNameAndRequest(type)
+ requestName = dd.first
+ requestQuery = dd.second
+ }
+
+ override fun getBody(): ByteArray {
+
+ val data = MatoAPIFetcher.makeRequestParameters(requestName, variables, requestQuery)
+
+ return data.toString().toByteArray()
+ }
+
+ override fun parseNetworkResponse(response: NetworkResponse?): Response<JSONObject> {
+ if (response==null)
+ return Response.error(VolleyError("Null response"))
+ else if(response.statusCode != 200)
+ return Response.error(VolleyError("Response not ready, status "+response.statusCode))
+ val obj:JSONObject
+ try {
+ obj = JSONObject(String(response.data)).getJSONObject("data")
+ }catch (ex: JSONException){
+ Log.e("BusTO-VolleyJSON","Cannot parse response as JSON")
+ ex.printStackTrace()
+ return Response.error(VolleyError("Error parsing JSON"))
+ }
+
+ return Response.success(obj, HttpHeaderParser.parseCacheHeaders(response))
+ }
+
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt b/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/backend/mato/ResponseParsing.kt
@@ -0,0 +1,120 @@
+/*
+ BusTO - Backend components
+ Copyright (C) 2022 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.backend.mato
+
+import it.reyboz.bustorino.data.gtfs.*
+import org.json.JSONObject
+
+abstract class ResponseParsing{
+
+ companion object{
+ fun parseAgencyJSON(jsonObject: JSONObject): GtfsAgency {
+ return GtfsAgency(
+ jsonObject.getString("gtfsId"),
+ jsonObject.getString("name"),
+ jsonObject.getString("url"),
+ jsonObject.getString("fareUrl"),
+ jsonObject.getString("phone"),
+ null
+ )
+ }
+
+ /**
+ * Parse a feed request json, containing the GTFS agencies it is served by
+ */
+ fun parseFeedJSON(jsonObject: JSONObject): Pair<GtfsFeed, ArrayList<GtfsAgency>> {
+
+ val agencies = ArrayList<GtfsAgency>()
+ val feed = GtfsFeed(jsonObject.getString("feedId"))
+ val oo = jsonObject.getJSONArray("agencies")
+ agencies.ensureCapacity(oo.length())
+ for (i in 0 until oo.length()){
+ val agObj = oo.getJSONObject(i)
+
+ agencies.add(
+ GtfsAgency(
+ agObj.getString("gtfsId"),
+ agObj.getString("name"),
+ agObj.getString("url"),
+ agObj.getString("fareUrl"),
+ agObj.getString("phone"),
+ feed
+ )
+ )
+ }
+ return Pair(feed, agencies)
+ }
+
+ fun parseRouteJSON(jsonObject: JSONObject): GtfsRoute {
+
+ val agencyJSON = jsonObject.getJSONObject("agency")
+ val agencyId = agencyJSON.getString("gtfsId")
+
+
+ return GtfsRoute(
+ jsonObject.getString("gtfsId"),
+ agencyId,
+ jsonObject.getString("shortName"),
+ jsonObject.getString("longName"),
+ jsonObject.getString("desc"),
+ GtfsMode.getByValue(jsonObject.getInt("type"))!!,
+ jsonObject.getString("color"),
+ jsonObject.getString("textColor")
+
+ )
+ }
+
+ /**
+ * Parse a route pattern from the JSON response of the MaTO server
+ */
+ fun parseRoutePatternsStopsJSON(jsonObject: JSONObject) : ArrayList<MatoPattern>{
+ val routeGtfsId = jsonObject.getString("gtfsId")
+
+ val patternsJSON = jsonObject.getJSONArray("patterns")
+ val patternsOut = ArrayList<MatoPattern>(patternsJSON.length())
+ var mPatternJSON: JSONObject
+ for(i in 0 until patternsJSON.length()){
+ mPatternJSON = patternsJSON.getJSONObject(i)
+
+ val stopsJSON = mPatternJSON.getJSONArray("stops")
+
+ val stopsCodes = ArrayList<String>(stopsJSON.length())
+ for(k in 0 until stopsJSON.length()){
+ stopsCodes.add(
+ stopsJSON.getJSONObject(k).getString("gtfsId")
+ )
+ }
+
+ val geometry = mPatternJSON.getJSONObject("patternGeometry")
+ val numGeo = geometry.getInt("length")
+ val polyline = geometry.getString("points")
+
+ patternsOut.add(
+ MatoPattern(
+ mPatternJSON.getString("name"), mPatternJSON.getString("code"),
+ mPatternJSON.getString("semanticHash"), mPatternJSON.getInt("directionId"),
+ routeGtfsId,mPatternJSON.getString("headsign"), polyline, numGeo, stopsCodes
+ )
+ )
+ }
+ return patternsOut
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt b/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
--- a/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
+++ b/src/it/reyboz/bustorino/backend/mato/VolleyAllStopsRequest.kt
@@ -31,7 +31,7 @@
listener: Response.Listener<List<Palina>>,
errorListener: Response.ErrorListener,
) : MapiVolleyRequest<List<Palina>>(
- MatoAPIFetcher.QueryType.ALL_STOPS,listener, errorListener) {
+ MatoQueries.QueryType.ALL_STOPS,listener, errorListener) {
private val FEEDS = JSONArray()
init {
@@ -73,7 +73,7 @@
return Response.success(palinas, HttpHeaderParser.parseCacheHeaders(response))
}
companion object{
- val FEEDS_STR = arrayOf("gtt")
+ //val FEEDS_STR = arrayOf("gtt")
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/backend/utils.java b/src/it/reyboz/bustorino/backend/utils.java
--- a/src/it/reyboz/bustorino/backend/utils.java
+++ b/src/it/reyboz/bustorino/backend/utils.java
@@ -15,11 +15,15 @@
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import it.reyboz.bustorino.backend.mato.MatoAPIFetcher;
public abstract class utils {
private static final double EarthRadius = 6371e3;
+
+
public static Double measuredistanceBetween(double lat1,double long1,double lat2,double long2){
final double phi1 = Math.toRadians(lat1);
final double phi2 = Math.toRadians(lat2);
@@ -107,21 +111,58 @@
}
return busStopID;
}
+ final static Pattern ROMAN_PATTERN = Pattern.compile(
+ "^M{0,4}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$");
+ private static boolean isRomanNumber(String str){
+ if(str.isEmpty()) return false;
+ final Matcher matcher = ROMAN_PATTERN.matcher(str);
+ return matcher.find();
+ }
public static String toTitleCase(String givenString, boolean lowercaseRest) {
- String[] arr = givenString.split(" ");
+ String[] arr = givenString.trim().split(" ");
StringBuilder sb = new StringBuilder();
//Log.d("BusTO chars", "String parsing: "+givenString+" in array: "+ Arrays.toString(arr));
- for (int i = 0; i < arr.length; i++) {
- if (arr[i].length() > 1) {
- sb.append(Character.toUpperCase(arr[i].charAt(0)));
+ for (String s : arr) {
+ if (s.length() > 0) {
+ String[] allsubs = s.split("\\.");
+
+ boolean addPoint = s.contains(".");
+ /*if (s.contains(".lli")|| s.contains(".LLI")) //Fratelli
+ {
+ DOESN'T ALWAYS WORK
+ addPoint = false;
+ allsubs = new String[]{s};
+ }*/
+ boolean first = true;
+ for (String subs : allsubs) {
+ if(first) first=false;
+ else {
+ if (addPoint) sb.append(".");
+ sb.append(" ");
+ }
+ if(isRomanNumber(subs)){
+ //add and skip the rest
+ sb.append(subs);
+ continue;
+ }
+ sb.append(Character.toUpperCase(subs.charAt(0)));
+ if (lowercaseRest)
+ sb.append(subs.substring(1).toLowerCase(Locale.ROOT));
+ else
+ sb.append(subs.substring(1));
+
+ }
+ sb.append(" ");
+ /*sb.append(Character.toUpperCase(arr[i].charAt(0)));
if (lowercaseRest)
sb.append(arr[i].substring(1).toLowerCase(Locale.ROOT));
else
sb.append(arr[i].substring(1));
sb.append(" ");
- }
- else sb.append(arr[i]);
+
+ */
+ } else sb.append(s);
}
return sb.toString().trim();
}
diff --git a/src/it/reyboz/bustorino/data/AppDataProvider.java b/src/it/reyboz/bustorino/data/AppDataProvider.java
--- a/src/it/reyboz/bustorino/data/AppDataProvider.java
+++ b/src/it/reyboz/bustorino/data/AppDataProvider.java
@@ -153,7 +153,7 @@
Log.d("InsBranchWithProvider","line: "+c.getString(c.getColumnIndex(LinesTable.COLUMN_NAME))+"\n"
+c.getString(c.getColumnIndex(LinesTable.COLUMN_DESCRIPTION)));
}*/
- lineid = c.getInt(c.getColumnIndex(NextGenDB.Contract.LinesTable._ID));
+ lineid = c.getInt(c.getColumnIndexOrThrow(NextGenDB.Contract.LinesTable._ID));
c.close();
}
values.remove(NextGenDB.Contract.LinesTable.COLUMN_NAME);
@@ -191,7 +191,7 @@
@Override
public boolean onCreate() {
con = getContext();
- appDBHelper = new NextGenDB(getContext());
+ appDBHelper = NextGenDB.getInstance(getContext());
userDBHelper = new UserDB(getContext());
if(con!=null) {
preferences = new DBStatusManager(con,null);
diff --git a/src/it/reyboz/bustorino/data/AppRepository.java b/src/it/reyboz/bustorino/data/AppRepository.java
deleted file mode 100644
--- a/src/it/reyboz/bustorino/data/AppRepository.java
+++ /dev/null
@@ -1,23 +0,0 @@
-/*
- BusTO - Data components
- Copyright (C) 2021 Fabio Mazza
-
- This program is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
-
- This program is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License
- along with this program. If not, see <http://www.gnu.org/licenses/>.
- */
-package it.reyboz.bustorino.data;
-
-public class AppRepository {
-
-
-}
diff --git a/src/it/reyboz/bustorino/data/DBUpdateWorker.java b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
--- a/src/it/reyboz/bustorino/data/DBUpdateWorker.java
+++ b/src/it/reyboz/bustorino/data/DBUpdateWorker.java
@@ -47,13 +47,13 @@
public static final int SUCCESS_NO_ACTION_NEEDED = 9;
public static final int SUCCESS_UPDATE_DONE = 1;
- private final int notifi_ID=62341;
+ private final static int NOTIFIC_ID =32198;
public static final String FORCED_UPDATE = "FORCED-UPDATE";
public static final String DEBUG_TAG = "Busto-UpdateWorker";
- private static final long UPDATE_MIN_DELAY= 3*7*24*3600; //3 weeks
+ private static final long UPDATE_MIN_DELAY= 9*24*3600; //9 days
public DBUpdateWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
@@ -66,7 +66,14 @@
public Result doWork() {
//register Notification channel
final Context con = getApplicationContext();
- Notifications.createDefaultNotificationChannel(con);
+ //Notifications.createDefaultNotificationChannel(con);
+ //Use the new notification channels
+ Notifications.createNotificationChannel(con,con.getString(R.string.database_notification_channel),
+ con.getString(R.string.database_notification_channel_desc), NotificationManagerCompat.IMPORTANCE_LOW,
+ Notifications.DB_UPDATE_CHANNELS_ID
+ );
+ final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
+ final int notification_ID = 32198;
final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
final int current_DB_version = shPr.getInt(DatabaseUpdate.DB_VERSION_KEY,-10);
@@ -77,7 +84,17 @@
final long lastDBUpdateTime = shPr.getLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, 0);
long currentTime = System.currentTimeMillis()/1000;
- final int notificationID = showNotification();
+ //showNotification(notificationManager, notification_ID);
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(con,
+ Notifications.DB_UPDATE_CHANNELS_ID)
+ .setContentTitle(con.getString(R.string.database_update_msg_notif))
+ .setProgress(0,0,true)
+ .setPriority(NotificationCompat.PRIORITY_LOW);
+ builder.setSmallIcon(R.drawable.ic_bus_orange);
+
+
+ notificationManager.notify(notification_ID,builder.build());
+
Log.d(DEBUG_TAG, "Have previous version: "+current_DB_version +" and new version "+new_DB_version);
Log.d(DEBUG_TAG, "Update compulsory: "+isUpdateCompulsory);
/*
@@ -95,7 +112,7 @@
if (!(current_DB_version < new_DB_version || currentTime > lastDBUpdateTime + UPDATE_MIN_DELAY )
&& !isUpdateCompulsory) {
//don't need to update
- cancelNotification(notificationID);
+ cancelNotification(notification_ID);
return ListenableWorker.Result.success(new Data.Builder().
putInt(SUCCESS_REASON_KEY, SUCCESS_NO_ACTION_NEEDED).build());
}
@@ -106,8 +123,7 @@
DatabaseUpdate.setDBUpdatingFlag(con, shPr,false);
if (resultUpdate != DatabaseUpdate.Result.DONE){
- Fetcher.Result result = resultAtomicReference.get();
-
+ //Fetcher.Result result = resultAtomicReference.get();
final Data.Builder dataBuilder = new Data.Builder();
switch (resultUpdate){
case ERROR_STOPS_DOWNLOAD:
@@ -117,7 +133,7 @@
dataBuilder.put(ERROR_REASON_KEY, ERROR_DOWNLOADING_LINES);
break;
}
- cancelNotification(notificationID);
+ cancelNotification(notification_ID);
return ListenableWorker.Result.failure(dataBuilder.build());
}
Log.d(DEBUG_TAG, "Update finished successfully!");
@@ -127,7 +143,7 @@
currentTime = System.currentTimeMillis()/1000;
editor.putLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, currentTime);
editor.apply();
- cancelNotification(notificationID);
+ cancelNotification(notification_ID);
return ListenableWorker.Result.success(new Data.Builder().putInt(SUCCESS_REASON_KEY, SUCCESS_UPDATE_DONE).build());
}
@@ -144,19 +160,21 @@
.build();
}
-
- private int showNotification(){
- final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), Notifications.DEFAULT_CHANNEL_ID)
+ /*
+ private int showNotification(@NonNull final NotificationManagerCompat notificManager, final int notification_ID,
+ final String channel_ID){
+ final NotificationCompat.Builder builder = new NotificationCompat.Builder(getApplicationContext(), channel_ID)
.setContentTitle("Libre BusTO - Updating Database")
.setProgress(0,0,true)
.setPriority(NotificationCompat.PRIORITY_LOW);
builder.setSmallIcon(R.drawable.ic_bus_orange);
- final NotificationManagerCompat notifcManager = NotificationManagerCompat.from(getApplicationContext());
- final int notification_ID = 32198;
- notifcManager.notify(notification_ID,builder.build());
+
+
+ notificManager.notify(notification_ID,builder.build());
return notification_ID;
}
+ */
private void cancelNotification(int notificationID){
final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext());
diff --git a/src/it/reyboz/bustorino/data/DatabaseUpdate.java b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
--- a/src/it/reyboz/bustorino/data/DatabaseUpdate.java
+++ b/src/it/reyboz/bustorino/data/DatabaseUpdate.java
@@ -22,22 +22,31 @@
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
-import androidx.core.content.ContextCompat;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.Observer;
import androidx.work.*;
import it.reyboz.bustorino.R;
import it.reyboz.bustorino.backend.Fetcher;
import it.reyboz.bustorino.backend.FiveTAPIFetcher;
import it.reyboz.bustorino.backend.Palina;
import it.reyboz.bustorino.backend.Route;
-import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.backend.mato.MatoAPIFetcher;
+import it.reyboz.bustorino.data.gtfs.GtfsAgency;
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase;
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao;
+import it.reyboz.bustorino.data.gtfs.GtfsFeed;
+import it.reyboz.bustorino.data.gtfs.GtfsRoute;
+import it.reyboz.bustorino.data.gtfs.MatoPattern;
+import it.reyboz.bustorino.data.gtfs.PatternStop;
+import kotlin.Pair;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.ArrayList;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -59,131 +68,189 @@
DONE, ERROR_STOPS_DOWNLOAD, ERROR_LINES_DOWNLOAD
}
- /**
- * Request the server the version of the database
- * @return the version of the DB, or an error code
- */
- public static int getNewVersion(){
- AtomicReference<Fetcher.Result> gres = new AtomicReference<>();
- String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
- if(networkRequest == null){
- return VERSION_UNAVAILABLE;
- }
+ /**
+ * Request the server the version of the database
+ * @return the version of the DB, or an error code
+ */
+ public static int getNewVersion(){
+ AtomicReference<Fetcher.Result> gres = new AtomicReference<>();
+ String networkRequest = FiveTAPIFetcher.performAPIRequest(FiveTAPIFetcher.QueryType.STOPS_VERSION,null,gres);
+ if(networkRequest == null){
+ return VERSION_UNAVAILABLE;
+ }
- try {
- JSONObject resp = new JSONObject(networkRequest);
- return resp.getInt("id");
- } catch (JSONException e) {
- e.printStackTrace();
- Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
- return JSON_PARSING_ERROR;
- }
+ try {
+ JSONObject resp = new JSONObject(networkRequest);
+ return resp.getInt("id");
+ } catch (JSONException e) {
+ e.printStackTrace();
+ Log.e(DEBUG_TAG,"Error: wrong JSON response\nResponse:\t"+networkRequest);
+ return JSON_PARSING_ERROR;
}
- /**
- * Run the DB Update
- * @param con a context
- * @param gres a result reference
- * @return result of the update
- */
- public static Result performDBUpdate(Context con, AtomicReference<Fetcher.Result> gres) {
+ }
+ private static boolean updateGTFSAgencies(Context con, AtomicReference<Fetcher.Result> res){
- final FiveTAPIFetcher f = new FiveTAPIFetcher();
- /*
- final ArrayList<Stop> stops = f.getAllStopsFromGTT(gres);
- //final ArrayList<ContentProviderOperation> cpOp = new ArrayList<>();
+ final GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
- if (gres.get() != Fetcher.Result.OK) {
- Log.w(DEBUG_TAG, "Something went wrong downloading");
- return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+ final Pair<List<GtfsFeed>, ArrayList<GtfsAgency>> respair = MatoAPIFetcher.Companion.getFeedsAndAgencies(
+ con, res
+ );
- }
+ dao.insertAgenciesWithFeeds(respair.getFirst(), respair.getSecond());
- */
- final NextGenDB dbHelp = new NextGenDB(con.getApplicationContext());
- final SQLiteDatabase db = dbHelp.getWritableDatabase();
+ return true;
+ }
+ private static boolean updateGTFSRoutes(Context con, AtomicReference<Fetcher.Result> res){
- final List<Palina> palinasMatoAPI = MatoAPIFetcher.Companion.getAllStopsGTT(con, gres);
- if (gres.get() != Fetcher.Result.OK) {
- Log.w(DEBUG_TAG, "Something went wrong downloading");
- return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+ final GtfsDBDao dao = GtfsDatabase.Companion.getGtfsDatabase(con).gtfsDao();
- }
- //TODO: Get the type of stop from the lines
- //Empty the needed tables
- db.beginTransaction();
- //db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
- //db.delete(LinesTable.TABLE_NAME,null,null);
-
- //put new data
- long startTime = System.currentTimeMillis();
-
- Log.d(DEBUG_TAG, "Inserting " + palinasMatoAPI.size() + " stops");
- for (final Palina p : palinasMatoAPI) {
- final ContentValues cv = new ContentValues();
-
- cv.put(NextGenDB.Contract.StopsTable.COL_ID, p.ID);
- cv.put(NextGenDB.Contract.StopsTable.COL_NAME, p.getStopDefaultName());
- if (p.location != null)
- cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, p.location);
- cv.put(NextGenDB.Contract.StopsTable.COL_LAT, p.getLatitude());
- cv.put(NextGenDB.Contract.StopsTable.COL_LONG, p.getLongitude());
- if (p.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
- cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, p.routesThatStopHereToString());
- if (p.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, p.type.getCode());
- if (p.gtfsID != null) cv.put(NextGenDB.Contract.StopsTable.COL_GTFS_ID, p.gtfsID);
- //Log.d(DEBUG_TAG,cv.toString());
- //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
- //valuesArr[i] = cv;
- db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
+ final List<GtfsRoute> routes= MatoAPIFetcher.Companion.getRoutes(
+ con, res
+ );
+ dao.insertRoutes(routes);
+ if(res.get()!= Fetcher.Result.OK){
+ return false;
+ }
+ final ArrayList<String> gtfsRoutesIDs = new ArrayList<>(routes.size());
+ for(GtfsRoute r: routes){
+ gtfsRoutesIDs.add(r.getGtfsId());
+ }
+ long t0 = System.currentTimeMillis();
+ final ArrayList<MatoPattern> patterns = MatoAPIFetcher.Companion.getPatternsWithStops(con,gtfsRoutesIDs,res);
+ long tend = System.currentTimeMillis() - t0;
+ Log.d(DEBUG_TAG, "Downloaded patterns in "+tend+" ms");
+ if(res.get()!=Fetcher.Result.OK){
+ Log.e(DEBUG_TAG, "Something went wrong downloading patterns");
+ return false;
+ }
+ final ArrayList<PatternStop> patternStops = new ArrayList<>(patterns.size());
+ for(MatoPattern p: patterns){
+ final ArrayList<String> stopsIDs = p.getStopsGtfsIDs();
+ for (int i=0; i<stopsIDs.size(); i++){
+ patternStops.add(new PatternStop(p.getCode(),stopsIDs.get(i), i));
}
- db.setTransactionSuccessful();
- db.endTransaction();
- long endTime = System.currentTimeMillis();
- Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
+ }
+ dao.insertPatterns(patterns);
+ dao.insertPatternStops(patternStops);
- final ArrayList<Route> routes = f.getAllLinesFromGTT(gres);
+ return true;
+ }
- if (routes == null) {
- Log.w(DEBUG_TAG, "Something went wrong downloading the lines");
- dbHelp.close();
- return DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD;
- }
+ /**
+ * Run the DB Update
+ * @param con a context
+ * @param gres a result reference
+ * @return result of the update
+ */
+ public static Result performDBUpdate(Context con, AtomicReference<Fetcher.Result> gres) {
- db.beginTransaction();
- startTime = System.currentTimeMillis();
- for (Route r : routes) {
- final ContentValues cv = new ContentValues();
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_NAME, r.getName());
- switch (r.type) {
- case BUS:
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "URBANO");
- break;
- case RAILWAY:
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "FERROVIA");
- break;
- case LONG_DISTANCE_BUS:
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "EXTRA");
- break;
- }
- cv.put(NextGenDB.Contract.LinesTable.COLUMN_DESCRIPTION, r.description);
-
- //db.insert(LinesTable.TABLE_NAME,null,cv);
- int rows = db.update(NextGenDB.Contract.LinesTable.TABLE_NAME, cv, NextGenDB.Contract.LinesTable.COLUMN_NAME + " = ?", new String[]{r.getName()});
- if (rows < 1) { //we haven't changed anything
- db.insert(NextGenDB.Contract.LinesTable.TABLE_NAME, null, cv);
- }
- }
- db.setTransactionSuccessful();
- db.endTransaction();
- endTime = System.currentTimeMillis();
- Log.d(DEBUG_TAG, "Inserting lines took: " + ((double) (endTime - startTime) / 1000) + " s");
+ final FiveTAPIFetcher f = new FiveTAPIFetcher();
+
+ final NextGenDB dbHelp = NextGenDB.getInstance(con.getApplicationContext());
+ final SQLiteDatabase db = dbHelp.getWritableDatabase();
+
+ final List<Palina> palinasMatoAPI = MatoAPIFetcher.Companion.getAllStopsGTT(con, gres);
+ if (gres.get() != Fetcher.Result.OK) {
+ Log.w(DEBUG_TAG, "Something went wrong downloading");
+ return DatabaseUpdate.Result.ERROR_STOPS_DOWNLOAD;
+
+ }
+ //TODO: Get the type of stop from the lines
+ //Empty the needed tables
+ db.beginTransaction();
+ //db.execSQL("DELETE FROM "+StopsTable.TABLE_NAME);
+ //db.delete(LinesTable.TABLE_NAME,null,null);
+
+ //put new data
+ long startTime = System.currentTimeMillis();
+
+ Log.d(DEBUG_TAG, "Inserting " + palinasMatoAPI.size() + " stops");
+ for (final Palina p : palinasMatoAPI) {
+ final ContentValues cv = new ContentValues();
+
+ cv.put(NextGenDB.Contract.StopsTable.COL_ID, p.ID);
+ cv.put(NextGenDB.Contract.StopsTable.COL_NAME, p.getStopDefaultName());
+ if (p.location != null)
+ cv.put(NextGenDB.Contract.StopsTable.COL_LOCATION, p.location);
+ cv.put(NextGenDB.Contract.StopsTable.COL_LAT, p.getLatitude());
+ cv.put(NextGenDB.Contract.StopsTable.COL_LONG, p.getLongitude());
+ if (p.getAbsurdGTTPlaceName() != null) cv.put(NextGenDB.Contract.StopsTable.COL_PLACE, p.getAbsurdGTTPlaceName());
+ cv.put(NextGenDB.Contract.StopsTable.COL_LINES_STOPPING, p.routesThatStopHereToString());
+ if (p.type != null) cv.put(NextGenDB.Contract.StopsTable.COL_TYPE, p.type.getCode());
+ if (p.gtfsID != null) cv.put(NextGenDB.Contract.StopsTable.COL_GTFS_ID, p.gtfsID);
+ //Log.d(DEBUG_TAG,cv.toString());
+ //cpOp.add(ContentProviderOperation.newInsert(uritobeused).withValues(cv).build());
+ //valuesArr[i] = cv;
+ db.replace(NextGenDB.Contract.StopsTable.TABLE_NAME, null, cv);
+
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ long endTime = System.currentTimeMillis();
+ Log.d(DEBUG_TAG, "Inserting stops took: " + ((double) (endTime - startTime) / 1000) + " s");
+
+ // GTFS data fetching
+ AtomicReference<Fetcher.Result> gtfsRes = new AtomicReference<>(Fetcher.Result.OK);
+ updateGTFSAgencies(con, gtfsRes);
+ if (gtfsRes.get()!= Fetcher.Result.OK){
+ Log.w(DEBUG_TAG, "Could not insert the feeds and agencies stuff");
+ } else{
+ Log.d(DEBUG_TAG, "Done downloading agencies");
+ }
+ gtfsRes.set(Fetcher.Result.OK);
+ updateGTFSRoutes(con,gtfsRes);
+ if (gtfsRes.get()!= Fetcher.Result.OK){
+ Log.w(DEBUG_TAG, "Could not insert the routes into DB");
+ } else{
+ Log.d(DEBUG_TAG, "Done downloading routes from MaTO");
+ }
+ /*
+ final ArrayList<Route> routes = f.getAllLinesFromGTT(gres);
+
+ if (routes == null) {
+ Log.w(DEBUG_TAG, "Something went wrong downloading the lines");
dbHelp.close();
+ return DatabaseUpdate.Result.ERROR_LINES_DOWNLOAD;
- return DatabaseUpdate.Result.DONE;
}
+ db.beginTransaction();
+ startTime = System.currentTimeMillis();
+ for (Route r : routes) {
+ final ContentValues cv = new ContentValues();
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_NAME, r.getName());
+ switch (r.type) {
+ case BUS:
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "URBANO");
+ break;
+ case RAILWAY:
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "FERROVIA");
+ break;
+ case LONG_DISTANCE_BUS:
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_TYPE, "EXTRA");
+ break;
+ }
+ cv.put(NextGenDB.Contract.LinesTable.COLUMN_DESCRIPTION, r.description);
+
+ //db.insert(LinesTable.TABLE_NAME,null,cv);
+ int rows = db.update(NextGenDB.Contract.LinesTable.TABLE_NAME, cv, NextGenDB.Contract.LinesTable.COLUMN_NAME + " = ?", new String[]{r.getName()});
+ if (rows < 1) { //we haven't changed anything
+ db.insert(NextGenDB.Contract.LinesTable.TABLE_NAME, null, cv);
+ }
+ }
+ db.setTransactionSuccessful();
+ db.endTransaction();
+ endTime = System.currentTimeMillis();
+ Log.d(DEBUG_TAG, "Inserting lines took: " + ((double) (endTime - startTime) / 1000) + " s");
+
+ */
+ dbHelp.close();
+
+ return DatabaseUpdate.Result.DONE;
+ }
+
public static boolean setDBUpdatingFlag(Context con, boolean value){
final SharedPreferences shPr = con.getSharedPreferences(con.getString(R.string.mainSharedPreferences),MODE_PRIVATE);
return setDBUpdatingFlag(con, shPr, value);
@@ -199,17 +266,21 @@
* @param con the context to use
* @param forced if you want to force the request to go now
*/
- public static void requestDBUpdateWithWork(Context con, boolean forced){
+ public static void requestDBUpdateWithWork(Context con,boolean restart, boolean forced){
final SharedPreferences theShPr = PreferencesHolder.getMainSharedPreferences(con);
final WorkManager workManager = WorkManager.getInstance(con);
+ final Data reqData = new Data.Builder()
+ .putBoolean(DBUpdateWorker.FORCED_UPDATE, forced).build();
+
PeriodicWorkRequest wr = new PeriodicWorkRequest.Builder(DBUpdateWorker.class, 7, TimeUnit.DAYS)
- .setBackoffCriteria(BackoffPolicy.LINEAR, 30, TimeUnit.MINUTES)
+ .setBackoffCriteria(BackoffPolicy.LINEAR, 10, TimeUnit.MINUTES)
.setConstraints(new Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED)
.build())
+ .setInputData(reqData)
.build();
final int version = theShPr.getInt(DatabaseUpdate.DB_VERSION_KEY, -10);
final long lastDBUpdateTime = theShPr.getLong(DatabaseUpdate.DB_LAST_UPDATE_KEY, -10);
- if ((version >= 0 || lastDBUpdateTime >=0) && !forced)
+ if ((version >= 0 || lastDBUpdateTime >=0) && !restart)
workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
ExistingPeriodicWorkPolicy.KEEP, wr);
else workManager.enqueueUniquePeriodicWork(DBUpdateWorker.DEBUG_TAG,
@@ -221,4 +292,12 @@
TODO
}
*/
+
+ public static void watchUpdateWorkStatus(Context context, @NonNull LifecycleOwner lifecycleOwner,
+ @NonNull Observer<? super List<WorkInfo>> observer) {
+ WorkManager workManager = WorkManager.getInstance(context);
+ workManager.getWorkInfosForUniqueWorkLiveData(DBUpdateWorker.DEBUG_TAG).observe(
+ lifecycleOwner, observer
+ );
+ }
}
diff --git a/src/it/reyboz/bustorino/data/GtfsRepository.kt b/src/it/reyboz/bustorino/data/GtfsRepository.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/GtfsRepository.kt
@@ -0,0 +1,36 @@
+package it.reyboz.bustorino.data
+
+import androidx.lifecycle.LiveData
+import androidx.lifecycle.MutableLiveData
+import it.reyboz.bustorino.data.gtfs.GtfsDBDao
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPattern
+import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
+
+class GtfsRepository(
+ val gtfsDao: GtfsDBDao
+) {
+
+
+ fun getLinesLiveDataForFeed(feed: String): LiveData<List<GtfsRoute>>{
+ //return withContext(Dispatchers.IO){
+ return gtfsDao.getRoutesForFeed(feed)
+ //}
+ }
+ fun getPatternsForRouteID(routeID: String): LiveData<List<MatoPattern>>{
+ return if(routeID.isNotEmpty())
+ gtfsDao.getPatternsByRouteID(routeID)
+ else
+ MutableLiveData(listOf())
+ }
+
+ /**
+ * Get the patterns with the stops lists (gtfsIDs only)
+ */
+ fun getPatternsWithStopsForRouteID(routeID: String): LiveData<List<MatoPatternWithStops>>{
+ return if(routeID.isNotEmpty())
+ gtfsDao.getPatternsWithStopsByRouteID(routeID)
+ else
+ MutableLiveData(listOf())
+ }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/NextGenDB.java b/src/it/reyboz/bustorino/data/NextGenDB.java
--- a/src/it/reyboz/bustorino/data/NextGenDB.java
+++ b/src/it/reyboz/bustorino/data/NextGenDB.java
@@ -85,15 +85,24 @@
StopsTable.COL_LAT + " <= ? AND "+ StopsTable.COL_LONG +
" >= ? AND "+ StopsTable.COL_LONG + " <= ?";
+ public static final String QUERY_FROM_GTFS_ID_IN_TO_COMPLETE= StopsTable.COL_GTFS_ID +" IN ";
+
public static String QUERY_WHERE_ID = StopsTable.COL_ID+" = ?";
private final Context appContext;
+ private static NextGenDB INSTANCE;
- public NextGenDB(Context context) {
+ private NextGenDB(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
appContext = context.getApplicationContext();
}
+ public static NextGenDB getInstance(Context context) {
+ if (INSTANCE == null){
+ INSTANCE = new NextGenDB(context);
+ }
+ return INSTANCE;
+ }
@Override
@@ -124,7 +133,7 @@
db.execSQL(SQL_CREATE_BRANCH_TABLE);
db.execSQL(SQL_CREATE_CONNECTIONS_TABLE);
- DatabaseUpdate.requestDBUpdateWithWork(appContext, true);
+ DatabaseUpdate.requestDBUpdateWithWork(appContext, true, true);
}
if(oldVersion < 3 && newVersion == 3){
Log.d("BusTO-Database", "Running upgrades for version 3");
@@ -194,6 +203,53 @@
return stops;
}
+ /**
+ * Query stops in the database having these IDs
+ * REMEMBER TO CLOSE THE DB CONNECTION AFTERWARDS
+ * @param bustoDB readable database instance
+ * @param gtfsIDs gtfs IDs to query
+ * @return list of stops
+ */
+ public static synchronized ArrayList<Stop> queryAllStopsWithGtfsIDs(SQLiteDatabase bustoDB, List<String> gtfsIDs){
+ final ArrayList<Stop> stops = new ArrayList<>();
+
+ if(bustoDB == null){
+ Log.e(DEBUG_TAG, "Asked query for IDs but database is null");
+ return stops;
+ } else if (gtfsIDs == null || gtfsIDs.isEmpty()) {
+ return stops;
+ }
+
+ final StringBuilder builder = new StringBuilder(QUERY_FROM_GTFS_ID_IN_TO_COMPLETE);
+ boolean first = true;
+ builder.append(" ( ");
+ for(int i=0; i< gtfsIDs.size(); i++){
+ if(first){
+ first = false;
+ } else{
+ builder.append(", ");
+ }
+ builder.append("?");//.append("\"").append(id).append("\"");
+ }
+ builder.append(") ");
+ final String whereClause = builder.toString();
+
+ final String[] idsQuery = gtfsIDs.toArray(new String[0]);
+
+ try {
+ final Cursor result = bustoDB.query(StopsTable.TABLE_NAME,QUERY_COLUMN_stops_all, whereClause,
+ idsQuery,
+ null, null, null);
+ stops.addAll(getStopsFromCursorAllFields(result));
+ result.close();
+ } catch(SQLiteException e) {
+ Log.e(DEBUG_TAG, "SQLiteException occurred");
+ e.printStackTrace();
+
+ }
+ return stops;
+ }
+
/**
* Get the list of stop in the query, with all the possible fields {NextGenDB.QUERY_COLUMN_stops_all}
* @param result cursor from query
@@ -205,6 +261,7 @@
final int colLocation = result.getColumnIndex(StopsTable.COL_LOCATION);
final int colType = result.getColumnIndex(StopsTable.COL_TYPE);
final int colLat = result.getColumnIndex(StopsTable.COL_LAT);
+ final int colGtfsID = result.getColumnIndex(StopsTable.COL_GTFS_ID);
final int colLon = result.getColumnIndex(StopsTable.COL_LONG);
final int colLines = result.getColumnIndex(StopsTable.COL_LINES_STOPPING);
@@ -215,9 +272,15 @@
while(result.moveToNext()) {
final String stopID = result.getString(colID).trim();
- final Route.Type type;
- if(result.getString(colType) == null) type = Route.Type.BUS;
- else type = Route.getTypeFromSymbol(result.getString(colType));
+ Route.Type type;
+ //if(result.getString(colType) == null) type = Route.Type.BUS;
+ //else type = Route.getTypeFromSymbol(result.getString(colType));
+ //if(result.getInt(colType) == null) type = Route.Type.BUS;
+ try{
+ type = Route.Type.fromCode(result.getInt(colType));
+ } catch (Exception e){
+ type = Route.Type.BUS;
+ }
String lines = result.getString(colLines).trim();
String locationSometimesEmpty = result.getString(colLocation);
@@ -227,11 +290,39 @@
stops.add(new Stop(stopID, result.getString(colName), null,
locationSometimesEmpty, type, splitLinesString(lines),
- result.getDouble(colLat), result.getDouble(colLon))
+ result.getDouble(colLat), result.getDouble(colLon),
+ result.getString(colGtfsID))
);
}
return stops;
}
+ /*
+ static ArrayList<Stop> createStopListFromCursor(Cursor data){
+ ArrayList<Stop> stopList = new ArrayList<>();
+ final int col_id = data.getColumnIndex(StopsTable.COL_ID);
+ final int latInd = data.getColumnIndex(StopsTable.COL_LAT);
+ final int lonInd = data.getColumnIndex(StopsTable.COL_LONG);
+ final int nameindex = data.getColumnIndex(StopsTable.COL_NAME);
+ final int typeIndex = data.getColumnIndex(StopsTable.COL_TYPE);
+ final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
+
+ data.moveToFirst();
+ for(int i=0; i<data.getCount();i++){
+ String[] routes = data.getString(linesIndex).split(",");
+
+ stopList.add(new Stop(data.getString(col_id),data.getString(nameindex),null,null,
+ Route.Type.fromCode(data.getInt(typeIndex)),
+ Arrays.asList(routes), //the routes should be compact, not normalized yet
+ data.getDouble(latInd),data.getDouble(lonInd)
+ )
+ );
+ //Log.d("NearbyStopsFragment","Got stop with id "+data.getString(col_id)+
+ //" and name "+data.getString(nameindex));
+ data.moveToNext();
+ }
+ return stopList;
+ }
+ */
/**
* Insert batch content, already prepared as
diff --git a/src/it/reyboz/bustorino/data/OldDataRepository.java b/src/it/reyboz/bustorino/data/OldDataRepository.java
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/OldDataRepository.java
@@ -0,0 +1,58 @@
+/*
+ BusTO - Data components
+ Copyright (C) 2021 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data;
+
+import android.database.sqlite.SQLiteDatabase;
+import it.reyboz.bustorino.backend.Result;
+import it.reyboz.bustorino.backend.Stop;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+public class OldDataRepository {
+
+ private final Executor executor;
+ private final NextGenDB nextGenDB;
+
+ public OldDataRepository(Executor executor, final NextGenDB nextGenDB) {
+ this.executor = executor;
+ this.nextGenDB = nextGenDB;
+ }
+
+ public void requestStopsWithGtfsIDs(final List<String> gtfsIDs,
+ final Callback<List<Stop>> callback){
+ executor.execute(() -> {
+
+ try {
+ //final NextGenDB dbHelper = new NextGenDB(context);
+ final SQLiteDatabase db = nextGenDB.getReadableDatabase();
+
+ final List<Stop> stops = NextGenDB.queryAllStopsWithGtfsIDs(db, gtfsIDs);
+ //Result<List<Stop>> result = Result.success;
+
+ callback.onComplete(Result.success(stops));
+ } catch (Exception e){
+ callback.onComplete(Result.failure(e));
+ }
+ });
+ }
+
+ public interface Callback<T>{
+ void onComplete(Result<T> result);
+ }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
--- a/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/CsvTableInserter.kt
@@ -19,13 +19,12 @@
import android.content.Context
import android.util.Log
-import java.util.ArrayList
class CsvTableInserter(
val tableName: String, context: Context
) {
private val database: GtfsDatabase = GtfsDatabase.getGtfsDatabase(context)
- private val dao: StaticGtfsDao = database.gtfsDao()
+ private val databaseDao: GtfsDBDao = database.gtfsDao()
private val elementsList: MutableList< in GtfsTable> = mutableListOf()
@@ -35,12 +34,12 @@
private var countInsert = 0
init {
if(tableName == "stop_times") {
- stopsIDsPresent = dao.getAllStopsIDs().toHashSet()
- tripsIDsPresent = dao.getAllTripsIDs().toHashSet()
+ stopsIDsPresent = databaseDao.getAllStopsIDs().toHashSet()
+ tripsIDsPresent = databaseDao.getAllTripsIDs().toHashSet()
Log.d(DEBUG_TAG, "num stop IDs present: "+ stopsIDsPresent!!.size)
Log.d(DEBUG_TAG, "num trips IDs present: "+ tripsIDsPresent!!.size)
} else if(tableName == "routes"){
- dao.deleteAllRoutes()
+ databaseDao.deleteAllRoutes()
}
}
@@ -77,7 +76,7 @@
//have to insert
if (tableName == "routes")
- dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+ databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
else
insertDataInDatabase()
@@ -90,21 +89,21 @@
countInsert += elementsList.size
when(tableName){
"stops" -> {
- dao.insertStops(elementsList.filterIsInstance<GtfsStop>())
+ databaseDao.insertStops(elementsList.filterIsInstance<GtfsStop>())
}
- "routes" -> dao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
- "calendar" -> dao.insertServices(elementsList.filterIsInstance<GtfsService>())
- "calendar_dates" -> dao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
- "trips" -> dao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
- "stop_times"-> dao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
- "shapes" -> dao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
+ "routes" -> databaseDao.insertRoutes(elementsList.filterIsInstance<GtfsRoute>())
+ "calendar" -> databaseDao.insertServices(elementsList.filterIsInstance<GtfsService>())
+ "calendar_dates" -> databaseDao.insertDates(elementsList.filterIsInstance<GtfsServiceDate>())
+ "trips" -> databaseDao.insertTrips(elementsList.filterIsInstance<GtfsTrip>())
+ "stop_times"-> databaseDao.insertStopTimes(elementsList.filterIsInstance<GtfsStopTime>())
+ "shapes" -> databaseDao.insertShapes(elementsList.filterIsInstance<GtfsShape>())
}
///if(elementsList.size < MAX_ELEMENTS)
}
fun finishInsert(){
insertDataInDatabase()
- Log.d(DEBUG_TAG, "Inserted "+countInsert+" elements from "+tableName);
+ Log.d(DEBUG_TAG, "Inserted $countInsert elements from $tableName")
}
companion object{
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsAgency.kt
@@ -0,0 +1,55 @@
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = GtfsAgency.TABLE_NAME)
+data class GtfsAgency(
+ @PrimaryKey
+ @ColumnInfo(name = COL_GTFS_ID)
+ val gtfsId: String,
+ @ColumnInfo(name = COL_NAME)
+ val name: String,
+ @ColumnInfo(name = COL_URL)
+ val url: String,
+ @ColumnInfo(name = COL_FAREURL)
+ val fareUrl: String?,
+ @ColumnInfo(name = COL_PHONE)
+ val phone: String?,
+ @Embedded var feed: GtfsFeed?
+): GtfsTable{
+ constructor(valuesByColumn: Map<String,String>) : this(
+ valuesByColumn[COL_GTFS_ID]!!,
+ valuesByColumn[COL_NAME]!!,
+ valuesByColumn[COL_URL]!!,
+ valuesByColumn[COL_FAREURL],
+ valuesByColumn[COL_PHONE],
+ null
+ )
+
+ companion object{
+ const val TABLE_NAME="gtfs_agencies"
+
+ const val COL_GTFS_ID="gtfs_id"
+ const val COL_NAME="ag_name"
+ const val COL_URL="ag_url"
+ const val COL_FAREURL = "fare_url"
+ const val COL_PHONE = "phone"
+
+ val COLUMNS = arrayOf(
+ COL_GTFS_ID,
+ COL_NAME,
+ COL_URL,
+ COL_FAREURL,
+ COL_PHONE
+ )
+ const val CREATE_SQL =
+ "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID )"
+ }
+
+ override fun getColumns(): Array<String> {
+ return COLUMNS
+ }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
rename from src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
rename to src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
--- a/src/it/reyboz/bustorino/data/gtfs/StaticGtfsDao.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDBDao.kt
@@ -21,8 +21,8 @@
import androidx.room.*
@Dao
-interface StaticGtfsDao {
- @Query("SELECT * FROM "+GtfsRoute.DB_TABLE+" ORDER BY "+GtfsRoute.COL_SORT_ORDER)
+interface GtfsDBDao {
+ @Query("SELECT * FROM "+GtfsRoute.DB_TABLE)
fun getAllRoutes() : LiveData<List<GtfsRoute>>
@Query("SELECT "+GtfsTrip.COL_TRIP_ID+" FROM "+GtfsTrip.DB_TABLE)
@@ -40,14 +40,32 @@
)
fun getShapeByID(shapeID: String) : LiveData<List<GtfsShape>>
+
+ @Query("SELECT * FROM ${GtfsRoute.DB_TABLE} WHERE ${GtfsRoute.COL_AGENCY_ID} LIKE :agencyID")
+ fun getRoutesByAgency(agencyID:String) : LiveData<List<GtfsRoute>>
+
+ @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeID")
+ fun getPatternsByRouteID(routeID: String): LiveData<List<MatoPattern>>
+
+ @Query("SELECT * FROM ${PatternStop.TABLE_NAME} WHERE ${PatternStop.COL_PATTERN_ID} LIKE :patternGtfsID")
+ fun getStopsByPatternID(patternGtfsID: String): LiveData<List<PatternStop>>
+
+ @Transaction
+ @Query("SELECT * FROM ${MatoPattern.TABLE_NAME} WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeID")
+ fun getPatternsWithStopsByRouteID(routeID: String): LiveData<List<MatoPatternWithStops>>
+
+ fun getRoutesForFeed(feed:String): LiveData<List<GtfsRoute>>{
+ val agencyID = "${feed}:%"
+ return getRoutesByAgency(agencyID)
+ }
@Transaction
fun clearAndInsertRoutes(routes: List<GtfsRoute>){
deleteAllRoutes()
insertRoutes(routes)
}
-
+ @Transaction
@Insert(onConflict = OnConflictStrategy.REPLACE)
- fun insertRoutes(users: List<GtfsRoute>)
+ fun insertRoutes(routes: List<GtfsRoute>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insertStops(stops: List<GtfsStop>)
@Insert(onConflict = OnConflictStrategy.REPLACE)
@@ -87,4 +105,24 @@
@Query("DELETE FROM "+GtfsService.DB_TABLE)
fun deleteAllServices()
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertFeeds(feeds: List<GtfsFeed>)
+
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertAgencies(agencies: List<GtfsAgency>)
+
+ @Transaction
+ fun insertAgenciesWithFeeds(feeds: List<GtfsFeed>, agencies: List<GtfsAgency>){
+ insertFeeds(feeds)
+ insertAgencies(agencies)
+ }
+ //patterns
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertPatterns(patterns: List<MatoPattern>)
+
+ @Query("SELECT * FROM "+MatoPattern.TABLE_NAME+
+ " WHERE ${MatoPattern.COL_ROUTE_ID} LIKE :routeGtfsId")
+ fun getPatternsForRouteID(routeGtfsId: String) : List<MatoPattern>
+ @Insert(onConflict = OnConflictStrategy.REPLACE)
+ fun insertPatternStops(patternStops: List<PatternStop>)
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
--- a/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsDatabase.kt
@@ -18,24 +18,30 @@
package it.reyboz.bustorino.data.gtfs
import android.content.Context
+import android.util.Log
import androidx.room.*
+import androidx.room.migration.Migration
@Database(
entities = [
+ GtfsFeed::class,
+ GtfsAgency::class,
GtfsServiceDate::class,
GtfsStop::class,
GtfsService::class,
GtfsRoute::class,
GtfsStopTime::class,
GtfsTrip::class,
- GtfsShape::class],
+ GtfsShape::class,
+ MatoPattern::class,
+ PatternStop::class
+ ],
version = GtfsDatabase.VERSION,
- exportSchema = false,
)
@TypeConverters(Converters::class)
-public abstract class GtfsDatabase : RoomDatabase() {
+abstract class GtfsDatabase : RoomDatabase() {
- abstract fun gtfsDao() : StaticGtfsDao
+ abstract fun gtfsDao() : GtfsDBDao
companion object{
@Volatile
@@ -44,14 +50,36 @@
fun getGtfsDatabase(context: Context): GtfsDatabase{
return INSTANCE ?: synchronized(this){
val instance = Room.databaseBuilder(context.applicationContext,
- GtfsDatabase::class.java,
- "gtfs_database").build()
+ GtfsDatabase::class.java,
+ "gtfs_database")
+ .addMigrations(MIGRATION_1_2)
+ .build()
INSTANCE = instance
instance
}
}
- const val VERSION = 1
+ const val VERSION = 2
const val FOREIGNKEY_ONDELETE = ForeignKey.CASCADE
+
+ val MIGRATION_1_2 = Migration(1,2) {
+ Log.d("BusTO-Database", "Upgrading from version 1 to version 2 the Room Database")
+ //create table for feeds
+ it.execSQL("CREATE TABLE IF NOT EXISTS `gtfs_feeds` (`feed_id` TEXT NOT NULL, PRIMARY KEY(`feed_id`))")
+ //create table gtfs_agencies
+ it.execSQL("CREATE TABLE IF NOT EXISTS `gtfs_agencies` (`gtfs_id` TEXT NOT NULL, `ag_name` TEXT NOT NULL, `ag_url` TEXT NOT NULL, `fare_url` TEXT, `phone` TEXT, `feed_id` TEXT, PRIMARY KEY(`gtfs_id`))")
+
+ //recreate routes
+ it.execSQL("DROP TABLE IF EXISTS `routes_table`")
+ it.execSQL("CREATE TABLE IF NOT EXISTS `routes_table` (`route_id` TEXT NOT NULL, `agency_id` TEXT NOT NULL, `route_short_name` TEXT NOT NULL, `route_long_name` TEXT NOT NULL, `route_desc` TEXT NOT NULL, `route_mode` TEXT NOT NULL, `route_color` TEXT NOT NULL, `route_text_color` TEXT NOT NULL, PRIMARY KEY(`route_id`))")
+
+ //create patterns and stops
+ it.execSQL("CREATE TABLE IF NOT EXISTS `mato_patterns` (`pattern_name` TEXT NOT NULL, `pattern_code` TEXT NOT NULL, `pattern_hash` TEXT NOT NULL, `pattern_direction_id` INTEGER NOT NULL, `pattern_route_id` TEXT NOT NULL, `pattern_headsign` TEXT, `pattern_polyline` TEXT NOT NULL, `pattern_polylength` INTEGER NOT NULL, PRIMARY KEY(`pattern_code`), FOREIGN KEY(`pattern_route_id`) REFERENCES `routes_table`(`route_id`) ON UPDATE NO ACTION ON DELETE CASCADE )")
+ it.execSQL("CREATE TABLE IF NOT EXISTS `patterns_stops` (`pattern_gtfs_id` TEXT NOT NULL, `stop_gtfs_id` TEXT NOT NULL, `stop_order` INTEGER NOT NULL, PRIMARY KEY(`pattern_gtfs_id`, `stop_gtfs_id`, `stop_order`), FOREIGN KEY(`pattern_gtfs_id`) REFERENCES `mato_patterns`(`pattern_code`) ON UPDATE NO ACTION ON DELETE CASCADE )")
+
+
+ }
+
+
}
}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsFeed.kt
@@ -0,0 +1,50 @@
+/*
+ BusTO - Data components
+ Copyright (C) 2022 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.ColumnInfo
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = GtfsFeed.TABLE_NAME)
+data class GtfsFeed(
+ @PrimaryKey
+ @ColumnInfo(name = COL_GTFS_ID)
+ val gtfsId: String,
+): GtfsTable{
+ constructor(valuesByColumn: Map<String,String>) : this(
+ valuesByColumn[COL_GTFS_ID]!!,
+ )
+
+ companion object{
+ const val TABLE_NAME="gtfs_feeds"
+
+ const val COL_GTFS_ID="feed_id"
+
+
+ val COLUMNS = arrayOf(
+ COL_GTFS_ID,
+ )
+ const val CREATE_SQL =
+ "CREATE TABLE $TABLE_NAME ( $COL_GTFS_ID )"
+ }
+
+ override fun getColumns(): Array<String> {
+ return COLUMNS
+ }
+}
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsMode.kt
@@ -0,0 +1,19 @@
+package it.reyboz.bustorino.data.gtfs
+
+enum class GtfsMode(val intType: Int) {
+ TRAM(0),
+ SUBWAY(1),
+ RAIL(2),
+ BUS(3),
+ FERRY(4),
+ CABLE_TRAM(5),
+ GONDOLA(6),
+ FUNICULAR(7),
+ TROLLEYBUS(11),
+ MONORAIL(12);
+
+ companion object {
+ private val VALUES = values()
+ fun getByValue(value: Int) = VALUES.firstOrNull { it.intType == value }
+ }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
--- a/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
+++ b/src/it/reyboz/bustorino/data/gtfs/GtfsRoute.kt
@@ -23,26 +23,25 @@
@Entity(tableName=GtfsRoute.DB_TABLE)
data class GtfsRoute(
- @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
- val ID: String,
- @ColumnInfo(name = "agency_id")
+ @PrimaryKey @ColumnInfo(name = COL_ROUTE_ID)
+ val gtfsId: String,
+ @ColumnInfo(name = COL_AGENCY_ID)
val agencyID: String,
- @ColumnInfo(name = "route_short_name")
+ @ColumnInfo(name = "route_short_name")
val shortName: String,
- @ColumnInfo(name = "route_long_name")
+ @ColumnInfo(name = "route_long_name")
val longName: String,
- @ColumnInfo(name = "route_desc")
+ @ColumnInfo(name = "route_desc")
val description: String,
- @ColumnInfo(name ="route_type")
- val type: String,
+ @ColumnInfo(name = COL_MODE)
+ val mode: GtfsMode,
//@ColumnInfo(name ="route_url")
//val url: String,
- @ColumnInfo(name ="route_color")
+ @ColumnInfo(name = COL_COLOR)
val color: String,
- @ColumnInfo(name ="route_text_color")
+ @ColumnInfo(name = COL_TEXT_COLOR)
val textColor: String,
- @ColumnInfo(name = COL_SORT_ORDER)
- val sortOrder: Int
+
): GtfsTable {
constructor(valuesByColumn: Map<String,String>) : this(
@@ -51,18 +50,21 @@
valuesByColumn["route_short_name"]!!,
valuesByColumn["route_long_name"]!!,
valuesByColumn["route_desc"]!!,
- valuesByColumn["route_type"]!!,
- valuesByColumn["route_color"]!!,
- valuesByColumn["route_text_color"]!!,
- valuesByColumn[COL_SORT_ORDER]?.toInt()!!
+ valuesByColumn["route_type"]?.toInt()?.let { GtfsMode.getByValue(it) }!!,
+ valuesByColumn[COL_COLOR]!!,
+ valuesByColumn[COL_TEXT_COLOR]!!,
)
companion object {
const val DB_TABLE: String="routes_table"
const val COL_SORT_ORDER: String="route_sort_order"
+ const val COL_AGENCY_ID = "agency_id"
const val COL_ROUTE_ID = "route_id"
+ const val COL_MODE ="route_mode"
+ const val COL_COLOR="route_color"
+ const val COL_TEXT_COLOR="route_text_color"
val COLUMNS = arrayOf(COL_ROUTE_ID,
- "agency_id",
+ COL_AGENCY_ID,
"route_short_name",
"route_long_name",
"route_desc",
@@ -71,6 +73,8 @@
"route_text_color",
COL_SORT_ORDER
)
+
+ //const val CREATE_SQL = ""
}
override fun getColumns(): Array<String> {
diff --git a/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/data/gtfs/MatoPattern.kt
@@ -0,0 +1,140 @@
+/*
+ BusTO - Data components
+ Copyright (C) 2022 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.data.gtfs
+
+import androidx.room.*
+import it.reyboz.bustorino.backend.Stop
+
+@Entity(tableName = MatoPattern.TABLE_NAME,
+ foreignKeys = [
+ ForeignKey(entity = GtfsRoute::class,
+ parentColumns = [GtfsRoute.COL_ROUTE_ID],
+ childColumns = [MatoPattern.COL_ROUTE_ID],
+ onDelete = ForeignKey.CASCADE,
+ )
+ ]
+)
+data class MatoPattern(
+ @ColumnInfo(name= COL_NAME)
+ val name: String,
+ @ColumnInfo(name= COL_CODE)
+ @PrimaryKey
+ val code: String,
+ @ColumnInfo(name= COL_SEMANTIC_HASH)
+ val semanticHash: String,
+ @ColumnInfo(name= COL_DIRECTION_ID)
+ val directionId: Int,
+ @ColumnInfo(name= COL_ROUTE_ID)
+ val routeGtfsId: String,
+ @ColumnInfo(name= COL_HEADSIGN)
+ var headsign: String?,
+ @ColumnInfo(name= COL_GEOMETRY_POLY)
+ val patternGeometryPoly: String,
+ @ColumnInfo(name= COL_GEOMETRY_LENGTH)
+ val patternGeometryLength: Int,
+ @Ignore
+ val stopsGtfsIDs: ArrayList<String>
+
+):GtfsTable{
+
+ @Ignore
+ val servingStops= ArrayList<Stop>(4)
+ constructor(
+ name: String, code:String,
+ semanticHash: String, directionId: Int,
+ routeGtfsId: String, headsign: String?,
+ patternGeometryPoly: String, patternGeometryLength: Int
+ ): this(name, code, semanticHash, directionId, routeGtfsId, headsign, patternGeometryPoly, patternGeometryLength, ArrayList<String>(4))
+
+ companion object{
+ const val TABLE_NAME="mato_patterns"
+
+ const val COL_NAME="pattern_name"
+ const val COL_CODE="pattern_code"
+ const val COL_ROUTE_ID="pattern_route_id"
+ const val COL_SEMANTIC_HASH="pattern_hash"
+ const val COL_DIRECTION_ID="pattern_direction_id"
+ const val COL_HEADSIGN="pattern_headsign"
+ const val COL_GEOMETRY_POLY="pattern_polyline"
+ const val COL_GEOMETRY_LENGTH="pattern_polylength"
+
+ val COLUMNS = arrayOf(
+ COL_NAME,
+ COL_CODE,
+ COL_ROUTE_ID,
+ COL_SEMANTIC_HASH,
+ COL_DIRECTION_ID,
+ COL_HEADSIGN,
+ COL_GEOMETRY_POLY,
+ COL_GEOMETRY_LENGTH
+ )
+ }
+ override fun getColumns(): Array<String> {
+ return COLUMNS
+ }
+}
+
+//DO NOT USE EMBEDDED!!! -> copies all data
+
+@Entity(tableName=PatternStop.TABLE_NAME,
+ primaryKeys = [
+ PatternStop.COL_PATTERN_ID,
+ PatternStop.COL_STOP_GTFS,
+ PatternStop.COL_ORDER
+ ],
+ foreignKeys = [
+ ForeignKey(entity = MatoPattern::class,
+ parentColumns = [MatoPattern.COL_CODE],
+ childColumns = [PatternStop.COL_PATTERN_ID],
+ onDelete = ForeignKey.CASCADE
+ )
+ ]
+)
+data class PatternStop(
+ @ColumnInfo(name= COL_PATTERN_ID)
+ val patternId: String,
+ @ColumnInfo(name=COL_STOP_GTFS)
+ val stopGtfsId: String,
+ @ColumnInfo(name=COL_ORDER)
+ val order: Int,
+){
+ companion object{
+ const val TABLE_NAME="patterns_stops"
+
+ const val COL_PATTERN_ID="pattern_gtfs_id"
+ const val COL_STOP_GTFS="stop_gtfs_id"
+ const val COL_ORDER="stop_order"
+ }
+}
+
+data class MatoPatternWithStops(
+ @Embedded val pattern: MatoPattern,
+ @Relation(
+ parentColumn = MatoPattern.COL_CODE,
+ entityColumn = PatternStop.COL_PATTERN_ID,
+
+ )
+ var stopsIndices: List<PatternStop>)
+ {
+
+
+ init {
+ stopsIndices = stopsIndices.sortedBy { p-> p.order }
+
+ }
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
--- a/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
+++ b/src/it/reyboz/bustorino/fragments/FavoritesFragment.java
@@ -19,7 +19,6 @@
import android.app.AlertDialog;
import android.content.Context;
-import android.content.DialogInterface;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
@@ -44,9 +43,8 @@
import java.util.ArrayList;
import java.util.List;
-import com.google.android.material.floatingactionbutton.FloatingActionButton;
import it.reyboz.bustorino.*;
-import it.reyboz.bustorino.adapters.AdapterListener;
+import it.reyboz.bustorino.adapters.StopAdapterListener;
import it.reyboz.bustorino.adapters.StopRecyclerAdapter;
import it.reyboz.bustorino.backend.Stop;
import it.reyboz.bustorino.data.FavoritesViewModel;
@@ -64,11 +62,17 @@
public static final String FRAGMENT_TAG = "BusTOFavFragment";
- private final AdapterListener adapterListener = new AdapterListener() {
+ private final StopAdapterListener adapterListener = new StopAdapterListener() {
@Override
public void onTappedStop(Stop stop) {
mListener.requestArrivalsForStopID(stop.ID);
}
+
+ @Override
+ public boolean onLongPressOnStop(Stop stop) {
+ Log.d("BusTO-FavoritesFrag", "LongPressOnStop");
+ return true;
+ }
};
@@ -123,6 +127,7 @@
angeryBusImageView = root.findViewById(R.id.angeryBusImageView);
favoriteTipTextView = root.findViewById(R.id.favoriteTipTextView);
+ //register for the context menu
registerForContextMenu(favoriteRecyclerView);
FavoritesViewModel model = new ViewModelProvider(this).get(FavoritesViewModel.class);
@@ -150,12 +155,13 @@
}
/*
This method is apparently NOT CALLED ANYMORE
+ Called on Android 6
*/
@Override
public void onCreateContextMenu(@NonNull ContextMenu menu, @NonNull View v,
ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
- Log.d("Favorites Fragment", "Creating context menu on "+v);
+ Log.d("Favorites Fragment", "Creating context menu ");
if (v.getId() == R.id.favoritesRecyclerView) {
// if we aren't attached to activity, return null
if (getActivity()==null) return;
@@ -244,7 +250,7 @@
* redrwaing everything.
*/
// Show results
- favoriteRecyclerView.setAdapter(new StopRecyclerAdapter(busStops,adapterListener));
+ favoriteRecyclerView.setAdapter(new StopRecyclerAdapter(busStops,adapterListener, StopRecyclerAdapter.Use.FAVORITES));
}
public void showBusStopUsernameInputDialog(final Stop busStop) {
diff --git a/src/it/reyboz/bustorino/fragments/FragmentKind.java b/src/it/reyboz/bustorino/fragments/FragmentKind.java
--- a/src/it/reyboz/bustorino/fragments/FragmentKind.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentKind.java
@@ -18,5 +18,6 @@
package it.reyboz.bustorino.fragments;
public enum FragmentKind {
- STOPS,ARRIVALS,FAVORITES,NEARBY_STOPS,NEARBY_ARRIVALS, MAP, MAIN_SCREEN_FRAGMENT
+ STOPS,ARRIVALS,FAVORITES,NEARBY_STOPS,NEARBY_ARRIVALS, MAP, MAIN_SCREEN_FRAGMENT,
+ LINES
}
diff --git a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
--- a/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
+++ b/src/it/reyboz/bustorino/fragments/FragmentListenerMain.java
@@ -17,6 +17,9 @@
*/
package it.reyboz.bustorino.fragments;
+/**
+ * This interface is for the subfragments
+ */
public interface FragmentListenerMain extends CommonFragmentListener {
void toggleSpinner(boolean state);
diff --git a/src/it/reyboz/bustorino/fragments/LinesFragment.kt b/src/it/reyboz/bustorino/fragments/LinesFragment.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/LinesFragment.kt
@@ -0,0 +1,289 @@
+/*
+ BusTO - Fragments components
+ Copyright (C) 2022 Fabio Mazza
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package it.reyboz.bustorino.fragments
+
+import android.content.Context
+import android.os.Bundle
+import android.util.Log
+import android.view.*
+import android.widget.*
+import android.widget.AdapterView.INVALID_POSITION
+import android.widget.AdapterView.OnItemSelectedListener
+import androidx.lifecycle.ViewModelProvider
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import it.reyboz.bustorino.R
+import it.reyboz.bustorino.adapters.NameCapitalize
+import it.reyboz.bustorino.adapters.StopAdapterListener
+import it.reyboz.bustorino.adapters.StopRecyclerAdapter
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
+import it.reyboz.bustorino.data.gtfs.PatternStop
+import it.reyboz.bustorino.util.LinesNameSorter
+
+class LinesFragment : ScreenBaseFragment() {
+
+ companion object {
+ fun newInstance(){
+ val fragment = LinesFragment()
+ }
+ const val DEBUG_TAG="BusTO-LinesFragment"
+ const val FRAGMENT_TAG="LinesFragment"
+ }
+
+
+ private lateinit var viewModel: LinesViewModel
+
+ private lateinit var linesSpinner: Spinner
+ private lateinit var patternsSpinner: Spinner
+
+ private lateinit var currentRoutes: List<GtfsRoute>
+ private lateinit var currentPatterns: List<MatoPatternWithStops>
+ private lateinit var currentPatternStops: List<PatternStop>
+
+ private lateinit var routeDescriptionTextView: TextView
+ private lateinit var stopsRecyclerView: RecyclerView
+
+ private var linesAdapter: ArrayAdapter<String>? = null
+ private var patternsAdapter: ArrayAdapter<String>? = null
+ private var mListener: CommonFragmentListener? = null
+
+ private val linesNameSorter = LinesNameSorter()
+ private val linesComparator = Comparator<GtfsRoute> { a,b ->
+ return@Comparator linesNameSorter.compare(a.shortName, b.shortName)
+ }
+
+ private val adapterListener = object : StopAdapterListener {
+ override fun onTappedStop(stop: Stop?) {
+ //var r = ""
+ //stop?.let { r= it.stopDisplayName.toString() }
+ Toast.makeText(context,R.string.long_press_for_options,Toast.LENGTH_SHORT).show()
+ }
+
+ override fun onLongPressOnStop(stop: Stop?): Boolean {
+ Log.d("BusTO-LinesFrag", "LongPressOnStop")
+ return true
+ }
+ }
+
+
+ override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?): View? {
+ val rootView = inflater.inflate(R.layout.fragment_lines, container, false)
+
+ linesSpinner = rootView.findViewById(R.id.linesSpinner)
+ patternsSpinner = rootView.findViewById(R.id.patternsSpinner)
+
+ routeDescriptionTextView = rootView.findViewById(R.id.routeDescriptionTextView)
+ stopsRecyclerView = rootView.findViewById(R.id.patternStopsRecyclerView)
+
+ /*
+ Stop busStop = (Stop) parent.getItemAtPosition(position);
+
+ if(mListener!=null){
+ mListener.requestArrivalsForStopID(busStop.ID);
+ }
+
+ });
+
+ */
+ val llManager = LinearLayoutManager(context)
+ llManager.orientation = LinearLayoutManager.VERTICAL
+
+ stopsRecyclerView.layoutManager = llManager
+ //allow the context menu to be opened
+ registerForContextMenu(stopsRecyclerView)
+
+ if(context!=null) {
+ patternsAdapter = ArrayAdapter(context!!, android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
+ patternsSpinner.adapter = patternsAdapter
+ linesAdapter = ArrayAdapter(context!!, android.R.layout.simple_spinner_dropdown_item, ArrayList<String>())
+ linesSpinner.adapter = linesAdapter
+
+
+ linesSpinner.onItemSelectedListener = object: OnItemSelectedListener{
+ override fun onItemSelected(p0: AdapterView<*>?, p1: View?, pos: Int, p3: Long) {
+ val selRoute = currentRoutes.get(pos)
+
+ routeDescriptionTextView.text = selRoute.longName
+
+ viewModel.setRouteIDQuery(selRoute.gtfsId)
+
+ }
+
+ override fun onNothingSelected(p0: AdapterView<*>?) {
+
+ }
+ }
+
+ patternsSpinner.onItemSelectedListener = object : OnItemSelectedListener{
+ override fun onItemSelected(p0: AdapterView<*>?, p1: View?, position: Int, p3: Long) {
+ val patternWithStops = currentPatterns.get(position)
+
+ setPatternAndReqStops(patternWithStops)
+
+ }
+
+ override fun onNothingSelected(p0: AdapterView<*>?) {
+ }
+ }
+ }
+
+ return rootView
+ }
+
+ override fun onAttach(context: Context) {
+ super.onAttach(context)
+ if(context is CommonFragmentListener)
+ mListener = context
+ else throw RuntimeException(context.toString()
+ + " must implement CommonFragmentListener")
+ }
+
+ override fun onResume() {
+ super.onResume()
+ mListener?.readyGUIfor(FragmentKind.LINES)
+ }
+
+ override fun onActivityCreated(savedInstanceState: Bundle?) {
+ super.onActivityCreated(savedInstanceState)
+ viewModel = ViewModelProvider(this).get(LinesViewModel::class.java)
+
+ //val lines = viewModel.();
+ viewModel.routesGTTLiveData.observe(this) {
+ setRoutes(it)
+ }
+
+ viewModel.patternsWithStopsByRouteLiveData.observe(this){
+ patterns ->
+ run {
+ currentPatterns = patterns.sortedBy { p->"${p.pattern.directionId} - ${p.pattern.headsign}" }
+ patternsAdapter?.let {
+ it.clear()
+ it.addAll(currentPatterns.map { p->"${p.pattern.directionId} - ${p.pattern.headsign}" })
+ it.notifyDataSetChanged()
+ }
+
+ val pos = patternsSpinner.selectedItemPosition
+ if(pos!= INVALID_POSITION){
+ setPatternAndReqStops(currentPatterns[pos])
+ }
+
+ }
+ }
+
+ viewModel.stopsForPatternLiveData.observe(this){stops->
+ Log.d("BusTO-LinesFragment", "Setting stops from DB")
+ setCurrentStops(stops)
+ }
+
+ }
+
+ override fun getBaseViewForSnackBar(): View? {
+ return null
+ }
+
+ private fun setRoutes(routes: List<GtfsRoute>){
+ currentRoutes = routes.sortedWith<GtfsRoute>(linesComparator)
+
+ linesAdapter?.clear()
+ linesAdapter?.addAll(currentRoutes.map { r -> r.shortName })
+ linesAdapter?.notifyDataSetChanged()
+ }
+
+ private fun setCurrentStops(stops: List<Stop>){
+
+ val orderBy = currentPatternStops.withIndex().associate{it.value.stopGtfsId to it.index}
+ val stopsSorted = stops.sortedBy { s -> orderBy[s.gtfsID] }
+ val numStops = stopsSorted.size
+ Log.d(DEBUG_TAG, "RecyclerView adapter is: ${stopsRecyclerView.adapter}")
+
+ var setNewAdapter = true;
+ if(stopsRecyclerView.adapter is StopRecyclerAdapter){
+ val adapter = stopsRecyclerView.adapter as StopRecyclerAdapter
+ if(adapter.stops.size == stopsSorted.size && (adapter.stops.get(0).gtfsID == stopsSorted.get(0).gtfsID)
+ && (adapter.stops.get(numStops-1).gtfsID == stopsSorted.get(numStops-1).gtfsID)
+ ){
+ Log.d(DEBUG_TAG, "Found same stops on recyclerview")
+ setNewAdapter = false
+ }
+ /*else {
+ Log.d(DEBUG_TAG, "Found adapter on recyclerview, but not the same stops")
+ adapter.stops = stopsSorted
+ adapter.notifyDataSetChanged()
+ }*/
+
+ }
+ if(setNewAdapter){
+ stopsRecyclerView.adapter = StopRecyclerAdapter(
+ stopsSorted, adapterListener, StopRecyclerAdapter.Use.LINES,
+ NameCapitalize.FIRST
+ )
+ }
+
+
+ }
+
+ private fun setPatternAndReqStops(patternWithStops: MatoPatternWithStops){
+ Log.d(DEBUG_TAG, "Requesting stops for pattern ${patternWithStops.pattern.code}")
+ currentPatternStops = patternWithStops.stopsIndices.sortedBy { i-> i.order }
+
+ viewModel.requestStopsForPatternWithStops(patternWithStops)
+ }
+
+ override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
+ super.onCreateContextMenu(menu, v, menuInfo)
+ Log.d("BusTO-LinesFragment", "Creating context menu ")
+
+
+ if (v.id == R.id.patternStopsRecyclerView) {
+ // if we aren't attached to activity, return null
+ if (activity == null) return
+ val inflater = activity!!.menuInflater
+ inflater.inflate(R.menu.menu_line_item, menu)
+
+
+ }
+ }
+
+
+ override fun onContextItemSelected(item: MenuItem): Boolean {
+ val info = item.getMenuInfo();
+
+ if (stopsRecyclerView.getAdapter() !is StopRecyclerAdapter) return false
+ val adapter =stopsRecyclerView.adapter as StopRecyclerAdapter
+ val stop = adapter.stops.get(adapter.getPosition())
+
+ val acId = item.itemId
+ if(acId == R.id.action_view_on_map){
+ // view on the map
+ if ((stop.latitude == null) or (stop.longitude == null) or (mListener == null) ) {
+ Toast.makeText(context, R.string.cannot_show_on_map_no_position, Toast.LENGTH_SHORT).show()
+ return true
+ }
+ mListener!!.showMapCenteredOnStop(stop)
+ return true
+ } else if (acId == R.id.action_show_arrivals){
+ mListener?.requestArrivalsForStopID(stop.ID);
+ return true
+ }
+ return false
+ }
+
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/LinesViewModel.kt b/src/it/reyboz/bustorino/fragments/LinesViewModel.kt
new file mode 100644
--- /dev/null
+++ b/src/it/reyboz/bustorino/fragments/LinesViewModel.kt
@@ -0,0 +1,77 @@
+package it.reyboz.bustorino.fragments
+
+import android.app.Application
+import android.util.Log
+import androidx.lifecycle.*
+import it.reyboz.bustorino.backend.Stop
+import it.reyboz.bustorino.data.GtfsRepository
+import it.reyboz.bustorino.data.NextGenDB
+import it.reyboz.bustorino.data.OldDataRepository
+import it.reyboz.bustorino.data.gtfs.GtfsDatabase
+import it.reyboz.bustorino.data.gtfs.GtfsRoute
+import it.reyboz.bustorino.data.gtfs.MatoPatternWithStops
+import java.util.concurrent.Executors
+
+class LinesViewModel(application: Application) : AndroidViewModel(application) {
+
+ private val gtfsRepo: GtfsRepository
+ private val oldRepo: OldDataRepository
+ //val patternsByRouteLiveData: LiveData<List<MatoPattern>>
+
+ private val routeIDToSearch = MutableLiveData<String>()
+
+ val stopsForPatternLiveData = MutableLiveData<List<Stop>>()
+ val executor = Executors.newFixedThreadPool(2)
+
+ init {
+ val gtfsDao = GtfsDatabase.getGtfsDatabase(application).gtfsDao()
+ gtfsRepo = GtfsRepository(gtfsDao)
+
+ oldRepo = OldDataRepository(executor, NextGenDB.getInstance(application))
+
+ }
+
+ val routesGTTLiveData: LiveData<List<GtfsRoute>> by lazy{
+ gtfsRepo.getLinesLiveDataForFeed("gtt")
+ }
+ val patternsWithStopsByRouteLiveData = routeIDToSearch.switchMap {
+ gtfsRepo.getPatternsWithStopsForRouteID(it)
+
+ }
+ val routesName: LiveData<List<String>> = Transformations.map(routesGTTLiveData) {
+ it.map { route -> route.longName }
+ }
+
+ fun setRouteIDQuery(routeID: String){
+ routeIDToSearch.value = routeID
+ }
+
+ fun requestStopsForGTFSIDs(gtfsIDs: List<String>){
+ oldRepo.requestStopsWithGtfsIDs(gtfsIDs) {
+ if (it.isSuccess) {
+ stopsForPatternLiveData.postValue(it.result)
+ } else {
+ Log.e("BusTO-LinesVM", "Got error on callback with stops for gtfsID")
+ it.exception?.printStackTrace()
+ }
+ }
+ }
+
+ fun requestStopsForPatternWithStops(patternStops: MatoPatternWithStops){
+ val gtfsIDs = ArrayList<String>()
+ for(pat in patternStops.stopsIndices){
+ gtfsIDs.add(pat.stopGtfsId)
+ }
+ requestStopsForGTFSIDs(gtfsIDs)
+ }
+
+
+ /*fun getLinesGTT(): MutableLiveData<List<GtfsRoute>> {
+ val routesData = MutableLiveData<List<GtfsRoute>>()
+ viewModelScope.launch {
+ val routes=gtfsRepo.getLinesForFeed("gtt")
+ routesData.postValue(routes)
+ }
+ return routesData
+ }*/
+}
\ No newline at end of file
diff --git a/src/it/reyboz/bustorino/fragments/MapFragment.java b/src/it/reyboz/bustorino/fragments/MapFragment.java
--- a/src/it/reyboz/bustorino/fragments/MapFragment.java
+++ b/src/it/reyboz/bustorino/fragments/MapFragment.java
@@ -610,7 +610,7 @@
final BoundingBoxLimit limit = limits[0];
//Log.d(DEBUG_TAG, "Async Stop Fetcher started working");
- NextGenDB dbHelper = new NextGenDB(fragmentWeakReference.get().getContext());
+ NextGenDB dbHelper = NextGenDB.getInstance(fragmentWeakReference.get().getContext());
ArrayList<Stop> stops = dbHelper.queryAllInsideMapView(limit.latitFrom, limit.latitTo,
limit.longFrom, limit.latitTo);
dbHelper.close();
diff --git a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
--- a/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/NearbyStopsFragment.java
@@ -28,6 +28,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
import androidx.loader.app.LoaderManager;
import androidx.loader.content.CursorLoader;
import androidx.loader.content.Loader;
@@ -36,6 +37,8 @@
import androidx.appcompat.widget.AppCompatButton;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import androidx.work.WorkInfo;
+
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -49,6 +52,9 @@
import it.reyboz.bustorino.adapters.ArrivalsStopAdapter;
import it.reyboz.bustorino.backend.*;
import it.reyboz.bustorino.backend.FiveTAPIFetcher.QueryType;
+import it.reyboz.bustorino.backend.mato.MapiArrivalRequest;
+import it.reyboz.bustorino.data.DatabaseUpdate;
+import it.reyboz.bustorino.data.NextGenDB;
import it.reyboz.bustorino.middleware.AppLocationManager;
import it.reyboz.bustorino.data.AppDataProvider;
import it.reyboz.bustorino.data.NextGenDB.Contract.*;
@@ -62,8 +68,7 @@
private FragmentListenerMain mListener;
private FragmentLocationListener fragmentLocationListener;
- private final String[] PROJECTION = {StopsTable.COL_ID,StopsTable.COL_LAT,StopsTable.COL_LONG,
- StopsTable.COL_NAME,StopsTable.COL_TYPE,StopsTable.COL_LINES_STOPPING};
+
private final static String DEBUG_TAG = "NearbyStopsFragment";
private final static String FRAGMENT_TYPE_KEY = "FragmentType";
public final static int TYPE_STOPS = 19, TYPE_ARRIVALS = 20;
@@ -78,7 +83,6 @@
private SquareStopAdapter dataAdapter;
private AutoFitGridLayoutManager gridLayoutManager;
- boolean canStartDBQuery = true;
private Location lastReceivedLocation = null;
private ProgressBar circlingProgressBar,flatProgressBar;
private int distance;
@@ -100,6 +104,10 @@
private ArrivalsManager arrivalsManager = null;
private ArrivalsStopAdapter arrivalsStopAdapter = null;
+ private boolean dbUpdateRunning = false;
+
+ private ArrayList<Stop> currentNearbyStops = new ArrayList<>();
+
public NearbyStopsFragment() {
// Required empty public constructor
}
@@ -129,10 +137,10 @@
}
locManager = AppLocationManager.getInstance(getContext());
fragmentLocationListener = new FragmentLocationListener(this);
- globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences),Context.MODE_PRIVATE);
-
-
- globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
+ if (getContext()!=null) {
+ globalSharedPref = getContext().getSharedPreferences(getString(R.string.mainSharedPreferences), Context.MODE_PRIVATE);
+ globalSharedPref.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
+ }
}
@@ -153,51 +161,28 @@
titleTextView = root.findViewById(R.id.titleTextView);
switchButton = root.findViewById(R.id.switchButton);
- preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
+ scrollListener = new CommonScrollListener(mListener,false);
+ switchButton.setOnClickListener(v -> switchFragmentType());
+ Log.d(DEBUG_TAG, "onCreateView");
+
+ DatabaseUpdate.watchUpdateWorkStatus(getContext(), this, new Observer<List<WorkInfo>>() {
@Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- Log.d(DEBUG_TAG,"Key "+key+" was changed");
- if(key.equals(getString(R.string.databaseUpdatingPref))){
- if(!sharedPreferences.getBoolean(getString(R.string.databaseUpdatingPref),true)){
- canStartDBQuery = true;
- Log.d(DEBUG_TAG,"The database has finished updating, can start update now");
- }
+ public void onChanged(List<WorkInfo> workInfos) {
+ if(workInfos.isEmpty()) return;
+
+ WorkInfo wi = workInfos.get(0);
+ if (wi.getState() == WorkInfo.State.RUNNING && locManager.isRequesterRegistered(fragmentLocationListener)) {
+ locManager.removeLocationRequestFor(fragmentLocationListener);
+ dbUpdateRunning = true;
+ } else if(!locManager.isRequesterRegistered(fragmentLocationListener)){
+ locManager.addLocationRequestFor(fragmentLocationListener);
+ dbUpdateRunning = false;
}
}
- };
- scrollListener = new CommonScrollListener(mListener,false);
- switchButton.setOnClickListener(v -> {
- switchFragmentType();
});
- Log.d(DEBUG_TAG, "onCreateView");
return root;
}
- protected ArrayList<Stop> createStopListFromCursor(Cursor data){
- ArrayList<Stop> stopList = new ArrayList<>();
- final int col_id = data.getColumnIndex(StopsTable.COL_ID);
- final int latInd = data.getColumnIndex(StopsTable.COL_LAT);
- final int lonInd = data.getColumnIndex(StopsTable.COL_LONG);
- final int nameindex = data.getColumnIndex(StopsTable.COL_NAME);
- final int typeIndex = data.getColumnIndex(StopsTable.COL_TYPE);
- final int linesIndex = data.getColumnIndex(StopsTable.COL_LINES_STOPPING);
-
- data.moveToFirst();
- for(int i=0; i<data.getCount();i++){
- String[] routes = data.getString(linesIndex).split(",");
-
- stopList.add(new Stop(data.getString(col_id),data.getString(nameindex),null,null,
- Route.Type.fromCode(data.getInt(typeIndex)),
- Arrays.asList(routes), //the routes should be compact, not normalized yet
- data.getDouble(latInd),data.getDouble(lonInd)
- )
- );
- //Log.d("NearbyStopsFragment","Got stop with id "+data.getString(col_id)+
- //" and name "+data.getString(nameindex));
- data.moveToNext();
- }
- return stopList;
- }
/**
* Use this method to set the fragment type
@@ -220,7 +205,7 @@
@Override
- public void onAttach(Context context) {
+ public void onAttach(@NonNull Context context) {
super.onAttach(context);
/// TODO: RISOLVERE PROBLEMA: il context qui e' l'Activity non il Fragment
if (context instanceof FragmentListenerMain) {
@@ -235,7 +220,6 @@
@Override
public void onPause() {
super.onPause();
- canStartDBQuery = false;
gridRecyclerView.setAdapter(null);
locManager.removeLocationRequestFor(fragmentLocationListener);
@@ -245,9 +229,9 @@
@Override
public void onResume() {
super.onResume();
- canStartDBQuery = !globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
try{
- if(canStartDBQuery) locManager.addLocationRequestFor(fragmentLocationListener);
+ if(!dbUpdateRunning && !locManager.isRequesterRegistered(fragmentLocationListener))
+ locManager.addLocationRequestFor(fragmentLocationListener);
} catch (SecurityException ex){
//ignored
//try another location provider
@@ -314,14 +298,15 @@
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
//BUILD URI
- lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
+ if (args!=null)
+ lastReceivedLocation = args.getParcelable(BUNDLE_LOCATION);
Uri.Builder builder = new Uri.Builder();
builder.scheme("content").authority(AppDataProvider.AUTHORITY)
.appendPath("stops").appendPath("location")
.appendPath(String.valueOf(lastReceivedLocation.getLatitude()))
.appendPath(String.valueOf(lastReceivedLocation.getLongitude()))
.appendPath(String.valueOf(distance)); //distance
- CursorLoader cl = new CursorLoader(getContext(),builder.build(),PROJECTION,null,null,null);
+ CursorLoader cl = new CursorLoader(getContext(),builder.build(),NextGenDB.QUERY_COLUMN_stops_all,null,null,null);
cl.setUpdateThrottle(2000);
return cl;
}
@@ -331,50 +316,58 @@
public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor cursor) {
if (0 > MAX_DISTANCE) throw new AssertionError();
//Cursor might be null
- if(cursor==null){
- Log.e(DEBUG_TAG,"Null cursor, something really wrong happened");
+ if (cursor == null) {
+ Log.e(DEBUG_TAG, "Null cursor, something really wrong happened");
return;
}
- Log.d(DEBUG_TAG, "Num stops found: "+cursor.getCount()+", Current distance: "+distance);
+ Log.d(DEBUG_TAG, "Num stops found: " + cursor.getCount() + ", Current distance: " + distance);
- if(!isDBUpdating() && (cursor.getCount()<MIN_NUM_STOPS && distance<=MAX_DISTANCE)){
- distance = distance*2;
+ if (!dbUpdateRunning && (cursor.getCount() < MIN_NUM_STOPS && distance <= MAX_DISTANCE)) {
+ distance = distance * 2;
Bundle d = new Bundle();
- d.putParcelable(BUNDLE_LOCATION,lastReceivedLocation);
- getLoaderManager().restartLoader(LOADER_ID,d,this);
+ d.putParcelable(BUNDLE_LOCATION, lastReceivedLocation);
+ getLoaderManager().restartLoader(LOADER_ID, d, this);
//Log.d(DEBUG_TAG, "Doubling distance now!");
return;
}
- Log.d("LoadFromCursor","Number of nearby stops: "+cursor.getCount());
+ Log.d("LoadFromCursor", "Number of nearby stops: " + cursor.getCount());
////////
+ if(cursor.getCount()>0)
+ currentNearbyStops = NextGenDB.getStopsFromCursorAllFields(cursor);
- if(cursor.getCount()>0) {
- ArrayList<Stop> stopList = createStopListFromCursor(cursor);
- double minDistance = Double.POSITIVE_INFINITY;
- for(Stop s: stopList){
- minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation));
- }
+ showCurrentStops();
+ }
+ /**
+ * Display the stops, or run new set of requests for arrivals
+ */
+ private void showCurrentStops(){
+ if (currentNearbyStops.isEmpty()) {
+ setNoStopsLayout();
+ return;
+ }
- //quick trial to hopefully always get the stops in the correct order
- Collections.sort(stopList,new StopSorterByDistance(lastReceivedLocation));
- switch (fragment_type){
- case TYPE_STOPS:
- showStopsInRecycler(stopList);
- break;
- case TYPE_ARRIVALS:
- arrivalsManager = new ArrivalsManager(stopList);
- flatProgressBar.setVisibility(View.VISIBLE);
- flatProgressBar.setProgress(0);
- flatProgressBar.setIndeterminate(false);
- //for the moment, be satisfied with only one location
- //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
- break;
- default:
- }
+ double minDistance = Double.POSITIVE_INFINITY;
+ for(Stop s: currentNearbyStops){
+ minDistance = Math.min(minDistance, s.getDistanceFromLocation(lastReceivedLocation));
+ }
- } else {
- setNoStopsLayout();
+
+ //quick trial to hopefully always get the stops in the correct order
+ Collections.sort(currentNearbyStops,new StopSorterByDistance(lastReceivedLocation));
+ switch (fragment_type){
+ case TYPE_STOPS:
+ showStopsInRecycler(currentNearbyStops);
+ break;
+ case TYPE_ARRIVALS:
+ arrivalsManager = new ArrivalsManager(currentNearbyStops);
+ flatProgressBar.setVisibility(View.VISIBLE);
+ flatProgressBar.setProgress(0);
+ flatProgressBar.setIndeterminate(false);
+ //for the moment, be satisfied with only one location
+ //AppLocationManager.getInstance(getContext()).removeLocationRequestFor(fragmentLocationListener);
+ break;
+ default:
}
}
@@ -411,15 +404,12 @@
gridRecyclerView.setAdapter(arrivalsStopAdapter);
}
fragmentLocationListener.lastUpdateTime = -1;
- locManager.removeLocationRequestFor(fragmentLocationListener);
- locManager.addLocationRequestFor(fragmentLocationListener);
+ //locManager.removeLocationRequestFor(fragmentLocationListener);
+ //locManager.addLocationRequestFor(fragmentLocationListener);
+ showCurrentStops();
}
//useful methods
- protected boolean isDBUpdating(){
- return globalSharedPref.getBoolean(getString(R.string.databaseUpdatingPref),false);
- }
-
/////// GUI METHODS ////////
private void showStopsInRecycler(List<Stop> stops){
@@ -454,7 +444,8 @@
if(p.queryAllRoutes().size() == 0) continue;
for(Route r: p.queryAllRoutes()){
//if there are no routes, should not do anything
- routesPairList.add(new Pair<>(p,r));
+ if (r.passaggi != null && !r.passaggi.isEmpty())
+ routesPairList.add(new Pair<>(p,r));
}
}
if (getContext()==null){
@@ -493,31 +484,30 @@
messageTextView.setVisibility(View.GONE);
}
- class ArrivalsManager implements FiveTAPIVolleyRequest.ResponseListener, Response.ErrorListener{
- final HashMap<String,Palina> mStops;
- final Map<String,List<Route>> routesToAdd = new HashMap<>();
+ class ArrivalsManager implements Response.Listener<Palina>, Response.ErrorListener{
+ final HashMap<String,Palina> palinasDone = new HashMap<>();
+ //final Map<String,List<Route>> routesToAdd = new HashMap<>();
final static String REQUEST_TAG = "NearbyArrivals";
- private final QueryType[] types = {QueryType.ARRIVALS,QueryType.DETAILS};
final NetworkVolleyManager volleyManager;
int activeRequestCount = 0,reqErrorCount = 0, reqSuccessCount=0;
ArrivalsManager(List<Stop> stops){
- mStops = new HashMap<>();
volleyManager = NetworkVolleyManager.getInstance(getContext());
int MAX_ARRIVAL_STOPS = 35;
+ Date currentDate = new Date();
+ int timeRange = 3600;
+ int departures = 10;
+ int numreq = 0;
for(Stop s: stops.subList(0,Math.min(stops.size(), MAX_ARRIVAL_STOPS))){
- mStops.put(s.ID,new Palina(s));
- for(QueryType t: types) {
- final FiveTAPIVolleyRequest req = FiveTAPIVolleyRequest.getNewRequest(t, s.ID, this, this);
- if (req != null) {
- req.setTag(REQUEST_TAG);
- volleyManager.addToRequestQueue(req);
- activeRequestCount++;
- }
- }
+
+ final MapiArrivalRequest req = new MapiArrivalRequest(s.ID, currentDate, timeRange, departures, this, this);
+ req.setTag(REQUEST_TAG);
+ volleyManager.addToRequestQueue(req);
+ activeRequestCount++;
+ numreq++;
}
- flatProgressBar.setMax(activeRequestCount);
+ flatProgressBar.setMax(numreq);
}
@@ -546,40 +536,19 @@
}
@Override
- public void onResponse(Palina result, QueryType type) {
+ public void onResponse(Palina result) {
//counter for requests
activeRequestCount--;
reqSuccessCount++;
-
-
- final Palina palinaInMap = mStops.get(result.ID);
+ //final Palina palinaInMap = palinasDone.get(result.ID);
//palina cannot be null here
//sorry for the brutal crash when it happens
- if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
- //necessary to split the Arrivals and Details cases
- switch (type){
- case ARRIVALS:
- palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
- final List<Route> possibleRoutes = routesToAdd.get(result.ID);
- if(possibleRoutes!=null) {
- palinaInMap.addInfoFromRoutes(possibleRoutes);
- routesToAdd.remove(result.ID);
- }
- break;
- case DETAILS:
- if(palinaInMap.queryAllRoutes().size()>0){
- //merge the branches
- palinaInMap.addInfoFromRoutes(result.queryAllRoutes());
- } else {
- routesToAdd.put(result.ID,result.queryAllRoutes());
- }
- break;
- default:
- throw new IllegalArgumentException("Wrong QueryType in onResponse");
- }
-
+ //if(palinaInMap == null) throw new IllegalStateException("Cannot get the palina from the map");
+ //add the palina to the successful one
+ //TODO: Avoid redoing everything every time a new Result arrives
+ palinasDone.put(result.ID, result);
final ArrayList<Palina> outList = new ArrayList<>();
- for(Palina p: mStops.values()){
+ for(Palina p: palinasDone.values()){
final List<Route> routes = p.queryAllRoutes();
if(routes!=null && routes.size()>0) outList.add(p);
}
@@ -613,14 +582,14 @@
public void onLocationChanged(Location location) {
//set adapter
float accuracy = location.getAccuracy();
- if(accuracy<60 && canStartDBQuery) {
+ if(accuracy<60 && !dbUpdateRunning) {
distance = 20;
final Bundle msgBundle = new Bundle();
msgBundle.putParcelable(BUNDLE_LOCATION,location);
getLoaderManager().restartLoader(LOADER_ID,msgBundle,callbacks);
}
lastUpdateTime = System.currentTimeMillis();
- Log.d("BusTO:NearPositListen","can start loader "+ canStartDBQuery);
+ Log.d("BusTO:NearPositListen","can start loader "+ !dbUpdateRunning);
}
@Override
diff --git a/src/it/reyboz/bustorino/fragments/SettingsFragment.java b/src/it/reyboz/bustorino/fragments/SettingsFragment.java
--- a/src/it/reyboz/bustorino/fragments/SettingsFragment.java
+++ b/src/it/reyboz/bustorino/fragments/SettingsFragment.java
@@ -25,10 +25,13 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.*;
+import androidx.room.Database;
import it.reyboz.bustorino.R;
+import it.reyboz.bustorino.data.DatabaseUpdate;
import java.lang.ref.WeakReference;
@@ -63,6 +66,25 @@
//ListPreference preference = findPreference(R.string.arrival_times)
+ Preference dbUpdateNow = findPreference("pref_db_update_now");
+ if (dbUpdateNow!=null)
+ dbUpdateNow.setOnPreferenceClickListener(
+ new Preference.OnPreferenceClickListener() {
+ @Override
+ public boolean onPreferenceClick(@NonNull Preference preference) {
+ //trigger update
+ if(getContext()!=null) {
+ DatabaseUpdate.requestDBUpdateWithWork(getContext().getApplicationContext(), true, true);
+ Toast.makeText(getContext(),R.string.requesting_db_update,Toast.LENGTH_SHORT).show();
+ return true;
+ }
+ return false;
+ }
+ }
+ );
+ else {
+ Log.e("BusTO-Preferences", "Cannot find db update preference");
+ }
}
diff --git a/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java b/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
--- a/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
+++ b/src/it/reyboz/bustorino/middleware/AsyncArrivalsSearcher.java
@@ -250,7 +250,7 @@
@Override
public void run() {
- final NextGenDB nextGenDB = new NextGenDB(context);
+ final NextGenDB nextGenDB = NextGenDB.getInstance(context);
//ContentValues[] values = new ContentValues[routesToInsert.size()];
ArrayList<ContentValues> branchesValues = new ArrayList<>(routesToInsert.size()*4);
ArrayList<ContentValues> connectionsVals = new ArrayList<>(routesToInsert.size()*4);
diff --git a/src/it/reyboz/bustorino/util/LinesNameSorter.java b/src/it/reyboz/bustorino/util/LinesNameSorter.java
--- a/src/it/reyboz/bustorino/util/LinesNameSorter.java
+++ b/src/it/reyboz/bustorino/util/LinesNameSorter.java
@@ -23,19 +23,93 @@
public class LinesNameSorter implements Comparator<String> {
@Override
public int compare(String name1, String name2) {
+ name1 = name1.trim();
+ name2 = name2.trim();
+ /*
if(name1.length()>name2.length()) return 1;
if(name1.length()==name2.length()) {
+
+
try{
int num1 = Integer.parseInt(name1.trim());
int num2 = Integer.parseInt(name2.trim());
return num1-num2;
} catch (NumberFormatException ex){
//Log.d("BUSTO Compare lines","Cannot compare lines "+name1+" and "+name2);
+ //return name1.compareTo(name2);
+ //One of them is not a line
+ String trim1 = name1.substring(0, name1.length() - 1).trim();
+ String trim2 = name2.substring(0, name2.length()-1).trim();
+ if(isInteger(trim1)){ //cut away the last part
+ //this means it's a line
+ return compare(trim1, name2);
+ } else if(isInteger(trim2)){
+ return compare(name1,trim2);
+ }
+ return name1.compareTo(name2);
+ }
+ }**/
+ //One of them is not
+ int num1 = -1;
+ if(isInteger(name1)) num1 = Integer.parseInt(name1);
+ int num2 = -1;
+ if (isInteger(name2)) num2 = Integer.parseInt(name2);
+
+ if(num1 >= 0 && num2 >=0){
+ //we're very happy
+ return (num1-num2)*10;
+ } else if (num1>=0) {
+ //name2 is not fully integer
+ final String name2sub = name2.substring(0, name2.length()-1).trim();
+ char lastchar = name2.charAt(name2.length()-1);
+ if(isInteger(name2sub)){
+ num2 = Integer.parseInt(name2sub);
+ int diff = (num1-num2)*10;
+ return diff - incrementFromLastChar(lastchar);
+ } else{
+ //failed
+ return name1.compareTo(name2);
+ }
+ } else if (num2>=0) {
+ //name1 is not fully integer
+ final String name1sub = name1.substring(0, name1.length()-1).trim();
+ char lastchar = name1.charAt(name1.length()-1);
+ if (isInteger(name1sub)){
+ num1 = Integer.parseInt(name1sub);
+ int diff = (num1-num2)*10;
+ return diff + incrementFromLastChar(lastchar);
+ } else {
return name1.compareTo(name2);
}
}
- return -1;
+ //last case
+ return name1.compareTo(name2);
}
+
+ public static boolean isInteger(String strNum) {
+ if (strNum == null) {
+ return false;
+ }
+ try {
+ int d = Integer.parseInt(strNum);
+ } catch (NumberFormatException nfe) {
+ return false;
+ }
+ return true;
+ }
+ private static int incrementFromLastChar(char lastchar){
+ switch (lastchar){
+ case 'B':
+ case 'b':
+ case '/':
+ return 1;
+ case 'n':
+ case 'N':
+ return 3;
+ default:
+ return 6;
+ }
+ }
}

File Metadata

Mime Type
text/plain
Expires
Tue, Oct 22, 16:27 (9 h, 49 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
638597
Default Alt Text
D83.1729607227.diff (218 KB)

Event Timeline