diff --git a/app/schemas/com.denizk0461.weserplaner.db.AppDatabase/22.json b/app/schemas/com.denizk0461.weserplaner.db.AppDatabase/22.json new file mode 100644 index 0000000..2552759 --- /dev/null +++ b/app/schemas/com.denizk0461.weserplaner.db.AppDatabase/22.json @@ -0,0 +1,387 @@ +{ + "formatVersion": 1, + "database": { + "version": 22, + "identityHash": "d55bef3b8778cd6346bf65455993008d", + "entities": [ + { + "tableName": "timetables", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "studip_events", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`eventId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `timetableId` INTEGER NOT NULL, `title` TEXT NOT NULL, `lecturer` TEXT NOT NULL, `room` TEXT NOT NULL, `day` INTEGER NOT NULL, `timeslotStart` TEXT NOT NULL, `timeslotEnd` TEXT NOT NULL, `timeslotId` INTEGER NOT NULL, `colour` INTEGER NOT NULL, FOREIGN KEY(`timetableId`) REFERENCES `timetables`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "eventId", + "columnName": "eventId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timetableId", + "columnName": "timetableId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "lecturer", + "columnName": "lecturer", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "day", + "columnName": "day", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "timeslotStart", + "columnName": "timeslotStart", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeslotEnd", + "columnName": "timeslotEnd", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeslotId", + "columnName": "timeslotId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "colour", + "columnName": "colour", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "eventId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "timetables", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "timetableId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "offer_date", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `date` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "offer_canteen", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `canteen` TEXT NOT NULL, `openingHours` TEXT NOT NULL, `news` TEXT NOT NULL, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canteen", + "columnName": "canteen", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "openingHours", + "columnName": "openingHours", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "news", + "columnName": "news", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "offer_category", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `dateId` INTEGER NOT NULL, `canteenId` INTEGER NOT NULL, `category` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`dateId`) REFERENCES `offer_date`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`canteenId`) REFERENCES `offer_canteen`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dateId", + "columnName": "dateId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "canteenId", + "columnName": "canteenId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "category", + "columnName": "category", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "offer_date", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "dateId" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "offer_canteen", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "canteenId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "offer_item", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`itemId` INTEGER NOT NULL, `categoryId` INTEGER NOT NULL, `title` TEXT NOT NULL, `price` TEXT NOT NULL, `dietary_preferences` TEXT NOT NULL, `allergens` TEXT NOT NULL, PRIMARY KEY(`itemId`), FOREIGN KEY(`categoryId`) REFERENCES `offer_category`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "itemId", + "columnName": "itemId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "dietaryPreferences", + "columnName": "dietary_preferences", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "allergens", + "columnName": "allergens", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": false, + "columnNames": [ + "itemId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "offer_category", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "categoryId" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "event_tasks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`taskId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `eventId` INTEGER NOT NULL, `dueDate` INTEGER NOT NULL, `notifyDate` INTEGER NOT NULL, `title` TEXT NOT NULL, `notes` TEXT NOT NULL, `room` TEXT NOT NULL, `isFinished` INTEGER NOT NULL, FOREIGN KEY(`eventId`) REFERENCES `studip_events`(`eventId`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "fields": [ + { + "fieldPath": "taskId", + "columnName": "taskId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "eventId", + "columnName": "eventId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "dueDate", + "columnName": "dueDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "notifyDate", + "columnName": "notifyDate", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "notes", + "columnName": "notes", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "room", + "columnName": "room", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "isFinished", + "columnName": "isFinished", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "taskId" + ] + }, + "indices": [], + "foreignKeys": [ + { + "table": "studip_events", + "onDelete": "CASCADE", + "onUpdate": "NO ACTION", + "columns": [ + "eventId" + ], + "referencedColumns": [ + "eventId" + ] + } + ] + } + ], + "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, 'd55bef3b8778cd6346bf65455993008d')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/weserplaner/activity/MainActivity.kt b/app/src/main/java/com/denizk0461/weserplaner/activity/MainActivity.kt index e5eff5c..4ce7e3c 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/activity/MainActivity.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/activity/MainActivity.kt @@ -37,6 +37,12 @@ class MainActivity : FragmentActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) +// if (viewModel.getTimetableCount() == 0) { +// viewModel.insertTimetable( +// Timetable(0, "default") +// ) +// } + /* * Check whether the user is launching the app for the first time in order to show a * tutorial. This value will be changed to 'false' upon completing the tutorial, thus not diff --git a/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt b/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt index cc33d14..7d068ba 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/db/AppDAO.kt @@ -73,6 +73,11 @@ interface AppDAO { @Query("DELETE FROM studip_events") fun nukeEvents() + /* --- timetables --- */ + + @Insert + fun insertTimetable(timetable: Timetable) + /* --- event tasks --- */ /** diff --git a/app/src/main/java/com/denizk0461/weserplaner/db/AppDatabase.kt b/app/src/main/java/com/denizk0461/weserplaner/db/AppDatabase.kt index f51b683..d445aa9 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/db/AppDatabase.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/db/AppDatabase.kt @@ -14,6 +14,7 @@ import com.denizk0461.weserplaner.model.* */ @Database( entities = [ + Timetable::class, StudIPEvent::class, OfferDate::class, OfferCanteen::class, @@ -21,10 +22,11 @@ import com.denizk0461.weserplaner.model.* OfferItem::class, EventTask::class, ], - version = 21, + version = 22, exportSchema = true, autoMigrations = [ AutoMigration(from = 20, to = 21), +// AutoMigration(from = 21, to = 22), ], ) abstract class AppDatabase : RoomDatabase() { @@ -58,6 +60,7 @@ abstract class AppDatabase : RoomDatabase() { "event_db", ).addMigrations( migrationAddNewsToOfferCanteen_19_20, + migration_21_22, // migrationAddTimetableId_20_21, // migrationAddEventTask_20_21, ) @@ -79,5 +82,48 @@ abstract class AppDatabase : RoomDatabase() { database.execSQL("ALTER TABLE offer_canteen ADD COLUMN news TEXT NOT NULL DEFAULT ''") } } + + private val migration_21_22 = object : Migration(21, 22) { + override fun migrate(database: SupportSQLiteDatabase) { + // Add 'timetables' table + database.execSQL("CREATE TABLE 'timetables' ('id' INTEGER NOT NULL UNIQUE, 'name' TEXT NOT NULL, PRIMARY KEY('id' AUTOINCREMENT));") + + // Insert a timetable + database.execSQL("INSERT INTO timetables VALUES (0, 'default')") + + /* + * Create temporary Stud.IP event table. This is necessary because SQLite doesn't + * implement ADD FOREIGN KEY on ALTER TABLE >:( + */ + database.execSQL(""" + CREATE TABLE IF NOT EXISTS studip_events_temp ( + 'eventId' INTEGER NOT NULL UNIQUE, + 'timetableId' INTEGER NOT NULL, + 'title' TEXT NOT NULL, + 'lecturer' TEXT NOT NULL, + 'room' TEXT NOT NULL, + 'day' INTEGER NOT NULL, + 'timeslotStart' TEXT NOT NULL, + 'timeslotEnd' TEXT NOT NULL, + 'timeslotId' INTEGER NOT NULL, + 'colour' INTEGER NOT NULL DEFAULT 0, + FOREIGN KEY('timetableId') REFERENCES 'timetables'('id') ON DELETE CASCADE, + PRIMARY KEY('eventId' AUTOINCREMENT) + ); + """.trimIndent()) + + // Copy data from old table to new table + database.execSQL(""" + INSERT INTO studip_events_temp (eventId, timetableId, title, lecturer, room, day, timeslotStart, timeslotEnd, timeslotId, colour) + SELECT eventId, timetableId, title, lecturer, room, day, timeslotStart, timeslotEnd, timeslotId, colour FROM studip_events + """.trimIndent()) + + // Delete old table + database.execSQL("DROP TABLE studip_events") + + // Rename new table to the old table's name + database.execSQL("ALTER TABLE studip_events_temp RENAME TO studip_events") + } + } } } \ No newline at end of file diff --git a/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt b/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt index d72281a..e5e3053 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/db/AppRepository.kt @@ -159,6 +159,10 @@ class AppRepository(app: Application) { dao.delete(event) } + fun insertTimetable(timetable: Timetable) { + dao.insertTimetable(timetable) + } + /** * Retrieves all canteen offer date objects. * diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/StudIPEvent.kt b/app/src/main/java/com/denizk0461/weserplaner/model/StudIPEvent.kt index b75e204..6f0adf0 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/model/StudIPEvent.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/model/StudIPEvent.kt @@ -4,6 +4,7 @@ import android.os.Parcel import android.os.Parcelable import androidx.room.ColumnInfo import androidx.room.Entity +import androidx.room.ForeignKey import androidx.room.PrimaryKey /** @@ -20,10 +21,20 @@ import androidx.room.PrimaryKey * @param timeslotId minute the event starts at - used for ordering * @param colour user-defined colour the event will be shown in - UNIMPLEMENTED */ -@Entity(tableName = "studip_events") +@Entity( + tableName = "studip_events", + foreignKeys = [ + ForeignKey( + Timetable::class, + ["id"], + ["timetableId"], + ForeignKey.CASCADE, + ), + ], +) data class StudIPEvent( @PrimaryKey(autoGenerate = true) val eventId: Int = 0, - @ColumnInfo(defaultValue = "0") val timetableId: Int, + val timetableId: Int, val title: String, val lecturer: String, val room: String, diff --git a/app/src/main/java/com/denizk0461/weserplaner/model/Timetable.kt b/app/src/main/java/com/denizk0461/weserplaner/model/Timetable.kt new file mode 100644 index 0000000..f5a5988 --- /dev/null +++ b/app/src/main/java/com/denizk0461/weserplaner/model/Timetable.kt @@ -0,0 +1,17 @@ +package com.denizk0461.weserplaner.model + +import androidx.room.Entity +import androidx.room.PrimaryKey + +/** + * Timetable parent object that [StudIPEvent]s are assigned to. These can be named to denote + * different semesters. + * + * @param id unique primary key value + * @param name title of the timetable + */ +@Entity(tableName = "timetables") +data class Timetable( + @PrimaryKey(autoGenerate = true) val id: Int, + val name: String, +) diff --git a/app/src/main/java/com/denizk0461/weserplaner/viewmodel/MainViewModel.kt b/app/src/main/java/com/denizk0461/weserplaner/viewmodel/MainViewModel.kt index bc9d165..7203ef6 100644 --- a/app/src/main/java/com/denizk0461/weserplaner/viewmodel/MainViewModel.kt +++ b/app/src/main/java/com/denizk0461/weserplaner/viewmodel/MainViewModel.kt @@ -1,6 +1,7 @@ package com.denizk0461.weserplaner.viewmodel import android.app.Application +import com.denizk0461.weserplaner.model.Timetable import com.denizk0461.weserplaner.values.AppLayout /** diff --git a/app/src/main/res/values-v29/themes.xml b/app/src/main/res/values-v29/themes.xml deleted file mode 100644 index 5b2a3a2..0000000 --- a/app/src/main/res/values-v29/themes.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - \ No newline at end of file