diff --git a/app/build.gradle b/app/build.gradle index 0e51f48618..d3c95e952d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,5 @@ plugins { - id "com.diffplug.gradle.spotless" version "3.16.0" + id "com.diffplug.gradle.spotless" version "3.23.0" } apply plugin: 'com.android.application' @@ -20,8 +20,8 @@ android { applicationId "com.eventyay.attendee" minSdkVersion 21 targetSdkVersion 28 - versionCode 8 - versionName "0.2.1" + versionCode 9 + versionName "0.3.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true multiDexEnabled true @@ -95,20 +95,20 @@ repositories { } dependencies { - def lifecycle_version = "2.1.0-alpha04" - def koin_version = "1.0.2" - def roomVersion = "2.1.0-alpha07" + def lifecycle_version = "2.2.0-alpha01" + def koin_version = "2.0.0-GA6" + def roomVersion = "2.1.0-beta01" def ktx_version = "1.0.0" def ktx2_version = "2.0.0" - def nav_version = "2.1.0-alpha02" + def nav_version = "2.1.0-alpha04" def anko_version = "0.10.8" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.multidex:multidex:2.0.1' - implementation 'androidx.appcompat:appcompat:1.1.0-alpha04' - implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha5' + implementation 'androidx.appcompat:appcompat:1.1.0-alpha05' + implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta1' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha04' + implementation 'androidx.recyclerview:recyclerview:1.1.0-alpha05' implementation 'com.google.android.material:material:1.1.0-alpha06' implementation "androidx.browser:browser:1.0.0" implementation 'androidx.exifinterface:exifinterface:1.0.0' @@ -143,7 +143,7 @@ dependencies { implementation 'com.jakewharton.timber:timber:4.7.1' implementation 'com.jakewharton.threetenabp:threetenabp:1.2.0' - implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.8" + implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.9" implementation 'com.github.jasminb:jsonapi-converter:0.9' implementation 'com.squareup.okhttp3:logging-interceptor:3.14.1' implementation 'com.squareup.retrofit2:retrofit:2.5.0' @@ -162,7 +162,7 @@ dependencies { implementation 'com.squareup.picasso:picasso:2.71828' // Stripe - implementation 'com.stripe:stripe-android:8.7.0' + implementation 'com.stripe:stripe-android:9.0.1' // QR Code implementation 'com.journeyapps:zxing-android-embedded:3.6.0' @@ -176,7 +176,7 @@ dependencies { implementation "org.jetbrains.anko:anko-design:$anko_version" //Mapbox java sdk - implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:4.7.0' + implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:4.8.0' // Stetho debugImplementation 'com.facebook.stetho:stetho:1.5.1' @@ -184,9 +184,12 @@ dependencies { releaseImplementation 'com.github.iamareebjamal:stetho-noop:1.2.1' testImplementation 'com.github.iamareebjamal:stetho-noop:1.2.1' + //LeakCanary + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-alpha-1' + testImplementation 'junit:junit:4.12' testImplementation "io.mockk:mockk:1.9.3" - testImplementation 'org.threeten:threetenbp:1.3.8' + testImplementation 'org.threeten:threetenbp:1.4.0' testImplementation "org.koin:koin-test:$koin_version" testImplementation 'androidx.arch.core:core-testing:2.0.1' androidTestImplementation 'androidx.test:runner:1.1.1' diff --git a/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/1.json b/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/1.json deleted file mode 100644 index 51d7dd1a55..0000000000 --- a/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/1.json +++ /dev/null @@ -1,1077 +0,0 @@ -{ - "formatVersion": 1, - "database": { - "version": 1, - "identityHash": "0b3cd25764884626e03f56b0600d1c76", - "entities": [ - { - "tableName": "Event", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `identifier` TEXT NOT NULL, `startsAt` TEXT NOT NULL, `endsAt` TEXT NOT NULL, `timezone` TEXT NOT NULL, `privacy` TEXT NOT NULL, `paymentCountry` TEXT, `paypalEmail` TEXT, `thumbnailImageUrl` TEXT, `schedulePublishedOn` TEXT, `paymentCurrency` TEXT, `organizerDescription` TEXT, `originalImageUrl` TEXT, `onsiteDetails` TEXT, `organizerName` TEXT, `largeImageUrl` TEXT, `deletedAt` TEXT, `ticketUrl` TEXT, `locationName` TEXT, `codeOfConduct` TEXT, `state` TEXT, `searchableLocationName` TEXT, `description` TEXT, `pentabarfUrl` TEXT, `xcalUrl` TEXT, `logoUrl` TEXT, `externalEventUrl` TEXT, `iconImageUrl` TEXT, `icalUrl` TEXT, `createdAt` TEXT, `bankDetails` TEXT, `chequeDetails` TEXT, `isComplete` INTEGER NOT NULL, `latitude` REAL, `longitude` REAL, `refundPolicy` TEXT, `canPayByStripe` INTEGER NOT NULL, `canPayByCheque` INTEGER NOT NULL, `canPayByBank` INTEGER NOT NULL, `canPayByPaypal` INTEGER NOT NULL, `canPayOnsite` INTEGER NOT NULL, `isSponsorsEnabled` INTEGER NOT NULL, `hasOrganizerInfo` INTEGER NOT NULL, `isSessionsSpeakersEnabled` INTEGER NOT NULL, `isTicketingEnabled` INTEGER NOT NULL, `isTaxEnabled` INTEGER NOT NULL, `isMapShown` INTEGER NOT NULL, `favorite` INTEGER NOT NULL, `eventTopic` INTEGER, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "identifier", - "columnName": "identifier", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "startsAt", - "columnName": "startsAt", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "endsAt", - "columnName": "endsAt", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "timezone", - "columnName": "timezone", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "privacy", - "columnName": "privacy", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "paymentCountry", - "columnName": "paymentCountry", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "paypalEmail", - "columnName": "paypalEmail", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "thumbnailImageUrl", - "columnName": "thumbnailImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "schedulePublishedOn", - "columnName": "schedulePublishedOn", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "paymentCurrency", - "columnName": "paymentCurrency", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "organizerDescription", - "columnName": "organizerDescription", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "originalImageUrl", - "columnName": "originalImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "onsiteDetails", - "columnName": "onsiteDetails", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "organizerName", - "columnName": "organizerName", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "largeImageUrl", - "columnName": "largeImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "deletedAt", - "columnName": "deletedAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ticketUrl", - "columnName": "ticketUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "locationName", - "columnName": "locationName", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "codeOfConduct", - "columnName": "codeOfConduct", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "state", - "columnName": "state", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "searchableLocationName", - "columnName": "searchableLocationName", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "description", - "columnName": "description", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "pentabarfUrl", - "columnName": "pentabarfUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "xcalUrl", - "columnName": "xcalUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "logoUrl", - "columnName": "logoUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "externalEventUrl", - "columnName": "externalEventUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "iconImageUrl", - "columnName": "iconImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "icalUrl", - "columnName": "icalUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "createdAt", - "columnName": "createdAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "bankDetails", - "columnName": "bankDetails", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "chequeDetails", - "columnName": "chequeDetails", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isComplete", - "columnName": "isComplete", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "latitude", - "columnName": "latitude", - "affinity": "REAL", - "notNull": false - }, - { - "fieldPath": "longitude", - "columnName": "longitude", - "affinity": "REAL", - "notNull": false - }, - { - "fieldPath": "refundPolicy", - "columnName": "refundPolicy", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "canPayByStripe", - "columnName": "canPayByStripe", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "canPayByCheque", - "columnName": "canPayByCheque", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "canPayByBank", - "columnName": "canPayByBank", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "canPayByPaypal", - "columnName": "canPayByPaypal", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "canPayOnsite", - "columnName": "canPayOnsite", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isSponsorsEnabled", - "columnName": "isSponsorsEnabled", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "hasOrganizerInfo", - "columnName": "hasOrganizerInfo", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isSessionsSpeakersEnabled", - "columnName": "isSessionsSpeakersEnabled", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isTicketingEnabled", - "columnName": "isTicketingEnabled", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isTaxEnabled", - "columnName": "isTaxEnabled", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isMapShown", - "columnName": "isMapShown", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "favorite", - "columnName": "favorite", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "eventTopic", - "columnName": "eventTopic", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_Event_eventTopic", - "unique": false, - "columnNames": [ - "eventTopic" - ], - "createSql": "CREATE INDEX `index_Event_eventTopic` ON `${TABLE_NAME}` (`eventTopic`)" - } - ], - "foreignKeys": [] - }, - { - "tableName": "User", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `firstName` TEXT, `lastName` TEXT, `email` TEXT, `contact` TEXT, `details` TEXT, `thumbnailImageUrl` TEXT, `iconImageUrl` TEXT, `smallImageUrl` TEXT, `avatarUrl` TEXT, `facebookUrl` TEXT, `twitterUrl` TEXT, `instagramUrl` TEXT, `googlePlusUrl` TEXT, `originalImageUrl` TEXT, `isVerified` INTEGER NOT NULL, `isAdmin` INTEGER, `isSuperAdmin` INTEGER, `createdAt` TEXT, `lastAccessedAt` TEXT, `deletedAt` TEXT, PRIMARY KEY(`id`))", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "firstName", - "columnName": "firstName", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastName", - "columnName": "lastName", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "email", - "columnName": "email", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "contact", - "columnName": "contact", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "details", - "columnName": "details", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "thumbnailImageUrl", - "columnName": "thumbnailImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "iconImageUrl", - "columnName": "iconImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "smallImageUrl", - "columnName": "smallImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "avatarUrl", - "columnName": "avatarUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "facebookUrl", - "columnName": "facebookUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "twitterUrl", - "columnName": "twitterUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "instagramUrl", - "columnName": "instagramUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "googlePlusUrl", - "columnName": "googlePlusUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "originalImageUrl", - "columnName": "originalImageUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isVerified", - "columnName": "isVerified", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isAdmin", - "columnName": "isAdmin", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isSuperAdmin", - "columnName": "isSuperAdmin", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "createdAt", - "columnName": "createdAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastAccessedAt", - "columnName": "lastAccessedAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "deletedAt", - "columnName": "deletedAt", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [] - }, - { - "tableName": "SocialLink", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `link` TEXT NOT NULL, `name` TEXT NOT NULL, `event` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "link", - "columnName": "link", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "event", - "columnName": "event", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_SocialLink_event", - "unique": false, - "columnNames": [ - "event" - ], - "createSql": "CREATE INDEX `index_SocialLink_event` ON `${TABLE_NAME}` (`event`)" - } - ], - "foreignKeys": [ - { - "table": "Event", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "event" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Ticket", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `description` TEXT, `type` TEXT, `name` TEXT NOT NULL, `maxOrder` INTEGER NOT NULL, `isFeeAbsorbed` INTEGER, `isDescriptionVisible` INTEGER, `price` REAL, `position` TEXT, `quantity` TEXT, `isHidden` INTEGER, `salesStartsAt` TEXT, `salesEndsAt` TEXT, `minOrder` INTEGER NOT NULL, `event` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "description", - "columnName": "description", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "name", - "columnName": "name", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "maxOrder", - "columnName": "maxOrder", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "isFeeAbsorbed", - "columnName": "isFeeAbsorbed", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isDescriptionVisible", - "columnName": "isDescriptionVisible", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "price", - "columnName": "price", - "affinity": "REAL", - "notNull": false - }, - { - "fieldPath": "position", - "columnName": "position", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "quantity", - "columnName": "quantity", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isHidden", - "columnName": "isHidden", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "salesStartsAt", - "columnName": "salesStartsAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "salesEndsAt", - "columnName": "salesEndsAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "minOrder", - "columnName": "minOrder", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "event", - "columnName": "event", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_Ticket_event", - "unique": false, - "columnNames": [ - "event" - ], - "createSql": "CREATE INDEX `index_Ticket_event` ON `${TABLE_NAME}` (`event`)" - } - ], - "foreignKeys": [ - { - "table": "Event", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "event" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Attendee", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `firstname` TEXT, `lastname` TEXT, `email` TEXT, `address` TEXT, `city` TEXT, `state` TEXT, `country` TEXT, `isCheckedIn` INTEGER, `pdfUrl` TEXT, `ticketId` TEXT, `event` INTEGER, `ticket` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`ticket`) REFERENCES `Ticket`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "firstname", - "columnName": "firstname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "lastname", - "columnName": "lastname", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "email", - "columnName": "email", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "address", - "columnName": "address", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "city", - "columnName": "city", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "state", - "columnName": "state", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "country", - "columnName": "country", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "isCheckedIn", - "columnName": "isCheckedIn", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "pdfUrl", - "columnName": "pdfUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ticketId", - "columnName": "ticketId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "event", - "columnName": "event", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "ticket", - "columnName": "ticket", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_Attendee_event", - "unique": false, - "columnNames": [ - "event" - ], - "createSql": "CREATE INDEX `index_Attendee_event` ON `${TABLE_NAME}` (`event`)" - } - ], - "foreignKeys": [ - { - "table": "Event", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "event" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Ticket", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "ticket" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "EventTopic", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER, `name` TEXT, `slug` TEXT, `event` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "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 - }, - { - "fieldPath": "event", - "columnName": "event", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_EventTopic_event", - "unique": false, - "columnNames": [ - "event" - ], - "createSql": "CREATE INDEX `index_EventTopic_event` ON `${TABLE_NAME}` (`event`)" - } - ], - "foreignKeys": [ - { - "table": "Event", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "event" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "Order", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `paymentMode` TEXT, `country` TEXT, `status` TEXT, `amount` REAL, `identifier` TEXT, `orderNotes` TEXT, `completedAt` TEXT, `city` TEXT, `address` TEXT, `createdAt` TEXT, `zipcode` TEXT, `paidVia` TEXT, `discountCodeId` TEXT, `ticketsPdfUrl` TEXT, `transactionId` TEXT, `event` INTEGER, `attendees` TEXT, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`attendees`) REFERENCES `Attendee`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "paymentMode", - "columnName": "paymentMode", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "country", - "columnName": "country", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "status", - "columnName": "status", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "amount", - "columnName": "amount", - "affinity": "REAL", - "notNull": false - }, - { - "fieldPath": "identifier", - "columnName": "identifier", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "orderNotes", - "columnName": "orderNotes", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "completedAt", - "columnName": "completedAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "city", - "columnName": "city", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "address", - "columnName": "address", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "createdAt", - "columnName": "createdAt", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "zipcode", - "columnName": "zipcode", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "paidVia", - "columnName": "paidVia", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "discountCodeId", - "columnName": "discountCodeId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "ticketsPdfUrl", - "columnName": "ticketsPdfUrl", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "transactionId", - "columnName": "transactionId", - "affinity": "TEXT", - "notNull": false - }, - { - "fieldPath": "event", - "columnName": "event", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "attendees", - "columnName": "attendees", - "affinity": "TEXT", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [ - { - "name": "index_Order_event", - "unique": false, - "columnNames": [ - "event" - ], - "createSql": "CREATE INDEX `index_Order_event` ON `${TABLE_NAME}` (`event`)" - } - ], - "foreignKeys": [ - { - "table": "Event", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "event" - ], - "referencedColumns": [ - "id" - ] - }, - { - "table": "Attendee", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "attendees" - ], - "referencedColumns": [ - "id" - ] - } - ] - }, - { - "tableName": "CustomForm", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `form` TEXT NOT NULL, `fieldIdentifier` TEXT NOT NULL, `type` TEXT NOT NULL, `isRequired` INTEGER, `isIncluded` INTEGER, `isFixed` INTEGER, `ticketsNumber` INTEGER, `event` INTEGER, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", - "fields": [ - { - "fieldPath": "id", - "columnName": "id", - "affinity": "INTEGER", - "notNull": true - }, - { - "fieldPath": "form", - "columnName": "form", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "fieldIdentifier", - "columnName": "fieldIdentifier", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "type", - "columnName": "type", - "affinity": "TEXT", - "notNull": true - }, - { - "fieldPath": "isRequired", - "columnName": "isRequired", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isIncluded", - "columnName": "isIncluded", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "isFixed", - "columnName": "isFixed", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "ticketsNumber", - "columnName": "ticketsNumber", - "affinity": "INTEGER", - "notNull": false - }, - { - "fieldPath": "event", - "columnName": "event", - "affinity": "INTEGER", - "notNull": false - } - ], - "primaryKey": { - "columnNames": [ - "id" - ], - "autoGenerate": false - }, - "indices": [], - "foreignKeys": [ - { - "table": "Event", - "onDelete": "CASCADE", - "onUpdate": "NO ACTION", - "columns": [ - "event" - ], - "referencedColumns": [ - "id" - ] - } - ] - } - ], - "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, \"0b3cd25764884626e03f56b0600d1c76\")" - ] - } -} diff --git a/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/2.json b/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/5.json similarity index 79% rename from app/schemas/org.fossasia.openevent.general.OpenEventDatabase/2.json rename to app/schemas/org.fossasia.openevent.general.OpenEventDatabase/5.json index 4373ff2b08..d25d70b5dc 100644 --- a/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/2.json +++ b/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/5.json @@ -1,8 +1,8 @@ { "formatVersion": 1, "database": { - "version": 3, - "identityHash": "1badd5e17f181e602314a4244c04cf56", + "version": 5, + "identityHash": "1c62806166a2886751e22adada684c1a", "entities": [ { "tableName": "Event", @@ -864,7 +864,7 @@ }, { "tableName": "Order", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `paymentMode` TEXT, `country` TEXT, `status` TEXT, `amount` REAL, `identifier` TEXT, `orderNotes` TEXT, `completedAt` TEXT, `city` TEXT, `address` TEXT, `createdAt` TEXT, `zipcode` TEXT, `paidVia` TEXT, `discountCodeId` TEXT, `ticketsPdfUrl` TEXT, `transactionId` TEXT, `event` INTEGER, `attendees` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`attendees`) REFERENCES `Attendee`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `paymentMode` TEXT, `country` TEXT, `status` TEXT, `amount` REAL NOT NULL, `identifier` TEXT, `orderNotes` TEXT, `completedAt` TEXT, `city` TEXT, `address` TEXT, `createdAt` TEXT, `zipcode` TEXT, `paidVia` TEXT, `discountCodeId` TEXT, `ticketsPdfUrl` TEXT, `transactionId` TEXT, `event` INTEGER, `attendees` TEXT NOT NULL, PRIMARY KEY(`id`), FOREIGN KEY(`event`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`attendees`) REFERENCES `Attendee`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "fields": [ { "fieldPath": "id", @@ -894,7 +894,7 @@ "fieldPath": "amount", "columnName": "amount", "affinity": "REAL", - "notNull": false + "notNull": true }, { "fieldPath": "identifier", @@ -1098,7 +1098,7 @@ }, { "tableName": "Speaker", - "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `email` TEXT, `photoUrl` TEXT, `shortBiography` TEXT, `longBiography` TEXT, `speakingExperience` TEXT, `location` TEXT, `country` TEXT, `city` TEXT, `organisation` TEXT, `gender` TEXT, `website` TEXT, `twitter` TEXT, `facebook` TEXT, `linkedin` TEXT, `github` TEXT, `isFeatured` INTEGER NOT NULL, PRIMARY KEY(`id`))", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `email` TEXT, `photoUrl` TEXT, `shortBiography` TEXT, `longBiography` TEXT, `speakingExperience` TEXT, `position` TEXT, `mobile` TEXT, `location` TEXT, `country` TEXT, `city` TEXT, `organisation` TEXT, `gender` TEXT, `website` TEXT, `twitter` TEXT, `facebook` TEXT, `linkedin` TEXT, `github` TEXT, `isFeatured` INTEGER NOT NULL, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -1142,6 +1142,18 @@ "affinity": "TEXT", "notNull": false }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "mobile", + "columnName": "mobile", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "location", "columnName": "location", @@ -1284,12 +1296,298 @@ ] } ] + }, + { + "tableName": "Sponsor", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `description` TEXT, `url` TEXT, `logoUrl` TEXT, `level` INTEGER NOT NULL, `type` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "url", + "columnName": "url", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "logoUrl", + "columnName": "logoUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "type", + "columnName": "type", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "SponsorWithEvent", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`event_id` INTEGER NOT NULL, `sponsor_id` INTEGER NOT NULL, PRIMARY KEY(`event_id`, `sponsor_id`), FOREIGN KEY(`event_id`) REFERENCES `Event`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`sponsor_id`) REFERENCES `Sponsor`(`id`) ON UPDATE NO ACTION ON DELETE NO ACTION )", + "fields": [ + { + "fieldPath": "eventId", + "columnName": "event_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "sponsorId", + "columnName": "sponsor_id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "columnNames": [ + "event_id", + "sponsor_id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_SponsorWithEvent_event_id", + "unique": false, + "columnNames": [ + "event_id" + ], + "createSql": "CREATE INDEX `index_SponsorWithEvent_event_id` ON `${TABLE_NAME}` (`event_id`)" + }, + { + "name": "index_SponsorWithEvent_sponsor_id", + "unique": false, + "columnNames": [ + "sponsor_id" + ], + "createSql": "CREATE INDEX `index_SponsorWithEvent_sponsor_id` ON `${TABLE_NAME}` (`sponsor_id`)" + } + ], + "foreignKeys": [ + { + "table": "Event", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "event_id" + ], + "referencedColumns": [ + "id" + ] + }, + { + "table": "Sponsor", + "onDelete": "NO ACTION", + "onUpdate": "NO ACTION", + "columns": [ + "sponsor_id" + ], + "referencedColumns": [ + "id" + ] + } + ] + }, + { + "tableName": "Session", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `shortAbstract` TEXT, `comments` TEXT, `longAbstract` TEXT, `level` TEXT, `signupUrl` TEXT, `endsAt` TEXT, `language` TEXT, `title` TEXT, `startsAt` TEXT, `slidesUrl` TEXT, `averageRating` REAL, `submittedAt` TEXT, `deletedAt` TEXT, `subtitle` TEXT, `createdAt` TEXT, `state` TEXT, `lastModifiedAt` TEXT, `videoUrl` TEXT, `audioUrl` TEXT, `sessionType` TEXT, `microlocation` TEXT, PRIMARY KEY(`id`))", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "shortAbstract", + "columnName": "shortAbstract", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "comments", + "columnName": "comments", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "longAbstract", + "columnName": "longAbstract", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "level", + "columnName": "level", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "signupUrl", + "columnName": "signupUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "endsAt", + "columnName": "endsAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "language", + "columnName": "language", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "startsAt", + "columnName": "startsAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "slidesUrl", + "columnName": "slidesUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "averageRating", + "columnName": "averageRating", + "affinity": "REAL", + "notNull": false + }, + { + "fieldPath": "submittedAt", + "columnName": "submittedAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "deletedAt", + "columnName": "deletedAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "subtitle", + "columnName": "subtitle", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "lastModifiedAt", + "columnName": "lastModifiedAt", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "videoUrl", + "columnName": "videoUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "audioUrl", + "columnName": "audioUrl", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "sessionType", + "columnName": "sessionType", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "microlocation", + "columnName": "microlocation", + "affinity": "TEXT", + "notNull": false + } + ], + "primaryKey": { + "columnNames": [ + "id" + ], + "autoGenerate": false + }, + "indices": [ + { + "name": "index_Session_sessionType", + "unique": false, + "columnNames": [ + "sessionType" + ], + "createSql": "CREATE INDEX `index_Session_sessionType` ON `${TABLE_NAME}` (`sessionType`)" + }, + { + "name": "index_Session_microlocation", + "unique": false, + "columnNames": [ + "microlocation" + ], + "createSql": "CREATE INDEX `index_Session_microlocation` ON `${TABLE_NAME}` (`microlocation`)" + } + ], + "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, \"1badd5e17f181e602314a4244c04cf56\")" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, \"1c62806166a2886751e22adada684c1a\")" ] } -} +} \ No newline at end of file diff --git a/app/src/fdroid/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt b/app/src/fdroid/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt index 45d1a097c5..a449689b1e 100644 --- a/app/src/fdroid/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt +++ b/app/src/fdroid/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt @@ -15,6 +15,8 @@ class SmartAuthViewModel : ViewModel() { val progress: LiveData = mutableProgress private val mutableApiExceptionRequestCodePair = MutableLiveData>() val apiExceptionCodePair: LiveData> = mutableApiExceptionRequestCodePair + private val mutableStatus = MutableLiveData() + val isCredentialStored: LiveData = mutableStatus fun requestCredentials(any: Any) { return diff --git a/app/src/fdroid/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt b/app/src/fdroid/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt index c460e1e3eb..463dc13f8e 100644 --- a/app/src/fdroid/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt +++ b/app/src/fdroid/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt @@ -1,5 +1,5 @@ package org.fossasia.openevent.general.di -import org.koin.dsl.module.module +import org.koin.dsl.module val flavorSpecificModule = module { } diff --git a/app/src/fdroid/java/org/fossasia/openevent/general/search/GeoLocationViewModel.kt b/app/src/fdroid/java/org/fossasia/openevent/general/search/GeoLocationViewModel.kt index 3481f821c2..c5f128a361 100644 --- a/app/src/fdroid/java/org/fossasia/openevent/general/search/GeoLocationViewModel.kt +++ b/app/src/fdroid/java/org/fossasia/openevent/general/search/GeoLocationViewModel.kt @@ -8,17 +8,13 @@ import org.fossasia.openevent.general.common.SingleLiveEvent class GeoLocationViewModel(locationService: LocationService) : ViewModel() { private val mutableLocation = MutableLiveData() val location: LiveData = mutableLocation - private val mutableVisibility = MutableLiveData() + private val mutableVisibility = MutableLiveData(false) val currentLocationVisibility: LiveData = mutableVisibility private val mutableOpenLocationSettings = MutableLiveData() val openLocationSettings: LiveData = mutableOpenLocationSettings private val mutableErrorMessage = SingleLiveEvent() val errorMessage: LiveData = mutableErrorMessage - init { - mutableVisibility.value = false - } - fun configure() { mutableVisibility.value = false return diff --git a/app/src/main/java/org/fossasia/openevent/general/MainActivity.kt b/app/src/main/java/org/fossasia/openevent/general/MainActivity.kt index 3ca99dfc82..3a13a2685f 100644 --- a/app/src/main/java/org/fossasia/openevent/general/MainActivity.kt +++ b/app/src/main/java/org/fossasia/openevent/general/MainActivity.kt @@ -8,7 +8,6 @@ import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment import androidx.navigation.ui.NavigationUI.setupWithNavController import kotlinx.android.synthetic.main.activity_main.navigation -import kotlinx.android.synthetic.main.activity_main.navigationAuth import kotlinx.android.synthetic.main.activity_main.mainFragmentCoordinatorLayout import org.fossasia.openevent.general.auth.EditProfileFragment import org.fossasia.openevent.general.auth.RC_CREDENTIALS_READ @@ -44,7 +43,6 @@ class MainActivity : AppCompatActivity() { private fun setupBottomNavigationMenu(navController: NavController) { setupWithNavController(navigation, navController) - setupWithNavController(navigationAuth, navController) navigation.setOnNavigationItemReselectedListener { val hostFragment = supportFragmentManager.findFragmentById(R.id.frameContainer) @@ -64,17 +62,11 @@ class MainActivity : AppCompatActivity() { R.id.favoriteFragment -> navAnimVisible(navigation, this@MainActivity) else -> navAnimGone(navigation, this@MainActivity) } - when (id) { - R.id.loginFragment, - R.id.signUpFragment -> navAnimVisible(navigationAuth, this@MainActivity) - else -> navAnimGone(navigationAuth, this@MainActivity) - } } override fun onBackPressed() { when (currentFragmentId) { - R.id.loginFragment, - R.id.signUpFragment -> { + R.id.authFragment -> { navController.popBackStack(R.id.eventsFragment, false) mainFragmentCoordinatorLayout.snackbar(R.string.sign_in_canceled) } diff --git a/app/src/main/java/org/fossasia/openevent/general/OpenEventDatabase.kt b/app/src/main/java/org/fossasia/openevent/general/OpenEventDatabase.kt index 198d50ba95..f2b365afa9 100644 --- a/app/src/main/java/org/fossasia/openevent/general/OpenEventDatabase.kt +++ b/app/src/main/java/org/fossasia/openevent/general/OpenEventDatabase.kt @@ -20,6 +20,11 @@ import org.fossasia.openevent.general.event.topic.EventTopicsDao import org.fossasia.openevent.general.event.types.EventTypeConverter import org.fossasia.openevent.general.order.Order import org.fossasia.openevent.general.order.OrderDao +import org.fossasia.openevent.general.sessions.Session +import org.fossasia.openevent.general.sessions.SessionDao +import org.fossasia.openevent.general.sessions.microlocation.MicroLocationConverter +import org.fossasia.openevent.general.sessions.sessiontype.SessionTypeConverter +import org.fossasia.openevent.general.sessions.track.TrackConverter import org.fossasia.openevent.general.social.SocialLink import org.fossasia.openevent.general.social.SocialLinksDao import org.fossasia.openevent.general.speakers.Speaker @@ -36,10 +41,10 @@ import org.fossasia.openevent.general.ticket.TicketIdConverter @Database(entities = [Event::class, User::class, SocialLink::class, Ticket::class, Attendee::class, EventTopic::class, Order::class, CustomForm::class, Speaker::class, SpeakerWithEvent::class, Sponsor::class, - SponsorWithEvent::class], version = 4) + SponsorWithEvent::class, Session::class], version = 6) @TypeConverters(EventIdConverter::class, EventTopicConverter::class, EventTypeConverter::class, - EventSubTopicConverter::class, TicketIdConverter::class, - AttendeeIdConverter::class, ListAttendeeIdConverter::class) + EventSubTopicConverter::class, TicketIdConverter::class, MicroLocationConverter::class, + AttendeeIdConverter::class, ListAttendeeIdConverter::class, SessionTypeConverter::class, TrackConverter::class) abstract class OpenEventDatabase : RoomDatabase() { abstract fun eventDao(): EventDao @@ -63,4 +68,6 @@ abstract class OpenEventDatabase : RoomDatabase() { abstract fun sponsorDao(): SponsorDao abstract fun sponsorWithEventDao(): SponsorWithEventDao + + abstract fun sessionDao(): SessionDao } diff --git a/app/src/main/java/org/fossasia/openevent/general/OpenEventGeneral.kt b/app/src/main/java/org/fossasia/openevent/general/OpenEventGeneral.kt index e693708074..7cf66b7c06 100644 --- a/app/src/main/java/org/fossasia/openevent/general/OpenEventGeneral.kt +++ b/app/src/main/java/org/fossasia/openevent/general/OpenEventGeneral.kt @@ -8,10 +8,11 @@ import org.fossasia.openevent.general.di.apiModule import org.fossasia.openevent.general.di.commonModule import org.fossasia.openevent.general.di.databaseModule import org.fossasia.openevent.general.di.flavorSpecificModule -import org.fossasia.openevent.general.di.fragmentsModule import org.fossasia.openevent.general.di.networkModule import org.fossasia.openevent.general.di.viewModelModule -import org.koin.android.ext.android.startKoin +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin import timber.log.Timber class OpenEventGeneral : MultiDexApplication() { @@ -25,15 +26,18 @@ class OpenEventGeneral : MultiDexApplication() { override fun onCreate() { super.onCreate() appContext = applicationContext - startKoin(this, listOf( - commonModule, - apiModule, - viewModelModule, - networkModule, - databaseModule, - flavorSpecificModule, - fragmentsModule - )) + startKoin { + androidLogger() + androidContext(this@OpenEventGeneral) + modules( + commonModule, + apiModule, + viewModelModule, + networkModule, + databaseModule, + flavorSpecificModule + ) + } Timber.plant(Timber.DebugTree()) AndroidThreeTen.init(applicationContext) diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/Attendee.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/Attendee.kt index 941448e685..976bd5b570 100644 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/Attendee.kt +++ b/app/src/main/java/org/fossasia/openevent/general/attendees/Attendee.kt @@ -39,6 +39,7 @@ data class Attendee( @ColumnInfo(index = true) @Relationship("event") var event: EventId? = null, + @ColumnInfo(index = true) @Relationship("ticket") var ticket: TicketId? = null ) diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeDao.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeDao.kt index ae8ffb36d2..7158699785 100644 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeDao.kt +++ b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeDao.kt @@ -19,11 +19,14 @@ interface AttendeeDao { fun insertCustomForms(customForms: List) @Query("DELETE FROM Attendee") - fun deleteAll() + fun deleteAllAttendees() @Query("SELECT * from Attendee WHERE id in (:ids)") fun getAttendeesWithIds(ids: List): Single> @Query("SELECT * from CustomForm WHERE event = :eventId") fun getCustomFormsForId(eventId: Long): Single> + + @Query("SELECT * FROM Attendee") + fun getAllAttendees(): Single> } diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeViewModel.kt index e6530c09ae..d525d19058 100644 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeViewModel.kt @@ -246,11 +246,11 @@ class AttendeeViewModel( private fun createOrder() { val attendeeList = attendees.map { AttendeeId(it.id) }.toList() - var amount = totalAmount.value + var amount: Float = totalAmount.value ?: 0F var paymentMode: String? = paymentOption.toLowerCase() - if (amount == null || amount <= 0) { + if (amount <= 0) { paymentMode = resource.getString(R.string.free) - amount = null + amount = 0F } val eventId = event.value?.id if (eventId != null) { diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeIdConverter.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeIdConverter.kt index adcb0787e9..0f1bbcfaa5 100644 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeIdConverter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeIdConverter.kt @@ -3,6 +3,7 @@ package org.fossasia.openevent.general.attendees import androidx.room.TypeConverter import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper class ListAttendeeIdConverter { @@ -14,7 +15,7 @@ class ListAttendeeIdConverter { @TypeConverter fun toListAttendeeId(attendeeList: String): List { - val objectMapper = ObjectMapper() + val objectMapper = jacksonObjectMapper() val mapType = object : TypeReference>() {} return objectMapper.readValue(attendeeList, mapType) } diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/AuthApi.kt b/app/src/main/java/org/fossasia/openevent/general/auth/AuthApi.kt index 4f06d1dd31..2e86794e5b 100644 --- a/app/src/main/java/org/fossasia/openevent/general/auth/AuthApi.kt +++ b/app/src/main/java/org/fossasia/openevent/general/auth/AuthApi.kt @@ -3,6 +3,7 @@ package org.fossasia.openevent.general.auth import io.reactivex.Single import org.fossasia.openevent.general.auth.change.ChangeRequestToken import org.fossasia.openevent.general.auth.change.ChangeRequestTokenResponse +import org.fossasia.openevent.general.auth.forgot.Email import org.fossasia.openevent.general.auth.forgot.RequestToken import org.fossasia.openevent.general.auth.forgot.RequestTokenResponse import retrofit2.http.Body @@ -33,4 +34,7 @@ interface AuthApi { @POST("upload/image") fun uploadImage(@Body uploadImage: UploadImage): Single + + @POST("users/checkEmail") + fun checkEmail(@Body email: Email): Single } diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/AuthFragment.kt b/app/src/main/java/org/fossasia/openevent/general/auth/AuthFragment.kt new file mode 100644 index 0000000000..715194c95c --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/auth/AuthFragment.kt @@ -0,0 +1,116 @@ +package org.fossasia.openevent.general.auth + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.Observer +import androidx.navigation.Navigation +import androidx.navigation.fragment.navArgs +import kotlinx.android.synthetic.main.fragment_auth.view.getStartedButton +import kotlinx.android.synthetic.main.fragment_auth.view.email +import kotlinx.android.synthetic.main.fragment_auth.view.rootLayout +import org.fossasia.openevent.general.BuildConfig +import org.fossasia.openevent.general.PLAY_STORE_BUILD_FLAVOR +import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.utils.Utils +import org.fossasia.openevent.general.utils.Utils.hideSoftKeyboard +import org.fossasia.openevent.general.utils.Utils.show +import org.fossasia.openevent.general.utils.Utils.progressDialog +import org.fossasia.openevent.general.utils.extensions.nonNull +import org.jetbrains.anko.design.longSnackbar +import org.jetbrains.anko.design.snackbar +import org.koin.androidx.viewmodel.ext.android.sharedViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +class AuthFragment : Fragment() { + private lateinit var rootView: View + private val authViewModel by viewModel() + private val safeArgs: AuthFragmentArgs by navArgs() + private val smartAuthViewModel by sharedViewModel() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (BuildConfig.FLAVOR == PLAY_STORE_BUILD_FLAVOR) { + smartAuthViewModel.requestCredentials(SmartAuthUtil.getCredentialsClient(requireActivity())) + smartAuthViewModel.isCredentialStored + .nonNull() + .observe(this, Observer { + if (it) redirectToLogin() + }) + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_auth, container, false) + + Utils.setToolbar(activity, "", true) + setHasOptionsMenu(true) + + val progressDialog = progressDialog(context) + + val snackbarMessage = safeArgs.snackbarMessage + if (!snackbarMessage.isNullOrEmpty()) rootView.snackbar(snackbarMessage) + + rootView.getStartedButton.setOnClickListener { + hideSoftKeyboard(context, rootView) + authViewModel.checkUser(rootView.email.text.toString()) + } + + authViewModel.isUserExists + .nonNull() + .observe(viewLifecycleOwner, Observer { + if (it) + redirectToLogin(rootView.email.text.toString()) + else + redirectToSignUp() + authViewModel.mutableStatus.postValue(null) + }) + + authViewModel.progress + .nonNull() + .observe(viewLifecycleOwner, Observer { + progressDialog.show(it) + }) + + smartAuthViewModel.progress + .nonNull() + .observe(viewLifecycleOwner, Observer { + progressDialog.show(it) + }) + + authViewModel.error + .nonNull() + .observe(viewLifecycleOwner, Observer { + rootView.rootLayout.longSnackbar(it) + }) + + return rootView + } + + private fun redirectToLogin(email: String = "") { + Navigation.findNavController(rootView) + .navigate(AuthFragmentDirections + .actionAuthToLogIn(email, safeArgs.redirectedFrom) + ) + } + + private fun redirectToSignUp() { + Navigation.findNavController(rootView) + .navigate(AuthFragmentDirections + .actionAuthToSignUp(rootView.email.text.toString(), safeArgs.redirectedFrom) + ) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + activity?.onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/AuthService.kt b/app/src/main/java/org/fossasia/openevent/general/auth/AuthService.kt index c249341f6e..8db9a02d4c 100644 --- a/app/src/main/java/org/fossasia/openevent/general/auth/AuthService.kt +++ b/app/src/main/java/org/fossasia/openevent/general/auth/AuthService.kt @@ -2,18 +2,22 @@ package org.fossasia.openevent.general.auth import io.reactivex.Completable import io.reactivex.Single +import org.fossasia.openevent.general.attendees.AttendeeDao import org.fossasia.openevent.general.auth.change.ChangeRequestToken import org.fossasia.openevent.general.auth.change.ChangeRequestTokenResponse import org.fossasia.openevent.general.auth.change.Password import org.fossasia.openevent.general.auth.forgot.Email import org.fossasia.openevent.general.auth.forgot.RequestToken import org.fossasia.openevent.general.auth.forgot.RequestTokenResponse +import org.fossasia.openevent.general.order.OrderDao import timber.log.Timber class AuthService( private val authApi: AuthApi, private val authHolder: AuthHolder, - private val userDao: UserDao + private val userDao: UserDao, + private val orderDao: OrderDao, + private val attendeeDao: AttendeeDao ) { fun login(username: String, password: String): Single { if (username.isEmpty() || password.isEmpty()) @@ -55,6 +59,8 @@ class AuthService( return Completable.fromAction { authHolder.token = null userDao.deleteUser(authHolder.getId()) + orderDao.deleteAllOrders() + attendeeDao.deleteAllAttendees() } } @@ -79,4 +85,8 @@ class AuthService( val changeRequestToken = ChangeRequestToken(Password(oldPassword, newPassword)) return authApi.changeRequestToken(changeRequestToken) } + + fun checkEmail(email: String): Single { + return authApi.checkEmail(Email(email)) + } } diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/AuthViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/auth/AuthViewModel.kt new file mode 100644 index 0000000000..3a3d0b50bb --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/auth/AuthViewModel.kt @@ -0,0 +1,47 @@ +package org.fossasia.openevent.general.auth + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.fossasia.openevent.general.R +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import org.fossasia.openevent.general.data.Network +import org.fossasia.openevent.general.data.Resource +import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers +import timber.log.Timber + +class AuthViewModel( + private val authService: AuthService, + private val network: Network, + private val resource: Resource +) : ViewModel() { + + private val compositeDisposable = CompositeDisposable() + private val mutableProgress = MutableLiveData() + val progress: LiveData = mutableProgress + val mutableStatus = MutableLiveData() + val isUserExists: LiveData = mutableStatus + private val mutableError = MutableLiveData() + val error: LiveData = mutableError + + fun checkUser(email: String) { + if (!network.isNetworkConnected()) { + mutableError.value = resource.getString(R.string.no_internet_message) + return + } + compositeDisposable += authService.checkEmail(email) + .withDefaultSchedulers() + .doOnSubscribe { + mutableProgress.value = true + }.doFinally { + mutableProgress.value = false + }.subscribe({ + mutableStatus.value = !it.result + Timber.d("Success!") + }, { + mutableError.value = resource.getString(R.string.error) + Timber.d(it, "Failed") + }) + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/CheckEmailResponse.kt b/app/src/main/java/org/fossasia/openevent/general/auth/CheckEmailResponse.kt new file mode 100644 index 0000000000..5b5bd601ce --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/auth/CheckEmailResponse.kt @@ -0,0 +1,5 @@ +package org.fossasia.openevent.general.auth + +class CheckEmailResponse( + val result: Boolean +) diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/EditProfileFragment.kt b/app/src/main/java/org/fossasia/openevent/general/auth/EditProfileFragment.kt index 4e7124255a..c88a8b30d3 100644 --- a/app/src/main/java/org/fossasia/openevent/general/auth/EditProfileFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/auth/EditProfileFragment.kt @@ -14,6 +14,7 @@ import android.view.View import android.view.ViewGroup import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toBitmap import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Observer @@ -127,11 +128,7 @@ class EditProfileFragment : Fragment() { }) rootView.profilePhotoFab.setOnClickListener { - if (permissionGranted) { - showFileChooser() - } else { - requestPermissions(READ_STORAGE, REQUEST_CODE) - } + showEditPhotoDialog() } return rootView @@ -152,12 +149,37 @@ class EditProfileFragment : Fragment() { } } + private fun showEditPhotoDialog() { + AlertDialog.Builder(requireContext()) + .setMessage(getString(R.string.edit_profile_photo_message)) + .setNegativeButton(getString(R.string.delete)) { _, _ -> + clearAvatar() + }.setPositiveButton(getString(R.string.new_photo)) { _, _ -> + if (permissionGranted) { + showFileChooser() + } else { + requestPermissions(READ_STORAGE, REQUEST_CODE) + } + }.create().show() + } + + private fun clearAvatar() { + val drawable = requireDrawable(requireContext(), R.drawable.ic_account_circle_grey) + Picasso.get() + .load(R.drawable.ic_account_circle_grey) + .placeholder(drawable) + .transform(CircleTransform()) + .into(rootView.profilePhoto) + editProfileViewModel.encodedImage = encodeImage(drawable.toBitmap(120, 120)) + editProfileViewModel.avatarUpdated = true + } + private fun encodeImage(bitmap: Bitmap): String { val baos = ByteArrayOutputStream() bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos) val bytes = baos.toByteArray() - //create temp file + // create temp file try { val tempAvatar = File(context?.cacheDir, "tempAvatar") diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/LoginFragment.kt b/app/src/main/java/org/fossasia/openevent/general/auth/LoginFragment.kt index cf547918b7..32e811a057 100644 --- a/app/src/main/java/org/fossasia/openevent/general/auth/LoginFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/auth/LoginFragment.kt @@ -13,7 +13,6 @@ import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.navigation.Navigation.findNavController import androidx.navigation.fragment.navArgs -import kotlinx.android.synthetic.main.activity_main.navigationAuth import kotlinx.android.synthetic.main.fragment_login.email import kotlinx.android.synthetic.main.fragment_login.password import kotlinx.android.synthetic.main.fragment_login.loginButton @@ -28,6 +27,10 @@ import kotlinx.android.synthetic.main.fragment_login.view.tick import org.fossasia.openevent.general.BuildConfig import org.fossasia.openevent.general.PLAY_STORE_BUILD_FLAVOR import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.event.EVENT_DETAIL_FRAGMENT +import org.fossasia.openevent.general.notification.NOTIFICATION_FRAGMENT +import org.fossasia.openevent.general.order.ORDERS_FRAGMENT +import org.fossasia.openevent.general.ticket.TICKETS_FRAGMNET import org.fossasia.openevent.general.utils.Utils import org.fossasia.openevent.general.utils.Utils.show import org.fossasia.openevent.general.utils.Utils.hideSoftKeyboard @@ -55,7 +58,6 @@ class LoginFragment : Fragment() { val progressDialog = progressDialog(context) Utils.setToolbar(activity, getString(R.string.login)) setHasOptionsMenu(true) - showSnackbar() if (loginViewModel.isLoggedIn()) popBackStack() @@ -65,7 +67,9 @@ class LoginFragment : Fragment() { hideSoftKeyboard(context, rootView) } - if (BuildConfig.FLAVOR == PLAY_STORE_BUILD_FLAVOR) { + if (safeArgs.email.isNotEmpty()) { + rootView.email.text = SpannableStringBuilder(safeArgs.email) + } else if (BuildConfig.FLAVOR == PLAY_STORE_BUILD_FLAVOR) { smartAuthViewModel.requestCredentials(SmartAuthUtil.getCredentialsClient(requireActivity())) @@ -82,8 +86,8 @@ class LoginFragment : Fragment() { }) smartAuthViewModel.apiExceptionCodePair.nonNull().observe(viewLifecycleOwner, Observer { - SmartAuthUtil.handleResolvableApiException( - it.first, requireActivity(), it.second) + SmartAuthUtil.handleResolvableApiException( + it.first, requireActivity(), it.second) }) smartAuthViewModel.progress @@ -92,6 +96,7 @@ class LoginFragment : Fragment() { progressDialog.show(it) }) } + loginViewModel.progress .nonNull() .observe(viewLifecycleOwner, Observer { @@ -143,10 +148,8 @@ class LoginFragment : Fragment() { rootView.sentEmailLayout.visibility = View.VISIBLE rootView.loginLayout.visibility = View.GONE Utils.setToolbar(activity, show = false) - Utils.navAnimGone(activity?.navigationAuth, requireContext()) } else { Utils.setToolbar(activity, getString(R.string.login)) - Utils.navAnimVisible(activity?.navigationAuth, requireContext()) } }) @@ -165,7 +168,6 @@ class LoginFragment : Fragment() { rootView.tick.setOnClickListener { rootView.sentEmailLayout.visibility = View.GONE Utils.setToolbar(activity, getString(R.string.login)) - Utils.navAnimVisible(activity?.navigationAuth, requireContext()) rootView.loginLayout.visibility = View.VISIBLE } @@ -189,7 +191,16 @@ class LoginFragment : Fragment() { } private fun popBackStack() { - findNavController(rootView).popBackStack() + val destinationId = + when (safeArgs.redirectedFrom) { + PROFILE_FRAGMENT -> R.id.profileFragment + EVENT_DETAIL_FRAGMENT -> R.id.eventDetailsFragment + ORDERS_FRAGMENT -> R.id.orderUnderUserFragment + TICKETS_FRAGMNET -> R.id.ticketsFragment + NOTIFICATION_FRAGMENT -> R.id.notificationFragment + else -> R.id.eventsFragment + } + findNavController(rootView).popBackStack(destinationId, false) rootView.snackbar(R.string.welcome_back) } @@ -200,17 +211,10 @@ class LoginFragment : Fragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { - findNavController(rootView).popBackStack(R.id.eventsFragment, false) - rootView.snackbar(R.string.sign_in_canceled) + activity?.onBackPressed() true } else -> super.onOptionsItemSelected(item) } } - - private fun showSnackbar() { - safeArgs.snackbarMessage?.let { textSnackbar -> - rootView.loginCoordinatorLayout.snackbar(textSnackbar) - } - } } diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/ProfileFragment.kt b/app/src/main/java/org/fossasia/openevent/general/auth/ProfileFragment.kt index d3a4b73f03..193d056e66 100644 --- a/app/src/main/java/org/fossasia/openevent/general/auth/ProfileFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/auth/ProfileFragment.kt @@ -23,6 +23,7 @@ import kotlinx.android.synthetic.main.fragment_profile.view.logoutLL import kotlinx.android.synthetic.main.fragment_profile.view.manageEventsLL import kotlinx.android.synthetic.main.fragment_profile.view.settingsLL import kotlinx.android.synthetic.main.fragment_profile.view.ticketIssuesLL +import kotlinx.android.synthetic.main.fragment_profile.view.loginButton import org.fossasia.openevent.general.CircleTransform import org.fossasia.openevent.general.R import org.fossasia.openevent.general.utils.Utils @@ -33,6 +34,8 @@ import org.koin.androidx.viewmodel.ext.android.viewModel import org.fossasia.openevent.general.utils.Utils.setToolbar import org.jetbrains.anko.design.snackbar +const val PROFILE_FRAGMENT = "profileFragment" + class ProfileFragment : Fragment() { private val profileViewModel by viewModel() @@ -40,9 +43,7 @@ class ProfileFragment : Fragment() { private var emailSettings: String? = null private fun redirectToLogin() { - findNavController(rootView).navigate(ProfileFragmentDirections - .actionProfileToLogin(getString(R.string.log_in_first)) - ) + findNavController(rootView).navigate(ProfileFragmentDirections.actionProfileToAuth(null, PROFILE_FRAGMENT)) } private fun redirectToEventsFragment() { @@ -51,9 +52,13 @@ class ProfileFragment : Fragment() { override fun onStart() { super.onStart() - if (!profileViewModel.isLoggedIn()) { - redirectToLogin() - } + handleLayoutVisibility(profileViewModel.isLoggedIn()) + } + + private fun handleLayoutVisibility(isLoggedIn: Boolean) { + rootView.editProfileRL.isVisible = isLoggedIn + rootView.logoutLL.isVisible = isLoggedIn + rootView.loginButton.isVisible = !isLoggedIn } override fun onCreateView( @@ -93,7 +98,7 @@ class ProfileFragment : Fragment() { } }) - fetchProfile() + if (profileViewModel.isLoggedIn()) fetchProfile() rootView.manageEventsLL.setOnClickListener { startOrgaApp("com.eventyay.organizer") } @@ -106,6 +111,7 @@ class ProfileFragment : Fragment() { } rootView.logoutLL.setOnClickListener { showLogoutDialog() } + rootView.loginButton.setOnClickListener { redirectToLogin() } return rootView } diff --git a/app/src/main/java/org/fossasia/openevent/general/auth/SignUpFragment.kt b/app/src/main/java/org/fossasia/openevent/general/auth/SignUpFragment.kt index d7b3425ea5..eded359d4a 100644 --- a/app/src/main/java/org/fossasia/openevent/general/auth/SignUpFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/auth/SignUpFragment.kt @@ -39,12 +39,18 @@ import android.text.SpannableStringBuilder import android.text.TextPaint import android.text.method.LinkMovementMethod import android.text.style.ClickableSpan +import androidx.navigation.fragment.navArgs +import org.fossasia.openevent.general.event.EVENT_DETAIL_FRAGMENT +import org.fossasia.openevent.general.notification.NOTIFICATION_FRAGMENT +import org.fossasia.openevent.general.order.ORDERS_FRAGMENT +import org.fossasia.openevent.general.ticket.TICKETS_FRAGMNET import org.jetbrains.anko.design.longSnackbar import org.jetbrains.anko.design.snackbar class SignUpFragment : Fragment() { private val signUpViewModel by viewModel() + private val safeArgs: SignUpFragmentArgs by navArgs() private lateinit var rootView: View override fun onCreateView( @@ -98,6 +104,7 @@ class SignUpFragment : Fragment() { rootView.signUpText.text = paragraph rootView.signUpText.movementMethod = LinkMovementMethod.getInstance() + rootView.usernameSignUp.text = SpannableStringBuilder(safeArgs.email) lateinit var confirmPassword: String val signUp = SignUp() @@ -247,7 +254,16 @@ class SignUpFragment : Fragment() { } private fun redirectToMain() { - findNavController(rootView).popBackStack() + val destinationId = + when (safeArgs.redirectedFrom) { + PROFILE_FRAGMENT -> R.id.profileFragment + EVENT_DETAIL_FRAGMENT -> R.id.eventDetailsFragment + ORDERS_FRAGMENT -> R.id.orderUnderUserFragment + TICKETS_FRAGMNET -> R.id.ticketsFragment + NOTIFICATION_FRAGMENT -> R.id.notificationFragment + else -> R.id.eventsFragment + } + findNavController(rootView).popBackStack(destinationId, false) rootView.snackbar(R.string.logged_in_automatically) } @@ -277,8 +293,7 @@ class SignUpFragment : Fragment() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { - findNavController(rootView).popBackStack(R.id.eventsFragment, false) - rootView.snackbar(R.string.sign_in_canceled) + activity?.onBackPressed() true } else -> super.onOptionsItemSelected(item) diff --git a/app/src/main/java/org/fossasia/openevent/general/common/RecyclerViewCallbacks.kt b/app/src/main/java/org/fossasia/openevent/general/common/RecyclerViewCallbacks.kt index a32fd14651..bd76a35e32 100644 --- a/app/src/main/java/org/fossasia/openevent/general/common/RecyclerViewCallbacks.kt +++ b/app/src/main/java/org/fossasia/openevent/general/common/RecyclerViewCallbacks.kt @@ -38,3 +38,15 @@ interface SpeakerClickListener { */ fun onClick(speakerId: Long) } + +/** + * The callback interface for Speaker item clicks + */ +interface SessionClickListener { + /** + * The function to be invoked when a speaker item is clicked + * + * @param sessionId The ID of the clicked session + */ + fun onClick(sessionId: Long) +} diff --git a/app/src/main/java/org/fossasia/openevent/general/di/Modules.kt b/app/src/main/java/org/fossasia/openevent/general/di/Modules.kt index 633cc543d6..441e559ac8 100644 --- a/app/src/main/java/org/fossasia/openevent/general/di/Modules.kt +++ b/app/src/main/java/org/fossasia/openevent/general/di/Modules.kt @@ -29,17 +29,15 @@ import org.fossasia.openevent.general.auth.RequestAuthenticator import org.fossasia.openevent.general.auth.SignUp import org.fossasia.openevent.general.auth.SignUpViewModel import org.fossasia.openevent.general.auth.User +import org.fossasia.openevent.general.auth.AuthViewModel import org.fossasia.openevent.general.data.Network import org.fossasia.openevent.general.data.Preference import org.fossasia.openevent.general.event.Event import org.fossasia.openevent.general.event.EventApi import org.fossasia.openevent.general.event.EventDetailsViewModel import org.fossasia.openevent.general.event.EventId -import org.fossasia.openevent.general.event.EventLayoutType import org.fossasia.openevent.general.event.EventService -import org.fossasia.openevent.general.common.EventsDiffCallback import org.fossasia.openevent.general.data.Resource -import org.fossasia.openevent.general.event.EventsListAdapter import org.fossasia.openevent.general.event.EventsViewModel import org.fossasia.openevent.general.event.feedback.Feedback import org.fossasia.openevent.general.event.feedback.FeedbackApi @@ -52,9 +50,11 @@ import org.fossasia.openevent.general.event.topic.EventTopic import org.fossasia.openevent.general.event.topic.EventTopicApi import org.fossasia.openevent.general.event.types.EventType import org.fossasia.openevent.general.event.types.EventTypesApi -import org.fossasia.openevent.general.event.topic.SimilarEventsViewModel -import org.fossasia.openevent.general.favorite.FavoriteEventsRecyclerAdapter import org.fossasia.openevent.general.favorite.FavoriteEventsViewModel +import org.fossasia.openevent.general.notification.Notification +import org.fossasia.openevent.general.notification.NotificationApi +import org.fossasia.openevent.general.notification.NotificationService +import org.fossasia.openevent.general.notification.NotificationViewModel import org.fossasia.openevent.general.order.Charge import org.fossasia.openevent.general.order.ConfirmOrder import org.fossasia.openevent.general.order.Order @@ -74,16 +74,18 @@ import org.fossasia.openevent.general.search.SearchTypeViewModel import org.fossasia.openevent.general.search.LocationServiceImpl import org.fossasia.openevent.general.auth.SmartAuthViewModel import org.fossasia.openevent.general.connectivity.MutableConnectionLiveData -import org.fossasia.openevent.general.sessions.Microlocation import org.fossasia.openevent.general.sessions.Session import org.fossasia.openevent.general.sessions.SessionApi -import org.fossasia.openevent.general.sessions.SessionType +import org.fossasia.openevent.general.sessions.SessionService import org.fossasia.openevent.general.event.faq.EventFAQViewModel +import org.fossasia.openevent.general.sessions.SessionViewModel +import org.fossasia.openevent.general.sessions.microlocation.MicroLocation +import org.fossasia.openevent.general.sessions.sessiontype.SessionType +import org.fossasia.openevent.general.sessions.track.Track import org.fossasia.openevent.general.settings.SettingsViewModel import org.fossasia.openevent.general.social.SocialLink import org.fossasia.openevent.general.social.SocialLinkApi import org.fossasia.openevent.general.social.SocialLinksService -import org.fossasia.openevent.general.social.SocialLinksViewModel import org.fossasia.openevent.general.speakers.Speaker import org.fossasia.openevent.general.speakers.SpeakerApi import org.fossasia.openevent.general.speakers.SpeakerService @@ -99,8 +101,8 @@ import org.fossasia.openevent.general.ticket.TicketService import org.fossasia.openevent.general.ticket.TicketsViewModel import org.koin.android.ext.koin.androidApplication import org.koin.android.ext.koin.androidContext -import org.koin.androidx.viewmodel.ext.koin.viewModel -import org.koin.dsl.module.module +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module import retrofit2.Retrofit import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.converter.jackson.JacksonConverterFactory @@ -109,6 +111,8 @@ import java.util.concurrent.TimeUnit val commonModule = module { single { Preference() } single { Network() } + single { Resource() } + single { MutableConnectionLiveData() } factory { LocationServiceImpl(androidContext()) } } @@ -174,19 +178,23 @@ val apiModule = module { val retrofit: Retrofit = get() retrofit.create(SponsorApi::class.java) } + single { + val retrofit: Retrofit = get() + retrofit.create(NotificationApi::class.java) + } factory { AuthHolder(get()) } - factory { AuthService(get(), get(), get()) } + factory { AuthService(get(), get(), get(), get(), get()) } - factory { EventService(get(), get(), get(), get(), get(), get(), get(), get(), get()) } + factory { EventService(get(), get(), get(), get(), get(), get(), get(), get()) } factory { SpeakerService(get(), get(), get()) } factory { SponsorService(get(), get(), get()) } factory { TicketService(get(), get()) } factory { SocialLinksService(get(), get()) } factory { AttendeeService(get(), get(), get()) } factory { OrderService(get(), get(), get()) } - factory { Resource() } - factory { MutableConnectionLiveData() } + factory { SessionService(get(), get()) } + factory { NotificationService(get()) } } val viewModelModule = module { @@ -194,7 +202,8 @@ val viewModelModule = module { viewModel { EventsViewModel(get(), get(), get(), get()) } viewModel { ProfileViewModel(get(), get()) } viewModel { SignUpViewModel(get(), get(), get()) } - viewModel { EventDetailsViewModel(get(), get(), get(), get(), get()) } + viewModel { EventDetailsViewModel(get(), get(), get(), get(), get(), get(), get(), get()) } + viewModel { SessionViewModel(get(), get(), get()) } viewModel { SearchViewModel(get(), get(), get(), get()) } viewModel { AttendeeViewModel(get(), get(), get(), get(), get(), get(), get()) } viewModel { SearchLocationViewModel(get(), get()) } @@ -203,10 +212,8 @@ val viewModelModule = module { viewModel { TicketsViewModel(get(), get(), get(), get(), get()) } viewModel { AboutEventViewModel(get(), get()) } viewModel { EventFAQViewModel(get(), get()) } - viewModel { SocialLinksViewModel(get(), get(), get()) } viewModel { FavoriteEventsViewModel(get(), get()) } viewModel { SettingsViewModel(get()) } - viewModel { SimilarEventsViewModel(get(), get()) } viewModel { OrderCompletedViewModel(get(), get()) } viewModel { OrdersUnderUserViewModel(get(), get(), get(), get()) } viewModel { OrderDetailsViewModel(get(), get(), get()) } @@ -215,6 +222,8 @@ val viewModelModule = module { viewModel { SmartAuthViewModel() } viewModel { SpeakerViewModel(get(), get()) } viewModel { SponsorsViewModel(get(), get()) } + viewModel { NotificationViewModel(get(), get(), get(), get()) } + viewModel { AuthViewModel(get(), get(), get()) } } val networkModule = module { @@ -255,8 +264,8 @@ val networkModule = module { AttendeeId::class.java, Charge::class.java, Paypal::class.java, ConfirmOrder::class.java, CustomForm::class.java, EventLocation::class.java, EventType::class.java, EventSubTopic::class.java, Feedback::class.java, Speaker::class.java, - Session::class.java, SessionType::class.java, Microlocation::class.java, - Sponsor::class.java, EventFAQ::class.java) + Session::class.java, SessionType::class.java, MicroLocation::class.java, + Sponsor::class.java, EventFAQ::class.java, Notification::class.java, Track::class.java) Retrofit.Builder() .client(get()) @@ -282,6 +291,11 @@ val databaseModule = module { database.eventDao() } + factory { + val database: OpenEventDatabase = get() + database.sessionDao() + } + factory { val database: OpenEventDatabase = get() database.userDao() @@ -330,24 +344,3 @@ val databaseModule = module { database.sponsorDao() } } - -val fragmentsModule = module { - - factory { EventsDiffCallback() } - - scope(Scopes.EVENTS_FRAGMENT.toString()) { - EventsListAdapter(EventLayoutType.EVENTS, get()) - } - - scope(Scopes.SIMILAR_EVENTS_FRAGMENT.toString()) { - EventsListAdapter(EventLayoutType.SIMILAR_EVENTS, get()) - } - - scope(Scopes.FAVORITE_FRAGMENT.toString()) { - FavoriteEventsRecyclerAdapter(get()) - } - - scope(Scopes.SEARCH_RESULTS_FRAGMENT.toString()) { - FavoriteEventsRecyclerAdapter(get()) - } -} diff --git a/app/src/main/java/org/fossasia/openevent/general/di/Scopes.kt b/app/src/main/java/org/fossasia/openevent/general/di/Scopes.kt deleted file mode 100644 index 7f70782812..0000000000 --- a/app/src/main/java/org/fossasia/openevent/general/di/Scopes.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.fossasia.openevent.general.di - -/** - * Enum class to collect all possible Fragment scopes for Koin DI - * in one place. This list is expected to grow as Scopes are used in more - * fragments. - */ -enum class Scopes { - EVENTS_FRAGMENT, - SIMILAR_EVENTS_FRAGMENT, - FAVORITE_FRAGMENT, - SEARCH_RESULTS_FRAGMENT -} diff --git a/app/src/main/java/org/fossasia/openevent/general/event/EventDao.kt b/app/src/main/java/org/fossasia/openevent/general/event/EventDao.kt index c65685a9b3..66c623483e 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/EventDao.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/EventDao.kt @@ -24,6 +24,9 @@ interface EventDao { @Query("SELECT * from Event WHERE id = :id") fun getEvent(id: Long): Flowable + @Query("SELECT * FROM event WHERE id = :eventId") + fun getEventById(eventId: Long): Single + @Query("SELECT * from Event WHERE id in (:ids)") fun getEventWithIds(ids: List): Single> diff --git a/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsFragment.kt b/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsFragment.kt index b0ec403329..3d63b504d7 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsFragment.kt @@ -23,7 +23,6 @@ import androidx.navigation.Navigation.findNavController import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager import com.squareup.picasso.Picasso -import kotlinx.android.synthetic.main.content_event.similarEventsContainer import kotlinx.android.synthetic.main.content_event.view.eventDateDetailsFirst import kotlinx.android.synthetic.main.content_event.view.eventDateDetailsSecond import kotlinx.android.synthetic.main.content_event.view.eventDescription @@ -45,7 +44,10 @@ import kotlinx.android.synthetic.main.content_event.view.speakerRv import kotlinx.android.synthetic.main.content_event.view.speakersContainer import kotlinx.android.synthetic.main.content_event.view.sponsorsRecyclerView import kotlinx.android.synthetic.main.content_event.view.sponsorsSummaryContainer -import kotlinx.android.synthetic.main.content_event.view.feedbackContainer +import kotlinx.android.synthetic.main.content_event.view.socialLinksRecycler +import kotlinx.android.synthetic.main.content_event.view.socialLinkContainer +import kotlinx.android.synthetic.main.content_event.view.similarEventsRecycler +import kotlinx.android.synthetic.main.content_event.view.similarEventsContainer import kotlinx.android.synthetic.main.fragment_event.view.buttonTickets import kotlinx.android.synthetic.main.fragment_event.view.eventErrorCard import kotlinx.android.synthetic.main.fragment_event.view.container @@ -53,15 +55,17 @@ import kotlinx.android.synthetic.main.content_fetching_event_error.view.retry import kotlinx.android.synthetic.main.dialog_feedback.view.feedback import kotlinx.android.synthetic.main.dialog_feedback.view.feedbackTextInputLayout import kotlinx.android.synthetic.main.dialog_feedback.view.feedbackrating -import kotlinx.android.synthetic.main.fragment_event.eventCoordinatorLayout import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.common.SessionClickListener import org.fossasia.openevent.general.common.SpeakerClickListener +import org.fossasia.openevent.general.common.EventClickListener +import org.fossasia.openevent.general.common.FavoriteFabClickListener +import org.fossasia.openevent.general.common.EventsDiffCallback import org.fossasia.openevent.general.databinding.FragmentEventBinding import org.fossasia.openevent.general.event.EventUtils.loadMapUrl import org.fossasia.openevent.general.event.feedback.FeedbackRecyclerAdapter -import org.fossasia.openevent.general.event.topic.SimilarEventsFragment import org.fossasia.openevent.general.sessions.SessionRecyclerAdapter -import org.fossasia.openevent.general.social.SocialLinksFragment +import org.fossasia.openevent.general.social.SocialLinksRecyclerAdapter import org.fossasia.openevent.general.speakers.SpeakerRecyclerAdapter import org.fossasia.openevent.general.sponsor.SponsorClickListener import org.fossasia.openevent.general.sponsor.SponsorRecyclerAdapter @@ -76,9 +80,7 @@ import org.fossasia.openevent.general.utils.Utils.setToolbar import org.jetbrains.anko.design.longSnackbar import org.jetbrains.anko.design.snackbar -const val EVENT_ID = "eventId" -const val EVENT_TOPIC_ID = "eventTopicId" -const val EVENT_LOCATION = "eventLocation" +const val EVENT_DETAIL_FRAGMENT = "eventDetailFragment;" class EventDetailsFragment : Fragment() { private val eventViewModel by viewModel() @@ -87,18 +89,14 @@ class EventDetailsFragment : Fragment() { private val speakersAdapter = SpeakerRecyclerAdapter() private val sponsorsAdapter = SponsorRecyclerAdapter() private val sessionsAdapter = SessionRecyclerAdapter() + private val socialLinkAdapter = SocialLinksRecyclerAdapter() + private val similarEventsAdapter = EventsListAdapter(EventLayoutType.SIMILAR_EVENTS, EventsDiffCallback()) private lateinit var rootView: View private lateinit var binding: FragmentEventBinding - private var eventTopicId: Long? = null - private var eventLocation: String? = null - private lateinit var eventShare: Event - private var currency: String? = null private val LINE_COUNT: Int = 3 private val LINE_COUNT_ORGANIZER: Int = 2 private var menuActionBar: Menu? = null - private var title: String = "" - private var runOnce: Boolean = true override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -106,26 +104,19 @@ class EventDetailsFragment : Fragment() { .nonNull() .observe(this, Observer { loadEvent(it) - eventShare = it - title = eventShare.name + if (eventViewModel.similarEvents.value == null) { + val eventTopicId = it.eventTopic?.id ?: 0 + val eventLocation = it.searchableLocationName ?: it.locationName + eventViewModel.fetchSimilarEvents(safeArgs.eventId, eventTopicId, eventLocation) + } // Update favorite icon and external event url menu option activity?.invalidateOptionsMenu() - if (runOnce) { - loadSocialLinksFragment() - loadSimilarEventsFragment() - } - runOnce = false - Timber.d("Fetched events of id ${safeArgs.eventId}") showEventErrorScreen(false) setHasOptionsMenu(true) }) - eventViewModel.loadEventFeedback(safeArgs.eventId) - eventViewModel.fetchEventSpeakers(safeArgs.eventId) - eventViewModel.loadEventSessions(safeArgs.eventId) - eventViewModel.fetchEventSponsors(safeArgs.eventId) } override fun onCreateView( @@ -145,10 +136,11 @@ class EventDetailsFragment : Fragment() { loadTicketFragment() } - eventViewModel.error + eventViewModel.popMessage .nonNull() .observe(viewLifecycleOwner, Observer { - showEventErrorScreen(true) + rootView.snackbar(it) + showEventErrorScreen(it == getString(R.string.error_fetching_event_message)) }) eventViewModel.eventFeedback.observe(viewLifecycleOwner, Observer { @@ -160,14 +152,30 @@ class EventDetailsFragment : Fragment() { rootView.feedbackRv.isVisible = true rootView.noFeedBackTv.isVisible = false } - rootView.feedbackContainer.isVisible = it.isNotEmpty() }) + eventViewModel.submittedFeedback + .nonNull() + .observe(viewLifecycleOwner, Observer { + feedbackAdapter.add(it) + rootView.feedbackRv.isVisible = true + rootView.noFeedBackTv.isVisible = false + }) + rootView.feedbackBtn.setOnClickListener { checkForAuthentication() } - eventViewModel.loadEvent(safeArgs.eventId) + val socialLinkLinearLayoutManager = LinearLayoutManager(context) + socialLinkLinearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL + rootView.socialLinksRecycler.layoutManager = socialLinkLinearLayoutManager + rootView.socialLinksRecycler.adapter = socialLinkAdapter + + eventViewModel.socialLinks.observe(viewLifecycleOwner, Observer { + socialLinkAdapter.addAll(it) + rootView.socialLinkContainer.visibility = if (it.isEmpty()) View.GONE else View.VISIBLE + }) + rootView.retry.setOnClickListener { eventViewModel.loadEvent(safeArgs.eventId) } @@ -212,7 +220,7 @@ class EventDetailsFragment : Fragment() { if (scrollY > rootView.eventName.height + rootView.eventImage.height) /*Toolbar title set to name of Event if scrolled more than combined height of eventImage and eventName views*/ - setToolbar(activity, title) + setToolbar(activity, eventViewModel.event.value?.name ?: "") else // Toolbar title set to an empty string setToolbar(activity) @@ -224,19 +232,26 @@ class EventDetailsFragment : Fragment() { rootView.speakerRv.layoutManager = linearLayoutManagerSpeakers rootView.speakerRv.adapter = speakersAdapter - eventViewModel.loadEventSpeakers(safeArgs.eventId).observe(viewLifecycleOwner, Observer { + eventViewModel.eventSpeakers.observe(viewLifecycleOwner, Observer { speakersAdapter.addAll(it) - if (it.isEmpty()) { - rootView.speakersContainer.visibility = View.GONE - } else { - rootView.speakersContainer.visibility = View.VISIBLE - } + rootView.speakersContainer.visibility = if (it.isEmpty()) View.GONE else View.VISIBLE }) + + val sessionClickListener: SessionClickListener = object : SessionClickListener { + override fun onClick(sessionId: Long) { + findNavController(rootView).navigate(EventDetailsFragmentDirections + .actionEventDetailsToSession(sessionId)) + } + } + val linearLayoutManagerSessions = LinearLayoutManager(context) linearLayoutManagerSessions.orientation = LinearLayoutManager.HORIZONTAL rootView.sessionsRv.layoutManager = linearLayoutManagerSessions rootView.sessionsRv.adapter = sessionsAdapter + sessionsAdapter.apply { + onSessionClick = sessionClickListener + } eventViewModel.eventSessions.observe(viewLifecycleOwner, Observer { sessionsAdapter.addAll(it) @@ -247,10 +262,19 @@ class EventDetailsFragment : Fragment() { } }) - eventViewModel.loadEventSponsors(safeArgs.eventId).observe(viewLifecycleOwner, Observer { sponsors -> + eventViewModel.eventSponsors.observe(viewLifecycleOwner, Observer { sponsors -> sponsorsAdapter.addAll(sponsors) rootView.sponsorsSummaryContainer.visibility = if (sponsors.isEmpty()) View.GONE else View.VISIBLE - sponsorsAdapter.notifyDataSetChanged() + }) + + val similarLinearLayoutManager = LinearLayoutManager(context) + similarLinearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL + rootView.similarEventsRecycler.layoutManager = similarLinearLayoutManager + rootView.similarEventsRecycler.adapter = similarEventsAdapter + + eventViewModel.similarEvents.observe(viewLifecycleOwner, Observer { similarEvents -> + similarEventsAdapter.submitList(similarEvents) + rootView.similarEventsContainer.visibility = if (similarEvents.isNotEmpty()) View.VISIBLE else View.GONE }) return rootView @@ -284,7 +308,6 @@ class EventDetailsFragment : Fragment() { } } } - currency = Currency.getInstance(event.paymentCurrency ?: "USD").symbol // About event on-click val aboutEventOnClickListener = View.OnClickListener { findNavController(rootView).navigate(EventDetailsFragmentDirections @@ -324,21 +347,103 @@ class EventDetailsFragment : Fragment() { rootView.eventDateDetailsFirst.text = EventUtils.getFormattedEventDateTimeRange(startsAt, endsAt) rootView.eventDateDetailsSecond.text = EventUtils.getFormattedEventDateTimeRangeSecond(startsAt, endsAt) - // Similar Events Section - if (event.eventTopic != null || !event.locationName.isNullOrBlank() || - !event.searchableLocationName.isNullOrBlank()) { - similarEventsContainer.isVisible = true - eventTopicId = event.eventTopic?.id ?: 0 - eventLocation = - if (event.searchableLocationName.isNullOrBlank()) event.locationName - else event.searchableLocationName - } - // Add event to Calendar val dateClickListener = View.OnClickListener { startCalendar(event) } rootView.eventTimingLinearLayout.setOnClickListener(dateClickListener) } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + eventViewModel.connection + .nonNull() + .observe(this, Observer { isConnected -> + if (isConnected) { + showEventErrorScreen(false) + val currentEvent = eventViewModel.event.value + if (currentEvent == null) + eventViewModel.loadEvent(safeArgs.eventId) + else + loadEvent(currentEvent) + + val currentFeedback = eventViewModel.eventFeedback.value + if (currentFeedback == null) { + eventViewModel.fetchEventFeedback(safeArgs.eventId) + } else { + feedbackAdapter.addAll(currentFeedback) + if (currentFeedback.isEmpty()) { + rootView.feedbackRv.isVisible = false + rootView.noFeedBackTv.isVisible = true + } else { + rootView.feedbackRv.isVisible = true + rootView.noFeedBackTv.isVisible = false + } + } + + val currentSpeakers = eventViewModel.eventSpeakers.value + if (currentSpeakers == null) { + eventViewModel.fetchEventSpeakers(safeArgs.eventId) + } else { + speakersAdapter.addAll(currentSpeakers) + rootView.speakersContainer.visibility = + if (currentSpeakers.isEmpty()) View.GONE else View.VISIBLE + } + + val currentSessions = eventViewModel.eventSessions.value + if (currentSessions == null) { + eventViewModel.fetchEventSessions(safeArgs.eventId) + } else { + sessionsAdapter.addAll(currentSessions) + rootView.sessionContainer.visibility = + if (currentSessions.isEmpty()) View.GONE else View.VISIBLE + } + + val currentSponsors = eventViewModel.eventSponsors.value + if (currentSponsors == null) { + eventViewModel.fetchEventSponsors(safeArgs.eventId) + } else { + sponsorsAdapter.addAll(currentSponsors) + rootView.sponsorsSummaryContainer.visibility = + if (currentSponsors.isEmpty()) View.GONE else View.VISIBLE + } + + val currentSocialLinks = eventViewModel.socialLinks.value + if (currentSocialLinks == null) { + eventViewModel.fetchSocialLink(safeArgs.eventId) + } else { + socialLinkAdapter.addAll(currentSocialLinks) + rootView.socialLinkContainer.visibility = + if (currentSocialLinks.isEmpty()) View.GONE else View.VISIBLE + } + } else { + val currentEvent = eventViewModel.event.value + if (currentEvent == null) + showEventErrorScreen(true) + else + loadEvent(currentEvent) + } + }) + + val eventClickListener: EventClickListener = object : EventClickListener { + override fun onClick(eventID: Long) { + findNavController(view) + .navigate(EventDetailsFragmentDirections.actionSimilarEventsToEventDetails(eventID)) + } + } + + val favFabClickListener: FavoriteFabClickListener = object : FavoriteFabClickListener { + override fun onClick(event: Event, itemPosition: Int) { + eventViewModel.setFavorite(event.id, !event.favorite) + event.favorite = !event.favorite + similarEventsAdapter.notifyItemChanged(itemPosition) + } + } + + similarEventsAdapter.apply { + onEventClick = eventClickListener + onFavFabClick = favFabClickListener + } + } + override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { @@ -347,23 +452,23 @@ class EventDetailsFragment : Fragment() { } R.id.add_to_calendar -> { // Add event to Calendar - startCalendar(eventShare) + eventViewModel.event.value?.let { startCalendar(it) } true } R.id.report_event -> { - reportEvent(eventShare) + eventViewModel.event.value?.let { reportEvent(it) } true } R.id.open_external_event_url -> { - eventShare.externalEventUrl?.let { Utils.openUrl(requireContext(), it) } + eventViewModel.event.value?.externalEventUrl?.let { Utils.openUrl(requireContext(), it) } true } R.id.favorite_event -> { - eventViewModel.setFavorite(safeArgs.eventId, !(eventShare.favorite)) + eventViewModel.event.value?.let { eventViewModel.setFavorite(safeArgs.eventId, !it.favorite) } true } R.id.event_share -> { - EventUtils.share(eventShare, rootView.eventImage) + eventViewModel.event.value?.let { EventUtils.share(it, rootView.eventImage) } return true } R.id.open_faqs -> { @@ -406,48 +511,20 @@ class EventDetailsFragment : Fragment() { } override fun onPrepareOptionsMenu(menu: Menu) { - if (::eventShare.isInitialized) { - if (eventShare.externalEventUrl == null) { + eventViewModel.event.value?.let { currentEvent -> + if (currentEvent.externalEventUrl == null) menu.findItem(R.id.open_external_event_url).isVisible = false - } - setFavoriteIconFilled(eventShare.favorite) + setFavoriteIconFilled(currentEvent.favorite) } super.onPrepareOptionsMenu(menu) } private fun loadTicketFragment() { + val currency = Currency.getInstance(eventViewModel.event.value?.paymentCurrency ?: "USD").symbol findNavController(rootView).navigate(EventDetailsFragmentDirections .actionEventDetailsToTickets(safeArgs.eventId, currency)) } - private fun loadSocialLinksFragment() { - // Initialise SocialLinks Fragment - val socialLinksFragemnt = SocialLinksFragment() - val bundle = Bundle() - bundle.putLong(EVENT_ID, safeArgs.eventId) - socialLinksFragemnt.arguments = bundle - socialLinksFragemnt.setErrorSnack { - eventCoordinatorLayout.longSnackbar(it) - } - val transaction = childFragmentManager.beginTransaction() - transaction.add(R.id.frameContainerSocial, socialLinksFragemnt).commit() - } - - private fun loadSimilarEventsFragment() { - // Initialise SimilarEvents Fragment - val similarEventsFragment = SimilarEventsFragment() - val bundle = Bundle() - bundle.putLong(EVENT_ID, safeArgs.eventId) - eventTopicId?.let { bundle.putLong(EVENT_TOPIC_ID, it) } - eventLocation?.let { bundle.putString(EVENT_LOCATION, it) } - similarEventsFragment.arguments = bundle - similarEventsFragment.setErrorSnack { - eventCoordinatorLayout.longSnackbar(it) - } - childFragmentManager.beginTransaction() - .replace(R.id.frameContainerSimilarEvents, similarEventsFragment).commit() - } - private fun startMap(event: Event) { // start map intent val mapUrl = loadMapUrl(event) @@ -486,7 +563,7 @@ class EventDetailsFragment : Fragment() { private fun redirectToLogin() { findNavController(rootView).navigate(EventDetailsFragmentDirections - .actionEventDetailsToLogin(getString(R.string.log_in_first))) + .actionEventDetailsToAuth(getString(R.string.log_in_first), EVENT_DETAIL_FRAGMENT)) } private fun writeFeedback() { @@ -499,7 +576,6 @@ class EventDetailsFragment : Fragment() { eventViewModel.submitFeedback(layout.feedback.text.toString(), layout.feedbackrating.rating, safeArgs.eventId) - rootView.snackbar(R.string.feedback_submitted) } .setNegativeButton(getString(R.string.cancel)) { dialog, _ -> dialog.cancel() diff --git a/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsViewModel.kt index b964527343..9ce8dbdc6f 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/EventDetailsViewModel.kt @@ -12,9 +12,13 @@ import org.fossasia.openevent.general.auth.AuthHolder import org.fossasia.openevent.general.auth.User import org.fossasia.openevent.general.auth.UserId import org.fossasia.openevent.general.common.SingleLiveEvent +import org.fossasia.openevent.general.connectivity.MutableConnectionLiveData import org.fossasia.openevent.general.data.Resource import org.fossasia.openevent.general.event.feedback.Feedback import org.fossasia.openevent.general.sessions.Session +import org.fossasia.openevent.general.sessions.SessionService +import org.fossasia.openevent.general.social.SocialLinksService +import org.fossasia.openevent.general.social.SocialLink import org.fossasia.openevent.general.speakers.Speaker import org.fossasia.openevent.general.speakers.SpeakerService import org.fossasia.openevent.general.sponsor.Sponsor @@ -27,36 +31,53 @@ class EventDetailsViewModel( private val authHolder: AuthHolder, private val speakerService: SpeakerService, private val sponsorService: SponsorService, - private val resource: Resource + private val sessionService: SessionService, + private val socialLinksService: SocialLinksService, + private val resource: Resource, + private val mutableConnectionLiveData: MutableConnectionLiveData ) : ViewModel() { private val compositeDisposable = CompositeDisposable() + val connection: LiveData = mutableConnectionLiveData private val mutableProgress = MutableLiveData() val progress: LiveData = mutableProgress private val mutableUser = MutableLiveData() val user: LiveData = mutableUser - private val mutableError = SingleLiveEvent() - val error: LiveData = mutableError + private val mutablePopMessage = SingleLiveEvent() + val popMessage: LiveData = mutablePopMessage private val mutableEvent = MutableLiveData() val event: LiveData = mutableEvent private val mutableEventFeedback = MutableLiveData>() val eventFeedback: LiveData> = mutableEventFeedback + private val mutableSubmittedFeedback = MutableLiveData() + val submittedFeedback: LiveData = mutableSubmittedFeedback private val mutableEventSessions = MutableLiveData>() val eventSessions: LiveData> = mutableEventSessions - var eventSpeakers: LiveData> = MutableLiveData() + private val mutableEventSpeakers = MutableLiveData>() + val eventSpeakers: LiveData> = mutableEventSpeakers + private val mutableEventSponsors = MutableLiveData>() + val eventSponsors: LiveData> = mutableEventSponsors + private val mutableSocialLinks = MutableLiveData>() + val socialLinks: LiveData> = mutableSocialLinks + private val mutableSimilarEvents = MutableLiveData>() + val similarEvents: LiveData> = mutableSimilarEvents fun isLoggedIn() = authHolder.isLoggedIn() fun getId() = authHolder.getId() - fun loadEventFeedback(id: Long) { + fun fetchEventFeedback(id: Long) { + if (id == -1L) return + compositeDisposable += eventService.getEventFeedback(id) .withDefaultSchedulers() .subscribe({ mutableEventFeedback.value = it }, { Timber.e(it, "Error fetching events feedback") + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.feedback)) }) } @@ -66,43 +87,91 @@ class EventDetailsViewModel( compositeDisposable += eventService.submitFeedback(feedback) .withDefaultSchedulers() .subscribe({ - //Do Nothing + mutablePopMessage.value = resource.getString(R.string.feedback_submitted) + mutableSubmittedFeedback.value = it }, { - it.message.toString() == "HTTP 400 BAD REQUEST" + mutablePopMessage.value = resource.getString(R.string.error_submitting_feedback) }) } fun fetchEventSpeakers(id: Long) { - speakerService.fetchSpeakersForEvent(id) + if (id == -1L) return + + compositeDisposable += speakerService.fetchSpeakersForEvent(id) .withDefaultSchedulers() - .subscribe ({ - //Do Nothing + .subscribe({ + mutableEventSpeakers.value = it }, { Timber.e(it, "Error fetching speaker for event id %d", id) - mutableError.value = resource.getString(R.string.error_fetching_event_message) + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.speakers)) + }) + } + + fun fetchSocialLink(id: Long) { + if (id == -1L) return + + compositeDisposable += socialLinksService.getSocialLinks(id) + .withDefaultSchedulers() + .subscribe({ + mutableSocialLinks.value = it + }, { + Timber.e(it, "Error fetching social link for event id $id") + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.social_links)) }) } - fun loadEventSpeakers(id: Long): LiveData> { - eventSpeakers = speakerService.fetchSpeakersFromDb(id) - return eventSpeakers + fun fetchSimilarEvents(eventId: Long, topicId: Long, location: String?) { + if (eventId == -1L) return + + if (topicId != -1L) { + compositeDisposable += eventService.getSimilarEvents(topicId) + .withDefaultSchedulers() + .subscribe({ + val similarEventList = mutableListOf() + mutableSimilarEvents.value?.let { currentEvents -> similarEventList.addAll(currentEvents) } + val list = it.filter { it.id != eventId } + similarEventList.addAll(list) + mutableSimilarEvents.value = similarEventList + }, { + Timber.e(it, "Error fetching similar events") + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.similar_events)) + }) + } + + compositeDisposable += eventService.getEventsByLocation(location) + .withDefaultSchedulers() + .subscribe({ + val similarEventList = mutableListOf() + mutableSimilarEvents.value?.let { currentEvents -> similarEventList.addAll(currentEvents) } + val list = it.filter { it.id != eventId } + similarEventList.addAll(list) + mutableSimilarEvents.value = similarEventList + }, { + Timber.e(it, "Error fetching similar events") + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.similar_events)) + }) } fun fetchEventSponsors(id: Long) { - sponsorService.fetchSponsorsWithEvent(id) + if (id == -1L) return + + compositeDisposable += sponsorService.fetchSponsorsWithEvent(id) .withDefaultSchedulers() - .subscribe ({ - //Do Nothing + .subscribe({ + mutableEventSponsors.value = it }, { Timber.e(it, "Error fetching sponsor for event id %d", id) - mutableError.value = resource.getString(R.string.error_fetching_event_message) + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.sponsors)) }) } - fun loadEventSponsors(id: Long): LiveData> = sponsorService.fetchSponsorsFromDb(id) - fun loadEvent(id: Long) { - if (id.equals(-1)) { - mutableError.value = resource.getString(R.string.error_fetching_event_message) + if (id == -1L) { + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_message) return } compositeDisposable += eventService.getEvent(id) @@ -115,16 +184,20 @@ class EventDetailsViewModel( mutableEvent.value = it }, { Timber.e(it, "Error fetching event %d", id) - mutableError.value = resource.getString(R.string.error_fetching_event_message) + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_message) }) } - fun loadEventSessions(id: Long) { - compositeDisposable += eventService.getEventSessions(id) + fun fetchEventSessions(id: Long) { + if (id == -1L) return + + compositeDisposable += sessionService.fetchSessionForEvent(id) .withDefaultSchedulers() .subscribe({ mutableEventSessions.value = it }, { + mutablePopMessage.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.sessions)) Timber.e(it, "Error fetching events sessions") }) } @@ -132,8 +205,8 @@ class EventDetailsViewModel( fun loadMap(event: Event): String { // location handling val BASE_URL = "https://api.mapbox.com/v4/mapbox.emerald/pin-l-marker+673ab7" - val LOCATION = "(" + event.longitude + "," + event.latitude + ")/" + event.longitude + "," + event.latitude - return BASE_URL + LOCATION + ",15/900x500.png?access_token=" + MAPBOX_KEY + val LOCATION = "(${event.longitude},${event.latitude})/${event.longitude},${event.latitude}" + return "$BASE_URL$LOCATION,15/900x500.png?access_token=$MAPBOX_KEY" } fun setFavorite(eventId: Long, favorite: Boolean) { @@ -143,7 +216,7 @@ class EventDetailsViewModel( Timber.d("Success") }, { Timber.e(it, "Error") - mutableError.value = resource.getString(R.string.error) + mutablePopMessage.value = resource.getString(R.string.error) }) } diff --git a/app/src/main/java/org/fossasia/openevent/general/event/EventService.kt b/app/src/main/java/org/fossasia/openevent/general/event/EventService.kt index 6642044377..8ced85a905 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/EventService.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/EventService.kt @@ -14,9 +14,7 @@ import org.fossasia.openevent.general.event.topic.EventTopicApi import org.fossasia.openevent.general.event.topic.EventTopicsDao import org.fossasia.openevent.general.event.types.EventType import org.fossasia.openevent.general.event.types.EventTypesApi -import org.fossasia.openevent.general.sessions.Session -import org.fossasia.openevent.general.sessions.SessionApi -import java.util.Locale.filter +import java.util.Date class EventService( private val eventApi: EventApi, @@ -26,8 +24,7 @@ class EventService( private val eventTypesApi: EventTypesApi, private val eventLocationApi: EventLocationApi, private val eventFeedbackApi: FeedbackApi, - private val eventFAQApi: EventFAQApi, - private val eventSessionApi: SessionApi + private val eventFAQApi: EventFAQApi ) { fun getEvents(): Flowable> { @@ -77,12 +74,9 @@ class EventService( fun submitFeedback(feedback: Feedback): Single { return eventFeedbackApi.postfeedback(feedback) } - fun getEventSessions(id: Long): Single> { - return eventSessionApi.getSessionsForEvent(id) - } fun getSearchEvents(eventName: String, sortBy: String): Single> { return eventApi.searchEvents(sortBy, eventName).flatMap { apiList -> - var eventIds = apiList.map { it.id }.toList() + val eventIds = apiList.map { it.id }.toList() eventDao.getFavoriteEventWithinIds(eventIds).flatMap { favIds -> updateFavorites(apiList, favIds) } @@ -94,7 +88,8 @@ class EventService( } fun getEventsByLocation(locationName: String?): Single> { - val query = "[{\"name\":\"location-name\",\"op\":\"ilike\",\"val\":\"%$locationName%\"}]" + val query = "[{\"name\":\"location-name\",\"op\":\"ilike\",\"val\":\"%$locationName%\"}," + + "{\"name\":\"ends-at\",\"op\":\"ge\",\"val\":\"%${EventUtils.getTimeInISO8601(Date())}%\"}]" return eventApi.searchEvents("name", query).flatMap { apiList -> val eventIds = apiList.map { it.id }.toList() eventTopicsDao.insertEventTopics(getEventTopicList(apiList)) @@ -115,13 +110,25 @@ class EventService( return eventDao.getEvent(id) } - fun getEventFromApi(id: Long): Single { - return eventApi.getEventFromApi(id) + fun getEventById(eventId: Long): Single { + return eventDao.getEventById(eventId) + .onErrorResumeNext { + eventApi.getEventFromApi(eventId).map { + eventDao.insertEvent(it) + it + } + } } fun getEventsUnderUser(eventIds: List): Single> { val query = buildQuery(eventIds) return eventApi.eventsUnderUser(query) + .map { + eventDao.insertEvents(it) + it + }.onErrorResumeNext { + eventDao.getEventWithIds(eventIds).map { it } + } } fun setFavorite(eventId: Long, favorite: Boolean): Completable { diff --git a/app/src/main/java/org/fossasia/openevent/general/event/EventUtils.kt b/app/src/main/java/org/fossasia/openevent/general/event/EventUtils.kt index 40bcd47266..ac53d675a7 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/EventUtils.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/EventUtils.kt @@ -22,6 +22,7 @@ import java.io.FileOutputStream import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import java.util.TimeZone object EventUtils { @@ -207,6 +208,30 @@ object EventUtils { } } + fun getFormattedDateWithoutWeekday(date: ZonedDateTime): String { + val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("MMM d, y") + return try { + dateFormat.format(date) + } catch (e: IllegalArgumentException) { + Timber.e(e, "Error formatting Date") + "" + } + } + + fun getFormattedWeekDay(date: ZonedDateTime): String { + val dateFormat: DateTimeFormatter = DateTimeFormatter.ofPattern("EEE") + return try { + dateFormat.format(date) + } catch (e: IllegalArgumentException) { + Timber.e(e, "Error formatting Date") + "" + } + } + + fun getDayDifferenceFromToday(date: String): Long { + return (System.currentTimeMillis() - EventUtils.getTimeInMilliSeconds(date, null)) / (1000 * 60 * 60 * 24) + } + /** * share event detail along with event image * if image loading is successful then imageView tag will be set to String @@ -245,4 +270,11 @@ object EventUtils { bmpUri } } + + fun getTimeInISO8601(date: Date): String { + val tz = TimeZone.getTimeZone(TimeZone.getDefault().id) + val df = SimpleDateFormat("yyyy-MM-dd'T'HH:mm", Locale.getDefault()) + df.timeZone = tz + return df.format(date) + } } diff --git a/app/src/main/java/org/fossasia/openevent/general/event/EventsFragment.kt b/app/src/main/java/org/fossasia/openevent/general/event/EventsFragment.kt index d3217dc311..7937da2757 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/EventsFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/EventsFragment.kt @@ -5,6 +5,9 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.view.Menu +import android.view.MenuInflater +import android.view.MenuItem import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Observer @@ -19,19 +22,16 @@ import kotlinx.android.synthetic.main.fragment_events.view.locationTextView import kotlinx.android.synthetic.main.fragment_events.view.progressBar import kotlinx.android.synthetic.main.fragment_events.view.shimmerEvents import kotlinx.android.synthetic.main.fragment_events.view.swiperefresh -import kotlinx.android.synthetic.main.fragment_events.view.noEventsMessage +import kotlinx.android.synthetic.main.fragment_events.view.eventsEmptyView import kotlinx.android.synthetic.main.fragment_events.view.eventsNestedScrollView import org.fossasia.openevent.general.R import org.fossasia.openevent.general.ScrollToTop import org.fossasia.openevent.general.common.EventClickListener +import org.fossasia.openevent.general.common.EventsDiffCallback import org.fossasia.openevent.general.common.FavoriteFabClickListener import org.fossasia.openevent.general.data.Preference -import org.fossasia.openevent.general.di.Scopes import org.fossasia.openevent.general.search.SAVED_LOCATION import org.fossasia.openevent.general.utils.extensions.nonNull -import org.koin.android.ext.android.inject -import org.koin.androidx.scope.ext.android.bindScope -import org.koin.androidx.scope.ext.android.getOrCreateScope import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber import org.fossasia.openevent.general.utils.Utils.setToolbar @@ -51,15 +51,11 @@ class EventsFragment : Fragment(), ScrollToTop { private val eventsViewModel by viewModel() private lateinit var rootView: View private val preference = Preference() - private val eventsListAdapter: EventsListAdapter by inject( - scope = getOrCreateScope(Scopes.EVENTS_FRAGMENT.toString()) - ) + private val eventsListAdapter = EventsListAdapter(EventLayoutType.EVENTS, EventsDiffCallback()) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - bindScope(getOrCreateScope(Scopes.EVENTS_FRAGMENT.toString())) - eventsViewModel.events .nonNull() .observe(this, Observer { list -> @@ -75,7 +71,7 @@ class EventsFragment : Fragment(), ScrollToTop { savedInstanceState: Bundle? ): View? { rootView = inflater.inflate(R.layout.fragment_events, container, false) - + setHasOptionsMenu(true) if (preference.getString(SAVED_LOCATION).isNullOrEmpty()) { findNavController(requireActivity(), R.id.frameContainer).navigate(R.id.welcomeFragment) } @@ -197,13 +193,27 @@ class EventsFragment : Fragment(), ScrollToTop { type = hashTag)) } + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { + inflater.inflate(R.menu.events, menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + R.id.notifications -> { + findNavController(rootView).navigate(EventsFragmentDirections.actionEventsToNotification()) + true + } + else -> super.onOptionsItemSelected(item) + } + } + private fun showNoInternetScreen(show: Boolean) { rootView.homeScreenLL.visibility = if (!show) View.VISIBLE else View.GONE rootView.noInternetCard.visibility = if (show) View.VISIBLE else View.GONE } private fun showEmptyMessage(itemCount: Int) { - rootView.noEventsMessage.visibility = if (itemCount == 0) View.VISIBLE else View.GONE + rootView.eventsEmptyView.visibility = if (itemCount == 0) View.VISIBLE else View.GONE } override fun onStop() { diff --git a/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQFragment.kt b/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQFragment.kt index 05d29cdb67..b77a42065c 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQFragment.kt @@ -5,12 +5,11 @@ import android.view.LayoutInflater import android.view.MenuItem import android.view.View import android.view.ViewGroup -import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.navigation.fragment.navArgs import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.fragment_event_faq.view.faqContainer +import kotlinx.android.synthetic.main.fragment_event_faq.view.faqEmptyView import kotlinx.android.synthetic.main.fragment_event_faq.view.faqRv import org.fossasia.openevent.general.R import org.fossasia.openevent.general.about.AboutEventFragmentArgs @@ -33,7 +32,7 @@ class EventFAQFragment : Fragment() { eventFAQViewModel.eventFAQ.observe(viewLifecycleOwner, Observer { faqAdapter.addAll(it) - rootView.faqContainer.isVisible = !it.isEmpty() + rootView.faqEmptyView.visibility = if (it.isEmpty()) View.VISIBLE else View.GONE }) eventFAQViewModel.loadEventFaq(safeArgs.eventId) diff --git a/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQViewModel.kt index b29cf65f18..9699853874 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/faq/EventFAQViewModel.kt @@ -21,7 +21,7 @@ class EventFAQViewModel(private val eventService: EventService, private val reso val error: LiveData = mutableError fun loadEventFaq(id: Long) { - if (id.equals(-1)) { + if (id == -1L) { mutableError.value = Resource().getString(R.string.error_fetching_event_message) return } diff --git a/app/src/main/java/org/fossasia/openevent/general/event/feedback/FeedbackRecyclerAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/event/feedback/FeedbackRecyclerAdapter.kt index 510a376c22..771d53aa85 100644 --- a/app/src/main/java/org/fossasia/openevent/general/event/feedback/FeedbackRecyclerAdapter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/event/feedback/FeedbackRecyclerAdapter.kt @@ -6,7 +6,7 @@ import androidx.recyclerview.widget.RecyclerView import org.fossasia.openevent.general.R class FeedbackRecyclerAdapter : RecyclerView.Adapter() { - val feedbackList = ArrayList() + private val feedbackList = ArrayList() fun addAll(feedbackList: List) { if (feedbackList.isNotEmpty()) @@ -15,6 +15,11 @@ class FeedbackRecyclerAdapter : RecyclerView.Adapter() { notifyDataSetChanged() } + fun add(feedback: Feedback) { + feedbackList.add(0, feedback) + notifyItemInserted(0) + } + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FeedbackViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.item_feedback, parent, false) return FeedbackViewHolder(view) diff --git a/app/src/main/java/org/fossasia/openevent/general/event/topic/SimilarEventsFragment.kt b/app/src/main/java/org/fossasia/openevent/general/event/topic/SimilarEventsFragment.kt deleted file mode 100644 index 9b63a65165..0000000000 --- a/app/src/main/java/org/fossasia/openevent/general/event/topic/SimilarEventsFragment.kt +++ /dev/null @@ -1,161 +0,0 @@ -package org.fossasia.openevent.general.event.topic - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.navigation.Navigation.findNavController -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.navigation.fragment.navArgs -import kotlinx.android.synthetic.main.fragment_similar_events.moreLikeThis -import kotlinx.android.synthetic.main.fragment_similar_events.progressBar -import kotlinx.android.synthetic.main.fragment_similar_events.similarEventsDivider -import kotlinx.android.synthetic.main.fragment_similar_events.similarEventsRecycler -import kotlinx.android.synthetic.main.fragment_similar_events.view.similarEventsRecycler -import org.fossasia.openevent.general.R -import org.fossasia.openevent.general.di.Scopes -import org.fossasia.openevent.general.event.Event -import org.fossasia.openevent.general.common.EventClickListener -import org.fossasia.openevent.general.event.EventsListAdapter -import org.fossasia.openevent.general.common.FavoriteFabClickListener -import org.fossasia.openevent.general.event.EventDetailsFragmentDirections -import org.fossasia.openevent.general.event.EventLayoutType -import org.fossasia.openevent.general.utils.extensions.nonNull -import org.koin.android.ext.android.get -import org.koin.androidx.scope.ext.android.bindScope -import org.koin.androidx.scope.ext.android.getOrCreateScope -import org.koin.androidx.viewmodel.ext.android.viewModel -import timber.log.Timber - -class SimilarEventsFragment : Fragment() { - - private val similarEventsViewModel by viewModel() - private val safeArgs: SimilarEventsFragmentArgs by navArgs() - private lateinit var rootView: View - private lateinit var linearLayoutManager: LinearLayoutManager - private lateinit var similarEventsListAdapter: EventsListAdapter - private var similarIdEvents: MutableList = mutableListOf() - private var similarLocationEvents: MutableList = mutableListOf() - private var similarEvents: MutableList = mutableListOf() - private var showErrorSnack: ((String) -> Unit)? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - bindScope(getOrCreateScope(Scopes.SIMILAR_EVENTS_FRAGMENT.toString())) - similarEventsViewModel.eventId = safeArgs.eventId - similarEventsViewModel.loadSimilarIdEvents(safeArgs.eventTopicId) - similarEventsViewModel.loadSimilarLocationEvents(safeArgs.eventLocation.toString()) - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - rootView = inflater.inflate(R.layout.fragment_similar_events, container, false) - similarEventsViewModel.similarLocationEvents - .nonNull() - .observe(viewLifecycleOwner, Observer { - similarLocationEvents.clear() - similarLocationEvents = it.toMutableList() - Timber.d("Fetched similar location events of size %s", it.size) - setUpAdapter() - }) - - similarEventsViewModel.similarIdEvents - .nonNull() - .observe(viewLifecycleOwner, Observer { - similarIdEvents.clear() - similarIdEvents = it.toMutableList() - Timber.d("Fetched similar id events of size %s", it.size) - setUpAdapter() - }) - - similarEventsViewModel.error - .nonNull() - .observe(viewLifecycleOwner, Observer { - showErrorSnack?.invoke(it) - }) - - similarEventsViewModel.progress - .nonNull() - .observe(viewLifecycleOwner, Observer { - progressBar.isVisible = it - }) - - return rootView - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - similarEventsListAdapter = EventsListAdapter(EventLayoutType.SIMILAR_EVENTS, get()) - - val eventClickListener: EventClickListener = object : EventClickListener { - override fun onClick(eventID: Long) { - findNavController(view).navigate(EventDetailsFragmentDirections - .actionSimilarEventsToEventDetails(eventID)) - } - } - - val favFabClickListener: FavoriteFabClickListener = object : FavoriteFabClickListener { - override fun onClick(event: Event, itemPosition: Int) { - similarEventsViewModel.setFavorite(event.id, !event.favorite) - event.favorite = !event.favorite - similarEventsListAdapter.notifyItemChanged(itemPosition) - } - } - - similarEventsListAdapter.apply { - onEventClick = eventClickListener - onFavFabClick = favFabClickListener - } - - linearLayoutManager = LinearLayoutManager(requireContext()) - linearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL - view.similarEventsRecycler.layoutManager = linearLayoutManager - - view.similarEventsRecycler.adapter = similarEventsListAdapter - view.similarEventsRecycler.isNestedScrollingEnabled = false - } - - private fun handleVisibility(similarEvents: List) { - similarEventsDivider.isVisible = !similarEvents.isEmpty() - moreLikeThis.isVisible = !similarEvents.isEmpty() - similarEventsRecycler.isVisible = !similarEvents.isEmpty() - } - - /* - function to set errorSnackMessage CallBack, to be invoked , - to be invoked when snack error is generated - */ - fun setErrorSnack(errorSnack: (String) -> Unit) { - showErrorSnack = errorSnack - } - - private fun setUpAdapter() { - similarEvents.clear() - var id: Long - - when { - similarIdEvents.size != 0 && similarLocationEvents.size != 0 -> { - similarIdEvents.forEach { - id = it.id - if (similarLocationEvents.find { id == it.id } == null) similarEvents.add(it) - } - similarEvents.addAll(similarLocationEvents) - } - similarIdEvents.size == 0 -> similarEvents.addAll(similarLocationEvents) - similarLocationEvents.size == 0 -> similarEvents.addAll(similarIdEvents) - } - - handleVisibility(similarEvents) - Timber.d("Fetched Similar events of size %s", similarEvents.size) - if (similarEventsListAdapter.currentList.size != similarEvents.size) similarEvents.shuffle() - similarEventsListAdapter.submitList(similarEvents) - similarEventsListAdapter.notifyDataSetChanged() - } -} diff --git a/app/src/main/java/org/fossasia/openevent/general/event/topic/SimilarEventsViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/event/topic/SimilarEventsViewModel.kt deleted file mode 100644 index 432e5708c4..0000000000 --- a/app/src/main/java/org/fossasia/openevent/general/event/topic/SimilarEventsViewModel.kt +++ /dev/null @@ -1,80 +0,0 @@ -package org.fossasia.openevent.general.event.topic - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import io.reactivex.disposables.CompositeDisposable -import io.reactivex.rxkotlin.plusAssign -import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers -import org.fossasia.openevent.general.R -import org.fossasia.openevent.general.common.SingleLiveEvent -import org.fossasia.openevent.general.data.Resource -import org.fossasia.openevent.general.event.Event -import org.fossasia.openevent.general.event.EventService -import timber.log.Timber - -class SimilarEventsViewModel(private val eventService: EventService, private val resource: Resource) : ViewModel() { - - private val compositeDisposable = CompositeDisposable() - - private val mutableProgress = MutableLiveData() - val progress: LiveData = mutableProgress - private val mutableSimilarLocationEvents = MutableLiveData>() - val similarLocationEvents: LiveData> = mutableSimilarLocationEvents - private val mutableSimilarIdEvents = MutableLiveData>() - val similarIdEvents: LiveData> = mutableSimilarIdEvents - private val mutableError = SingleLiveEvent() - val error: LiveData = mutableError - - var eventId: Long = -1 - - fun loadSimilarIdEvents(id: Long) { - if (id == -1L) { - return - } - compositeDisposable += eventService.getSimilarEvents(id) - .withDefaultSchedulers() - .doOnSubscribe { - mutableProgress.value = true - }.subscribe({ - mutableProgress.value = false - mutableSimilarIdEvents.value = it.filter { it.id != eventId } - }, { - Timber.e(it, "Error fetching similar events") - mutableError.value = "Error fetching similar events" - }) - } - - fun loadSimilarLocationEvents(location: String) { - - compositeDisposable += eventService.getEventsByLocation(location) - .withDefaultSchedulers() - .doOnSubscribe { - mutableProgress.value = true - } - .doFinally { - mutableProgress.value = false - }.subscribe({ - mutableSimilarLocationEvents.value = it.filter { it.id != eventId } - }, { - Timber.e(it, "Error fetching similar events") - mutableError.value = resource.getString(R.string.fetch_similar_events_error_message) - }) - } - - fun setFavorite(eventId: Long, favorite: Boolean) { - compositeDisposable += eventService.setFavorite(eventId, favorite) - .withDefaultSchedulers() - .subscribe({ - Timber.d("Success") - }, { - Timber.e(it, "Error") - mutableError.value = resource.getString(R.string.error) - }) - } - - override fun onCleared() { - super.onCleared() - compositeDisposable.clear() - } -} diff --git a/app/src/main/java/org/fossasia/openevent/general/favorite/FavoriteFragment.kt b/app/src/main/java/org/fossasia/openevent/general/favorite/FavoriteFragment.kt index 81d8e4ba1d..fb6f7a9f58 100644 --- a/app/src/main/java/org/fossasia/openevent/general/favorite/FavoriteFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/favorite/FavoriteFragment.kt @@ -19,16 +19,13 @@ import kotlinx.android.synthetic.main.fragment_favorite.view.tomorrowChip import kotlinx.android.synthetic.main.fragment_favorite.view.weekendChip import kotlinx.android.synthetic.main.fragment_favorite.view.monthChip import org.fossasia.openevent.general.R -import org.fossasia.openevent.general.di.Scopes import org.fossasia.openevent.general.event.Event import org.fossasia.openevent.general.common.EventClickListener +import org.fossasia.openevent.general.common.EventsDiffCallback import org.fossasia.openevent.general.common.FavoriteFabClickListener import org.fossasia.openevent.general.data.Preference import org.fossasia.openevent.general.search.SAVED_LOCATION import org.fossasia.openevent.general.utils.extensions.nonNull -import org.koin.android.ext.android.inject -import org.koin.androidx.scope.ext.android.bindScope -import org.koin.androidx.scope.ext.android.getOrCreateScope import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber import org.fossasia.openevent.general.utils.Utils.setToolbar @@ -40,14 +37,7 @@ const val FAVORITE_EVENT_DATE_FORMAT: String = "favoriteEventDateFormat" class FavoriteFragment : Fragment() { private val favoriteEventViewModel by viewModel() private lateinit var rootView: View - private val favoriteEventsRecyclerAdapter: FavoriteEventsRecyclerAdapter by inject( - scope = getOrCreateScope(Scopes.FAVORITE_FRAGMENT.toString()) - ) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - bindScope(getOrCreateScope(Scopes.FAVORITE_FRAGMENT.toString())) - } + private val favoriteEventsRecyclerAdapter = FavoriteEventsRecyclerAdapter(EventsDiffCallback()) override fun onCreateView( inflater: LayoutInflater, diff --git a/app/src/main/java/org/fossasia/openevent/general/notification/Notification.kt b/app/src/main/java/org/fossasia/openevent/general/notification/Notification.kt new file mode 100644 index 0000000000..e433e0632c --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/notification/Notification.kt @@ -0,0 +1,21 @@ +package org.fossasia.openevent.general.notification + +import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.annotation.JsonNaming +import com.github.jasminb.jsonapi.IntegerIdHandler +import com.github.jasminb.jsonapi.annotations.Id +import com.github.jasminb.jsonapi.annotations.Type +import io.reactivex.annotations.NonNull + +@Type("notification") +@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) +data class Notification( + @Id(IntegerIdHandler::class) + @NonNull + val id: Long, + val message: String? = null, + val receivedAt: String? = null, + val isRead: Boolean? = null, + val title: String? = null, + val deletedAt: String? = null +) diff --git a/app/src/main/java/org/fossasia/openevent/general/notification/NotificationApi.kt b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationApi.kt new file mode 100644 index 0000000000..1c159bede4 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationApi.kt @@ -0,0 +1,24 @@ +package org.fossasia.openevent.general.notification + +import io.reactivex.Completable +import io.reactivex.Single +import retrofit2.http.DELETE +import retrofit2.http.Body +import retrofit2.http.GET +import retrofit2.http.PATCH +import retrofit2.http.Path + +interface NotificationApi { + + @GET("users/{userId}/notifications?sort=message") + fun getNotifications(@Path("userId") userId: Long): Single> + + @PATCH("notifications/{notification_id}") + fun updateNotification( + @Path("notification_id") notificationId: Long, + @Body notification: Notification + ): Single> + + @DELETE("notifications/{notification_id}") + fun deleteNotification(@Path("notification_id") notificationId: Long): Completable +} diff --git a/app/src/main/java/org/fossasia/openevent/general/notification/NotificationFragment.kt b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationFragment.kt new file mode 100644 index 0000000000..543bf61f8d --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationFragment.kt @@ -0,0 +1,146 @@ +package org.fossasia.openevent.general.notification + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.core.view.isVisible +import androidx.lifecycle.Observer +import androidx.navigation.Navigation +import androidx.recyclerview.widget.LinearLayoutManager +import kotlinx.android.synthetic.main.content_no_internet.view.retry +import kotlinx.android.synthetic.main.content_no_internet.view.noInternetCard +import kotlinx.android.synthetic.main.fragment_notification.view.notificationRecycler +import kotlinx.android.synthetic.main.fragment_notification.view.swiperefresh +import kotlinx.android.synthetic.main.fragment_notification.view.shimmerNotifications +import kotlinx.android.synthetic.main.fragment_notification.view.notificationCoordinatorLayout +import kotlinx.android.synthetic.main.fragment_notification.view.noNotification +import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.auth.LoginFragmentArgs +import org.fossasia.openevent.general.utils.Utils.setToolbar +import org.fossasia.openevent.general.utils.extensions.nonNull +import org.jetbrains.anko.design.snackbar +import org.koin.androidx.viewmodel.ext.android.viewModel + +const val NOTIFICATION_FRAGMENT = "notificationFragment" + +class NotificationFragment : Fragment() { + private val notificationViewModel by viewModel() + private val recyclerAdapter = NotificationsRecyclerAdapter() + private lateinit var rootView: View + + override fun onStart() { + super.onStart() + if (!notificationViewModel.isLoggedIn()) { + redirectToLogin() + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + rootView = inflater.inflate(R.layout.fragment_notification, container, false) + setToolbar(activity, getString(R.string.title_notifications), true) + setHasOptionsMenu(true) + + if (notificationViewModel.isLoggedIn()) { + initObservers() + if (notificationViewModel.notifications.value == null) { + notificationViewModel.getNotifications() + } + rootView.notificationRecycler.layoutManager = LinearLayoutManager(requireContext()) + rootView.notificationRecycler.adapter = recyclerAdapter + } + return rootView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + rootView.retry.setOnClickListener { + notificationViewModel.getNotifications() + } + + rootView.swiperefresh.setOnRefreshListener { + notificationViewModel.getNotifications() + } + } + + private fun initObservers() { + notificationViewModel.notifications + .nonNull() + .observe(viewLifecycleOwner, Observer { + showNoNotifications(it.isEmpty()) + recyclerAdapter.addAll(it) + recyclerAdapter.notifyDataSetChanged() + }) + + notificationViewModel.error + .nonNull() + .observe(viewLifecycleOwner, Observer { + rootView.swiperefresh.isRefreshing = false + rootView.notificationCoordinatorLayout.snackbar(it) + }) + + notificationViewModel.progress + .nonNull() + .observe(viewLifecycleOwner, Observer { + if (it) { + rootView.shimmerNotifications.startShimmer() + rootView.noNotification.isVisible = false + rootView.notificationRecycler.isVisible = false + } else { + rootView.shimmerNotifications.stopShimmer() + rootView.swiperefresh.isRefreshing = it + } + rootView.shimmerNotifications.isVisible = it + }) + + notificationViewModel.noInternet + .nonNull() + .observe(viewLifecycleOwner, Observer { + if (it) { + rootView.notificationCoordinatorLayout + .snackbar(resources.getString(R.string.no_internet_connection_message)) + rootView.swiperefresh.isRefreshing = !it + } + showNoInternet(it && notificationViewModel.notifications.value.isNullOrEmpty()) + }) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + activity?.onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + private fun showNoInternet(visible: Boolean) { + rootView.noInternetCard.isVisible = visible + rootView.notificationRecycler.isVisible = !visible + } + + private fun showNoNotifications(visible: Boolean) { + rootView.noNotification.isVisible = visible + rootView.notificationRecycler.isVisible = !visible + } + + private fun redirectToLogin() { + LoginFragmentArgs(getString(R.string.log_in_first)) + .toBundle() + .also { + Navigation.findNavController(rootView).navigate( + NotificationFragmentDirections.actionNotificationToAuth( + getString(R.string.log_in_first), + NOTIFICATION_FRAGMENT) + ) + } + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/notification/NotificationService.kt b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationService.kt new file mode 100644 index 0000000000..5cd32b1c9e --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationService.kt @@ -0,0 +1,11 @@ +package org.fossasia.openevent.general.notification + +import io.reactivex.Single + +class NotificationService( + private val notificationApi: NotificationApi +) { + fun getNotifications(userId: Long): Single> { + return notificationApi.getNotifications(userId) + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/notification/NotificationViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationViewModel.kt new file mode 100644 index 0000000000..d8dcc81622 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationViewModel.kt @@ -0,0 +1,66 @@ +package org.fossasia.openevent.general.notification + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.auth.AuthHolder +import org.fossasia.openevent.general.common.SingleLiveEvent +import org.fossasia.openevent.general.data.Network +import org.fossasia.openevent.general.data.Resource +import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers +import timber.log.Timber + +class NotificationViewModel( + private val notificationService: NotificationService, + private val authHolder: AuthHolder, + private val network: Network, + private val resource: Resource +) : ViewModel() { + + private val compositeDisposable = CompositeDisposable() + private val mutableNotifications = MutableLiveData>() + val notifications: LiveData> = mutableNotifications + + private val mutableProgress = MutableLiveData() + val progress: LiveData = mutableProgress + + private val mutableNoInternet = SingleLiveEvent() + val noInternet: LiveData = mutableNoInternet + + private val mutableError = SingleLiveEvent() + val error: LiveData = mutableError + + fun getId() = authHolder.getId() + + fun isLoggedIn() = authHolder.isLoggedIn() + + fun getNotifications() { + + if (!isConnected()) { + return + } + + compositeDisposable += notificationService.getNotifications(getId()) + .withDefaultSchedulers() + .doOnSubscribe { + mutableProgress.value = true + }.doFinally { + mutableProgress.value = false + }.subscribe({ + mutableNotifications.value = it + Timber.d("Notification retrieve successful") + }, { + mutableError.value = resource.getString(R.string.msg_failed_to_load_notification) + Timber.d(it, resource.getString(R.string.msg_failed_to_load_notification)) + }) + } + + fun isConnected(): Boolean { + val isConnected = network.isNetworkConnected() + mutableNoInternet.value = !isConnected + return isConnected + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/notification/NotificationsRecyclerAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationsRecyclerAdapter.kt new file mode 100644 index 0000000000..17de5f0363 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationsRecyclerAdapter.kt @@ -0,0 +1,29 @@ +package org.fossasia.openevent.general.notification + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import org.fossasia.openevent.general.R + +class NotificationsRecyclerAdapter : RecyclerView.Adapter() { + + private val notificationList = ArrayList() + + fun addAll(list: List) { + notificationList.clear() + notificationList.addAll(list) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationsViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.item_card_notification, parent, false) + return NotificationsViewHolder(view) + } + + override fun onBindViewHolder(holder: NotificationsViewHolder, position: Int) { + holder.bind(notificationList[position]) + } + + override fun getItemCount(): Int { + return notificationList.size + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/notification/NotificationsViewHolder.kt b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationsViewHolder.kt new file mode 100644 index 0000000000..227f62ba0d --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/notification/NotificationsViewHolder.kt @@ -0,0 +1,29 @@ +package org.fossasia.openevent.general.notification + +import android.text.method.LinkMovementMethod +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import kotlinx.android.synthetic.main.item_card_notification.view.* +import org.fossasia.openevent.general.event.EventUtils +import org.fossasia.openevent.general.utils.stripHtml + +class NotificationsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + fun bind( + notification: Notification + + ) { + itemView.title.text = notification.title + itemView.message.text = notification.message.stripHtml() + itemView.message.movementMethod = LinkMovementMethod.getInstance() + notification.receivedAt?.let { + val dayDiff = EventUtils.getDayDifferenceFromToday(it) + val formattedDateTime = EventUtils.getEventDateTime(it, null) + itemView.time.text = when (dayDiff) { + 0L -> EventUtils.getFormattedTime(formattedDateTime) + in 1..6 -> EventUtils.getFormattedWeekDay(formattedDateTime) + else -> EventUtils.getFormattedDateWithoutWeekday(formattedDateTime) + } + } + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/order/Order.kt b/app/src/main/java/org/fossasia/openevent/general/order/Order.kt index 116265dfd4..5c19a8a665 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/Order.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/Order.kt @@ -2,7 +2,6 @@ package org.fossasia.openevent.general.order import androidx.room.ColumnInfo import androidx.room.Entity -import androidx.room.ForeignKey import androidx.room.PrimaryKey import com.fasterxml.jackson.databind.PropertyNamingStrategy import com.fasterxml.jackson.databind.annotation.JsonNaming @@ -11,18 +10,12 @@ import com.github.jasminb.jsonapi.annotations.Id import com.github.jasminb.jsonapi.annotations.Relationship import com.github.jasminb.jsonapi.annotations.Type import io.reactivex.annotations.NonNull -import org.fossasia.openevent.general.attendees.Attendee import org.fossasia.openevent.general.attendees.AttendeeId -import org.fossasia.openevent.general.event.Event import org.fossasia.openevent.general.event.EventId @Type("order") @JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) -@Entity(foreignKeys = [ - (ForeignKey(entity = Event::class, parentColumns = ["id"], - childColumns = ["event"], onDelete = ForeignKey.CASCADE)), - (ForeignKey(entity = Attendee::class, parentColumns = ["id"], - childColumns = ["attendees"], onDelete = ForeignKey.CASCADE))]) +@Entity data class Order( @Id(IntegerIdHandler::class) @PrimaryKey @@ -31,7 +24,7 @@ data class Order( val paymentMode: String? = null, val country: String? = null, val status: String? = null, - val amount: Float? = null, + val amount: Float = 0F, val identifier: String? = null, val orderNotes: String? = null, val completedAt: String? = null, diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderDao.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderDao.kt index 35b2a67c53..2214378142 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderDao.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderDao.kt @@ -3,6 +3,8 @@ package org.fossasia.openevent.general.order import androidx.room.Dao import androidx.room.Insert import androidx.room.OnConflictStrategy +import androidx.room.Query +import io.reactivex.Single @Dao interface OrderDao { @@ -11,4 +13,13 @@ interface OrderDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertOrder(order: Order) + + @Query("SELECT * FROM `order`") + fun getAllOrders(): Single> + + @Query("DELETE FROM `order`") + fun deleteAllOrders() + + @Query("SELECT * FROM `order` WHERE id = :orderId") + fun getOrderById(orderId: Long): Single } diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsFragment.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsFragment.kt index 7d31a77ce2..4627a63ca2 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsFragment.kt @@ -1,5 +1,6 @@ package org.fossasia.openevent.general.order +import android.app.AlertDialog import android.content.Intent import android.graphics.Bitmap import android.graphics.Canvas @@ -24,6 +25,7 @@ import kotlinx.android.synthetic.main.fragment_order_details.view.orderDetailCoo import kotlinx.android.synthetic.main.fragment_order_details.view.orderDetailsRecycler import kotlinx.android.synthetic.main.fragment_order_details.view.progressBar import kotlinx.android.synthetic.main.item_card_order_details.view.orderDetailCardView +import kotlinx.android.synthetic.main.item_enlarged_qr.view.enlargedQrImage import org.fossasia.openevent.general.BuildConfig import org.fossasia.openevent.general.R import org.fossasia.openevent.general.utils.extensions.nonNull @@ -46,20 +48,18 @@ class OrderDetailsFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - ordersRecyclerAdapter.setOrderIdentifier(safeArgs.orders) + ordersRecyclerAdapter.setOrderIdentifier(safeArgs.orderIdentifier) orderDetailsViewModel.event .nonNull() .observe(this, Observer { ordersRecyclerAdapter.setEvent(it) - ordersRecyclerAdapter.notifyDataSetChanged() }) orderDetailsViewModel.attendees .nonNull() .observe(this, Observer { ordersRecyclerAdapter.addAll(it) - ordersRecyclerAdapter.notifyDataSetChanged() Timber.d("Fetched attendees of size %s", ordersRecyclerAdapter.itemCount) }) } @@ -102,7 +102,15 @@ class OrderDetailsFragment : Fragment() { } } - ordersRecyclerAdapter.setListener(eventDetailsListener) + ordersRecyclerAdapter.setSeeEventListener(eventDetailsListener) + + val qrImageListener = object : OrderDetailsRecyclerAdapter.QrImageClickListener { + override fun onClick(qrImage: Bitmap) { + showEnlargedQrImage(qrImage) + } + } + + ordersRecyclerAdapter.setQrImageClickListener(qrImageListener) orderDetailsViewModel.progress .nonNull() @@ -117,7 +125,7 @@ class OrderDetailsFragment : Fragment() { }) orderDetailsViewModel.loadEvent(safeArgs.eventId) - orderDetailsViewModel.loadAttendeeDetails(safeArgs.orders) + orderDetailsViewModel.loadAttendeeDetails(safeArgs.orderId) return rootView } @@ -141,6 +149,23 @@ class OrderDetailsFragment : Fragment() { } } + private fun showEnlargedQrImage(bitmap: Bitmap) { + val brightAttributes = activity?.window?.attributes + orderDetailsViewModel.brightness = brightAttributes?.screenBrightness + brightAttributes?.screenBrightness = 1f + activity?.window?.attributes = brightAttributes + + val dialogLayout = layoutInflater.inflate(R.layout.item_enlarged_qr, null) + dialogLayout.enlargedQrImage.setImageBitmap(bitmap) + AlertDialog.Builder(requireContext()) + .setOnDismissListener { + val attributes = activity?.window?.attributes + attributes?.screenBrightness = orderDetailsViewModel.brightness + activity?.window?.attributes = attributes + }.setView(dialogLayout) + .create().show() + } + private fun shareCurrentTicket() { val currentTicketViewHolder = rootView.orderDetailsRecycler.findViewHolderForAdapterPosition(orderDetailsViewModel.currentTicketPosition) diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsRecyclerAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsRecyclerAdapter.kt index 9e45822d8a..50d89d8b8c 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsRecyclerAdapter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsRecyclerAdapter.kt @@ -1,5 +1,6 @@ package org.fossasia.openevent.general.order +import android.graphics.Bitmap import androidx.recyclerview.widget.RecyclerView import android.view.LayoutInflater import android.view.ViewGroup @@ -14,21 +15,28 @@ class OrderDetailsRecyclerAdapter : RecyclerView.Adapter private var event: Event? = null private var orderIdentifier: String? = null private var eventDetailsListener: EventDetailsListener? = null + private var onQrImageClicked: QrImageClickListener? = null fun addAll(attendeeList: List) { if (attendees.isNotEmpty()) this.attendees.clear() this.attendees.addAll(attendeeList) + notifyDataSetChanged() } fun setEvent(event: Event?) { this.event = event + notifyDataSetChanged() } - fun setListener(listener: EventDetailsListener) { + fun setSeeEventListener(listener: EventDetailsListener) { eventDetailsListener = listener } + fun setQrImageClickListener(listener: QrImageClickListener) { + onQrImageClicked = listener + } + fun setOrderIdentifier(orderId: String?) { orderIdentifier = orderId } @@ -40,7 +48,7 @@ class OrderDetailsRecyclerAdapter : RecyclerView.Adapter override fun onBindViewHolder(holder: OrderDetailsViewHolder, position: Int) { val order = attendees[position] - holder.bind(order, event, orderIdentifier, eventDetailsListener) + holder.bind(order, event, orderIdentifier, eventDetailsListener, onQrImageClicked) } override fun getItemCount(): Int { @@ -50,4 +58,8 @@ class OrderDetailsRecyclerAdapter : RecyclerView.Adapter interface EventDetailsListener { fun onClick(eventID: Long) } + + interface QrImageClickListener { + fun onClick(qrImage: Bitmap) + } } diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewHolder.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewHolder.kt index c81461800f..d2dfd6e172 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewHolder.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewHolder.kt @@ -32,67 +32,70 @@ class OrderDetailsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) attendee: Attendee, event: Event?, orderIdentifier: String?, - eventDetailsListener: OrderDetailsRecyclerAdapter.EventDetailsListener? + eventDetailsListener: OrderDetailsRecyclerAdapter.EventDetailsListener?, + qrImageClickListener: OrderDetailsRecyclerAdapter.QrImageClickListener? ) { - if (event != null) { - val formattedDateTime = EventUtils.getEventDateTime(event.startsAt, event.timezone) - val formattedDate = EventUtils.getFormattedDateShort(formattedDateTime) - val formattedTime = EventUtils.getFormattedTime(formattedDateTime) - val timezone = EventUtils.getFormattedTimeZone(formattedDateTime) + if (event == null) return + val formattedDateTime = EventUtils.getEventDateTime(event.startsAt, event.timezone) + val formattedDate = EventUtils.getFormattedDateShort(formattedDateTime) + val formattedTime = EventUtils.getFormattedTime(formattedDateTime) + val timezone = EventUtils.getFormattedTimeZone(formattedDateTime) - itemView.eventName.text = event.name - itemView.location.text = event.locationName - itemView.date.text = "$formattedDate\n$formattedTime $timezone" - itemView.eventSummary.text = event.description?.stripHtml() + itemView.eventName.text = event.name + itemView.location.text = event.locationName + itemView.date.text = "$formattedDate\n$formattedTime $timezone" + itemView.eventSummary.text = event.description?.stripHtml() - if (event.organizerName.isNullOrEmpty()) { - itemView.organizerLabel.visibility = View.GONE - } else { - itemView.organizer.text = event.organizerName - } - itemView.map.setOnClickListener { - val mapUrl = loadMapUrl(event) - val mapIntent = Intent(Intent.ACTION_VIEW, Uri.parse(mapUrl)) - val packageManager = itemView.context?.packageManager - if (packageManager != null && mapIntent.resolveActivity(packageManager) != null) { - itemView.context.startActivity(mapIntent) - } + if (event.organizerName.isNullOrEmpty()) { + itemView.organizerLabel.visibility = View.GONE + } else { + itemView.organizer.text = event.organizerName + } + itemView.map.setOnClickListener { + val mapUrl = loadMapUrl(event) + val mapIntent = Intent(Intent.ACTION_VIEW, Uri.parse(mapUrl)) + val packageManager = itemView.context?.packageManager + if (packageManager != null && mapIntent.resolveActivity(packageManager) != null) { + itemView.context.startActivity(mapIntent) } - if (!attendee.pdfUrl.isNullOrBlank()) { - itemView.downloadButton.isEnabled = true - itemView.downloadButton.setOnClickListener { - itemView.context.browse(attendee.pdfUrl) - } + } + if (!attendee.pdfUrl.isNullOrBlank()) { + itemView.downloadButton.isEnabled = true + itemView.downloadButton.setOnClickListener { + itemView.context.browse(attendee.pdfUrl) } + } - itemView.calendar.setOnClickListener { - val intent = Intent(Intent.ACTION_INSERT) - intent.type = "vnd.android.cursor.item/event" - intent.putExtra(CalendarContract.Events.TITLE, event.name) - intent.putExtra(CalendarContract.Events.DESCRIPTION, event.description?.stripHtml()) - intent.putExtra(CalendarContract.Events.EVENT_LOCATION, event.locationName) - intent.putExtra(CalendarContract.Events.CALENDAR_TIME_ZONE, event.timezone) - intent.putExtra( - CalendarContract.EXTRA_EVENT_BEGIN_TIME, - EventUtils.getTimeInMilliSeconds(event.startsAt, event.timezone)) - intent.putExtra( - CalendarContract.EXTRA_EVENT_END_TIME, - EventUtils.getTimeInMilliSeconds(event.endsAt, event.timezone)) - itemView.context.startActivity(intent) - } + itemView.calendar.setOnClickListener { + val intent = Intent(Intent.ACTION_INSERT) + intent.type = "vnd.android.cursor.item/event" + intent.putExtra(CalendarContract.Events.TITLE, event.name) + intent.putExtra(CalendarContract.Events.DESCRIPTION, event.description?.stripHtml()) + intent.putExtra(CalendarContract.Events.EVENT_LOCATION, event.locationName) + intent.putExtra(CalendarContract.Events.CALENDAR_TIME_ZONE, event.timezone) + intent.putExtra( + CalendarContract.EXTRA_EVENT_BEGIN_TIME, + EventUtils.getTimeInMilliSeconds(event.startsAt, event.timezone)) + intent.putExtra( + CalendarContract.EXTRA_EVENT_END_TIME, + EventUtils.getTimeInMilliSeconds(event.endsAt, event.timezone)) + itemView.context.startActivity(intent) + } - itemView.eventDetails.setOnClickListener { - eventDetailsListener?.onClick(event.id) - } + itemView.eventDetails.setOnClickListener { + eventDetailsListener?.onClick(event.id) } itemView.name.text = "${attendee.firstname} ${attendee.lastname}" val ticketIdentifier = "$orderIdentifier-${attendee.id}" itemView.orderIdentifier.text = ticketIdentifier - val bitmap = qrCode.generateQrBitmap(ticketIdentifier, 200, 200) + val bitmap = qrCode.generateQrBitmap(ticketIdentifier, 400, 400) if (bitmap != null) { itemView.qrCodeView.setImageBitmap(bitmap) + itemView.qrCodeView.setOnClickListener { + qrImageClickListener?.onClick(bitmap) + } } else { itemView.qrCodeView.visibility = View.GONE } diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewModel.kt index f3dcb05b58..0921f049c2 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderDetailsViewModel.kt @@ -29,13 +29,14 @@ class OrderDetailsViewModel( private val mutableProgress = MutableLiveData() val progress: LiveData = mutableProgress var currentTicketPosition: Int = 0 + var brightness: Float? = 0f fun loadEvent(id: Long) { - if (id.equals(-1)) { + if (id == -1L) { throw IllegalStateException("ID should never be -1") } - compositeDisposable += eventService.getEventFromApi(id) + compositeDisposable += eventService.getEventById(id) .withDefaultSchedulers() .subscribe({ mutableEvent.value = it @@ -45,21 +46,34 @@ class OrderDetailsViewModel( }) } - fun loadAttendeeDetails(id: String) { - if (id.equals(-1)) { - throw IllegalStateException("ID should never be -1") - } + fun loadAttendeeDetails(orderId: Long) { + if (orderId == -1L) return - compositeDisposable += orderService.attendeesUnderOrder(id) + compositeDisposable += orderService.getOrderById(orderId) .withDefaultSchedulers() .doOnSubscribe { mutableProgress.value = true - }.doFinally { - mutableProgress.value = false }.subscribe({ + loadAttendeeUnderOrder(it) + }, { + Timber.e(it, "Error fetching attendee details") + mutableProgress.value = false + message.value = resource.getString(R.string.error_fetching_attendee_details_message) + }) + } + + private fun loadAttendeeUnderOrder(order: Order) { + val orderIdentifier = order.identifier ?: return + + compositeDisposable += orderService + .getAttendeesUnderOrder(orderIdentifier, order.attendees.map { it.id }) + .withDefaultSchedulers() + .subscribe({ mutableAttendees.value = it + mutableProgress.value = false }, { Timber.e(it, "Error fetching attendee details") + mutableProgress.value = false message.value = resource.getString(R.string.error_fetching_attendee_details_message) }) } diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderService.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderService.kt index 99daff01b6..415e066901 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderService.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderService.kt @@ -12,12 +12,10 @@ class OrderService( fun placeOrder(order: Order): Single { return orderApi.placeOrder(order) .map { order -> - val attendeeIds = order.attendees?.map { order.id } - if (attendeeIds != null) { - attendeeDao.getAttendeesWithIds(attendeeIds).map { - if (it.size == attendeeIds.size) { - orderDao.insertOrder(order) - } + val attendeeIds = order.attendees.map { order.id } + attendeeDao.getAttendeesWithIds(attendeeIds).map { + if (it.size == attendeeIds.size) { + orderDao.insertOrder(order) } } order @@ -32,11 +30,27 @@ class OrderService( return orderApi.confirmOrder(identifier, order) } - fun orderUser(userId: Long): Single> { + fun getOrdersOfUser(userId: Long): Single> { return orderApi.ordersUnderUser(userId) + .map { + orderDao.insertOrders(it) + it + }.onErrorResumeNext { + orderDao.getAllOrders().map { it } + } } - fun attendeesUnderOrder(orderIdentifier: String): Single> { + fun getOrderById(orderId: Long): Single { + return orderDao.getOrderById(orderId) + } + + fun getAttendeesUnderOrder(orderIdentifier: String, attendeesIds: List): Single> { return orderApi.attendeesUnderOrder(orderIdentifier) + .map { + attendeeDao.insertAttendees(it) + it + }.onErrorResumeNext { + attendeeDao.getAttendeesWithIds(attendeesIds) + } } } diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrdersRecyclerAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrdersRecyclerAdapter.kt index ab7fe3b53c..55117b93c5 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrdersRecyclerAdapter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrdersRecyclerAdapter.kt @@ -8,19 +8,19 @@ import org.fossasia.openevent.general.event.Event class OrdersRecyclerAdapter : RecyclerView.Adapter() { - private val eventAndOrderIdentifier = ArrayList>() - private val showExpired = false + private val eventAndOrderIdentifier = ArrayList>() + private var showExpired = false private var clickListener: OrderClickListener? = null - var attendeesNumber = listOf() fun setListener(listener: OrderClickListener) { clickListener = listener } - fun addAllPairs(list: List>, showExpired: Boolean) { + fun addAllPairs(list: List>, showExpired: Boolean) { if (eventAndOrderIdentifier.isNotEmpty()) this.eventAndOrderIdentifier.clear() eventAndOrderIdentifier.addAll(list) + this.showExpired = showExpired } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): OrdersViewHolder { @@ -29,23 +29,14 @@ class OrdersRecyclerAdapter : RecyclerView.Adapter() { } override fun onBindViewHolder(holder: OrdersViewHolder, position: Int) { - attendeesNumber[position]?.let { - holder.bind(eventAndOrderIdentifier[position].first, - clickListener, - eventAndOrderIdentifier[position].second, - it, showExpired) - } + holder.bind(eventAndOrderIdentifier[position], showExpired, clickListener) } override fun getItemCount(): Int { return eventAndOrderIdentifier.size } - fun setAttendeeNumber(number: List) { - attendeesNumber = number - } - interface OrderClickListener { - fun onClick(eventID: Long, orderIdentifier: String) + fun onClick(eventID: Long, orderIdentifier: String, orderId: Long) } } diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserFragment.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserFragment.kt index 196c9e868f..c7dd5ccaa5 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserFragment.kt @@ -32,6 +32,8 @@ import timber.log.Timber import org.fossasia.openevent.general.utils.Utils.setToolbar import org.jetbrains.anko.design.longSnackbar +const val ORDERS_FRAGMENT = "ordersFragment" + class OrdersUnderUserFragment : Fragment(), ScrollToTop { private lateinit var rootView: View @@ -55,7 +57,7 @@ class OrdersUnderUserFragment : Fragment(), ScrollToTop { rootView = inflater.inflate(R.layout.fragment_orders_under_user, container, false) when (safeArgs.showExpired) { true -> { - setToolbar(activity, "Past Tickets") + setToolbar(activity, getString(R.string.past_tickets)) setHasOptionsMenu(true) navAnimGone(activity?.navigation, requireContext()) } @@ -77,9 +79,9 @@ class OrdersUnderUserFragment : Fragment(), ScrollToTop { if (safeArgs.showExpired) rootView.expireFilter.isVisible = false val recyclerViewClickListener = object : OrdersRecyclerAdapter.OrderClickListener { - override fun onClick(eventID: Long, orderIdentifier: String) { - findNavController(rootView).navigate(OrdersUnderUserFragmentDirections - .actionOrderUserToOrderDetails(eventID, orderIdentifier)) + override fun onClick(eventID: Long, orderIdentifier: String, orderId: Long) { + findNavController(rootView).navigate(OrdersUnderUserFragmentDirections + .actionOrderUserToOrderDetails(eventID, orderIdentifier, orderId)) } } @@ -109,13 +111,7 @@ class OrdersUnderUserFragment : Fragment(), ScrollToTop { showNoTicketsScreen(it) }) - ordersUnderUserVM.attendeesNumber - .nonNull() - .observe(viewLifecycleOwner, Observer { - ordersRecyclerAdapter.setAttendeeNumber(it) - }) - - ordersUnderUserVM.eventAndOrderIdentifier + ordersUnderUserVM.eventAndOrder .nonNull() .observe(viewLifecycleOwner, Observer { val list = it.sortedByDescending { @@ -144,7 +140,7 @@ class OrdersUnderUserFragment : Fragment(), ScrollToTop { private fun redirectToLogin() { findNavController(rootView).navigate(OrdersUnderUserFragmentDirections - .actionOrderUserToLogin(getString(R.string.log_in_first))) + .actionOrderUserToAuth(getString(R.string.log_in_first), ORDERS_FRAGMENT)) } override fun scrollToTop() = rootView.ordersNestedScrollView.smoothScrollTo(0, 0) diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserViewModel.kt index 47815b5f01..363be89612 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrdersUnderUserViewModel.kt @@ -24,17 +24,14 @@ class OrdersUnderUserViewModel( private val compositeDisposable = CompositeDisposable() private lateinit var order: List - private val mutableAttendeesNumber = MutableLiveData>() - val attendeesNumber: LiveData> = mutableAttendeesNumber private var eventIdMap = mutableMapOf() private val eventIdAndTimes = mutableMapOf() private val mutableMessage = SingleLiveEvent() val message: LiveData = mutableMessage - private val mutableEventAndOrderIdentifier = MutableLiveData>>() - val eventAndOrderIdentifier: LiveData>> = - mutableEventAndOrderIdentifier - private val mutableshowShimmerResults = MutableLiveData() - val showShimmerResults: LiveData = mutableshowShimmerResults + private val mutableEventAndOrder = MutableLiveData>>() + val eventAndOrder: LiveData>> = mutableEventAndOrder + private val mutableShowShimmerResults = MutableLiveData() + val showShimmerResults: LiveData = mutableShowShimmerResults private val mutableNoTickets = MutableLiveData() val noTickets: LiveData = mutableNoTickets @@ -43,24 +40,22 @@ class OrdersUnderUserViewModel( fun isLoggedIn() = authHolder.isLoggedIn() fun ordersUnderUser(showExpired: Boolean) { - compositeDisposable += orderService.orderUser(getId()) + compositeDisposable += orderService.getOrdersOfUser(getId()) .withDefaultSchedulers() .doOnSubscribe { - mutableshowShimmerResults.value = true + mutableShowShimmerResults.value = true mutableNoTickets.value = false }.subscribe({ order = it - mutableAttendeesNumber.value = it.map { it.attendees.size } - val eventIds = it.mapNotNull { order -> order.event?.id } if (eventIds.isNotEmpty()) { eventsUnderUser(eventIds, showExpired) } else { - mutableshowShimmerResults.value = false + mutableShowShimmerResults.value = false mutableNoTickets.value = true } }, { - mutableshowShimmerResults.value = false + mutableShowShimmerResults.value = false mutableNoTickets.value = true mutableMessage.value = resource.getString(R.string.list_orders_fail_message) Timber.d(it, "Failed to list Orders under a user ") @@ -71,7 +66,7 @@ class OrdersUnderUserViewModel( compositeDisposable += eventService.getEventsUnderUser(eventIds) .withDefaultSchedulers() .doFinally { - mutableshowShimmerResults.value = false + mutableShowShimmerResults.value = false }.subscribe({ val events = ArrayList() it.map { @@ -83,22 +78,20 @@ class OrdersUnderUserViewModel( } eventIdMap[it.id] = it } - var eventAndIdentifier = ArrayList>() - var finalList: List> + val eventAndIdentifier = ArrayList>() order.forEach { val event = eventIdMap[it.event?.id] - if (event != null && it.identifier != null) - eventAndIdentifier.add(Pair(event, it.identifier)) + if (event != null) + eventAndIdentifier.add(Pair(event, it)) } - finalList = eventAndIdentifier - when (showExpired) { - false -> finalList = finalList.filter { + val finalList = when (showExpired) { + false -> eventAndIdentifier.filter { EventUtils.getTimeInMilliSeconds(it.first.endsAt, null) > System.currentTimeMillis() } - true -> finalList = finalList.filter { + true -> eventAndIdentifier.filter { EventUtils.getTimeInMilliSeconds(it.first.endsAt, null) < System.currentTimeMillis() } } if (finalList.isEmpty()) mutableNoTickets.value = true - mutableEventAndOrderIdentifier.value = finalList + mutableEventAndOrder.value = finalList }, { mutableMessage.value = resource.getString(R.string.list_events_fail_message) Timber.d(it, "Failed to list events under a user ") diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrdersViewHolder.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrdersViewHolder.kt index 3571f7b9c0..4128b623d8 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrdersViewHolder.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrdersViewHolder.kt @@ -13,12 +13,12 @@ import org.fossasia.openevent.general.event.EventUtils class OrdersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind( - event: Event, - clickListener: OrdersRecyclerAdapter.OrderClickListener?, - orderIdentifier: String?, - attendeesNumber: Int, - showExpired: Boolean + eventAndOrder: Pair, + showExpired: Boolean, + listener: OrdersRecyclerAdapter.OrderClickListener? ) { + val event = eventAndOrder.first + val order = eventAndOrder.second val formattedDateTime = EventUtils.getEventDateTime(event.startsAt, event.timezone) val formattedTime = EventUtils.getFormattedTime(formattedDateTime) val timezone = EventUtils.getFormattedTimeZone(formattedDateTime) @@ -26,9 +26,10 @@ class OrdersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { itemView.eventName.text = event.name itemView.time.text = "Starts at $formattedTime $timezone" itemView.setOnClickListener { - orderIdentifier?.let { it1 -> clickListener?.onClick(event.id, it1) } + listener?.onClick(event.id, order.identifier ?: "", order.id) } + val attendeesNumber = order.attendees.size if (attendeesNumber == 1) { itemView.ticketsNumber.text = "See $attendeesNumber Ticket" } else { @@ -44,7 +45,7 @@ class OrdersViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { .placeholder(R.drawable.header) .into(itemView.eventImage) } - if (!showExpired) { + if (showExpired) { val matrix = ColorMatrix() matrix.setSaturation(0F) itemView.eventImage.colorFilter = ColorMatrixColorFilter(matrix) diff --git a/app/src/main/java/org/fossasia/openevent/general/order/QrCode.kt b/app/src/main/java/org/fossasia/openevent/general/order/QrCode.kt index 4738deca70..34ef8b8cbe 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/QrCode.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/QrCode.kt @@ -2,6 +2,7 @@ package org.fossasia.openevent.general.order import android.graphics.Bitmap import com.google.zxing.BarcodeFormat +import com.google.zxing.EncodeHintType import com.google.zxing.MultiFormatWriter import com.google.zxing.WriterException import com.journeyapps.barcodescanner.BarcodeEncoder @@ -13,7 +14,9 @@ class QrCode { fun generateQrBitmap(text: String?, width: Int, height: Int): Bitmap? { try { - val bitMatrix = multiFormatWriter.encode(text, BarcodeFormat.QR_CODE, width, height) + val hint = HashMap() + hint[EncodeHintType.MARGIN] = 1 + val bitMatrix = multiFormatWriter.encode(text, BarcodeFormat.QR_CODE, width, height, hint) return barcodeEncoder.createBitmap(bitMatrix) } catch (e: WriterException) { Timber.d(e, "Writer Exception") diff --git a/app/src/main/java/org/fossasia/openevent/general/search/PlaceSuggestionsAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/search/PlaceSuggestionsAdapter.kt index 5f4f5129fa..ca25da4352 100644 --- a/app/src/main/java/org/fossasia/openevent/general/search/PlaceSuggestionsAdapter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/search/PlaceSuggestionsAdapter.kt @@ -36,7 +36,7 @@ class PlaceSuggestionsAdapter : } override fun areContentsTheSame(oldItem: CarmenFeature, newItem: CarmenFeature): Boolean { - return oldItem == newItem + return oldItem.equals(newItem) } } } diff --git a/app/src/main/java/org/fossasia/openevent/general/search/SearchFragment.kt b/app/src/main/java/org/fossasia/openevent/general/search/SearchFragment.kt index be408efcba..4eb6f186a6 100644 --- a/app/src/main/java/org/fossasia/openevent/general/search/SearchFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/search/SearchFragment.kt @@ -127,11 +127,19 @@ class SearchFragment : Fragment() { rootView.fabSearch.setOnClickListener { queryListener.onQueryTextSubmit(searchView.query.toString()) } + + if (searchViewModel.isQuerying) { + searchItem.expandActionView() + searchView.setQuery(searchViewModel.searchViewQuery, false) + searchView.clearFocus() + } super.onPrepareOptionsMenu(menu) } override fun onDestroyView() { super.onDestroyView() + searchViewModel.isQuerying = !searchView.isIconified + searchViewModel.searchViewQuery = searchView.query.toString() searchView.isSaveEnabled = false } diff --git a/app/src/main/java/org/fossasia/openevent/general/search/SearchResultsFragment.kt b/app/src/main/java/org/fossasia/openevent/general/search/SearchResultsFragment.kt index 3d9a8bc930..345b7391c0 100644 --- a/app/src/main/java/org/fossasia/openevent/general/search/SearchResultsFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/search/SearchResultsFragment.kt @@ -29,39 +29,34 @@ import kotlinx.android.synthetic.main.fragment_search_results.view.shimmerSearch import org.fossasia.openevent.general.R import org.fossasia.openevent.general.common.EventClickListener import org.fossasia.openevent.general.common.FavoriteFabClickListener -import org.fossasia.openevent.general.di.Scopes import org.fossasia.openevent.general.event.Event import org.fossasia.openevent.general.event.types.EventType import org.fossasia.openevent.general.favorite.FavoriteEventsRecyclerAdapter import org.fossasia.openevent.general.utils.Utils.setToolbar import org.fossasia.openevent.general.utils.extensions.nonNull import org.jetbrains.anko.design.longSnackbar -import org.koin.android.ext.android.inject -import org.koin.androidx.scope.ext.android.bindScope -import org.koin.androidx.scope.ext.android.getOrCreateScope import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber import androidx.appcompat.view.ContextThemeWrapper +import org.fossasia.openevent.general.common.EventsDiffCallback class SearchResultsFragment : Fragment(), CompoundButton.OnCheckedChangeListener { private lateinit var rootView: View private val searchViewModel by viewModel() private val safeArgs: SearchResultsFragmentArgs by navArgs() - private val favoriteEventsRecyclerAdapter: FavoriteEventsRecyclerAdapter by inject( - scope = getOrCreateScope(Scopes.SEARCH_RESULTS_FRAGMENT.toString()) - ) + private val favoriteEventsRecyclerAdapter = FavoriteEventsRecyclerAdapter(EventsDiffCallback()) + private lateinit var days: Array private lateinit var eventDate: String private lateinit var eventType: String private var eventTypesList: List? = arrayListOf() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - bindScope(getOrCreateScope(Scopes.SEARCH_RESULTS_FRAGMENT.toString())) days = resources.getStringArray(R.array.days) - eventDate = safeArgs.date - eventType = safeArgs.type + eventDate = searchViewModel.savedTime ?: safeArgs.date + eventType = searchViewModel.savedType ?: safeArgs.type searchViewModel.loadEventTypes() searchViewModel.eventTypes @@ -74,7 +69,7 @@ class SearchResultsFragment : Fragment(), CompoundButton.OnCheckedChangeListener override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { rootView = inflater.inflate(R.layout.fragment_search_results, container, false) - setChips(safeArgs.date, safeArgs.type) + setChips(eventDate, eventType) setToolbar(activity, getString(R.string.search_results)) setHasOptionsMenu(true) @@ -139,7 +134,7 @@ class SearchResultsFragment : Fragment(), CompoundButton.OnCheckedChangeListener } private fun setChips(date: String = eventDate, type: String = eventType) { - if (rootView.chipGroup.childCount>0) { + if (rootView.chipGroup.childCount> 0) { rootView.chipGroup.removeAllViews() } when { @@ -217,6 +212,7 @@ class SearchResultsFragment : Fragment(), CompoundButton.OnCheckedChangeListener val date = eventDate val freeEvents = safeArgs.freeEvents val sortBy = safeArgs.sort + searchViewModel.setChipNotClickable() searchViewModel.searchEvent = query searchViewModel.loadEvents(location, date, type, freeEvents, sortBy) } @@ -258,7 +254,9 @@ class SearchResultsFragment : Fragment(), CompoundButton.OnCheckedChangeListener override fun onCheckedChanged(buttonView: CompoundButton?, isChecked: Boolean) { if (isChecked) { - if (buttonView?.text == "Clear All") { + if (buttonView?.text == getString(R.string.clear_all)) { + searchViewModel.savedTime = null + searchViewModel.savedType = null eventDate = getString(R.string.anytime) eventType = getString(R.string.anything) rootView.noSearchResults.isVisible = false @@ -271,6 +269,7 @@ class SearchResultsFragment : Fragment(), CompoundButton.OnCheckedChangeListener } days.forEach { if (it == buttonView?.text) { + searchViewModel.savedTime = it eventDate = it setChips(date = it) rootView.noSearchResults.isVisible = false @@ -283,6 +282,7 @@ class SearchResultsFragment : Fragment(), CompoundButton.OnCheckedChangeListener } eventTypesList?.forEach { if (it.name == buttonView?.text) { + searchViewModel.savedType = it.name eventType = it.name setChips(type = it.name) rootView.noSearchResults.isVisible = false diff --git a/app/src/main/java/org/fossasia/openevent/general/search/SearchViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/search/SearchViewModel.kt index d7d6cbbd69..36aba5fe65 100644 --- a/app/src/main/java/org/fossasia/openevent/general/search/SearchViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/search/SearchViewModel.kt @@ -14,6 +14,7 @@ import org.fossasia.openevent.general.data.Preference import org.fossasia.openevent.general.data.Resource import org.fossasia.openevent.general.event.Event import org.fossasia.openevent.general.event.EventService +import org.fossasia.openevent.general.event.EventUtils import org.fossasia.openevent.general.event.types.EventType import org.fossasia.openevent.general.utils.DateTimeUtils.getNextDate import org.fossasia.openevent.general.utils.DateTimeUtils.getNextMonth @@ -22,6 +23,7 @@ import org.fossasia.openevent.general.utils.DateTimeUtils.getNextToNextMonth import org.fossasia.openevent.general.utils.DateTimeUtils.getNextToWeekendDate import org.fossasia.openevent.general.utils.DateTimeUtils.getWeekendDate import timber.log.Timber +import java.util.Date class SearchViewModel( private val eventService: EventService, @@ -53,6 +55,8 @@ class SearchViewModel( private val savedNextToNextMonth = getNextToNextMonth() private val mutableEventTypes = MutableLiveData>() val eventTypes: LiveData> = mutableEventTypes + var searchViewQuery: String = "" + var isQuerying = false fun loadEventTypes() { compositeDisposable += eventService.getEventTypes() @@ -74,6 +78,10 @@ class SearchViewModel( savedTime = preference.getString(SAVED_TIME) } + fun setChipNotClickable() { + mutableChipClickable.value = false + } + fun loadEvents(location: String, time: String, type: String, freeEvents: Boolean, sortBy: String) { if (mutableEvents.value != null) { mutableChipClickable.value = true @@ -91,7 +99,11 @@ class SearchViewModel( | 'op':'eq', | 'val':'0' | } - |} + |}, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' + | } """.trimIndent() else "" val query: String = when { @@ -99,7 +111,11 @@ class SearchViewModel( | 'name':'name', | 'op':'ilike', | 'val':'%$searchEvent%' - |}]""".trimMargin().replace("'", "'") + |}, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' + | }]""".trimMargin().replace("'", "'") time == "Anytime" && type == "Anything" -> """[{ | 'and':[{ | 'name':'location-name', @@ -109,6 +125,10 @@ class SearchViewModel( | 'name':'name', | 'op':'ilike', | 'val':'%$searchEvent%' + | }, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' | }$freeStuffFilter] |}]""".trimMargin().replace("'", "\"") time == "Anytime" -> """[{ @@ -128,6 +148,10 @@ class SearchViewModel( | 'op':'eq', | 'val':'$type' | } + | }, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' | }$freeStuffFilter] |}]""".trimMargin().replace("'", "\"") time == "Today" -> """[{ @@ -155,7 +179,11 @@ class SearchViewModel( | 'op':'eq', | 'val':'$type' | } - | }$freeStuffFilter] + | }, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' + | }$freeStuffFilter] |}]""".trimMargin().replace("'", "\"") time == "Tomorrow" -> """[{ | 'and':[{ @@ -182,7 +210,11 @@ class SearchViewModel( | 'op':'eq', | 'val':'$type' | } - | }$freeStuffFilter] + | }, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' + | }$freeStuffFilter] |}]""".trimMargin().replace("'", "\"") time == "This weekend" -> """[{ | 'and':[{ @@ -209,7 +241,11 @@ class SearchViewModel( | 'op':'eq', | 'val':'$type' | } - | }$freeStuffFilter] + | }, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' + | }$freeStuffFilter] |}]""".trimMargin().replace("'", "\"") time == "In the next month" -> """[{ | 'and':[{ @@ -236,7 +272,11 @@ class SearchViewModel( | 'op':'eq', | 'val':'$type' | } - | }$freeStuffFilter] + | }, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' + | }$freeStuffFilter] |}]""".trimMargin().replace("'", "\"") else -> """[{ @@ -264,7 +304,11 @@ class SearchViewModel( | 'op':'eq', | 'val':'$type' | } - | }$freeStuffFilter] + | }, { + | 'name':'ends-at', + | 'op':'ge', + | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' + | }$freeStuffFilter] |}]""".trimMargin().replace("'", "\"") } compositeDisposable += eventService.getSearchEvents(query, sortBy) diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/Session.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/Session.kt index dd918a5354..a373b51541 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sessions/Session.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/Session.kt @@ -1,16 +1,26 @@ package org.fossasia.openevent.general.sessions +import androidx.annotation.NonNull +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.PrimaryKey import com.fasterxml.jackson.databind.PropertyNamingStrategy import com.fasterxml.jackson.databind.annotation.JsonNaming import com.github.jasminb.jsonapi.LongIdHandler import com.github.jasminb.jsonapi.annotations.Id import com.github.jasminb.jsonapi.annotations.Relationship import com.github.jasminb.jsonapi.annotations.Type +import org.fossasia.openevent.general.sessions.microlocation.MicroLocation +import org.fossasia.openevent.general.sessions.sessiontype.SessionType +import org.fossasia.openevent.general.sessions.track.Track @Type("session") @JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) +@Entity data class Session( @Id(LongIdHandler::class) + @PrimaryKey + @NonNull val id: Long, val shortAbstract: String? = null, val comments: String? = null, @@ -31,8 +41,13 @@ data class Session( val lastModifiedAt: String? = null, val videoUrl: String? = null, val audioUrl: String? = null, + @ColumnInfo(index = true) @Relationship("session-type", resolve = true) var sessionType: SessionType? = null, + @ColumnInfo(index = true) @Relationship("microlocation", resolve = true) - var microlocation: Microlocation? = null + var microlocation: MicroLocation? = null, + @ColumnInfo(index = true) + @Relationship("track", resolve = true) + var track: Track? = null ) diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionApi.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionApi.kt index 70088e26e9..9aa14f07e9 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionApi.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionApi.kt @@ -7,7 +7,7 @@ import retrofit2.http.Query interface SessionApi { - @GET("events/{eventId}/sessions?include=session-type,microlocation") + @GET("events/{eventId}/sessions?include=session-type,microlocation,track") fun getSessionsForEvent( @Path("eventId") eventId: Long, @Query("sort") sort: String = "created-at", diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionDao.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionDao.kt new file mode 100644 index 0000000000..1e68db8f5a --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionDao.kt @@ -0,0 +1,27 @@ +package org.fossasia.openevent.general.sessions + +import androidx.lifecycle.LiveData +import androidx.room.Dao +import androidx.room.Insert +import androidx.room.OnConflictStrategy.REPLACE +import androidx.room.Query +import io.reactivex.Flowable + +@Dao +interface SessionDao { + + @Insert(onConflict = REPLACE) + fun insertSessions(sessions: List) + + @Insert(onConflict = REPLACE) + fun insertSession(session: Session) + + @Query("SELECT * FROM Session WHERE id =:id") + fun getSessionById(id: Long): Flowable + + @Query("SELECT * FROM Session") + fun getAllSessions(): LiveData> + + @Query("DELETE FROM Session") + fun deleteCurrentSessions() +} diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionFragment.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionFragment.kt new file mode 100644 index 0000000000..6de3b57e19 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionFragment.kt @@ -0,0 +1,291 @@ +package org.fossasia.openevent.general.sessions + +import android.content.Intent +import android.graphics.Color +import android.net.Uri +import android.os.Bundle +import android.provider.CalendarContract +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.navigation.fragment.navArgs +import com.squareup.picasso.Picasso +import androidx.navigation.Navigation.findNavController +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager.HORIZONTAL +import kotlinx.android.synthetic.main.fragment_session.view.progressBar +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailTrack +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailAbstract +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailLanguage +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailContainer +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailLanguageContainer +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailLocationInfoContainer +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailLocation +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailTimeContainer +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailLocationImageMap +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailType +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailName +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailEndTime +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailStartTime +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailLocationContainer +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailInfoLocation +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailAbstractContainer +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailAbstractSeeMore +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailTrackContainer +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailSignUpButton +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailTrackIcon +import kotlinx.android.synthetic.main.fragment_session.view.speakersUnderSessionRecycler +import kotlinx.android.synthetic.main.fragment_session.view.speakersProgressBar +import kotlinx.android.synthetic.main.fragment_session.view.sessionDetailSpeakersContainer +import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.common.SpeakerClickListener +import org.fossasia.openevent.general.speakers.SpeakerRecyclerAdapter +import org.fossasia.openevent.general.event.EventUtils +import org.fossasia.openevent.general.utils.Utils +import org.fossasia.openevent.general.utils.Utils.setToolbar +import org.fossasia.openevent.general.utils.extensions.nonNull +import org.jetbrains.anko.design.snackbar +import org.koin.androidx.viewmodel.ext.android.viewModel + +const val LINE_COUNT_ABSTRACT = 3 + +class SessionFragment : Fragment() { + private lateinit var rootView: View + private val sessionViewModel by viewModel() + private val speakersAdapter = SpeakerRecyclerAdapter() + private val safeArgs: SessionFragmentArgs by navArgs() + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + rootView = inflater.inflate(R.layout.fragment_session, container, false) + + setToolbar(activity) + setHasOptionsMenu(true) + + sessionViewModel.error + .nonNull() + .observe(viewLifecycleOwner, Observer { + rootView.snackbar(it) + if (it == getString(R.string.error_fetching_speakers_for_session)) { + rootView.sessionDetailSpeakersContainer.visibility = View.GONE + } + }) + + sessionViewModel.session + .nonNull() + .observe(viewLifecycleOwner, Observer { + makeSessionView(it) + }) + + sessionViewModel.progress + .nonNull() + .observe(viewLifecycleOwner, Observer { + rootView.progressBar.visibility = if (it) View.VISIBLE else View.GONE + rootView.sessionDetailContainer.visibility = if (it) View.GONE else View.VISIBLE + }) + + sessionViewModel.speakersUnderSession + .nonNull() + .observe(viewLifecycleOwner, Observer { + speakersAdapter.addAll(it) + if (it.isEmpty()) + rootView.sessionDetailSpeakersContainer.visibility = View.GONE + else + rootView.speakersProgressBar.visibility = View.GONE + }) + + sessionViewModel.loadSession(safeArgs.sessionId) + val currentSpeakers = sessionViewModel.speakersUnderSession.value + if (currentSpeakers == null) + sessionViewModel.loadSpeakersUnderSession(safeArgs.sessionId) + else { + speakersAdapter.addAll(currentSpeakers) + if (currentSpeakers.isEmpty()) + rootView.sessionDetailSpeakersContainer.visibility = View.GONE + else + rootView.speakersProgressBar.visibility = View.GONE + } + + val layoutManager = LinearLayoutManager(context) + layoutManager.orientation = HORIZONTAL + rootView.speakersUnderSessionRecycler.layoutManager = layoutManager + rootView.speakersUnderSessionRecycler.adapter = speakersAdapter + + return rootView + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val speakerClickListener = object : SpeakerClickListener { + override fun onClick(speakerId: Long) { + findNavController(rootView).navigate(SessionFragmentDirections.actionSessionToSpeaker(speakerId)) + } + } + speakersAdapter.apply { + onSpeakerClick = speakerClickListener + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + return when (item.itemId) { + android.R.id.home -> { + activity?.onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + override fun onDestroy() { + super.onDestroy() + Utils.setNewHeaderColor(activity, resources.getColor(R.color.colorPrimaryDark), + resources.getColor(R.color.colorPrimary)) + } + + private fun makeSessionView(session: Session) { + when (session.title.isNullOrBlank()) { + true -> rootView.sessionDetailName.visibility = View.GONE + false -> { + rootView.sessionDetailName.text = session.title + setToolbar(activity, session.title) + } + } + + val type = session.sessionType + if (type == null) { + rootView.sessionDetailType.visibility = View.GONE + } else { + rootView.sessionDetailType.text = "Type: ${type.name}" + } + + val locationInfo = session.microlocation + if (locationInfo == null) { + rootView.sessionDetailLocationInfoContainer.visibility = View.GONE + rootView.sessionDetailLocationContainer.visibility = View.GONE + } else { + rootView.sessionDetailInfoLocation.text = locationInfo.name + rootView.sessionDetailLocation.text = locationInfo.name + if (locationInfo.latitude.isNullOrBlank() || locationInfo.longitude.isNullOrBlank()) { + rootView.sessionDetailLocationImageMap.visibility = View.GONE + } else { + rootView.sessionDetailLocationContainer.setOnClickListener { + startMap(locationInfo.latitude, locationInfo.longitude) + } + rootView.sessionDetailLocationImageMap.setOnClickListener { + startMap(locationInfo.latitude, locationInfo.longitude) + } + Picasso.get() + .load(sessionViewModel.loadMap(locationInfo.latitude, locationInfo.longitude)) + .placeholder(R.drawable.ic_map_black) + .error(R.drawable.ic_map_black) + .into(rootView.sessionDetailLocationImageMap) + } + } + + when (session.language.isNullOrBlank()) { + true -> rootView.sessionDetailLanguageContainer.visibility = View.GONE + false -> rootView.sessionDetailLanguage.text = session.language + } + + when (session.startsAt.isNullOrBlank()) { + true -> rootView.sessionDetailStartTime.visibility = View.GONE + false -> { + val formattedStartTime = EventUtils.getEventDateTime(session.startsAt, "") + val formattedTime = EventUtils.getFormattedTime(formattedStartTime) + val formattedDate = EventUtils.getFormattedDate(formattedStartTime) + val timezone = EventUtils.getFormattedTimeZone(formattedStartTime) + rootView.sessionDetailStartTime.text = "$formattedTime $timezone/ $formattedDate" + } + } + when (session.endsAt.isNullOrBlank()) { + true -> rootView.sessionDetailEndTime.visibility = View.GONE + false -> { + val formattedEndTime = EventUtils.getEventDateTime(session.endsAt, "") + val formattedTime = EventUtils.getFormattedTime(formattedEndTime) + val formattedDate = EventUtils.getFormattedDate(formattedEndTime) + val timezone = EventUtils.getFormattedTimeZone(formattedEndTime) + rootView.sessionDetailEndTime.text = "- $formattedTime $timezone/ $formattedDate" + } + } + if (session.startsAt.isNullOrBlank() && session.endsAt.isNullOrBlank()) + rootView.sessionDetailTimeContainer.visibility = View.GONE + else + rootView.sessionDetailTimeContainer.setOnClickListener { + saveSessionToCalendar(session) + } + + val description = session.longAbstract ?: session.shortAbstract + when (description.isNullOrBlank()) { + true -> rootView.sessionDetailAbstractContainer.visibility = View.GONE + false -> { + rootView.sessionDetailAbstract.text = description + val sessionAbstractClickListener = View.OnClickListener { + if (rootView.sessionDetailAbstractSeeMore.text == getString(R.string.see_more)) { + rootView.sessionDetailAbstractSeeMore.text = getString(R.string.see_less) + rootView.sessionDetailAbstract.minLines = 0 + rootView.sessionDetailAbstract.maxLines = Int.MAX_VALUE + } else { + rootView.sessionDetailAbstractSeeMore.text = getString(R.string.see_more) + rootView.sessionDetailAbstract.setLines(LINE_COUNT_ABSTRACT + 1) + } + } + + rootView.sessionDetailAbstract.post { + if (rootView.sessionDetailAbstract.lineCount > LINE_COUNT_ABSTRACT) { + rootView.sessionDetailAbstractSeeMore.visibility = View.VISIBLE + rootView.sessionDetailAbstractContainer.setOnClickListener(sessionAbstractClickListener) + } + } + } + } + + val track = session.track + when (track == null) { + true -> rootView.sessionDetailTrackContainer.visibility = View.GONE + false -> { + rootView.sessionDetailTrack.text = track.name + val trackColor = Color.parseColor(track.color) + rootView.sessionDetailTrackIcon.setColorFilter(trackColor) + Utils.setNewHeaderColor(activity, trackColor) + } + } + + when (session.signupUrl.isNullOrBlank()) { + true -> rootView.sessionDetailSignUpButton.visibility = View.GONE + false -> rootView.sessionDetailSignUpButton.setOnClickListener { + context?.let { Utils.openUrl(it, session.signupUrl) } + } + } + } + + private fun saveSessionToCalendar(session: Session) { + val intent = Intent(Intent.ACTION_INSERT) + intent.type = "vnd.android.cursor.item/event" + intent.putExtra(CalendarContract.Events.TITLE, session.title) + intent.putExtra(CalendarContract.Events.DESCRIPTION, session.shortAbstract) + intent.putExtra(CalendarContract.Events.EVENT_LOCATION, session.microlocation?.name) + + if (session.startsAt != null && session.endsAt != null) { + val formattedStartTime = EventUtils.getEventDateTime(session.startsAt, "") + val timezone = EventUtils.getFormattedTimeZone(formattedStartTime) + intent.putExtra(CalendarContract.Events.EVENT_TIMEZONE, timezone) + intent.putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, + EventUtils.getTimeInMilliSeconds(session.startsAt, timezone)) + intent.putExtra(CalendarContract.EXTRA_EVENT_END_TIME, + EventUtils.getTimeInMilliSeconds(session.endsAt, timezone)) + } + + startActivity(intent) + } + + private fun startMap(latitude: String, longitude: String) { + val mapUrl = "geo:<$latitude>,<$longitude>?q=<$latitude>,<$longitude>" + val mapIntent = Intent(Intent.ACTION_VIEW, Uri.parse(mapUrl)) + val packageManager = activity?.packageManager + if (packageManager != null && mapIntent.resolveActivity(packageManager) != null) { + startActivity(mapIntent) + } + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionRecyclerAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionRecyclerAdapter.kt index 6e00e50141..17e6a41623 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionRecyclerAdapter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionRecyclerAdapter.kt @@ -4,9 +4,11 @@ import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.common.SessionClickListener class SessionRecyclerAdapter : RecyclerView.Adapter() { - val sessionList = ArrayList() + private val sessionList = ArrayList() + var onSessionClick: SessionClickListener? = null fun addAll(sessionList: List) { if (sessionList.isNotEmpty()) @@ -21,9 +23,12 @@ class SessionRecyclerAdapter : RecyclerView.Adapter() { } override fun onBindViewHolder(holder: SessionViewHolder, position: Int) { - val speaker = sessionList[position] + val session = sessionList[position] - holder.bind(speaker) + holder.apply { + bind(session) + sessionClickListener = onSessionClick + } } override fun getItemCount(): Int { diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionService.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionService.kt new file mode 100644 index 0000000000..4d4f24bb24 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionService.kt @@ -0,0 +1,21 @@ +package org.fossasia.openevent.general.sessions + +import io.reactivex.Flowable +import io.reactivex.Single + +class SessionService( + private val sessionApi: SessionApi, + private val sessionDao: SessionDao +) { + fun fetchSessionForEvent(id: Long): Single> { + return sessionApi.getSessionsForEvent(id) + .doOnSuccess { sessions -> + sessionDao.deleteCurrentSessions() + sessionDao.insertSessions(sessions) + } + } + + fun fetchSession(id: Long): Flowable { + return sessionDao.getSessionById(id) + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionViewHolder.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionViewHolder.kt index e68515e953..d01c3763a2 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionViewHolder.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionViewHolder.kt @@ -1,5 +1,6 @@ package org.fossasia.openevent.general.sessions +import android.graphics.Color import android.view.View import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView @@ -8,11 +9,18 @@ import kotlinx.android.synthetic.main.item_session.view.sessionType import kotlinx.android.synthetic.main.item_session.view.sessiontime import kotlinx.android.synthetic.main.item_session.view.shortAbstract import kotlinx.android.synthetic.main.item_session.view.title +import kotlinx.android.synthetic.main.item_session.view.trackDetail +import kotlinx.android.synthetic.main.item_session.view.trackText +import kotlinx.android.synthetic.main.item_session.view.trackIcon +import org.fossasia.openevent.general.common.SessionClickListener import org.fossasia.openevent.general.event.EventUtils import org.fossasia.openevent.general.utils.nullToEmpty import org.fossasia.openevent.general.utils.stripHtml class SessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + var sessionClickListener: SessionClickListener? = null + fun bind(session: Session) { itemView.title.text = session.title session.sessionType.let { @@ -21,6 +29,15 @@ class SessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { session.microlocation.let { itemView.mircolocation.text = it?.name } + + session.track.let { + if (it == null) + itemView.trackDetail.visibility = View.GONE + else { + itemView.trackText.text = it.name + itemView.trackIcon.setColorFilter(Color.parseColor(it.color)) + } + } when (session.startsAt.isNullOrBlank()) { true -> itemView.sessiontime.isVisible = false false -> { @@ -36,5 +53,9 @@ class SessionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { true -> itemView.shortAbstract.isVisible = false false -> itemView.shortAbstract.text = shortBio } + + itemView.setOnClickListener { + sessionClickListener?.onClick(session.id) + } } } diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionViewModel.kt new file mode 100644 index 0000000000..e98a730989 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/SessionViewModel.kt @@ -0,0 +1,79 @@ +package org.fossasia.openevent.general.sessions + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import org.fossasia.openevent.general.BuildConfig.MAPBOX_KEY +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.rxkotlin.plusAssign +import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.common.SingleLiveEvent +import org.fossasia.openevent.general.data.Resource +import org.fossasia.openevent.general.speakers.Speaker +import org.fossasia.openevent.general.speakers.SpeakerService +import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers +import timber.log.Timber + +class SessionViewModel( + private val sessionService: SessionService, + private val speakerService: SpeakerService, + private val resource: Resource +) : ViewModel() { + private val compositeDisposable = CompositeDisposable() + + private val mutableSession = MutableLiveData() + val session: LiveData = mutableSession + private val mutableProgress = MutableLiveData(true) + val progress: LiveData = mutableProgress + private val mutableError = SingleLiveEvent() + val error: LiveData = mutableError + private val mutableSpeakers = MutableLiveData>() + val speakersUnderSession: LiveData> = mutableSpeakers + + fun loadSession(id: Long) { + if (id == -1L) { + mutableError.value = resource.getString(R.string.error_fetching_event_message) + return + } + + compositeDisposable += sessionService.fetchSession(id) + .withDefaultSchedulers() + .doOnSubscribe { mutableProgress.value = true } + .subscribe({ + mutableSession.value = it + mutableProgress.value = false + }, { + Timber.e(it, "Error fetching session id $id") + mutableError.value = resource.getString(R.string.error_fetching_event_section_message, + resource.getString(R.string.session)) + }) + } + + fun loadSpeakersUnderSession(id: Long) { + if (id == -1L) { + mutableError.value = resource.getString(R.string.error_fetching_speakers_for_session) + return + } + + compositeDisposable += speakerService.fetchSpeakerForSession(id) + .withDefaultSchedulers() + .subscribe({ + mutableSpeakers.value = it + }, { + Timber.e(it, "Error fetching speakers for session $id") + mutableError.value = resource.getString(R.string.error_fetching_speakers_for_session) + }) + } + + fun loadMap(latitude: String, longitude: String): String { + // location handling + val BASE_URL = "https://api.mapbox.com/v4/mapbox.emerald/pin-l-marker+673ab7" + val LOCATION = "($longitude,$latitude)/$longitude,$latitude" + return "$BASE_URL$LOCATION,15/900x500.png?access_token=$MAPBOX_KEY" + } + + override fun onCleared() { + super.onCleared() + compositeDisposable.clear() + } +} diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/Microlocation.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/microlocation/MicroLocation.kt similarity index 76% rename from app/src/main/java/org/fossasia/openevent/general/sessions/Microlocation.kt rename to app/src/main/java/org/fossasia/openevent/general/sessions/microlocation/MicroLocation.kt index e601ea7bbc..9673566556 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sessions/Microlocation.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/microlocation/MicroLocation.kt @@ -1,5 +1,7 @@ -package org.fossasia.openevent.general.sessions +package org.fossasia.openevent.general.sessions.microlocation +import androidx.room.Entity +import androidx.room.PrimaryKey import com.fasterxml.jackson.databind.PropertyNamingStrategy import com.fasterxml.jackson.databind.annotation.JsonNaming import com.github.jasminb.jsonapi.LongIdHandler @@ -8,8 +10,10 @@ import com.github.jasminb.jsonapi.annotations.Type @Type("microlocation") @JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) -data class Microlocation( +@Entity +data class MicroLocation( @Id(LongIdHandler::class) + @PrimaryKey val id: Long, val name: String, val room: String?, diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/microlocation/MicroLocationConverter.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/microlocation/MicroLocationConverter.kt new file mode 100644 index 0000000000..a6a1fdb953 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/microlocation/MicroLocationConverter.kt @@ -0,0 +1,14 @@ +package org.fossasia.openevent.general.sessions.microlocation + +import androidx.room.TypeConverter +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper + +class MicroLocationConverter { + @TypeConverter + fun toMicroLoation(json: String) = + jacksonObjectMapper().readerFor(MicroLocation::class.java).readValue(json) + + @TypeConverter + fun toJson(microLocation: MicroLocation?) = ObjectMapper().writeValueAsString(microLocation) +} diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionType.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/sessiontype/SessionType.kt similarity index 77% rename from app/src/main/java/org/fossasia/openevent/general/sessions/SessionType.kt rename to app/src/main/java/org/fossasia/openevent/general/sessions/sessiontype/SessionType.kt index 5d3a7a7b20..be89840698 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sessions/SessionType.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/sessiontype/SessionType.kt @@ -1,5 +1,7 @@ -package org.fossasia.openevent.general.sessions +package org.fossasia.openevent.general.sessions.sessiontype +import androidx.room.Entity +import androidx.room.PrimaryKey import com.fasterxml.jackson.databind.PropertyNamingStrategy import com.fasterxml.jackson.databind.annotation.JsonNaming import com.github.jasminb.jsonapi.LongIdHandler @@ -8,8 +10,10 @@ import com.github.jasminb.jsonapi.annotations.Type @Type("session-type") @JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) +@Entity data class SessionType( @Id(LongIdHandler::class) + @PrimaryKey val id: Long, val name: String, val length: String?, diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/sessiontype/SessionTypeConverter.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/sessiontype/SessionTypeConverter.kt new file mode 100644 index 0000000000..f485608d6f --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/sessiontype/SessionTypeConverter.kt @@ -0,0 +1,15 @@ +package org.fossasia.openevent.general.sessions.sessiontype + +import androidx.room.TypeConverter +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper + +class SessionTypeConverter { + + @TypeConverter + fun toSessionType(json: String): SessionType? = + jacksonObjectMapper().readerFor(SessionType::class.java).readValue(json) + + @TypeConverter + fun toJson(sessionType: SessionType?) = ObjectMapper().writeValueAsString(sessionType) +} diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/track/Track.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/track/Track.kt new file mode 100644 index 0000000000..1ece28962d --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/track/Track.kt @@ -0,0 +1,22 @@ +package org.fossasia.openevent.general.sessions.track + +import androidx.room.Entity +import androidx.room.PrimaryKey +import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.annotation.JsonNaming +import com.github.jasminb.jsonapi.LongIdHandler +import com.github.jasminb.jsonapi.annotations.Id +import com.github.jasminb.jsonapi.annotations.Type + +@Type("track") +@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) +@Entity +data class Track( + @Id(LongIdHandler::class) + @PrimaryKey + val id: Long, + val name: String, + val description: String?, + val color: String, + val fontColor: String? +) diff --git a/app/src/main/java/org/fossasia/openevent/general/sessions/track/TrackConverter.kt b/app/src/main/java/org/fossasia/openevent/general/sessions/track/TrackConverter.kt new file mode 100644 index 0000000000..39f996e391 --- /dev/null +++ b/app/src/main/java/org/fossasia/openevent/general/sessions/track/TrackConverter.kt @@ -0,0 +1,15 @@ +package org.fossasia.openevent.general.sessions.track + +import androidx.room.TypeConverter +import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper +import com.fasterxml.jackson.databind.ObjectMapper + +class TrackConverter { + + @TypeConverter + fun toTrack(json: String): Track? = + jacksonObjectMapper().readerFor(Track::class.java).readValue(json) + + @TypeConverter + fun toJson(track: Track?) = ObjectMapper().writeValueAsString(track) +} diff --git a/app/src/main/java/org/fossasia/openevent/general/settings/SettingsFragment.kt b/app/src/main/java/org/fossasia/openevent/general/settings/SettingsFragment.kt index c41131f4d0..125c3e1816 100644 --- a/app/src/main/java/org/fossasia/openevent/general/settings/SettingsFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/settings/SettingsFragment.kt @@ -22,8 +22,11 @@ import kotlinx.android.synthetic.main.dialog_change_password.view.confirmNewPass import kotlinx.android.synthetic.main.dialog_change_password.view.textInputLayoutNewPassword import kotlinx.android.synthetic.main.dialog_change_password.view.textInputLayoutConfirmNewPassword import org.fossasia.openevent.general.BuildConfig +import org.fossasia.openevent.general.PLAY_STORE_BUILD_FLAVOR import org.fossasia.openevent.general.R import org.fossasia.openevent.general.auth.ProfileViewModel +import org.fossasia.openevent.general.auth.SmartAuthUtil +import org.fossasia.openevent.general.auth.SmartAuthViewModel import org.fossasia.openevent.general.utils.Utils import org.fossasia.openevent.general.utils.nullToEmpty import org.koin.androidx.viewmodel.ext.android.viewModel @@ -41,6 +44,7 @@ class SettingsFragment : PreferenceFragmentCompat(), PreferenceChangeListener { private val WEBSITE_LINK: String = "https://eventyay.com/" private val settingsViewModel by viewModel() private val profileViewModel by viewModel() + private val smartAuthViewModel by viewModel() private val safeArgs: SettingsFragmentArgs by navArgs() override fun preferenceChange(evt: PreferenceChangeEvent?) { @@ -56,8 +60,8 @@ class SettingsFragment : PreferenceFragmentCompat(), PreferenceChangeListener { setHasOptionsMenu(true) // Set Email - preferenceScreen.findPreference(getString(R.string.key_profile)) - .summary = safeArgs.email + preferenceScreen.findPreference(getString(R.string.key_account)) + .summary = if (safeArgs.email.isNullOrEmpty()) getString(R.string.not_logged_in) else safeArgs.email // Set Build Version preferenceScreen.findPreference(getString(R.string.key_version)) @@ -65,6 +69,12 @@ class SettingsFragment : PreferenceFragmentCompat(), PreferenceChangeListener { preferenceScreen.findPreference(getString(R.string.key_timezone_switch)) .setDefaultValue(timeZonePreference.getBoolean("useEventTimeZone", false)) + + preferenceScreen.findPreference(getString(R.string.key_profile)).isVisible = profileViewModel.isLoggedIn() + preferenceScreen.findPreference(getString(R.string.key_change_password)).isVisible = + profileViewModel.isLoggedIn() + preferenceScreen.findPreference(getString(R.string.key_timezone_switch)).isVisible = + profileViewModel.isLoggedIn() } override fun onPreferenceTreeClick(preference: Preference?): Boolean { @@ -74,6 +84,16 @@ class SettingsFragment : PreferenceFragmentCompat(), PreferenceChangeListener { view?.snackbar(it) }) + settingsViewModel.updatedPassword + .nonNull() + .observe(viewLifecycleOwner, Observer { + if (BuildConfig.FLAVOR == PLAY_STORE_BUILD_FLAVOR) { + smartAuthViewModel.saveCredential(safeArgs.email.toString(), + it, + SmartAuthUtil.getCredentialsClient(requireActivity())) + } + }) + if (preference?.key == getString(R.string.key_visit_website)) { // Goes to website Utils.openUrl(requireContext(), WEBSITE_LINK) diff --git a/app/src/main/java/org/fossasia/openevent/general/settings/SettingsViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/settings/SettingsViewModel.kt index 8bb8addd53..99bc42942c 100644 --- a/app/src/main/java/org/fossasia/openevent/general/settings/SettingsViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/settings/SettingsViewModel.kt @@ -1,6 +1,7 @@ package org.fossasia.openevent.general.settings import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign @@ -14,6 +15,8 @@ class SettingsViewModel(private val authService: AuthService) : ViewModel() { private val compositeDisposable = CompositeDisposable() private val mutableSnackBar = SingleLiveEvent() val snackBar: LiveData = mutableSnackBar + private val mutableUpdatedPassword = MutableLiveData() + val updatedPassword: LiveData = mutableUpdatedPassword fun isLoggedIn() = authService.isLoggedIn() @@ -39,7 +42,10 @@ class SettingsViewModel(private val authService: AuthService) : ViewModel() { compositeDisposable += authService.changePassword(oldPassword, newPassword) .withDefaultSchedulers() .subscribe({ - if (it.passwordChanged) mutableSnackBar.value = "Password changed successfully!" + if (it.passwordChanged) { + mutableSnackBar.value = "Password changed successfully!" + mutableUpdatedPassword.value = newPassword + } }, { if (it.message.toString() == "HTTP 400 BAD REQUEST") mutableSnackBar.value = "Incorrect Old Password provided!" diff --git a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksFragment.kt b/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksFragment.kt deleted file mode 100644 index 9c728e797c..0000000000 --- a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksFragment.kt +++ /dev/null @@ -1,107 +0,0 @@ -package org.fossasia.openevent.general.social - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.core.view.isGone -import androidx.core.view.isVisible -import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.recyclerview.widget.LinearLayoutManager -import kotlinx.android.synthetic.main.fragment_social_links.eventHostDetails -import kotlinx.android.synthetic.main.fragment_social_links.socialLinksRecycler -import kotlinx.android.synthetic.main.fragment_social_links.view.progressBarSocial -import kotlinx.android.synthetic.main.fragment_social_links.view.socialLinkReload -import kotlinx.android.synthetic.main.fragment_social_links.view.socialLinksRecycler -import kotlinx.android.synthetic.main.fragment_social_links.view.socialNoInternet -import org.fossasia.openevent.general.R -import org.fossasia.openevent.general.event.EVENT_ID -import org.fossasia.openevent.general.utils.extensions.nonNull -import org.koin.androidx.viewmodel.ext.android.viewModel -import timber.log.Timber - -class SocialLinksFragment : Fragment() { - private val socialLinksRecyclerAdapter: SocialLinksRecyclerAdapter = SocialLinksRecyclerAdapter() - private val socialLinksViewModel by viewModel() - private lateinit var rootView: View - private var id: Long = -1 - private lateinit var linearLayoutManager: LinearLayoutManager - private var showErrorSnack: ((String) -> Unit)? = null - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val bundle = this.arguments - if (bundle != null) { - id = bundle.getLong(EVENT_ID, -1) - } - loadSocialLink() - } - - override fun onCreateView( - inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - rootView = inflater.inflate(R.layout.fragment_social_links, container, false) - - rootView.progressBarSocial.isIndeterminate = true - - linearLayoutManager = LinearLayoutManager(requireContext()) - linearLayoutManager.orientation = LinearLayoutManager.HORIZONTAL - rootView.socialLinksRecycler.layoutManager = linearLayoutManager - - rootView.socialLinksRecycler.adapter = socialLinksRecyclerAdapter - rootView.socialLinksRecycler.isNestedScrollingEnabled = false - - rootView.socialLinkReload.setOnClickListener { - loadSocialLink() - } - - socialLinksViewModel.progress - .nonNull() - .observe(viewLifecycleOwner, Observer { - rootView.progressBarSocial.isVisible = it - }) - - socialLinksViewModel.socialLinks - .nonNull() - .observe(viewLifecycleOwner, Observer { - socialLinksRecyclerAdapter.addAll(it) - handleVisibility(it) - socialLinksRecyclerAdapter.notifyDataSetChanged() - Timber.d("Fetched social-links of size %s", socialLinksRecyclerAdapter.itemCount) - }) - - socialLinksViewModel.error - .nonNull() - .observe(viewLifecycleOwner, Observer { - showErrorSnack?.invoke(it) - }) - - socialLinksViewModel.internetError - .nonNull() - .observe(viewLifecycleOwner, Observer { - rootView.socialNoInternet.isVisible = it - }) - - return rootView - } - - /* - function to set errorSnackMessage CallBack, to be invoked , - to be invoked when snack error is generated - */ - fun setErrorSnack(errorSnack: (String) -> Unit) { - showErrorSnack = errorSnack - } - - private fun handleVisibility(socialLinks: List) { - eventHostDetails.isGone = socialLinks.isEmpty() - socialLinksRecycler.isGone = socialLinks.isEmpty() - } - - private fun loadSocialLink() { - socialLinksViewModel.checkAndLoadSocialLinks(id) - } -} diff --git a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksRecyclerAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksRecyclerAdapter.kt index 33cc155fcb..be8dadca46 100644 --- a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksRecyclerAdapter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksRecyclerAdapter.kt @@ -14,6 +14,7 @@ class SocialLinksRecyclerAdapter : RecyclerView.Adapter() if (socialLinkList.isNotEmpty()) this.socialLinks.clear() this.socialLinks.addAll(socialLinkList) + notifyDataSetChanged() } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SocialLinksViewHolder { @@ -23,7 +24,6 @@ class SocialLinksRecyclerAdapter : RecyclerView.Adapter() override fun onBindViewHolder(holder: SocialLinksViewHolder, position: Int) { val socialLink = socialLinks[position] - holder.bind(socialLink) } diff --git a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksViewHolder.kt b/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksViewHolder.kt index 0239b2a14a..23cc5cebca 100644 --- a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksViewHolder.kt +++ b/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksViewHolder.kt @@ -26,15 +26,17 @@ class SocialLinksViewHolder(itemView: View, private var context: Context) : Recy } private fun getSocialLinkDrawableId(name: String): Int { - if (name.contains("github")) return R.drawable.ic_github - else if (name.contains("twitter")) return R.drawable.ic_twitter - else if (name.contains("facebook")) return R.drawable.ic_facebook - else if (name.contains("linkedin")) return R.drawable.ic_linkedin - else if (name.contains("youtube")) return R.drawable.ic_youtube - else if (name.contains("google")) return R.drawable.ic_google_plus - else if (name.contains("wiki")) return R.drawable.ic_wikipedia - else if (name.contains("flickr")) return R.drawable.ic_flickr - else if (name.contains("blog")) return R.drawable.ic_blogger - else return R.drawable.ic_link_black + return when { + name.contains("github") -> R.drawable.ic_github + name.contains("twitter") -> R.drawable.ic_twitter + name.contains("facebook") -> R.drawable.ic_facebook + name.contains("linkedin") -> R.drawable.ic_linkedin + name.contains("youtube") -> R.drawable.ic_youtube + name.contains("google") -> R.drawable.ic_google_plus + name.contains("wiki") -> R.drawable.ic_wikipedia + name.contains("flickr") -> R.drawable.ic_flickr + name.contains("blog") -> R.drawable.ic_blogger + else -> R.drawable.ic_link_black + } } } diff --git a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksViewModel.kt deleted file mode 100644 index dd9857325e..0000000000 --- a/app/src/main/java/org/fossasia/openevent/general/social/SocialLinksViewModel.kt +++ /dev/null @@ -1,60 +0,0 @@ -package org.fossasia.openevent.general.social - -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData -import androidx.lifecycle.ViewModel -import io.reactivex.disposables.CompositeDisposable -import org.fossasia.openevent.general.R -import io.reactivex.rxkotlin.plusAssign -import org.fossasia.openevent.general.utils.extensions.withDefaultSchedulers -import org.fossasia.openevent.general.common.SingleLiveEvent -import org.fossasia.openevent.general.data.Network -import org.fossasia.openevent.general.data.Resource -import timber.log.Timber - -class SocialLinksViewModel( - private val socialLinksService: SocialLinksService, - private val resource: Resource, - private val network: Network -) : ViewModel() { - - private val compositeDisposable = CompositeDisposable() - - private val mutableProgress = MutableLiveData() - val progress: LiveData = mutableProgress - private val mutableSocialLinks = MutableLiveData>() - val socialLinks: LiveData> = mutableSocialLinks - private val mutableError = SingleLiveEvent() - val error: LiveData = mutableError - private val mutableInternetError = MutableLiveData() - val internetError: LiveData = mutableInternetError - - private fun loadSocialLinks(id: Long) { - compositeDisposable += socialLinksService.getSocialLinks(id) - .withDefaultSchedulers() - .doOnSubscribe { - mutableProgress.value = true - }.subscribe({ - mutableSocialLinks.value = it - mutableProgress.value = false - }, { - Timber.e(it, resource.getString(R.string.error_fetching_social_links_message)) - mutableError.value = resource.getString(R.string.error_fetching_social_links_message) - mutableProgress.value = false - }) - } - - override fun onCleared() { - super.onCleared() - compositeDisposable.clear() - } - - fun checkAndLoadSocialLinks(id: Long) { - if (network.isNetworkConnected()) { - loadSocialLinks(id) - mutableInternetError.value = false - } else { - mutableInternetError.value = true - } - } -} diff --git a/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerApi.kt b/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerApi.kt index 72d16c5f89..fc4ac2a893 100644 --- a/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerApi.kt +++ b/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerApi.kt @@ -9,6 +9,9 @@ interface SpeakerApi { @GET("events/{id}/speakers") fun getSpeakerForEvent(@Path("id") id: Long): Single> + @GET("sessions/{sessionId}/speakers") + fun getSpeakersForSession(@Path("sessionId") id: Long): Single> + @GET("speakers/{speaker_id}") fun getSpeakerWithId(@Path("speaker_id") id: Long): Single } diff --git a/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerService.kt b/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerService.kt index b63a97c8a8..b7aab6f37b 100644 --- a/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerService.kt +++ b/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerService.kt @@ -24,6 +24,12 @@ class SpeakerService( return speakerWithEventDao.getSpeakerWithEventId(id) } + fun fetchSpeakerForSession(sessionId: Long): Single> = + speakerApi.getSpeakersForSession(sessionId) + .doOnSuccess { + speakerDao.insertSpeakers(it) + } + fun fetchSpeaker(id: Long): Flowable { return speakerDao.getSpeaker(id) } diff --git a/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerViewModel.kt b/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerViewModel.kt index c644d21c6f..2b8aac0885 100644 --- a/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerViewModel.kt +++ b/app/src/main/java/org/fossasia/openevent/general/speakers/SpeakerViewModel.kt @@ -31,7 +31,7 @@ class SpeakerViewModel( } compositeDisposable += speakerService.fetchSpeaker(id) .withDefaultSchedulers() - .subscribe ({ + .subscribe({ mutableSpeaker.value = it }, { Timber.e(it, "Error fetching speaker for id %d", id) diff --git a/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorRecyclerAdapter.kt b/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorRecyclerAdapter.kt index 9edcd21687..9429a8c57d 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorRecyclerAdapter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorRecyclerAdapter.kt @@ -14,6 +14,7 @@ class SponsorRecyclerAdapter : RecyclerView.Adapter() { fun addAll(newSponsors: List) { if (sponsorList.isNotEmpty()) sponsorList.clear() sponsorList.addAll(SponsorUtil.sortSponsorByLevel(newSponsors)) + notifyDataSetChanged() } override fun onBindViewHolder(holder: SponsorViewHolder, position: Int) { diff --git a/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorService.kt b/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorService.kt index c47e48cab8..b5df7c86b0 100644 --- a/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorService.kt +++ b/app/src/main/java/org/fossasia/openevent/general/sponsor/SponsorService.kt @@ -3,7 +3,7 @@ package org.fossasia.openevent.general.sponsor import androidx.lifecycle.LiveData import io.reactivex.Single -class SponsorService ( +class SponsorService( private val sponsorApi: SponsorApi, private val sponsorDao: SponsorDao, private val sponsorWithEventDao: SponsorWithEventDao diff --git a/app/src/main/java/org/fossasia/openevent/general/ticket/TicketIdConverter.kt b/app/src/main/java/org/fossasia/openevent/general/ticket/TicketIdConverter.kt index 6e0028fb91..f2825ee50a 100644 --- a/app/src/main/java/org/fossasia/openevent/general/ticket/TicketIdConverter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/ticket/TicketIdConverter.kt @@ -5,12 +5,14 @@ import androidx.room.TypeConverter class TicketIdConverter { @TypeConverter - fun fromTicketId(ticketId: TicketId): Long { - return ticketId.id + fun fromTicketId(ticketId: TicketId?): Long? { + return ticketId?.id } @TypeConverter - fun toTicketId(id: Long): TicketId { - return TicketId(id) + fun toTicketId(id: Long?): TicketId? { + return id?.let { + TicketId(id) + } } } diff --git a/app/src/main/java/org/fossasia/openevent/general/ticket/TicketsFragment.kt b/app/src/main/java/org/fossasia/openevent/general/ticket/TicketsFragment.kt index 0e77060269..cfed350625 100644 --- a/app/src/main/java/org/fossasia/openevent/general/ticket/TicketsFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/ticket/TicketsFragment.kt @@ -34,6 +34,8 @@ import org.koin.androidx.viewmodel.ext.android.viewModel import org.fossasia.openevent.general.utils.Utils.setToolbar import org.jetbrains.anko.design.longSnackbar +const val TICKETS_FRAGMNET = "ticketsFragment" + class TicketsFragment : Fragment() { private val ticketsRecyclerAdapter: TicketsRecyclerAdapter = TicketsRecyclerAdapter() private val ticketsViewModel by viewModel() @@ -149,8 +151,8 @@ class TicketsFragment : Fragment() { } private fun redirectToLogin() { - findNavController(rootView).navigate(TicketsFragmentDirections.actionTicketsToLogin( - getString(R.string.log_in_first) + findNavController(rootView).navigate(TicketsFragmentDirections.actionTicketsToAuth( + getString(R.string.log_in_first), TICKETS_FRAGMNET )) } diff --git a/app/src/main/java/org/fossasia/openevent/general/utils/Utils.kt b/app/src/main/java/org/fossasia/openevent/general/utils/Utils.kt index 50c263d53d..42ff9b0b43 100644 --- a/app/src/main/java/org/fossasia/openevent/general/utils/Utils.kt +++ b/app/src/main/java/org/fossasia/openevent/general/utils/Utils.kt @@ -5,6 +5,8 @@ import android.app.AlertDialog import android.app.ProgressDialog import android.content.Context import android.graphics.BitmapFactory +import android.graphics.Color +import android.graphics.drawable.ColorDrawable import android.net.ConnectivityManager import android.net.Uri import android.view.View @@ -16,6 +18,7 @@ import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.content.res.AppCompatResources import androidx.browser.customtabs.CustomTabsIntent import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.navigation.NavOptions @@ -94,6 +97,20 @@ object Utils { } } + fun setNewHeaderColor(activity: Activity?, color: Int) { + if (activity is AppCompatActivity) { + activity.supportActionBar?.setBackgroundDrawable(ColorDrawable(color)) + activity.window.statusBarColor = ColorUtils.blendARGB(color, Color.BLACK, 0.2f) + } + } + + fun setNewHeaderColor(activity: Activity?, statusColor: Int, actionBarColor: Int) { + if (activity is AppCompatActivity) { + activity.supportActionBar?.setBackgroundDrawable(ColorDrawable(actionBarColor)) + activity.window.statusBarColor = statusColor + } + } + fun checkAndLoadFragment( fragmentManager: FragmentManager, fragment: Fragment, diff --git a/app/src/main/java/org/fossasia/openevent/general/utils/extensions/RxExtensions.kt b/app/src/main/java/org/fossasia/openevent/general/utils/extensions/RxExtensions.kt index 53d3d1c8f4..cff6378ca4 100644 --- a/app/src/main/java/org/fossasia/openevent/general/utils/extensions/RxExtensions.kt +++ b/app/src/main/java/org/fossasia/openevent/general/utils/extensions/RxExtensions.kt @@ -6,11 +6,11 @@ import io.reactivex.Single import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers -fun Single .withDefaultSchedulers(): +fun Single.withDefaultSchedulers(): Single = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) -fun Flowable .withDefaultSchedulers(): +fun Flowable.withDefaultSchedulers(): Flowable = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) -fun Completable .withDefaultSchedulers(): +fun Completable.withDefaultSchedulers(): Completable = subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) diff --git a/app/src/main/res/drawable/ic_faq.xml b/app/src/main/res/drawable/ic_faq.xml new file mode 100644 index 0000000000..93be57d0b1 --- /dev/null +++ b/app/src/main/res/drawable/ic_faq.xml @@ -0,0 +1,5 @@ + + + + diff --git a/app/src/main/res/drawable/ic_language_black.xml b/app/src/main/res/drawable/ic_language_black.xml new file mode 100644 index 0000000000..180a16a034 --- /dev/null +++ b/app/src/main/res/drawable/ic_language_black.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications_none.xml b/app/src/main/res/drawable/ic_notifications_none.xml new file mode 100644 index 0000000000..6b0bba0649 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_none.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_notifications_white.xml b/app/src/main/res/drawable/ic_notifications_white.xml new file mode 100644 index 0000000000..2ad953fe29 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_white.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_track.xml b/app/src/main/res/drawable/ic_track.xml new file mode 100644 index 0000000000..edb2307df5 --- /dev/null +++ b/app/src/main/res/drawable/ic_track.xml @@ -0,0 +1,8 @@ + + + + + + + diff --git a/app/src/main/res/layout-land/item_card_order_details.xml b/app/src/main/res/layout-land/item_card_order_details.xml index d6f20b4678..7fac21ad3a 100644 --- a/app/src/main/res/layout-land/item_card_order_details.xml +++ b/app/src/main/res/layout-land/item_card_order_details.xml @@ -205,6 +205,8 @@ android:layout_height="@dimen/event_details_image" android:layout_gravity="center" android:layout_margin="@dimen/layout_margin_medium" + android:padding="@dimen/padding_large" + android:foreground="?selectableItemBackground" android:scaleType="centerCrop" /> - - diff --git a/app/src/main/res/layout/content_event.xml b/app/src/main/res/layout/content_event.xml index 35e1c77884..5ea8871464 100644 --- a/app/src/main/res/layout/content_event.xml +++ b/app/src/main/res/layout/content_event.xml @@ -386,13 +386,34 @@ - + android:paddingRight="@dimen/padding_large" + android:descendantFocusability="blocksDescendants" + android:animateLayoutChanges="true" + android:visibility="gone" + tools:visibility="visible"> + + + + - + + + android:layout_marginLeft="@dimen/layout_margin_large" + android:layout_marginRight="@dimen/layout_margin_large" + android:layout_marginBottom="@dimen/layout_margin_large" + android:text="@string/more_like_this" + android:textColor="@color/black" + android:textSize="@dimen/event_details_headers" /> + + diff --git a/app/src/main/res/layout/fragment_auth.xml b/app/src/main/res/layout/fragment_auth.xml new file mode 100644 index 0000000000..b5429dda1e --- /dev/null +++ b/app/src/main/res/layout/fragment_auth.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_event_faq.xml b/app/src/main/res/layout/fragment_event_faq.xml index 437f301d0e..71d533158b 100644 --- a/app/src/main/res/layout/fragment_event_faq.xml +++ b/app/src/main/res/layout/fragment_event_faq.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_events.xml b/app/src/main/res/layout/fragment_events.xml index 3287b19c10..539c2384fc 100644 --- a/app/src/main/res/layout/fragment_events.xml +++ b/app/src/main/res/layout/fragment_events.xml @@ -85,13 +85,47 @@ - + android:padding="@dimen/padding_large" + android:orientation="vertical" + android:visibility="gone" + tools:visibility="visible"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index 025a8a2248..5bfcc6fc06 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -217,9 +217,20 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:visibility="gone" android:elevation="@dimen/card_elevation" /> + + diff --git a/app/src/main/res/layout/fragment_session.xml b/app/src/main/res/layout/fragment_session.xml new file mode 100644 index 0000000000..d745bed64f --- /dev/null +++ b/app/src/main/res/layout/fragment_session.xml @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_similar_events.xml b/app/src/main/res/layout/fragment_similar_events.xml deleted file mode 100644 index b957689b5f..0000000000 --- a/app/src/main/res/layout/fragment_similar_events.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_social_links.xml b/app/src/main/res/layout/fragment_social_links.xml deleted file mode 100644 index 775c6a79ac..0000000000 --- a/app/src/main/res/layout/fragment_social_links.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/item_card_notification.xml b/app/src/main/res/layout/item_card_notification.xml new file mode 100644 index 0000000000..d48ad7b0d7 --- /dev/null +++ b/app/src/main/res/layout/item_card_notification.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_card_order_details.xml b/app/src/main/res/layout/item_card_order_details.xml index 82991762c6..9e55790b58 100644 --- a/app/src/main/res/layout/item_card_order_details.xml +++ b/app/src/main/res/layout/item_card_order_details.xml @@ -28,6 +28,8 @@ android:layout_height="@dimen/event_details_image" android:layout_gravity="center" android:layout_margin="@dimen/layout_margin_medium" + android:padding="@dimen/padding_large" + android:foreground="?selectableItemBackground" android:scaleType="centerCrop" /> + diff --git a/app/src/main/res/layout/item_session.xml b/app/src/main/res/layout/item_session.xml index 4db0652d6b..9dc7f02960 100644 --- a/app/src/main/res/layout/item_session.xml +++ b/app/src/main/res/layout/item_session.xml @@ -7,6 +7,7 @@ android:layout_margin="@dimen/layout_margin_medium" android:elevation="@dimen/card_elevation" app:cardBackgroundColor="@android:color/white" + android:foreground="?android:attr/selectableItemBackground" app:cardCornerRadius="@dimen/card_corner_radius"> + + + + + android:textSize="@dimen/text_size_large" + tools:text="@string/description_preview"/> diff --git a/app/src/main/res/layout/item_speaker.xml b/app/src/main/res/layout/item_speaker.xml index f80fd6a253..25d08c1a4c 100644 --- a/app/src/main/res/layout/item_speaker.xml +++ b/app/src/main/res/layout/item_speaker.xml @@ -6,7 +6,8 @@ android:layout_margin="@dimen/layout_margin_medium" app:cardCornerRadius="@dimen/card_corner_radius" android:elevation="@dimen/card_elevation" - app:cardBackgroundColor="@android:color/white"> + app:cardBackgroundColor="@android:color/white" + android:foreground="?android:attr/selectableItemBackground"> + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/events.xml b/app/src/main/res/menu/events.xml new file mode 100644 index 0000000000..d2c0428a7f --- /dev/null +++ b/app/src/main/res/menu/events.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/main/res/menu/search.xml b/app/src/main/res/menu/search.xml index 0ebf0d440d..ff58e60042 100644 --- a/app/src/main/res/menu/search.xml +++ b/app/src/main/res/menu/search.xml @@ -7,5 +7,5 @@ android:title="@string/search" app:iconTint="@android:color/white" app:actionViewClass="android.widget.SearchView" - app:showAsAction="ifRoom|collapseActionView" /> + app:showAsAction="always|collapseActionView" /> diff --git a/app/src/main/res/navigation/navigation_graph.xml b/app/src/main/res/navigation/navigation_graph.xml index 0a777e805c..5ec958ce41 100644 --- a/app/src/main/res/navigation/navigation_graph.xml +++ b/app/src/main/res/navigation/navigation_graph.xml @@ -44,30 +44,15 @@ app:popExitAnim="@anim/slide_out_right" app:enterAnim="@anim/slide_in_right" app:exitAnim="@anim/slide_out_left"/> + - - - - - - - - + + + + + android:defaultValue=""/> + + + - + app:nullable="false" + android:defaultValue="''"/> + + tools:layout="@layout/fragment_signup"> + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index 8f68b6e903..26281dfdd0 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -186,4 +186,8 @@ খুব ছোট পাসওয়ার্ড! দেশ শর্তাবলী গ্রহণ করুন! + + + আপনার কোন বিজ্ঞপ্তি নেই.. + diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 123a72034e..252665b423 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -180,5 +180,8 @@ आपका पासवर्ड और पुष्टिकरण पासवर्ड मेल नहीं खाते हैं! पासवर्ड बहुत छोटा है! + + आपके पास कोई सूचना नहीं है.. + diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 2ad6f94df2..4de030a1f7 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -188,13 +188,11 @@ Không thể tải thông tin người tham dự Không thể tải sự kiện Không thể tải sự kiện - Không thể tải sự kiện tương tự Lỗi Không thể tải các sự kiện được yêu thích Không thể tải vé của người dùng Không thể tải sự kiện của người dùng Lỗi thêm vào yêu thích - Không thể tải liên kết xã hội Không thể tải vé Lựa chọn hình ảnh Thay đổi của bạn chưa được lưu lại @@ -221,4 +219,7 @@ Lưu vé sự kiện Hoàn tác Rời %1$s khỏi sự kiện yêu thích + + + Bạn không có bất kỳ thông báo nào.. diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 660f4280db..808585d3f0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -16,6 +16,7 @@ Event host details Organizer Location + Track What\'s good in where? We\'re having difficulty connecting to the server. Check your connection or try again later. @@ -53,7 +54,7 @@ Username Log out Settings - Login + Log In I forgot my password We just sent you an email with a link to reset\nyour password Check your email! @@ -70,6 +71,9 @@ Welcome back! Update Profile Photo + Edit your profile picture + Delete + New Photo eventyay @@ -118,6 +122,7 @@ View Not seeing your tickets? Learn more about how to find them. No past tickets. + Past Tickets Past > Find my tickets No tickets available! @@ -161,6 +166,7 @@ version about rating + account Rate Us Open Event Android Suggest Improvement @@ -187,6 +193,7 @@ Legal Visit Website Visit_Website + No account logged in Are you sure you want to log out? @@ -259,6 +266,11 @@ We\'ll show you what\'s good near you Pick a city + + Let\'s get started + Sign up or log in to see what\'s happening near you + Get Started + Your email address is invalid! Your password and confirmation password do not match! @@ -278,13 +290,12 @@ Error fetching event Error fetching events - Error fetching similar events + Error fetching speakers under this session Error Error fetching favorite events Failed to list Orders under a user Failed to list events under a user Error adding to favorites - Error fetching Social Links Error fetching tickets @@ -299,11 +310,16 @@ Please provide first name and last name! Error updating user! User updated successfully! + "Error fetching %1$s for the event + Fail on submitting the feedback + Feedback submitted Failure Information is not loaded! Please check your internet connection Popular Locations + Social Link + Similar Events Anything And I\'m up for @@ -316,16 +332,25 @@ Feedback submitted Successfully Be the first to write a review Frequently Asked Questions + There are no frequenly asked questions for this event. Filter Done Filter Search Free stuff only Speakers Sponsors + Sessions + Session Some description about this sponsor: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. name starts-at Sort By Category + + Failed to load notifications + Notifications + You don\'t have any notification.. + Color Code: + diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 62e2a98d4f..13323c7fcd 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -2,34 +2,13 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/app/src/playStore/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt b/app/src/playStore/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt index e7ddce7f05..1f6832fc70 100644 --- a/app/src/playStore/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt +++ b/app/src/playStore/java/org/fossasia/openevent/general/auth/SmartAuthViewModel.kt @@ -25,6 +25,8 @@ class SmartAuthViewModel : ViewModel() { val progress: LiveData = mutableProgress private val mutableApiExceptionRequestCodePair = MutableLiveData>() val apiExceptionCodePair: LiveData> = mutableApiExceptionRequestCodePair + private val mutableStatus = MutableLiveData() + val isCredentialStored: LiveData = mutableStatus fun requestCredentials(credentialsClient: CredentialsClient) { if (requestedCredentialsEarlier) return @@ -41,7 +43,10 @@ class SmartAuthViewModel : ViewModel() { if (task.isSuccessful) { mutableId.value = task.result?.credential?.id mutablePassword.value = task.result?.credential?.password + mutableStatus.value = true return@OnCompleteListener + } else { + mutableStatus.value = false } val e = task.exception if (e is ResolvableApiException) { @@ -52,7 +57,7 @@ class SmartAuthViewModel : ViewModel() { }) } - fun saveCredential (id: String, password: String, credentialsClient: CredentialsClient) { + fun saveCredential(id: String, password: String, credentialsClient: CredentialsClient) { val credential = Credential.Builder(id).setPassword(password).build() credentialsClient.save(credential).addOnCompleteListener( OnCompleteListener { task -> diff --git a/app/src/playStore/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt b/app/src/playStore/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt index ef77fdc8c9..c26a90790b 100644 --- a/app/src/playStore/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt +++ b/app/src/playStore/java/org/fossasia/openevent/general/di/FlavorSpecificModules.kt @@ -1,8 +1,8 @@ package org.fossasia.openevent.general.di import org.fossasia.openevent.general.welcome.WelcomeViewModel -import org.koin.androidx.viewmodel.ext.koin.viewModel -import org.koin.dsl.module.module +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module val flavorSpecificModule = module { viewModel { WelcomeViewModel(get(), get()) } diff --git a/app/src/test/java/org/fossasia/openevent/general/DependencyTest.kt b/app/src/test/java/org/fossasia/openevent/general/DependencyTest.kt index 1ba83b2d73..c1d2ae383d 100644 --- a/app/src/test/java/org/fossasia/openevent/general/DependencyTest.kt +++ b/app/src/test/java/org/fossasia/openevent/general/DependencyTest.kt @@ -7,19 +7,18 @@ import org.fossasia.openevent.general.di.databaseModule import org.fossasia.openevent.general.di.networkModule import org.fossasia.openevent.general.di.viewModelModule import org.junit.Test -import org.koin.android.ext.koin.with -import org.koin.standalone.StandAloneContext.startKoin +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.koinApplication import org.koin.test.KoinTest -import org.koin.test.dryRun +import org.koin.test.check.checkModules import org.mockito.Mockito.mock class DependencyTest : KoinTest { @Test fun testDependencies() { - // start Koin - startKoin(listOf(commonModule, apiModule, viewModelModule, - networkModule, databaseModule)) with mock(Application::class.java) - // dry run of given module list - dryRun() + koinApplication { + androidContext(mock(Application::class.java)) + modules(commonModule, apiModule, databaseModule, networkModule, viewModelModule) + }.checkModules() } } diff --git a/build.gradle b/build.gradle index e6b9c532bc..f87cb0e5b0 100644 --- a/build.gradle +++ b/build.gradle @@ -9,7 +9,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.3.2' + classpath 'com.android.tools.build:gradle:3.4.1' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" classpath 'android.arch.navigation:navigation-safe-args-gradle-plugin:1.0.0' diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 9d0195a4c0..3e4034c777 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Jan 21 18:56:58 IST 2019 +#Thu May 16 13:42:01 IST 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip