diff --git a/core/schemas/in.testpress.database.TestpressDatabase/25.json b/core/schemas/in.testpress.database.TestpressDatabase/25.json new file mode 100644 index 000000000..603e7827d --- /dev/null +++ b/core/schemas/in.testpress.database.TestpressDatabase/25.json @@ -0,0 +1,2978 @@ +{ + "formatVersion": 1, + "database": { + "version": 25, + "identityHash": "42001ecd253e6b0b9f7f18e4e51e27c3", + "entities": [ + { + "tableName": "ContentEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `description` TEXT, `image` TEXT, `url` TEXT NOT NULL, `chapterSlug` TEXT NOT NULL, `chapterUrl` TEXT, `modified` TEXT, `examUrl` TEXT, `videoUrl` TEXT, `attachmentUrl` TEXT, `htmlUrl` TEXT, `isLocked` INTEGER NOT NULL, `isScheduled` INTEGER NOT NULL, `attemptsCount` INTEGER NOT NULL, `bookmarkId` INTEGER, `videoWatchedPercentage` INTEGER, `active` INTEGER NOT NULL, `htmlId` INTEGER, `hasStarted` INTEGER NOT NULL, `isCourseAvailable` INTEGER, `coverImageSmall` TEXT, `coverImageMedium` TEXT, `coverImage` TEXT, `nextContentId` INTEGER, `hasEnded` INTEGER, `examStartUrl` TEXT, `order` INTEGER, `chapterId` INTEGER, `freePreview` INTEGER, `title` TEXT, `courseId` INTEGER, `examId` INTEGER, `contentId` INTEGER, `videoId` INTEGER, `attachmentId` INTEGER, `liveStreamId` INTEGER, `contentType` TEXT, `icon` TEXT, `start` TEXT, `end` TEXT, `treePath` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chapterSlug", + "columnName": "chapterSlug", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "chapterUrl", + "columnName": "chapterUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "examUrl", + "columnName": "examUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videoUrl", + "columnName": "videoUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentUrl", + "columnName": "attachmentUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "htmlUrl", + "columnName": "htmlUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isLocked", + "columnName": "isLocked", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isScheduled", + "columnName": "isScheduled", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attemptsCount", + "columnName": "attemptsCount", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bookmarkId", + "columnName": "bookmarkId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "videoWatchedPercentage", + "columnName": "videoWatchedPercentage", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "active", + "columnName": "active", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "htmlId", + "columnName": "htmlId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasStarted", + "columnName": "hasStarted", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isCourseAvailable", + "columnName": "isCourseAvailable", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "coverImageSmall", + "columnName": "coverImageSmall", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverImageMedium", + "columnName": "coverImageMedium", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "coverImage", + "columnName": "coverImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "nextContentId", + "columnName": "nextContentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "hasEnded", + "columnName": "hasEnded", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "examStartUrl", + "columnName": "examStartUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "chapterId", + "columnName": "chapterId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "freePreview", + "columnName": "freePreview", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "courseId", + "columnName": "courseId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "examId", + "columnName": "examId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentId", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachmentId", + "columnName": "attachmentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "liveStreamId", + "columnName": "liveStreamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "contentType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "treePath", + "columnName": "treePath", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OfflineVideo", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `title` TEXT, `description` TEXT, `remoteThumbnail` TEXT, `localThumbnail` TEXT, `duration` TEXT NOT NULL, `url` TEXT, `contentId` INTEGER, `percentageDownloaded` INTEGER NOT NULL, `bytesDownloaded` INTEGER NOT NULL, `totalSize` INTEGER NOT NULL, `courseId` INTEGER, `lastWatchPosition` TEXT, `watchedTimeRanges` TEXT NOT NULL, `syncState` TEXT NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "remoteThumbnail", + "columnName": "remoteThumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localThumbnail", + "columnName": "localThumbnail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentId", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "percentageDownloaded", + "columnName": "percentageDownloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "bytesDownloaded", + "columnName": "bytesDownloaded", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "totalSize", + "columnName": "totalSize", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "courseId", + "columnName": "courseId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastWatchPosition", + "columnName": "lastWatchPosition", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "watchedTimeRanges", + "columnName": "watchedTimeRanges", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "syncState", + "columnName": "syncState", + "affinity": "TEXT", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ProductEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `endDate` TEXT, `image` TEXT, `surl` TEXT, `title` TEXT, `paymentLink` TEXT, `buyNowText` TEXT, `furl` TEXT, `descriptionHtml` TEXT, `currentPrice` TEXT, `slug` TEXT, `startDate` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "endDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "surl", + "columnName": "surl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "paymentLink", + "columnName": "paymentLink", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "buyNowText", + "columnName": "buyNowText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "furl", + "columnName": "furl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "descriptionHtml", + "columnName": "descriptionHtml", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "currentPrice", + "columnName": "currentPrice", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "startDate", + "columnName": "startDate", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "PriceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, `price` TEXT, `validity` INTEGER, `endDate` TEXT, `startDate` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "price", + "columnName": "price", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "validity", + "columnName": "validity", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "endDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "startDate", + "columnName": "startDate", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CourseEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `image` TEXT, `examsCount` INTEGER, `created` TEXT, `description` TEXT, `title` TEXT, `chaptersCount` INTEGER, `deviceAccessControl` TEXT, `createdBy` INTEGER, `enableDiscussions` INTEGER, `url` TEXT, `contentsCount` INTEGER, `contentsUrl` TEXT, `chaptersUrl` TEXT, `modified` TEXT, `videosCount` INTEGER, `externalContentLink` TEXT, `attachmentsCount` INTEGER, `slug` TEXT, `htmlContentsCount` INTEGER, `order` INTEGER, `externalLinkLabel` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "image", + "columnName": "image", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "examsCount", + "columnName": "examsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "chaptersCount", + "columnName": "chaptersCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "deviceAccessControl", + "columnName": "deviceAccessControl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy", + "columnName": "createdBy", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "enableDiscussions", + "columnName": "enableDiscussions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentsCount", + "columnName": "contentsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentsUrl", + "columnName": "contentsUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "chaptersUrl", + "columnName": "chaptersUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videosCount", + "columnName": "videosCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "externalContentLink", + "columnName": "externalContentLink", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attachmentsCount", + "columnName": "attachmentsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "htmlContentsCount", + "columnName": "htmlContentsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "externalLinkLabel", + "columnName": "externalLinkLabel", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ProductCourseEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`courseId` INTEGER NOT NULL, `productId` INTEGER NOT NULL, PRIMARY KEY(`productId`, `courseId`))", + "fields": [ + { + "fieldPath": "courseId", + "columnName": "courseId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "productId", + "columnName": "productId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "productId", + "courseId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ProductPriceEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`priceId` INTEGER NOT NULL, `productId` INTEGER NOT NULL, PRIMARY KEY(`productId`, `priceId`))", + "fields": [ + { + "fieldPath": "priceId", + "columnName": "priceId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "productId", + "columnName": "productId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "productId", + "priceId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CommentEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `url` TEXT, `userEmail` TEXT, `userUrl` TEXT, `comment` TEXT, `submitDate` TEXT, `upvotes` INTEGER, `downvotes` INTEGER, `typeOfVote` INTEGER, `voteId` INTEGER, `created` TEXT, `modified` TEXT, `contentId` INTEGER, `contentUrl` TEXT, `profileId` INTEGER, `profileUrl` TEXT, `username` TEXT, `displayName` TEXT, `firstName` TEXT, `lastName` TEXT, `email` TEXT, `photo` TEXT, `largeImage` TEXT, `mediumImage` TEXT, `smallImage` TEXT, `xSmallImage` TEXT, `miniImage` TEXT, `birthDate` TEXT, `gender` TEXT, `address1` TEXT, `address2` TEXT, `city` TEXT, `zip` TEXT, `state` TEXT, `stateChoices` TEXT, `phone` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "userEmail", + "columnName": "userEmail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "userUrl", + "columnName": "userUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment", + "columnName": "comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "submitDate", + "columnName": "submitDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "upvotes", + "columnName": "upvotes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "downvotes", + "columnName": "downvotes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "typeOfVote", + "columnName": "typeOfVote", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "voteId", + "columnName": "voteId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentObject.id", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentObject.url", + "columnName": "contentUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.id", + "columnName": "profileId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "user.url", + "columnName": "profileUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.firstName", + "columnName": "firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.lastName", + "columnName": "lastName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.email", + "columnName": "email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.photo", + "columnName": "photo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.largeImage", + "columnName": "largeImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.mediumImage", + "columnName": "mediumImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.smallImage", + "columnName": "smallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.xSmallImage", + "columnName": "xSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.miniImage", + "columnName": "miniImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.birthDate", + "columnName": "birthDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.gender", + "columnName": "gender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.address1", + "columnName": "address1", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.address2", + "columnName": "address2", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.city", + "columnName": "city", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.zip", + "columnName": "zip", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.stateChoices", + "columnName": "stateChoices", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "user.phone", + "columnName": "phone", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "DiscussionPostEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `shortWebUrl` TEXT, `shortUrl` TEXT, `webUrl` TEXT, `created` TEXT, `commentsUrl` TEXT, `url` TEXT, `modified` TEXT, `upvotes` INTEGER, `downvotes` INTEGER, `title` TEXT, `summary` TEXT, `isActive` INTEGER, `publishedDate` TEXT, `commentsCount` INTEGER, `isLocked` INTEGER, `subject` INTEGER, `viewsCount` INTEGER, `participantsCount` INTEGER, `lastCommentedTime` TEXT, `contentHtml` TEXT, `isPublic` INTEGER, `shortLink` TEXT, `institute` INTEGER, `slug` TEXT, `isPublished` INTEGER, `isApproved` INTEGER, `forum` INTEGER, `ipAddress` TEXT, `voteId` INTEGER, `typeOfVote` INTEGER, `published` INTEGER, `modifiedDate` INTEGER, `creatorId` INTEGER, `commentorId` INTEGER, `categoryId` INTEGER, `created_by_id` INTEGER, `created_by_url` TEXT, `created_by_username` TEXT, `created_by_firstName` TEXT, `created_by_lastName` TEXT, `created_by_displayName` TEXT, `created_by_photo` TEXT, `created_by_largeImage` TEXT, `created_by_mediumImage` TEXT, `created_by_mediumSmallImage` TEXT, `created_by_smallImage` TEXT, `created_by_xSmallImage` TEXT, `created_by_miniImage` TEXT, `last_commented_by_id` INTEGER, `last_commented_by_url` TEXT, `last_commented_by_username` TEXT, `last_commented_by_firstName` TEXT, `last_commented_by_lastName` TEXT, `last_commented_by_displayName` TEXT, `last_commented_by_photo` TEXT, `last_commented_by_largeImage` TEXT, `last_commented_by_mediumImage` TEXT, `last_commented_by_mediumSmallImage` TEXT, `last_commented_by_smallImage` TEXT, `last_commented_by_xSmallImage` TEXT, `last_commented_by_miniImage` TEXT, `category_id` INTEGER, `category_name` TEXT, `category_color` TEXT, `category_slug` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "shortWebUrl", + "columnName": "shortWebUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "shortUrl", + "columnName": "shortUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "webUrl", + "columnName": "webUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "created", + "columnName": "created", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "commentsUrl", + "columnName": "commentsUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "modified", + "columnName": "modified", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "upvotes", + "columnName": "upvotes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "downvotes", + "columnName": "downvotes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "summary", + "columnName": "summary", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isActive", + "columnName": "isActive", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "publishedDate", + "columnName": "publishedDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "commentsCount", + "columnName": "commentsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isLocked", + "columnName": "isLocked", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "subject", + "columnName": "subject", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "viewsCount", + "columnName": "viewsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "participantsCount", + "columnName": "participantsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastCommentedTime", + "columnName": "lastCommentedTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "contentHtml", + "columnName": "contentHtml", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isPublic", + "columnName": "isPublic", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "shortLink", + "columnName": "shortLink", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "institute", + "columnName": "institute", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isPublished", + "columnName": "isPublished", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isApproved", + "columnName": "isApproved", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forum", + "columnName": "forum", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "ipAddress", + "columnName": "ipAddress", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "voteId", + "columnName": "voteId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "typeOfVote", + "columnName": "typeOfVote", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "published", + "columnName": "published", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "modifiedDate", + "columnName": "modifiedDate", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "creatorId", + "columnName": "creatorId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "commentorId", + "columnName": "commentorId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "createdBy.id", + "columnName": "created_by_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "createdBy.url", + "columnName": "created_by_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.username", + "columnName": "created_by_username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.firstName", + "columnName": "created_by_firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.lastName", + "columnName": "created_by_lastName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.displayName", + "columnName": "created_by_displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.photo", + "columnName": "created_by_photo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.largeImage", + "columnName": "created_by_largeImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.mediumImage", + "columnName": "created_by_mediumImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.mediumSmallImage", + "columnName": "created_by_mediumSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.smallImage", + "columnName": "created_by_smallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.xSmallImage", + "columnName": "created_by_xSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdBy.miniImage", + "columnName": "created_by_miniImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.id", + "columnName": "last_commented_by_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.url", + "columnName": "last_commented_by_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.username", + "columnName": "last_commented_by_username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.firstName", + "columnName": "last_commented_by_firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.lastName", + "columnName": "last_commented_by_lastName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.displayName", + "columnName": "last_commented_by_displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.photo", + "columnName": "last_commented_by_photo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.largeImage", + "columnName": "last_commented_by_largeImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.mediumImage", + "columnName": "last_commented_by_mediumImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.mediumSmallImage", + "columnName": "last_commented_by_mediumSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.smallImage", + "columnName": "last_commented_by_smallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.xSmallImage", + "columnName": "last_commented_by_xSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastCommentedBy.miniImage", + "columnName": "last_commented_by_miniImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "category.id", + "columnName": "category_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "category.name", + "columnName": "category_name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "category.color", + "columnName": "category_color", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "category.slug", + "columnName": "category_slug", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "LastLoadedPageData", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`resourceType` TEXT NOT NULL, `previous` INTEGER, `next` INTEGER, PRIMARY KEY(`resourceType`))", + "fields": [ + { + "fieldPath": "resourceType", + "columnName": "resourceType", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "previous", + "columnName": "previous", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "next", + "columnName": "next", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "resourceType" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "UserEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `url` TEXT, `username` TEXT, `firstName` TEXT, `lastName` TEXT, `displayName` TEXT, `photo` TEXT, `largeImage` TEXT, `mediumImage` TEXT, `mediumSmallImage` TEXT, `smallImage` TEXT, `xSmallImage` TEXT, `miniImage` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "username", + "columnName": "username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "firstName", + "columnName": "firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastName", + "columnName": "lastName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "displayName", + "columnName": "displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "photo", + "columnName": "photo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "largeImage", + "columnName": "largeImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mediumImage", + "columnName": "mediumImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mediumSmallImage", + "columnName": "mediumSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "smallImage", + "columnName": "smallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "xSmallImage", + "columnName": "xSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "miniImage", + "columnName": "miniImage", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "CategoryEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, `color` TEXT, `slug` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "color", + "columnName": "color", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "DiscussionThreadAnswerEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `forumThreadId` INTEGER, `approved_by_id` INTEGER, `approved_by_url` TEXT, `approved_by_username` TEXT, `approved_by_firstName` TEXT, `approved_by_lastName` TEXT, `approved_by_displayName` TEXT, `approved_by_photo` TEXT, `approved_by_largeImage` TEXT, `approved_by_mediumImage` TEXT, `approved_by_mediumSmallImage` TEXT, `approved_by_smallImage` TEXT, `approved_by_xSmallImage` TEXT, `approved_by_miniImage` TEXT, `comment_id` INTEGER, `comment_url` TEXT, `comment_userEmail` TEXT, `comment_userUrl` TEXT, `comment_comment` TEXT, `comment_submitDate` TEXT, `comment_upvotes` INTEGER, `comment_downvotes` INTEGER, `comment_typeOfVote` INTEGER, `comment_voteId` INTEGER, `comment_created` TEXT, `comment_modified` TEXT, `comment_contentId` INTEGER, `comment_contentUrl` TEXT, `comment_profileId` INTEGER, `comment_profileUrl` TEXT, `comment_username` TEXT, `comment_displayName` TEXT, `comment_firstName` TEXT, `comment_lastName` TEXT, `comment_email` TEXT, `comment_photo` TEXT, `comment_largeImage` TEXT, `comment_mediumImage` TEXT, `comment_smallImage` TEXT, `comment_xSmallImage` TEXT, `comment_miniImage` TEXT, `comment_birthDate` TEXT, `comment_gender` TEXT, `comment_address1` TEXT, `comment_address2` TEXT, `comment_city` TEXT, `comment_zip` TEXT, `comment_state` TEXT, `comment_stateChoices` TEXT, `comment_phone` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "forumThreadId", + "columnName": "forumThreadId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "approvedBy.id", + "columnName": "approved_by_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "approvedBy.url", + "columnName": "approved_by_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.username", + "columnName": "approved_by_username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.firstName", + "columnName": "approved_by_firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.lastName", + "columnName": "approved_by_lastName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.displayName", + "columnName": "approved_by_displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.photo", + "columnName": "approved_by_photo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.largeImage", + "columnName": "approved_by_largeImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.mediumImage", + "columnName": "approved_by_mediumImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.mediumSmallImage", + "columnName": "approved_by_mediumSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.smallImage", + "columnName": "approved_by_smallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.xSmallImage", + "columnName": "approved_by_xSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "approvedBy.miniImage", + "columnName": "approved_by_miniImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.id", + "columnName": "comment_id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "comment.url", + "columnName": "comment_url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.userEmail", + "columnName": "comment_userEmail", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.userUrl", + "columnName": "comment_userUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.comment", + "columnName": "comment_comment", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.submitDate", + "columnName": "comment_submitDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.upvotes", + "columnName": "comment_upvotes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "comment.downvotes", + "columnName": "comment_downvotes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "comment.typeOfVote", + "columnName": "comment_typeOfVote", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "comment.voteId", + "columnName": "comment_voteId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "comment.created", + "columnName": "comment_created", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.modified", + "columnName": "comment_modified", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.contentObject.id", + "columnName": "comment_contentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "comment.contentObject.url", + "columnName": "comment_contentUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.id", + "columnName": "comment_profileId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "comment.user.url", + "columnName": "comment_profileUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.username", + "columnName": "comment_username", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.displayName", + "columnName": "comment_displayName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.firstName", + "columnName": "comment_firstName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.lastName", + "columnName": "comment_lastName", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.email", + "columnName": "comment_email", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.photo", + "columnName": "comment_photo", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.largeImage", + "columnName": "comment_largeImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.mediumImage", + "columnName": "comment_mediumImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.smallImage", + "columnName": "comment_smallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.xSmallImage", + "columnName": "comment_xSmallImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.miniImage", + "columnName": "comment_miniImage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.birthDate", + "columnName": "comment_birthDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.gender", + "columnName": "comment_gender", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.address1", + "columnName": "comment_address1", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.address2", + "columnName": "comment_address2", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.city", + "columnName": "comment_city", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.zip", + "columnName": "comment_zip", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.state", + "columnName": "comment_state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.stateChoices", + "columnName": "comment_stateChoices", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comment.user.phone", + "columnName": "comment_phone", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ProductCategoryEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, `slug` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RunningContentEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `type` INTEGER NOT NULL, `order` INTEGER, `chapterId` INTEGER, `freePreview` INTEGER, `title` TEXT, `courseId` INTEGER, `examId` INTEGER, `contentId` INTEGER, `videoId` INTEGER, `attachmentId` INTEGER, `liveStreamId` INTEGER, `contentType` TEXT, `icon` TEXT, `start` TEXT, `end` TEXT, `treePath` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "chapterId", + "columnName": "chapterId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "freePreview", + "columnName": "freePreview", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "courseId", + "columnName": "courseId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "examId", + "columnName": "examId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentId", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachmentId", + "columnName": "attachmentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "liveStreamId", + "columnName": "liveStreamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "contentType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "treePath", + "columnName": "treePath", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "RunningContentRemoteKeys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`contentId` INTEGER NOT NULL, `prevKey` INTEGER, `nextKey` INTEGER, `courseId` INTEGER NOT NULL, `type` INTEGER NOT NULL, PRIMARY KEY(`contentId`))", + "fields": [ + { + "fieldPath": "contentId", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "prevKey", + "columnName": "prevKey", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "nextKey", + "columnName": "nextKey", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "courseId", + "columnName": "courseId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "contentId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "UpcomingContentEntity", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `order` INTEGER, `chapterId` INTEGER, `freePreview` INTEGER, `title` TEXT, `courseId` INTEGER, `examId` INTEGER, `contentId` INTEGER, `videoId` INTEGER, `attachmentId` INTEGER, `liveStreamId` INTEGER, `contentType` TEXT, `icon` TEXT, `start` TEXT, `end` TEXT, `treePath` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "chapterId", + "columnName": "chapterId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "freePreview", + "columnName": "freePreview", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "courseId", + "columnName": "courseId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "examId", + "columnName": "examId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentId", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "videoId", + "columnName": "videoId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attachmentId", + "columnName": "attachmentId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "liveStreamId", + "columnName": "liveStreamId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "contentType", + "columnName": "contentType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "icon", + "columnName": "icon", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "start", + "columnName": "start", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "end", + "columnName": "end", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "treePath", + "columnName": "treePath", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "UpcomingContentRemoteKeys", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`contentId` INTEGER NOT NULL, `prevKey` INTEGER, `nextKey` INTEGER, `courseId` INTEGER NOT NULL, PRIMARY KEY(`contentId`))", + "fields": [ + { + "fieldPath": "contentId", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "prevKey", + "columnName": "prevKey", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "nextKey", + "columnName": "nextKey", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "courseId", + "columnName": "courseId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "contentId" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Question", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `questionHtml` TEXT, `directionId` INTEGER, `answers` TEXT NOT NULL, `language` TEXT, `subjectId` INTEGER, `type` TEXT, `translations` TEXT NOT NULL, `marks` TEXT, `negativeMarks` TEXT, `parentId` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "questionHtml", + "columnName": "questionHtml", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "directionId", + "columnName": "directionId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "answers", + "columnName": "answers", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subjectId", + "columnName": "subjectId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "translations", + "columnName": "translations", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "marks", + "columnName": "marks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "negativeMarks", + "columnName": "negativeMarks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parentId", + "columnName": "parentId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Subject", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Direction", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `html` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "html", + "columnName": "html", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "ExamQuestion", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `order` INTEGER, `questionId` INTEGER, `sectionId` INTEGER, `marks` TEXT, `partialMarks` TEXT, `examId` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "questionId", + "columnName": "questionId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "sectionId", + "columnName": "sectionId", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "marks", + "columnName": "marks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "partialMarks", + "columnName": "partialMarks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "examId", + "columnName": "examId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Language", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `code` TEXT, `title` TEXT, `examId` INTEGER)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "code", + "columnName": "code", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "examId", + "columnName": "examId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "Section", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `order` INTEGER, `name` TEXT, `duration` TEXT, `cutOff` INTEGER, `instructions` TEXT, `parent` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "cutOff", + "columnName": "cutOff", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "instructions", + "columnName": "instructions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "parent", + "columnName": "parent", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OfflineExam", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `totalMarks` TEXT, `url` TEXT, `attemptsCount` INTEGER, `pausedAttemptsCount` INTEGER, `title` TEXT, `description` TEXT, `startDate` TEXT, `endDate` TEXT, `duration` TEXT, `numberOfQuestions` INTEGER, `negativeMarks` TEXT, `markPerQuestion` TEXT, `templateType` INTEGER, `allowRetake` INTEGER, `allowPdf` INTEGER, `showAnswers` INTEGER, `maxRetakes` INTEGER, `attemptsUrl` TEXT, `deviceAccessControl` TEXT, `commentsCount` INTEGER, `slug` TEXT, `selectedLanguage` TEXT, `variableMarkPerQuestion` INTEGER, `passPercentage` INTEGER, `enableRanks` INTEGER, `showScore` INTEGER, `showPercentile` INTEGER, `categories` TEXT, `isDetailsFetched` INTEGER, `isGrowthHackEnabled` INTEGER, `shareTextForSolutionUnlock` TEXT, `showAnalytics` INTEGER, `instructions` TEXT, `hasAudioQuestions` INTEGER, `rankPublishingDate` TEXT, `enableQuizMode` INTEGER, `disableAttemptResume` INTEGER, `allowPreemptiveSectionEnding` INTEGER, `examDataModifiedOn` TEXT, `isSyncRequired` INTEGER NOT NULL, `contentId` INTEGER, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "totalMarks", + "columnName": "totalMarks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attemptsCount", + "columnName": "attemptsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "pausedAttemptsCount", + "columnName": "pausedAttemptsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "startDate", + "columnName": "startDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endDate", + "columnName": "endDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "numberOfQuestions", + "columnName": "numberOfQuestions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "negativeMarks", + "columnName": "negativeMarks", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "markPerQuestion", + "columnName": "markPerQuestion", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "templateType", + "columnName": "templateType", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "allowRetake", + "columnName": "allowRetake", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "allowPdf", + "columnName": "allowPdf", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "showAnswers", + "columnName": "showAnswers", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "maxRetakes", + "columnName": "maxRetakes", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "attemptsUrl", + "columnName": "attemptsUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deviceAccessControl", + "columnName": "deviceAccessControl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "commentsCount", + "columnName": "commentsCount", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "slug", + "columnName": "slug", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "selectedLanguage", + "columnName": "selectedLanguage", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "variableMarkPerQuestion", + "columnName": "variableMarkPerQuestion", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "passPercentage", + "columnName": "passPercentage", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "enableRanks", + "columnName": "enableRanks", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "showScore", + "columnName": "showScore", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "showPercentile", + "columnName": "showPercentile", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "categories", + "columnName": "categories", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isDetailsFetched", + "columnName": "isDetailsFetched", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "isGrowthHackEnabled", + "columnName": "isGrowthHackEnabled", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "shareTextForSolutionUnlock", + "columnName": "shareTextForSolutionUnlock", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "showAnalytics", + "columnName": "showAnalytics", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "instructions", + "columnName": "instructions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "hasAudioQuestions", + "columnName": "hasAudioQuestions", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "rankPublishingDate", + "columnName": "rankPublishingDate", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "enableQuizMode", + "columnName": "enableQuizMode", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "disableAttemptResume", + "columnName": "disableAttemptResume", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "allowPreemptiveSectionEnding", + "columnName": "allowPreemptiveSectionEnding", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "examDataModifiedOn", + "columnName": "examDataModifiedOn", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "isSyncRequired", + "columnName": "isSyncRequired", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "contentId", + "columnName": "contentId", + "affinity": "INTEGER", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OfflineCourseAttempt", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `assessmentId` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "assessmentId", + "columnName": "assessmentId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OfflineAttempt", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `date` TEXT NOT NULL, `totalQuestions` INTEGER NOT NULL, `lastStartedTime` TEXT NOT NULL, `remainingTime` TEXT NOT NULL, `timeTaken` TEXT NOT NULL, `state` TEXT NOT NULL, `attemptType` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "totalQuestions", + "columnName": "totalQuestions", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastStartedTime", + "columnName": "lastStartedTime", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remainingTime", + "columnName": "remainingTime", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "timeTaken", + "columnName": "timeTaken", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attemptType", + "columnName": "attemptType", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OfflineAttemptSection", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `attemptSectionId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `state` TEXT NOT NULL, `remainingTime` TEXT, `name` TEXT NOT NULL, `duration` TEXT NOT NULL, `order` INTEGER NOT NULL, `instructions` TEXT, `attemptId` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attemptSectionId", + "columnName": "attemptSectionId", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "remainingTime", + "columnName": "remainingTime", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "duration", + "columnName": "duration", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "instructions", + "columnName": "instructions", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attemptId", + "columnName": "attemptId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "attemptSectionId" + ], + "autoGenerate": true + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "OfflineAttemptItem", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `question` TEXT NOT NULL, `selectedAnswers` TEXT NOT NULL, `review` INTEGER, `savedAnswers` TEXT NOT NULL, `order` INTEGER NOT NULL, `shortText` TEXT, `currentShortText` TEXT, `attemptSection` TEXT, `essayText` TEXT, `localEssayText` TEXT, `files` TEXT NOT NULL, `unSyncedFiles` TEXT NOT NULL, `attemptId` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "question", + "columnName": "question", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "selectedAnswers", + "columnName": "selectedAnswers", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "review", + "columnName": "review", + "affinity": "INTEGER", + "notNull": false + }, + { + "fieldPath": "savedAnswers", + "columnName": "savedAnswers", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "order", + "columnName": "order", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortText", + "columnName": "shortText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "currentShortText", + "columnName": "currentShortText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "attemptSection", + "columnName": "attemptSection", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "essayText", + "columnName": "essayText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "localEssayText", + "columnName": "localEssayText", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "files", + "columnName": "files", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "unSyncedFiles", + "columnName": "unSyncedFiles", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "attemptId", + "columnName": "attemptId", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": true + }, + "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, '42001ecd253e6b0b9f7f18e4e51e27c3')" + ] + } +} \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/Room.kt b/core/src/main/java/in/testpress/database/Room.kt index 6fc219d14..6acc8cb2b 100644 --- a/core/src/main/java/in/testpress/database/Room.kt +++ b/core/src/main/java/in/testpress/database/Room.kt @@ -29,8 +29,9 @@ import `in`.testpress.database.roommigration.RoomMigration20To21.MIGRATION_20_21 import `in`.testpress.database.roommigration.RoomMigration21To22.MIGRATION_21_22 import `in`.testpress.database.roommigration.RoomMigration22To23.MIGRATION_22_23 import `in`.testpress.database.roommigration.RoomMigration23To24.MIGRATION_23_24 +import `in`.testpress.database.roommigration.RoomMigration24To25.MIGRATION_24_25 -@Database(version = 24, +@Database(version = 25, entities = [ ContentEntity::class, OfflineVideo::class, @@ -59,7 +60,8 @@ import `in`.testpress.database.roommigration.RoomMigration23To24.MIGRATION_23_24 OfflineExam::class, OfflineCourseAttempt::class, OfflineAttempt::class, - OfflineAttemptSection::class + OfflineAttemptSection::class, + OfflineAttemptItem::class ], exportSchema = true) @TypeConverters(Converters::class) abstract class TestpressDatabase : RoomDatabase() { @@ -84,6 +86,7 @@ abstract class TestpressDatabase : RoomDatabase() { abstract fun offlineCourseAttemptDao():OfflineCourseAttemptDao abstract fun offlineAttemptDao():OfflineAttemptDao abstract fun offlineAttemptSectionDao():OfflineAttemptSectionDao + abstract fun offlineAttemptItemDoa(): OfflineAttemptItemDao companion object { private lateinit var INSTANCE: TestpressDatabase @@ -92,7 +95,8 @@ abstract class TestpressDatabase : RoomDatabase() { MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12, MIGRATION_12_13, MIGRATION_13_14, MIGRATION_14_15, MIGRATION_15_16, MIGRATION_16_17, MIGRATION_17_18, MIGRATION_18_19, - MIGRATION_19_20, MIGRATION_20_21, MIGRATION_21_22, MIGRATION_22_23, MIGRATION_23_24 + MIGRATION_19_20, MIGRATION_20_21, MIGRATION_21_22, MIGRATION_22_23, MIGRATION_23_24, + MIGRATION_24_25 ) operator fun invoke(context: Context): TestpressDatabase { diff --git a/core/src/main/java/in/testpress/database/dao/DirectionDao.kt b/core/src/main/java/in/testpress/database/dao/DirectionDao.kt index c2b014988..1547fda66 100644 --- a/core/src/main/java/in/testpress/database/dao/DirectionDao.kt +++ b/core/src/main/java/in/testpress/database/dao/DirectionDao.kt @@ -3,6 +3,11 @@ package `in`.testpress.database.dao import `in`.testpress.database.BaseDao import `in`.testpress.database.entities.Direction import androidx.room.Dao +import androidx.room.Query @Dao -interface DirectionDao: BaseDao \ No newline at end of file +interface DirectionDao : BaseDao { + + @Query("SELECT * FROM Direction WHERE id = :directionId") + suspend fun getDirectionById(directionId: Long): Direction? +} \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/dao/ExamQuestionDao.kt b/core/src/main/java/in/testpress/database/dao/ExamQuestionDao.kt index 17ae31afb..e65b2c2a9 100644 --- a/core/src/main/java/in/testpress/database/dao/ExamQuestionDao.kt +++ b/core/src/main/java/in/testpress/database/dao/ExamQuestionDao.kt @@ -10,4 +10,10 @@ interface ExamQuestionDao: BaseDao { @Query("DELETE FROM ExamQuestion WHERE examId = :examId") suspend fun deleteByExamId(examId: Long) + + @Query("SELECT DISTINCT sectionId FROM ExamQuestion WHERE examId = :examId") + suspend fun getUniqueSectionIdsByExamId(examId: Long): List + + @Query("SELECT * FROM ExamQuestion WHERE examId = :examId ORDER BY `order`") + suspend fun getExamQuestionsByExamId(examId: Long): List } \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/dao/OfflineAttemptItemDao.kt b/core/src/main/java/in/testpress/database/dao/OfflineAttemptItemDao.kt new file mode 100644 index 000000000..8b0a77323 --- /dev/null +++ b/core/src/main/java/in/testpress/database/dao/OfflineAttemptItemDao.kt @@ -0,0 +1,21 @@ +package `in`.testpress.database.dao + +import `in`.testpress.database.BaseDao +import `in`.testpress.database.entities.OfflineAttemptItem +import `in`.testpress.models.greendao.Attempt +import androidx.room.Dao +import androidx.room.Query +import androidx.room.Update + +@Dao +interface OfflineAttemptItemDao : BaseDao { + + @Query("SELECT * FROM OfflineAttemptItem WHERE attemptId = :attemptId") + suspend fun getOfflineAttemptItemByAttemptId(attemptId: Long): List + + @Update + suspend fun update(offlineAttemptItem: OfflineAttemptItem) + + @Query("SELECT * FROM OfflineAttemptItem WHERE id = :id") + suspend fun getAttemptItemById(id: Long): OfflineAttemptItem? +} \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/dao/QuestionDao.kt b/core/src/main/java/in/testpress/database/dao/QuestionDao.kt index 3b3370a8c..b60bd1163 100644 --- a/core/src/main/java/in/testpress/database/dao/QuestionDao.kt +++ b/core/src/main/java/in/testpress/database/dao/QuestionDao.kt @@ -3,6 +3,11 @@ package `in`.testpress.database.dao import `in`.testpress.database.BaseDao import `in`.testpress.database.entities.Question import androidx.room.Dao +import androidx.room.Query @Dao -interface QuestionDao: BaseDao \ No newline at end of file +interface QuestionDao : BaseDao { + + @Query("SELECT * FROM Question WHERE id = :id") + suspend fun getQuestionById(id: Long): Question? +} \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/dao/SubjectDao.kt b/core/src/main/java/in/testpress/database/dao/SubjectDao.kt index bca0a237b..18052a5af 100644 --- a/core/src/main/java/in/testpress/database/dao/SubjectDao.kt +++ b/core/src/main/java/in/testpress/database/dao/SubjectDao.kt @@ -3,6 +3,11 @@ package `in`.testpress.database.dao import `in`.testpress.database.BaseDao import `in`.testpress.database.entities.Subject import androidx.room.Dao +import androidx.room.Query @Dao -interface SubjectDao: BaseDao \ No newline at end of file +interface SubjectDao : BaseDao { + + @Query("SELECT * FROM Subject WHERE id = :subjectId") + suspend fun getSubjectById(subjectId: Long): Subject? +} \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/entities/OfflineAttemptItem.kt b/core/src/main/java/in/testpress/database/entities/OfflineAttemptItem.kt new file mode 100644 index 000000000..f3bf90453 --- /dev/null +++ b/core/src/main/java/in/testpress/database/entities/OfflineAttemptItem.kt @@ -0,0 +1,23 @@ +package `in`.testpress.database.entities + +import androidx.room.Entity +import androidx.room.PrimaryKey + +@Entity +data class OfflineAttemptItem( + @PrimaryKey(autoGenerate = true) + var id: Long = 0, + var question: Question, + var selectedAnswers: List = arrayListOf(), + var review: Boolean? = null, + var savedAnswers: List = arrayListOf(), + var order: Int, + var shortText: String? = null, + var currentShortText: String? = null, + var attemptSection: OfflineAttemptSection? = null, + var essayText: String? = null, + var localEssayText: String? = null, + var files: ArrayList = arrayListOf(), + var unSyncedFiles: List = arrayListOf(), + var attemptId: Long +) \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/entities/OfflineUserUploadedFile.kt b/core/src/main/java/in/testpress/database/entities/OfflineUserUploadedFile.kt new file mode 100644 index 000000000..88ca456be --- /dev/null +++ b/core/src/main/java/in/testpress/database/entities/OfflineUserUploadedFile.kt @@ -0,0 +1,7 @@ +package `in`.testpress.database.entities + +data class OfflineUserUploadedFile( + val id: Long? = null, + val url: String? = null, + val path: String? = null +) \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/mapping/OfflineAttemptSectionMapping.kt b/core/src/main/java/in/testpress/database/mapping/OfflineAttemptSectionMapping.kt new file mode 100644 index 000000000..8fd7c1f3f --- /dev/null +++ b/core/src/main/java/in/testpress/database/mapping/OfflineAttemptSectionMapping.kt @@ -0,0 +1,27 @@ +package `in`.testpress.database.mapping + +import `in`.testpress.database.entities.OfflineAttemptSection +import `in`.testpress.models.greendao.AttemptSection + +fun OfflineAttemptSection?.asGreenDoaModel(): AttemptSection? { + if (this == null) return null + val attemptSection = AttemptSection() + attemptSection.id = this.id + attemptSection.state = this.state + attemptSection.questionsUrl = null + attemptSection.startUrl = null + attemptSection.endUrl = null + attemptSection.remainingTime = this.remainingTime + attemptSection.name = this.name + attemptSection.duration = this.duration + attemptSection.order = this.order + attemptSection.instructions = this.instructions + attemptSection.attemptId = this.attemptId + return attemptSection +} + +fun List.asGreenDoaModels(): List { + return this.map { + it?.asGreenDoaModel() + } +} \ No newline at end of file diff --git a/core/src/main/java/in/testpress/database/roommigration/RoomMigration24To25.kt b/core/src/main/java/in/testpress/database/roommigration/RoomMigration24To25.kt new file mode 100644 index 000000000..5f48c1d31 --- /dev/null +++ b/core/src/main/java/in/testpress/database/roommigration/RoomMigration24To25.kt @@ -0,0 +1,12 @@ +package `in`.testpress.database.roommigration + +import androidx.room.migration.Migration +import androidx.sqlite.db.SupportSQLiteDatabase + +object RoomMigration24To25 { + val MIGRATION_24_25: Migration = object : Migration(24, 25) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE IF NOT EXISTS `OfflineAttemptItem` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `question` TEXT NOT NULL, `selectedAnswers` TEXT NOT NULL, `review` INTEGER, `savedAnswers` TEXT NOT NULL, `order` INTEGER NOT NULL, `shortText` TEXT, `currentShortText` TEXT, `attemptSection` TEXT, `essayText` TEXT, `localEssayText` TEXT, `files` TEXT NOT NULL, `unSyncedFiles` TEXT NOT NULL, `attemptId` INTEGER NOT NULL)") + } + } +} diff --git a/core/src/main/java/in/testpress/util/Converters.kt b/core/src/main/java/in/testpress/util/Converters.kt index 35cffd55b..1545fd129 100644 --- a/core/src/main/java/in/testpress/util/Converters.kt +++ b/core/src/main/java/in/testpress/util/Converters.kt @@ -1,6 +1,8 @@ package `in`.testpress.util import `in`.testpress.database.entities.Answer +import `in`.testpress.database.entities.OfflineAttemptSection +import `in`.testpress.database.entities.OfflineUserUploadedFile import `in`.testpress.database.entities.Question import androidx.room.TypeConverter import com.google.gson.Gson @@ -40,8 +42,10 @@ object Converters { @TypeConverter @JvmStatic - fun toIntList(value: String?): List? { - return value?.split(",")?.map { it.toInt() } + fun toIntList(value: String?): List { + if (value == null) return listOf() + if (value.isEmpty()) return listOf() + return value.split(",").map { it.toInt() } } @TypeConverter @@ -71,4 +75,46 @@ object Converters { val listType = object : TypeToken>() {}.type return Gson().fromJson(value, listType) } + + @TypeConverter + @JvmStatic + fun fromOfflineUserUploadedFileList(value: ArrayList?): String? { + val gson = Gson() + return gson.toJson(value) + } + + @TypeConverter + @JvmStatic + fun toOfflineUserUploadedFileList(value: String?): ArrayList? { + val listType = object : TypeToken>() {}.type + return Gson().fromJson(value, listType) + } + + @TypeConverter + @JvmStatic + fun fromQuestion(value: Question?): String? { + val gson = Gson() + return gson.toJson(value) + } + + @TypeConverter + @JvmStatic + fun toQuestion(value: String?): Question? { + val questionType = object : TypeToken() {}.type + return Gson().fromJson(value, questionType) + } + + @TypeConverter + @JvmStatic + fun fromOfflineAttemptSection(value: OfflineAttemptSection?): String? { + val gson = Gson() + return gson.toJson(value) + } + + @TypeConverter + @JvmStatic + fun toOfflineAttemptSection(value: String?): OfflineAttemptSection? { + val offlineAttemptSectionType = object : TypeToken() {}.type + return Gson().fromJson(value, offlineAttemptSectionType) + } } diff --git a/exam/src/main/java/in/testpress/exam/pager/TestQuestionsPager.java b/exam/src/main/java/in/testpress/exam/pager/TestQuestionsPager.java deleted file mode 100644 index 31934513a..000000000 --- a/exam/src/main/java/in/testpress/exam/pager/TestQuestionsPager.java +++ /dev/null @@ -1,36 +0,0 @@ -package in.testpress.exam.pager; - -import java.io.IOException; - -import in.testpress.exam.models.AttemptItem; -import in.testpress.exam.api.TestpressExamApiClient; -import in.testpress.models.TestpressApiResponse; -import in.testpress.network.BaseResourcePager; -import retrofit2.Response; - -public class TestQuestionsPager extends BaseResourcePager { - - private TestpressExamApiClient apiClient; - private final String questionsUrlFrag; - - public TestQuestionsPager(String questionsUrlFrag, TestpressExamApiClient apiClient) { - this.apiClient = apiClient; - this.questionsUrlFrag = questionsUrlFrag; - } - - @Override - protected Object getId(AttemptItem resource) { - return resource.getUrl(); - } - - @Override - public Response> getItems(int page, int size) throws IOException { - queryParams.put(TestpressExamApiClient.PAGE, page); - return apiClient.getQuestions(questionsUrlFrag, queryParams).execute(); - } - - public TestpressApiResponse getResponse() { - return response; - } - -} diff --git a/exam/src/main/java/in/testpress/exam/repository/AttemptRepository.kt b/exam/src/main/java/in/testpress/exam/repository/AttemptRepository.kt new file mode 100644 index 000000000..90080177e --- /dev/null +++ b/exam/src/main/java/in/testpress/exam/repository/AttemptRepository.kt @@ -0,0 +1,200 @@ +package `in`.testpress.exam.repository + +import `in`.testpress.core.TestpressCallback +import `in`.testpress.core.TestpressException +import `in`.testpress.database.TestpressDatabase +import `in`.testpress.database.entities.OfflineAttemptItem +import `in`.testpress.exam.api.TestpressExamApiClient +import `in`.testpress.exam.models.AttemptItem +import `in`.testpress.exam.network.NetworkAttemptSection +import `in`.testpress.exam.ui.TestFragment.Action +import `in`.testpress.exam.util.asAttemptItem +import `in`.testpress.models.TestpressApiResponse +import `in`.testpress.models.greendao.Attempt +import `in`.testpress.models.greendao.Exam +import `in`.testpress.network.Resource +import android.content.Context +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class AttemptRepository(val context: Context) { + + lateinit var exam : Exam + lateinit var attempt: Attempt + private val isOfflineExam: Boolean get() = exam.isOfflineExam + var page = 1 + val attemptItem = mutableListOf() + private var _totalQuestions = 0 + val totalQuestions get() = _totalQuestions + + private val database = TestpressDatabase.invoke(context) + private val examQuestionDao = database.examQuestionDao() + private val questionDao = database.questionDao() + private val offlineAttemptItemDao = database.offlineAttemptItemDoa() + private val subjectDao = database.subjectDao() + private val directionDao = database.directionDao() + private val languageDao = database.languageDao() + + + private val apiClient: TestpressExamApiClient = TestpressExamApiClient(context) + private val _attemptItemsResource = MutableLiveData>>() + val attemptItemsResource: LiveData>> get() = _attemptItemsResource + + private val _saveResultResource = MutableLiveData>>() + val saveResultResource: LiveData>> get() = _saveResultResource + + private val _updateSectionResource = MutableLiveData>>() + val updateSectionResource: LiveData>> get() = _updateSectionResource + + fun fetchAttemptItems(questionsUrlFrag: String, fetchSinglePageOnly: Boolean) { + _attemptItemsResource.postValue(Resource.loading(null)) + + if (isOfflineExam){ + createOfflineAttemptItemItem() + } else { + val queryParams = hashMapOf("page" to page) + apiClient.getQuestions(questionsUrlFrag, queryParams) + .enqueue(object : TestpressCallback>() { + override fun onSuccess(result: TestpressApiResponse) { + if (fetchSinglePageOnly) { + _totalQuestions = result.count + attemptItem.addAll(result.results) + _attemptItemsResource.postValue(Resource.success(attemptItem)) + if (result.hasMore()) { + page++ + } + return + } + if (result.hasMore()) { + _totalQuestions = result.count + attemptItem.addAll(result.results) + page++ + fetchAttemptItems(questionsUrlFrag, fetchSinglePageOnly) + } else { + attemptItem.addAll(result.results) + _attemptItemsResource.postValue(Resource.success(attemptItem)) + } + } + + override fun onException(exception: TestpressException) { + _attemptItemsResource.postValue(Resource.error(exception, null)) + } + + }) + } + } + + private fun createOfflineAttemptItemItem() { + CoroutineScope(Dispatchers.IO).launch { + val hasSections = examQuestionDao.getUniqueSectionIdsByExamId(exam.id).count() > 1 + //if (hasSections){ + + //} else { + createOfflineAttemptForAllQuestions() + //} + + } + } + + private suspend fun createOfflineAttemptForAllQuestions() { + val examQuestions = examQuestionDao.getExamQuestionsByExamId(exam.id) + val offlineAttemptItems = examQuestions.map { examQuestion -> + OfflineAttemptItem( + question = questionDao.getQuestionById(examQuestion.questionId!!)!!, + order = examQuestion.order!!, + attemptId = attempt.id + ) + } + offlineAttemptItemDao.insertAll(offlineAttemptItems) + createAttemptItem() + } + + private suspend fun createAttemptItem(){ + val offlineAttemptItems = offlineAttemptItemDao.getOfflineAttemptItemByAttemptId(attempt.id) + val attemptItems = offlineAttemptItems.map { offlineAttemptItem -> + val subject = offlineAttemptItem.question.subjectId?.let { subjectDao.getSubjectById(it) } + val direction = offlineAttemptItem.question.directionId?.let { directionDao.getDirectionById(it) } + val subjectName = subject?.name ?: "Uncategorized" + val directionHtml = direction?.html + offlineAttemptItem.asAttemptItem( + subjectName, + directionHtml, + ) + } + _attemptItemsResource.postValue(Resource.success(attemptItems)) + } + + suspend fun saveAnswer(position: Int, attemptItem: AttemptItem, action: Action) { + if (isOfflineExam){ + updateLocalAttemptItem(attemptItem) { updateAttemptItem -> + _saveResultResource.postValue(Resource.success(Triple(position, updateAttemptItem, action))) + } + } else { + apiClient.postAnswer(attemptItem).enqueue(object : TestpressCallback() { + override fun onSuccess(result: AttemptItem) { + _saveResultResource.postValue(Resource.success(Triple(position, result, action))) + } + + override fun onException(exception: TestpressException) { + _saveResultResource.postValue( + Resource.error( + exception, + Triple(position, null, action) + ) + ) + } + }) + } + } + + private suspend fun updateLocalAttemptItem( + attemptItem: AttemptItem, + callback: (AttemptItem) -> Unit + ) { + val offlineAttemptItem = + offlineAttemptItemDao.getAttemptItemById(attemptItem.id.toLong()) + if (offlineAttemptItem != null) { + if (attemptItem.attemptQuestion.type == "E") { + offlineAttemptItem.localEssayText = attemptItem.localEssayText + } else { + offlineAttemptItem.selectedAnswers = attemptItem.savedAnswers + offlineAttemptItem.savedAnswers = attemptItem.savedAnswers + offlineAttemptItem.currentShortText = attemptItem.currentShortText + } + offlineAttemptItem.unSyncedFiles = attemptItem.unSyncedFiles + offlineAttemptItem.review = attemptItem.currentReview + offlineAttemptItemDao.update(offlineAttemptItem) + } + val updatedAttemptItem = offlineAttemptItem!!.asAttemptItem( + attemptItem.attemptQuestion.subject, + attemptItem.attemptQuestion.direction + ) + callback.invoke(updatedAttemptItem) + } + + fun updateSection(url: String, action: Action) { + _updateSectionResource.postValue(Resource.loading(Pair(null, action))) + apiClient.updateSection(url).enqueue(object : TestpressCallback() { + override fun onSuccess(result: NetworkAttemptSection) { + _updateSectionResource.postValue(Resource.success(Pair(result, action))) + } + + override fun onException(exception: TestpressException) { + _updateSectionResource.postValue(Resource.error(exception, Pair(null, action))) + } + }) + } + + fun clearAttemptItem() { + attemptItem.clear() + } + + fun resetPageCount() { + page = 1 + } + +} \ No newline at end of file diff --git a/exam/src/main/java/in/testpress/exam/ui/TestFragment.java b/exam/src/main/java/in/testpress/exam/ui/TestFragment.java index b46740998..a6a3ced2e 100644 --- a/exam/src/main/java/in/testpress/exam/ui/TestFragment.java +++ b/exam/src/main/java/in/testpress/exam/ui/TestFragment.java @@ -15,14 +15,14 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.loader.app.LoaderManager; +import androidx.lifecycle.Observer; import androidx.core.content.ContextCompat; -import androidx.loader.content.Loader; import androidx.viewpager.widget.ViewPager; import androidx.slidingpanelayout.widget.SlidingPaneLayout; import androidx.appcompat.app.AlertDialog; import android.text.Html; +import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -51,10 +51,9 @@ import in.testpress.exam.models.AttemptItem; import in.testpress.exam.network.NetworkAttemptSection; import in.testpress.exam.network.NetworkAttemptSectionKt; -import in.testpress.exam.pager.TestQuestionsPager; import in.testpress.exam.api.TestpressExamApiClient; -import in.testpress.exam.ui.loaders.AttemptItemsLoader; import in.testpress.exam.ui.view.NonSwipeableViewPager; +import in.testpress.exam.ui.viewmodel.AttemptViewModel; import in.testpress.models.InstituteSettings; import in.testpress.models.greendao.Attempt; import in.testpress.models.greendao.AttemptSection; @@ -62,21 +61,23 @@ import in.testpress.models.greendao.CourseAttempt; import in.testpress.models.greendao.Exam; import in.testpress.models.greendao.Language; +import in.testpress.network.Resource; import in.testpress.network.RetrofitCall; import in.testpress.ui.BaseFragment; import in.testpress.ui.ExploreSpinnerAdapter; import in.testpress.util.CommonUtils; import in.testpress.util.EventsTrackerFacade; -import in.testpress.util.ThrowableLoader; import in.testpress.util.UIUtils; import in.testpress.util.ViewUtils; +import kotlin.Pair; +import kotlin.Triple; import static in.testpress.exam.ui.TestActivity.PARAM_COURSE_ATTEMPT; import static in.testpress.exam.ui.TestActivity.PARAM_COURSE_CONTENT; import static in.testpress.models.greendao.Attempt.COMPLETED; import static in.testpress.models.greendao.Attempt.NOT_STARTED; -public class TestFragment extends BaseFragment implements LoaderManager.LoaderCallbacks>, +public class TestFragment extends BaseFragment implements PlainSpinnerItemAdapter.SectionInfoClickListener, TestPanelListAdapter.ListItemClickListener { private static final int APP_BACKGROUND_DELAY = 60000; // 1m @@ -115,7 +116,6 @@ public class TestFragment extends BaseFragment implements LoaderManager.LoaderCa private CourseAttempt courseAttempt; private int currentQuestionIndex; List sections = new ArrayList<>(); - public TestQuestionsPager questionsResourcePager; List attemptItemList = new ArrayList<>(); CountDownTimer countDownTimer; long millisRemaining = -1; @@ -130,7 +130,7 @@ public class TestFragment extends BaseFragment implements LoaderManager.LoaderCa */ private HashMap plainSpinnerItemOffsets = new HashMap<>(); - private enum Action { PAUSE, END, UPDATE_ANSWER, END_SECTION } + public static enum Action { PAUSE, END, UPDATE_ANSWER, END_SECTION, START_SECTION } private RetrofitCall heartBeatApiRequest; private RetrofitCall endSectionApiRequest; private RetrofitCall startSectionApiRequest; @@ -144,19 +144,19 @@ public void run() { stopTimerOnAppWentBackground(); } }; - public int totalQuestions = 0; - private boolean isNextPageQuestionsBeingFetched = false; private InstituteSettings instituteSettings; private EventsTrackerFacade eventsTrackerFacade; + private AttemptViewModel attemptViewModel; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); + attemptViewModel = AttemptViewModel.Companion.initializeViewModel(requireActivity()); initializeAttemptAndExamVariables(savedInstanceState); - initializeResourcePager(); instituteSettings = TestpressSdk.getTestpressSession(getContext()).getInstituteSettings(); eventsTrackerFacade = new EventsTrackerFacade(getContext()); logEvent(EventsTrackerFacade.STARTED_EXAM); + apiClient = new TestpressExamApiClient(getActivity()); } private void logEvent(String name) { @@ -178,23 +178,14 @@ private void initializeAttemptAndExamVariables(Bundle savedInstanceState) { attempt = getArguments().getParcelable(PARAM_ATTEMPT); exam = getArguments().getParcelable(PARAM_EXAM); } - + exam.setIsOfflineExam(true); + attemptViewModel.setExamAndAttempt(exam, attempt); if (savedInstanceState != null && savedInstanceState.getParcelable(PARAM_ATTEMPT) != null) { attempt = savedInstanceState.getParcelable(PARAM_ATTEMPT); } sections = attempt.getSections(); } - private void initializeResourcePager() { - String questionUrl = attempt.getQuestionsUrlFrag(); - if (attempt.hasSectionalLock()) { - questionUrl = sections.get(attempt.getCurrentSectionPosition()).getQuestionsUrlFrag(); - } - questionUrl = questionUrl.replace("v2.3", "v2.2.1"); - apiClient = new TestpressExamApiClient(getActivity()); - questionsResourcePager = new TestQuestionsPager(questionUrl, apiClient); - } - @Override public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -220,6 +211,9 @@ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceStat if (exam == null && attempt.getRemainingTime().equals(DEFAULT_EXAM_TIME)) { view.findViewById(R.id.timer).setVisibility(View.GONE); } + observeAttemptItemResources(); + observeSaveAnswerResource(); + observeUpdateSectionResource(); } private void initializeLanguageFilter() { @@ -468,14 +462,15 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse @Override public void onPageSelected(int position) { + attemptViewModel.setCurrentQuestionPosition(position); if ( - (viewPagerAdapter.getCount() < totalQuestions) - && ((viewPagerAdapter.getCount() - position) <= 4) - && !isNextPageQuestionsBeingFetched + (viewPagerAdapter.getCount() < attemptViewModel.getTotalQuestions()) + && ((viewPagerAdapter.getCount() - position) <= 4) + && !attemptViewModel.isNextPageQuestionsBeingFetched() ) { - isNextPageQuestionsBeingFetched = true; + attemptViewModel.setNextPageQuestionsBeingFetched(true); questionsListProgressBar.setVisibility(View.VISIBLE); - getLoaderManager().restartLoader(0, null, TestFragment.this); + fetchAttemptItems(); } goToQuestion(position, true); } @@ -516,16 +511,16 @@ public void onScrollStateChanged(AbsListView absListView, int i) { @Override public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItems, int totalItems) { - boolean hasAllQuestionsFetched = (totalItems == totalQuestions); - if (hasAllQuestionsFetched || isNextPageQuestionsBeingFetched) { + boolean hasAllQuestionsFetched = (totalItems == attemptViewModel.getTotalQuestions()); + if (hasAllQuestionsFetched || attemptViewModel.isNextPageQuestionsBeingFetched()) { return; } if ((totalItems - firstVisibleItem) == visibleItems) { - if (attemptItemList.size() < totalQuestions) { - isNextPageQuestionsBeingFetched = true; + if (attemptItemList.size() < attemptViewModel.getTotalQuestions()) { + attemptViewModel.setNextPageQuestionsBeingFetched(true); questionsListProgressBar.setVisibility(View.VISIBLE); - getLoaderManager().restartLoader(0, null, TestFragment.this); + fetchAttemptItems(); } } } @@ -573,7 +568,7 @@ private void goToQuestion(int position, boolean saveCurrentOptions) { } currentQuestionIndex = position; questionsListAdapter.setCurrentAttemptItemIndex(position + 1); - if (slidingPaneLayout.isOpen() && !isNextPageQuestionsBeingFetched) { + if (slidingPaneLayout.isOpen() && !attemptViewModel.isNextPageQuestionsBeingFetched()) { slidingPaneLayout.closePane(); } if(plainSpinnerAdapter != null && plainSpinnerAdapter.getCount() > 1) { @@ -695,122 +690,142 @@ void pauseExam() { saveResult(viewPager.getCurrentItem(), Action.PAUSE); } - @NonNull - @Override - public Loader> onCreateLoader(int id, final Bundle args) { - if (attempt.hasSectionalLock()) { - progressDialog.setMessage(getString(R.string.testpress_loading_section_questions, - sections.get(attempt.getCurrentSectionPosition()).getName())); + private void observeAttemptItemResources(){ + attemptViewModel.getAttemptItemsResource().observe(requireActivity(), new Observer>>() { + @Override + public void onChanged(Resource> listResource) { + switch (listResource.getStatus()){ + case SUCCESS:{ + if (getActivity() == null) { + return; + } + if (progressDialog.isShowing()) { + progressDialog.dismiss(); + } - progressDialog.show(); - } else { - showProgress(R.string.testpress_loading_questions); - } - boolean fetchSinglePageOnly = attempt.hasSectionalLock(); - return new AttemptItemsLoader(getActivity(), this, fetchSinglePageOnly); - } + if (listResource.getData() == null || listResource.getData().isEmpty()) { // Display alert if no questions exist + new AlertDialog.Builder(getActivity(), R.style.TestpressAppCompatAlertDialogStyle) + .setTitle(R.string.testpress_no_questions) + .setMessage(R.string.testpress_no_questions_message) + .setCancelable(false) + .setNeutralButton(R.string.testpress_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + returnToHistory(); + } + }) + .show(); + return; + } - @Override - public void onLoadFinished(@NonNull final Loader> loader, - final List items) { + attemptItemList = listResource.getData(); + if (attempt.getRemainingTime() == null || attempt.getRemainingTime().equals("0:00:00")) { + endExam(); + return; + } + if (exam != null){ + initializeSectionSpinner(); + } - if (getActivity() == null) { - return; - } - if (progressDialog.isShowing()) { - progressDialog.dismiss(); - } - getLoaderManager().destroyLoader(loader.getId()); - //noinspection ThrowableResultOfMethodCallIgnored - TestpressException exception = ((ThrowableLoader>) loader).clearException(); - if(exception != null) { - AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), - R.style.TestpressAppCompatAlertDialogStyle); - builder.setPositiveButton(R.string.testpress_retry_again, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - progressDialog.show(); - getLoaderManager().restartLoader(loader.getId(), null, TestFragment.this); - } - }); - if (exception.isUnauthenticated()) { - builder.setTitle(R.string.testpress_authentication_failed); - builder.setMessage(R.string.testpress_please_login); - } else if (exception.isNetworkError()) { - builder.setTitle(R.string.testpress_network_error); - builder.setMessage(R.string.testpress_no_internet_try_again); - } else { - builder.setTitle(R.string.testpress_error_loading_questions); - builder.setMessage(R.string.testpress_some_thing_went_wrong_try_again); - } - builder.setNegativeButton(R.string.testpress_cancel, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - returnToHistory(); - } - }); - builder.setCancelable(false); - networkErrorAlertDialog = builder.show(); - return; - } + for (int i = 0; i< attemptItemList.size(); i++) { + attemptItemList.get(i).setIndex(i + 1); + } + questionsListAdapter.setItems(attemptItemList); - if (items == null || items.isEmpty()) { // Display alert if no questions exist - new AlertDialog.Builder(getActivity(), R.style.TestpressAppCompatAlertDialogStyle) - .setTitle(R.string.testpress_no_questions) - .setMessage(R.string.testpress_no_questions_message) - .setCancelable(false) - .setNeutralButton(R.string.testpress_ok, new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialogInterface, int i) { - returnToHistory(); + int currentQuestion = 0; + if (attemptViewModel.isNextPageQuestionsBeingFetched()) { + if (viewPager != null) { + currentQuestion = viewPager.getCurrentItem(); + } + updatePanel(); + } else { + questionsListView.setAdapter(questionsListAdapter); + startCountDownTimer(); } - }) - .show(); - return; - } + viewPagerAdapter = + new TestQuestionPagerAdapter(getFragmentManager(), attemptItemList, selectedLanguage, exam); - attemptItemList = items; - if (attempt.getRemainingTime() == null || attempt.getRemainingTime().equals("0:00:00")) { - endExam(); - return; - } - if (exam != null){ - initializeSectionSpinner(); - } + viewPager.setAdapter(viewPagerAdapter); - for (int i = 0; i< attemptItemList.size(); i++) { - attemptItemList.get(i).setIndex(i + 1); - } - questionsListAdapter.setItems(attemptItemList); + if (attemptViewModel.isNextPageQuestionsBeingFetched() || viewPager.getCurrentItem() != 0) { + viewPager.setCurrentItem(currentQuestion); + } else { + goToQuestion(0, false); + } + + if (attempt.getLastViewedQuestionId() != null){ + Integer position = getLastViewedQuestionIndex(); + viewPager.setCurrentItem(position); + } - int currentQuestion = 0; - if (isNextPageQuestionsBeingFetched) { - if (viewPager != null) { - currentQuestion = viewPager.getCurrentItem(); + questionsListProgressBar.setVisibility(View.GONE); + attemptViewModel.setNextPageQuestionsBeingFetched(false); + break; + } + case LOADING:{ + progressDialog.show(); + break; + } + case ERROR:{ + if (getActivity() == null) { + return; + } + if (progressDialog.isShowing()) { + progressDialog.dismiss(); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), + R.style.TestpressAppCompatAlertDialogStyle); + builder.setPositiveButton(R.string.testpress_retry_again, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + progressDialog.show(); + fetchAttemptItems(); + } + }); + if (listResource.getException().isUnauthenticated()) { + builder.setTitle(R.string.testpress_authentication_failed); + builder.setMessage(R.string.testpress_please_login); + } else if (listResource.getException().isNetworkError()) { + builder.setTitle(R.string.testpress_network_error); + builder.setMessage(R.string.testpress_no_internet_try_again); + } else { + builder.setTitle(R.string.testpress_error_loading_questions); + builder.setMessage(R.string.testpress_some_thing_went_wrong_try_again); + } + builder.setNegativeButton(R.string.testpress_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialogInterface, int i) { + returnToHistory(); + } + }); + builder.setCancelable(false); + networkErrorAlertDialog = builder.show(); + break; + } + } } - updatePanel(); - } else { - questionsListView.setAdapter(questionsListAdapter); - startCountDownTimer(); + }); + } + + private void fetchAttemptItems(){ + String questionUrl = attempt.getQuestionsUrlFrag(); + if (attempt.hasSectionalLock()) { + questionUrl = sections.get(attempt.getCurrentSectionPosition()).getQuestionsUrlFrag(); } - viewPagerAdapter = - new TestQuestionPagerAdapter(getFragmentManager(), attemptItemList, selectedLanguage, exam); + questionUrl = questionUrl.replace("v2.3", "v2.2.1"); - viewPager.setAdapter(viewPagerAdapter); + if (attempt.hasSectionalLock()) { + progressDialog.setMessage(getString(R.string.testpress_loading_section_questions, + sections.get(attempt.getCurrentSectionPosition()).getName())); - if (isNextPageQuestionsBeingFetched || viewPager.getCurrentItem() != 0) { - viewPager.setCurrentItem(currentQuestion); + progressDialog.show(); } else { - goToQuestion(0, false); - } - - if (attempt.getLastViewedQuestionId() != null){ - Integer position = getLastViewedQuestionIndex(); - viewPager.setCurrentItem(position); + showProgress(R.string.testpress_loading_questions); } + boolean fetchSinglePageOnly = attempt.hasSectionalLock(); - questionsListProgressBar.setVisibility(View.GONE); - isNextPageQuestionsBeingFetched = false; + attemptViewModel.fetchAttemptItems(questionUrl, fetchSinglePageOnly); } private void initializeSectionSpinner() { @@ -873,10 +888,6 @@ void groupAttemptItems(String spinnerItem, AttemptItem attemptItem, List } } - @Override - public void onLoaderReset(@NonNull final Loader> loader) { - } - private void returnToHistory() { if (getActivity() == null) { return; @@ -898,82 +909,95 @@ private void saveResult(final int position, final Action action) { return; } final AttemptItem attemptItem = attemptItemList.get(position); - final int currentSectionPosition = attempt.getCurrentSectionPosition(); - if (attemptItem.hasChanged()) { if (action != Action.UPDATE_ANSWER) { showProgress(R.string.testpress_saving_last_change); } - apiClient.postAnswer(attemptItem) - .enqueue(new TestpressCallback() { - @Override - public void onSuccess(AttemptItem newAttemptItem) { - if (getActivity() == null) { - return; - } - attemptItem.setSelectedAnswers(newAttemptItem.getSelectedAnswers()); - attemptItem.setShortText(newAttemptItem.getShortText()); - attemptItem.setReview(newAttemptItem.getReview()); - attemptItem.setFiles(newAttemptItem.getFiles()); - attemptItem.setEssayText(newAttemptItem.getEssayText()); - - if (isNonSectionalOrIBPSExam() || (attempt.hasSectionalLock() && sections.get(currentSectionPosition).equals("Running"))) { - attemptItemList.set(position, attemptItem); - } + attemptViewModel.saveAnswer(position,attemptItem,action); + } else if (action.equals(Action.PAUSE)) { + progressDialog.dismiss(); + returnToHistory(); + } + } - if (action.equals(Action.PAUSE)) { - progressDialog.dismiss(); - returnToHistory(); - } else if (action.equals(Action.END)) { - endExam(); - } else if (action.equals(Action.END_SECTION)) { - endSection(); - } else { - if (progressDialog.isShowing()) { - startCountDownTimer(millisRemaining); - progressDialog.dismiss(); - } - updatePanel(); - } + private void observeSaveAnswerResource() { + attemptViewModel.getSaveResultResource().observe(requireActivity(), new Observer>>() { + @Override + public void onChanged(Resource> hashMapResource) { + int position = hashMapResource.getData().getFirst(); + AttemptItem newAttemptItem = hashMapResource.getData().getSecond(); + Action action = hashMapResource.getData().getThird(); + final AttemptItem attemptItem = attemptItemList.get(position); + TestpressException exception = hashMapResource.getException(); + switch (hashMapResource.getStatus()) { + case SUCCESS: { + if (getActivity() == null) { + return; + } + attemptItem.setSelectedAnswers(newAttemptItem.getSelectedAnswers()); + attemptItem.setShortText(newAttemptItem.getShortText()); + attemptItem.setReview(newAttemptItem.getReview()); + attemptItem.setFiles(newAttemptItem.getFiles()); + attemptItem.setEssayText(newAttemptItem.getEssayText()); + + if (isNonSectionalOrIBPSExam() || (attempt.hasSectionalLock() && sections.get(attempt.getCurrentSectionPosition()).equals("Running"))) { + attemptItemList.set(position, attemptItem); } - @Override - public void onException(TestpressException exception) { - if (getActivity() == null) { - return; - } - if (action.equals(Action.PAUSE)) { + if (action.equals(Action.PAUSE)) { + progressDialog.dismiss(); + returnToHistory(); + } else if (action.equals(Action.END)) { + endExam(); + } else if (action.equals(Action.END_SECTION)) { + endSection(); + } else { + if (progressDialog.isShowing()) { + startCountDownTimer(millisRemaining); progressDialog.dismiss(); - returnToHistory(); - return; } + updatePanel(); + } + break; + } + case LOADING: { + break; + } + case ERROR: { + if (getActivity() == null) { + return; + } + if (action.equals(Action.PAUSE)) { + progressDialog.dismiss(); + returnToHistory(); + return; + } - TestpressError errorDetails = exception.getErrorBodyAs(exception.getResponse(), TestpressError.class); - - if (exception.isForbidden() && isMaxQuestionsAttemptedError(errorDetails)) { - clearAndLoadSameQuestion(position); - saveAnswerAlertDialog = showMaxQuestionsAttemptedError(errorDetails); - progressDialog.dismiss(); - } else { - stopTimer(); - progressDialog.dismiss(); - TestEngineAlertDialog alertDialog = new TestEngineAlertDialog(exception) { - @Override - protected void onRetry() { - if (action == Action.UPDATE_ANSWER) { - showProgress(R.string.testpress_saving_last_change); - } - saveResult(position, action); + TestpressError errorDetails = exception.getErrorBodyAs(exception.getResponse(), TestpressError.class); + + if (exception.isForbidden() && isMaxQuestionsAttemptedError(errorDetails)) { + clearAndLoadSameQuestion(position); + saveAnswerAlertDialog = showMaxQuestionsAttemptedError(errorDetails); + progressDialog.dismiss(); + } else { + stopTimer(); + progressDialog.dismiss(); + TestEngineAlertDialog alertDialog = new TestEngineAlertDialog(exception) { + @Override + protected void onRetry() { + if (action == Action.UPDATE_ANSWER) { + showProgress(R.string.testpress_saving_last_change); } - }; - saveAnswerAlertDialog = alertDialog.show(); - } + saveResult(position, action); + } + }; + saveAnswerAlertDialog = alertDialog.show(); } - }); - } else if (action.equals(Action.PAUSE)) { - progressDialog.dismiss(); - returnToHistory(); - } + break; + } + } + } + }); } private boolean isMaxQuestionsAttemptedError(TestpressError errorDetails) { @@ -1006,6 +1030,60 @@ public void onClick(DialogInterface dialogInterface, int i) { .show(); } + void observeUpdateSectionResource() { + attemptViewModel.getUpdateSectionResource().observe(requireActivity(), new Observer>>() { + @Override + public void onChanged(Resource> pairResource) { + switch (pairResource.getStatus()){ + case SUCCESS:{ + if (getActivity() == null) { + return; + } + AttemptSection greenDaoAttemptSection = NetworkAttemptSectionKt.createAttemptSection(pairResource.getData().getFirst()); + sections.set(greenDaoAttemptSection.getOrder(), greenDaoAttemptSection); + attempt.setSections(sections); + if (pairResource.getData().getSecond() == Action.END_SECTION){ + attemptViewModel.resetPageCount(); + onSectionEnded(); + } else { + String questionUrl = greenDaoAttemptSection.getQuestionsUrlFrag(); + questionUrl = questionUrl.replace("2.3","2.2"); + attemptViewModel.clearAttemptItem(); + attemptViewModel.fetchAttemptItems(questionUrl, true); + } + break; + } + case LOADING:{ + if (pairResource.getData().getSecond() == Action.END_SECTION){ + showProgress(R.string.testpress_ending_section); + } else { + showProgress(R.string.testpress_starting_section); + } + break; + } + case ERROR:{ + if (pairResource.getData().getSecond() == Action.END_SECTION){ + showException( + pairResource.getException(), + R.string.testpress_exam_paused_check_internet_to_end, + R.string.testpress_end, + "endSection" + ); + } else { + showException( + pairResource.getException(), + R.string.testpress_exam_paused_check_internet, + R.string.testpress_resume, + "startSection" + ); + } + break; + } + } + } + }); + } + void endSection() { stopTimer(); // Save attemptItem, if option or review is changed @@ -1018,35 +1096,12 @@ void endSection() { } } - showProgress(R.string.testpress_ending_section); AttemptSection section = sections.get(attempt.getCurrentSectionPosition()); if (section.getState().equals(COMPLETED)) { onSectionEnded(); return; } - endSectionApiRequest = apiClient.updateSection(section.getEndUrlFrag()) - .enqueue(new TestpressCallback() { - @Override - public void onSuccess(NetworkAttemptSection attemptSection) { - if (getActivity() == null) { - return; - } - AttemptSection greenDaoAttemptSection = NetworkAttemptSectionKt.createAttemptSection(attemptSection); - sections.set(greenDaoAttemptSection.getOrder(), greenDaoAttemptSection); - attempt.setSections(sections); - onSectionEnded(); - } - - @Override - public void onException(TestpressException exception) { - showException( - exception, - R.string.testpress_exam_paused_check_internet_to_end, - R.string.testpress_end, - "endSection" - ); - } - }); + attemptViewModel.updateSection(section.getEndUrlFrag(),Action.END_SECTION); } void onSectionEnded() { @@ -1061,37 +1116,8 @@ void onSectionEnded() { } void startSection() { - showProgress(R.string.testpress_starting_section); String sectionStartUrlFrag = sections.get(attempt.getCurrentSectionPosition()).getStartUrlFrag(); - startSectionApiRequest = apiClient.updateSection(sectionStartUrlFrag) - .enqueue(new TestpressCallback() { - @Override - public void onSuccess(NetworkAttemptSection section) { - if (getActivity() == null) { - return; - } - AttemptSection greenDaoAttemptSection = NetworkAttemptSectionKt.createAttemptSection(section); - sections.set(greenDaoAttemptSection.getOrder(),greenDaoAttemptSection); - attempt.setSections(sections); - String questionUrl = greenDaoAttemptSection.getQuestionsUrlFrag(); - questionUrl = questionUrl.replace("2.3","2.2"); - questionsResourcePager = - new TestQuestionsPager(questionUrl, apiClient); - - attemptItemList.clear(); - getLoaderManager().restartLoader(0, null, TestFragment.this); - } - - @Override - public void onException(TestpressException exception) { - showException( - exception, - R.string.testpress_exam_paused_check_internet, - R.string.testpress_resume, - "startSection" - ); - } - }); + attemptViewModel.updateSection(sectionStartUrlFrag,Action.START_SECTION); } void endExam() { @@ -1286,7 +1312,7 @@ void startCountDownTimer() { if (millisRemaining == 0) { onRemainingTimeOver(); } else if (attemptItemList.isEmpty()) { - getLoaderManager().restartLoader(0, null, this); + fetchAttemptItems(); } else { startCountDownTimer(millisRemaining); } diff --git a/exam/src/main/java/in/testpress/exam/ui/loaders/AttemptItemsLoader.java b/exam/src/main/java/in/testpress/exam/ui/loaders/AttemptItemsLoader.java deleted file mode 100644 index 902c3a8b2..000000000 --- a/exam/src/main/java/in/testpress/exam/ui/loaders/AttemptItemsLoader.java +++ /dev/null @@ -1,36 +0,0 @@ -package in.testpress.exam.ui.loaders; - - -import android.content.Context; -import android.util.Log; - -import java.util.List; - -import in.testpress.core.TestpressException; -import in.testpress.exam.models.AttemptItem; -import in.testpress.exam.ui.TestFragment; -import in.testpress.util.ThrowableLoader; - -public class AttemptItemsLoader extends ThrowableLoader> { - private TestFragment fragment; - private boolean fetchSinglePageOnly; - - public AttemptItemsLoader(Context context, TestFragment fragment, boolean fetchSinglePageOnly) { - super(context, null); - this.fragment = fragment; - this.fetchSinglePageOnly = fetchSinglePageOnly; - } - - @Override - public List loadData() throws TestpressException { - if (fetchSinglePageOnly) { - fragment.questionsResourcePager.next(); - fragment.totalQuestions = fragment.questionsResourcePager.getResponse().getCount(); - } else { - do { - fragment.questionsResourcePager.next(); - } while (fragment.questionsResourcePager.hasNext()); - } - return fragment.questionsResourcePager.getResources(); - } -} \ No newline at end of file diff --git a/exam/src/main/java/in/testpress/exam/ui/viewmodel/AttemptViewModel.kt b/exam/src/main/java/in/testpress/exam/ui/viewmodel/AttemptViewModel.kt new file mode 100644 index 000000000..99d2936c6 --- /dev/null +++ b/exam/src/main/java/in/testpress/exam/ui/viewmodel/AttemptViewModel.kt @@ -0,0 +1,64 @@ +package `in`.testpress.exam.ui.viewmodel + +import `in`.testpress.exam.models.AttemptItem +import `in`.testpress.exam.network.NetworkAttemptSection +import `in`.testpress.exam.repository.AttemptRepository +import `in`.testpress.exam.ui.TestFragment +import `in`.testpress.models.greendao.Attempt +import `in`.testpress.models.greendao.Exam +import `in`.testpress.network.Resource +import androidx.fragment.app.FragmentActivity +import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewModelScope +import kotlinx.coroutines.launch + +class AttemptViewModel(val repository: AttemptRepository) : ViewModel() { + + val attemptItemsResource: LiveData>> get() = repository.attemptItemsResource + + val saveResultResource: LiveData>> get() = repository.saveResultResource + + val updateSectionResource: LiveData>> get() = repository.updateSectionResource + + val totalQuestions: Int get() = repository.totalQuestions + + var isNextPageQuestionsBeingFetched: Boolean = false + var currentQuestionPosition = 0 + + fun setExamAndAttempt(exam: Exam, attempt: Attempt){ + repository.exam = exam + repository.attempt = attempt + } + + fun fetchAttemptItems(questionsUrlFrag: String, fetchSinglePageOnly: Boolean){ + repository.fetchAttemptItems(questionsUrlFrag, fetchSinglePageOnly) + } + + fun saveAnswer(position: Int, attemptItem: AttemptItem, action: TestFragment.Action){ + viewModelScope.launch { + repository.saveAnswer(position, attemptItem, action) + } + } + + fun updateSection(url: String, action: TestFragment.Action){ + repository.updateSection(url, action) + } + + fun clearAttemptItem() = repository.clearAttemptItem() + + fun resetPageCount() = repository.resetPageCount() + + companion object { + fun initializeViewModel(activity: FragmentActivity): AttemptViewModel { + return ViewModelProvider(activity, object : ViewModelProvider.Factory { + override fun create(modelClass: Class): T { + return AttemptViewModel( + AttemptRepository(activity) + ) as T + } + }).get(AttemptViewModel::class.java) + } + } +} \ No newline at end of file diff --git a/exam/src/main/java/in/testpress/exam/util/OfflineAttemptItemMapping.kt b/exam/src/main/java/in/testpress/exam/util/OfflineAttemptItemMapping.kt new file mode 100644 index 000000000..b1bed2e38 --- /dev/null +++ b/exam/src/main/java/in/testpress/exam/util/OfflineAttemptItemMapping.kt @@ -0,0 +1,57 @@ +package `in`.testpress.exam.util + +import `in`.testpress.database.entities.* +import `in`.testpress.database.mapping.asGreenDoaModel +import `in`.testpress.exam.models.AttemptAnswer +import `in`.testpress.exam.models.AttemptItem +import `in`.testpress.exam.models.AttemptQuestion +import `in`.testpress.exam.models.UserUploadedFile + +fun OfflineAttemptItem.asAttemptItem(subject: String, direction: String?): AttemptItem{ + return AttemptItem( + id.toInt(), + null, + question.asAttemptQuestion(subject, direction), + selectedAnswers, + review, + savedAnswers, + order, + null, + shortText, + currentShortText, + attemptSection?.asGreenDoaModel(), + essayText, + localEssayText, + files.asUserUploadedFiles(), + unSyncedFiles + ) +} + +fun Question.asAttemptQuestion(subject: String, direction: String?): AttemptQuestion { + return AttemptQuestion( + questionHtml, + answers.asAttemptAnswers(), + subject, + direction, + type, + language, + translations.asAttemptQuestions(subject, direction) as ArrayList, + marks, + negativeMarks, + ) +} + +fun List.asAttemptQuestions(subject: String, direction: String?) = this.map { it.asAttemptQuestion(subject, direction) } + +fun Answer.asAttemptAnswer(): AttemptAnswer { + return AttemptAnswer( + this.textHtml!!, + this.saveId?.toInt() ?: this.id?.toInt() + ) +} + +fun List.asAttemptAnswers() = this.map { it.asAttemptAnswer() } + +fun OfflineUserUploadedFile.asUserUploadedFile() = UserUploadedFile(id, url, path) + +fun List.asUserUploadedFiles() = this.map { it.asUserUploadedFile() } \ No newline at end of file