diff --git a/.travis.yml b/.travis.yml index 16a4d54552..88e4656f07 100644 --- a/.travis.yml +++ b/.travis.yml @@ -24,3 +24,8 @@ script: - ./gradlew build after_success: - bash scripts/update-apk.sh + +branches: + only: + - master + - development diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3db8554c1c..d99a015e3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ ## How to Contribute - +First please go through the [Open Source Developer Guide and Best Practices at FOSSASIA](https://blog.fossasia.org/open-source-developer-guide-and-best-practices-at-fossasia/) ### Raising an issue: This is an Open Source project and we would be happy to see contributors who report bugs and file feature requests submitting pull requests as well. This project adheres to the Contributor Covenant code of conduct. diff --git a/README.md b/README.md index 08004ca02a..013d15b805 100644 --- a/README.md +++ b/README.md @@ -5,22 +5,27 @@ [![Mailing List](https://img.shields.io/badge/Mailing%20List-FOSSASIA-blue.svg)](https://groups.google.com/forum/#!forum/open-event) [![Twitter Follow](https://img.shields.io/twitter/follow/eventyay.svg?style=social&label=Follow&maxAge=2592000?style=flat-square)](https://twitter.com/eventyay) -An events app to discover events happening around the world using the Open Event Platform on Eventyay.com. +An events app to discover events happening around the world using the Open Event Platform on [Eventyay](https://eventyay.com). + +Eventyay Attendee App provides following features for users: +- All events by the organizers can be viewed +- Functionality to filter out events by date, time, location and event name +- Users can buy tickets and register as attendees for any event +- Pay for their orders via PayPal and Stripe +- All important event details such as location, date, and timing of the event can be viewed +- Users can view all the tickets bought for an event with their status +- Easy check-in using QR code for Tickets and see check-in timings +- Users can view similar events +- Users have the privilege to mark an event as favorite Application is available here: Get it on Google Play Get it on F-Droid -## Roadmap - -Planned features & enhancements are: - - - ## Communication -Please join our mailing list to discuss questions regarding the project: https://groups.google.com/forum/#!forum/open-event +Please join our mailing list to discuss questions regarding the project [here](https://groups.google.com/forum/#!forum/open-event) Our chat channel is on gitter [here](https://gitter.im/fossasia/open-event-attendee-android) @@ -32,11 +37,12 @@ Our chat channel is on gitter [here](https://gitter.im/fossasia/open-event-atten + - + @@ -44,7 +50,7 @@ Our chat channel is on gitter [here](https://gitter.im/fossasia/open-event-atten ## Development -A native Android app using Kotlin for writing code. +A native Android app using Kotlin for writing code and [Open event server](https://github.com/fossasia/open-event-server) for API. ### Libraries used and their documentation @@ -58,6 +64,10 @@ A native Android app using Kotlin for writing code. - JSON API Converter [Docs](https://github.com/jasminb/jsonapi-converter) - OkHttp [Docs](http://square.github.io/okhttp/) - Room Persistence Library [Docs](https://developer.android.com/topic/libraries/architecture/room) +- PayPal [Docs](https://github.com/paypal/PayPal-Android-SDK) +- Navigation Architecture Component [Docs](https://developer.android.com/guide/navigation/navigation-getting-started) +- Mapbox [Docs](https://docs.mapbox.com/) +- Stetho [Docs](https://github.com/facebook/stetho) ### Project Conventions @@ -108,13 +118,7 @@ Please help us follow the best practices to make it easy for the reviewer as wel * If you would like to work on an issue, drop in a comment at the issue. If it is already assigned to someone, but there is no sign of any work being done, please free to drop in a comment so that the issue can be assigned to you if the previous assignee has dropped it entirely. ## For Testers: Testing the App -If you are a tester and want to test the app, you have two ways to do that: -1. **Installing APK on your device:** You can get debug APK as well as Release APK in apk branch of the repository. After each PR merge, both the APKs are automatically updated. So, just download the APK you want and install it on your device. The APKs will always be the latest one. - -## Open Event Attendee Android Suggestions - -- Suggestion form link: [Form](https://docs.google.com/forms/d/e/1FAIpQLSd7Y1T1xoXeYaAG_b6Tu1YYK-jZssoC5ltmQbkUX0kmDZaKYw/viewform) -- Suggestion responses link: [Sheet](https://docs.google.com/spreadsheets/d/1SzR75MBEVrTY1sDM3KAMm9wltiulDAp0QT5hv9eJkKM/edit#gid=1676755229) +**Installing APK on your device:** You can get debug APK as well as Release APK in apk branch of the repository. After each PR merge, both the APKs are automatically updated. So, just download the APK you want and install it on your device. The APKs will always be the latest one. ## License diff --git a/app/build.gradle b/app/build.gradle index 56ce131f28..bb0ab2d95b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,5 +1,5 @@ plugins { - id "com.diffplug.gradle.spotless" version "3.24.2" + id "com.diffplug.gradle.spotless" version "3.24.3" } apply plugin: 'com.android.application' @@ -11,6 +11,7 @@ apply plugin: "com.github.b3er.local.properties" def STRIPE_API_TOKEN = System.getenv('STRIPE_API_TOKEN') ?: "YOUR_API_KEY" def MAPBOX_KEY = System.getenv('MAPBOX_KEY') ?: "pk.eyJ1IjoiYW5nbWFzMSIsImEiOiJjanNqZDd0N2YxN2Q5NDNuNTBiaGt6eHZqIn0.BCrxjW6rP_OuOuGtbhVEQg" +def PAYPAL_CLIENT_ID= System.getenv('PAYPAL_CLIENT_ID') ?: "YOUR_API_KEY" def LOCAL_KEY_PRESENT = project.hasProperty('SIGNING_KEY_FILE') && rootProject.file(SIGNING_KEY_FILE).exists() android { @@ -22,8 +23,8 @@ android { applicationId "com.eventyay.attendee" minSdkVersion 21 targetSdkVersion 28 - versionCode 14 - versionName "0.7.0" + versionCode 15 + versionName "0.8.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" vectorDrawables.useSupportLibrary = true multiDexEnabled true @@ -58,6 +59,7 @@ android { buildConfigField "String", "DEFAULT_BASE_URL", '"https://api.eventyay.com/v1/"' buildConfigField "String", "MAPBOX_KEY", '"'+MAPBOX_KEY+'"' buildConfigField "String", "STRIPE_API_KEY", '"'+STRIPE_API_TOKEN+'"' + buildConfigField "String", "PAYPAL_CLIENT_ID", '"'+PAYPAL_CLIENT_ID+'"' resValue "string", "FRONTEND_HOST", "eventyay.com" if (LOCAL_KEY_PRESENT || TRAVIS_BUILD) signingConfig signingConfigs.release @@ -66,6 +68,7 @@ android { buildConfigField "String", "DEFAULT_BASE_URL", '"https://open-event-api-dev.herokuapp.com/v1/"' buildConfigField "String", "MAPBOX_KEY", '"'+MAPBOX_KEY+'"' buildConfigField "String", "STRIPE_API_KEY", '"'+STRIPE_API_TOKEN+'"' + buildConfigField "String", "PAYPAL_CLIENT_ID", '"'+PAYPAL_CLIENT_ID+'"' resValue "string", "FRONTEND_HOST", "open-event-fe.netlify.com" } } @@ -121,22 +124,22 @@ repositories { } dependencies { - def lifecycle_version = "2.2.0-alpha03" + def lifecycle_version = "2.2.0-alpha05" def koin_version = "2.0.1" def roomVersion = "2.1.0" - def ktx_version = "1.0.0" - def ktx2_version = "2.0.0" - def nav_version = "2.1.0-beta02" + def ktx_version = "1.1.0" + def ktx2_version = "2.1.0" + def nav_version = "2.1.0" def anko_version = "0.10.8" def paging_version = "2.1.0" implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'androidx.multidex:multidex:2.0.1' - implementation 'androidx.appcompat:appcompat:1.1.0-rc01' + implementation 'androidx.appcompat:appcompat:1.1.0' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2' implementation 'androidx.cardview:cardview:1.0.0' - implementation 'androidx.recyclerview:recyclerview:1.1.0-beta03' - implementation 'com.google.android.material:material:1.1.0-alpha09' + implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04' + implementation 'com.google.android.material:material:1.1.0-alpha10' implementation "androidx.browser:browser:1.0.0" implementation 'androidx.exifinterface:exifinterface:1.0.0' implementation "androidx.lifecycle:lifecycle-extensions:${lifecycle_version}" @@ -146,7 +149,7 @@ dependencies { implementation "androidx.room:room-rxjava2:${roomVersion}" kapt "androidx.room:room-compiler:${roomVersion}" testImplementation "androidx.room:room-testing:${roomVersion}" - implementation 'androidx.preference:preference:1.1.0-rc01' + implementation 'androidx.preference:preference:1.1.0' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" // KTX @@ -172,9 +175,9 @@ dependencies { implementation "com.fasterxml.jackson.module:jackson-module-kotlin:2.9.6" implementation 'com.github.jasminb:jsonapi-converter:0.9' - implementation 'com.squareup.okhttp3:logging-interceptor:4.1.0' - implementation 'com.squareup.retrofit2:retrofit:2.6.1' - implementation 'com.squareup.retrofit2:converter-jackson:2.6.1' + implementation 'com.squareup.okhttp3:logging-interceptor:4.2.1' + implementation 'com.squareup.retrofit2:retrofit:2.6.2' + implementation 'com.squareup.retrofit2:converter-jackson:2.6.2' // Cards Shimmer Animation implementation 'com.facebook.shimmer:shimmer:0.5.0' @@ -182,14 +185,14 @@ dependencies { // RxJava implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxkotlin:2.4.0' - implementation 'io.reactivex.rxjava2:rxjava:2.2.11' - implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.1' + implementation 'io.reactivex.rxjava2:rxjava:2.2.12' + implementation 'com.squareup.retrofit2:adapter-rxjava2:2.6.2' // Picasso implementation 'com.squareup.picasso:picasso:2.71828' // Stripe - implementation 'com.stripe:stripe-android:10.3.0' + implementation 'com.stripe:stripe-android:11.1.4' // QR Code implementation 'com.journeyapps:zxing-android-embedded:3.6.0' @@ -203,7 +206,7 @@ dependencies { implementation "org.jetbrains.anko:anko-design:$anko_version" //Mapbox java sdk - implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:4.8.0' + implementation 'com.mapbox.mapboxsdk:mapbox-sdk-services:4.9.0' // SimpleCropView implementation 'com.isseiaoki:simplecropview:1.1.8' @@ -215,7 +218,7 @@ dependencies { testImplementation 'com.github.iamareebjamal:stetho-noop:1.2.1' //LeakCanary - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-2' + debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.0-beta-3' // Paging implementation "androidx.paging:paging-runtime:$paging_version" @@ -231,11 +234,13 @@ dependencies { implementation 'net.cachapa.expandablelayout:expandablelayout:2.9.2' + //PayPal + implementation 'com.paypal.sdk:paypal-android-sdk:2.16.0' testImplementation 'junit:junit:4.12' testImplementation 'org.threeten:threetenbp:1.4.0' testImplementation "org.koin:koin-test:$koin_version" - testImplementation 'androidx.arch.core:core-testing:2.0.1' + testImplementation 'androidx.arch.core:core-testing:2.1.0' androidTestImplementation 'androidx.test:runner:1.2.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' } diff --git a/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/9.json b/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/9.json index 72ca4cf89e..861f489e6f 100644 --- a/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/9.json +++ b/app/schemas/org.fossasia.openevent.general.OpenEventDatabase/9.json @@ -2,7 +2,7 @@ "formatVersion": 1, "database": { "version": 9, - "identityHash": "dfbd417bf9235824cb59ac1a0a03fa1f", + "identityHash": "ceaeb211922de9668d95362a86917dac", "entities": [ { "tableName": "Event", @@ -701,7 +701,7 @@ }, { "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, `jobTitle` TEXT, `phone` TEXT, `taxBusinessInfo` TEXT, `billingAddress` TEXT, `homeAddress` TEXT, `shippingAddress` TEXT, `company` TEXT, `workAddress` TEXT, `workPhone` TEXT, `website` TEXT, `blog` TEXT, `github` TEXT, `facebook` TEXT, `twitter` TEXT, `gender` TEXT, `isCheckedIn` INTEGER, `checkinTimes` TEXT, `isCheckedOut` INTEGER NOT NULL, `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 )", + "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, `jobTitle` TEXT, `phone` TEXT, `taxBusinessInfo` TEXT, `billingAddress` TEXT, `homeAddress` TEXT, `shippingAddress` TEXT, `company` TEXT, `workAddress` TEXT, `workPhone` TEXT, `website` TEXT, `blog` TEXT, `github` TEXT, `facebook` TEXT, `twitter` TEXT, `gender` TEXT, `isCheckedIn` INTEGER, `checkinTimes` TEXT, `isCheckedOut` INTEGER NOT NULL, `pdfUrl` TEXT, `ticketId` TEXT, `checkedIn` TEXT, `checkedOut` 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", @@ -871,6 +871,18 @@ "affinity": "TEXT", "notNull": false }, + { + "fieldPath": "checkedIn", + "columnName": "checkedIn", + "affinity": "TEXT", + "notNull": false + }, + { + "fieldPath": "checkedOut", + "columnName": "checkedOut", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "event", "columnName": "event", @@ -994,7 +1006,7 @@ }, { "tableName": "Order", - "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, `isBillingEnabled` INTEGER NOT NULL, `taxBusinessInfo` TEXT, `company` TEXT, `event` INTEGER, `attendees` TEXT NOT NULL, PRIMARY KEY(`id`))", + "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, `state` TEXT, `city` TEXT, `address` TEXT, `createdAt` TEXT, `zipcode` TEXT, `paidVia` TEXT, `discountCodeId` TEXT, `ticketsPdfUrl` TEXT, `transactionId` TEXT, `isBillingEnabled` INTEGER NOT NULL, `taxBusinessInfo` TEXT, `company` TEXT, `isExpired` INTEGER, `event` INTEGER, `attendees` TEXT NOT NULL, PRIMARY KEY(`id`))", "fields": [ { "fieldPath": "id", @@ -1044,6 +1056,12 @@ "affinity": "TEXT", "notNull": false }, + { + "fieldPath": "state", + "columnName": "state", + "affinity": "TEXT", + "notNull": false + }, { "fieldPath": "city", "columnName": "city", @@ -1110,6 +1128,12 @@ "affinity": "TEXT", "notNull": false }, + { + "fieldPath": "isExpired", + "columnName": "isExpired", + "affinity": "INTEGER", + "notNull": false + }, { "fieldPath": "event", "columnName": "event", @@ -2211,7 +2235,7 @@ "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, 'dfbd417bf9235824cb59ac1a0a03fa1f')" + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ceaeb211922de9668d95362a86917dac')" ] } } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d4a7e94378..3933bce40a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -37,6 +37,13 @@ + + + + (json) + } + + @TypeConverter + fun toJson(attendee: Attendee?) = ObjectMapper().writeValueAsString(attendee) +} diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeFragment.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeFragment.kt index d9215c2713..b0ba3f139b 100644 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeFragment.kt @@ -1,7 +1,9 @@ package org.fossasia.openevent.general.attendees import androidx.appcompat.app.AlertDialog +import android.app.Activity import android.content.Context +import android.content.Intent import android.os.Bundle import android.os.CountDownTimer import android.telephony.TelephonyManager @@ -25,9 +27,16 @@ import androidx.recyclerview.widget.LinearLayoutManager import androidx.navigation.Navigation.findNavController import androidx.navigation.fragment.navArgs import com.stripe.android.Stripe -import com.stripe.android.TokenCallback import com.stripe.android.model.Card import com.stripe.android.model.Token +import com.paypal.android.sdk.payments.PayPalService +import com.paypal.android.sdk.payments.PayPalConfiguration +import com.paypal.android.sdk.payments.PayPalPayment +import com.paypal.android.sdk.payments.ShippingAddress +import com.paypal.android.sdk.payments.PaymentActivity +import com.paypal.android.sdk.payments.PaymentConfirmation +import com.stripe.android.ApiResultCallback +import java.math.BigDecimal import kotlinx.android.synthetic.main.fragment_attendee.view.cvc import kotlinx.android.synthetic.main.fragment_attendee.view.email import kotlinx.android.synthetic.main.fragment_attendee.view.firstName @@ -68,6 +77,7 @@ import kotlinx.android.synthetic.main.fragment_attendee.view.countryPicker import kotlinx.android.synthetic.main.fragment_attendee.view.billingInfoContainer import kotlinx.android.synthetic.main.fragment_attendee.view.billingCity import kotlinx.android.synthetic.main.fragment_attendee.view.billingCompany +import kotlinx.android.synthetic.main.fragment_attendee.view.billingState import kotlinx.android.synthetic.main.fragment_attendee.view.taxId import kotlinx.android.synthetic.main.fragment_attendee.view.billingAddress import kotlinx.android.synthetic.main.fragment_attendee.view.firstNameLayout @@ -125,6 +135,8 @@ import java.util.Calendar import java.util.Currency import kotlin.collections.ArrayList +private const val PAYPAL_REQUEST_CODE = 101 + class AttendeeFragment : Fragment(), ComplexBackPressFragment { private lateinit var rootView: View @@ -250,6 +262,7 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { super.onDestroy() if (this::timer.isInitialized) timer.cancel() + activity?.stopService(Intent(activity, PayPalService::class.java)) } override fun handleBackPress() { @@ -328,9 +341,9 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { rootView.ticketsRecycler.isNestedScrollingEnabled = false rootView.taxLayout.isVisible = safeArgs.taxAmount > 0f - rootView.taxPrice.text = "${safeArgs.currency}${"%.2f".format(safeArgs.taxAmount)}" + rootView.taxPrice.text = "${Currency.getInstance(safeArgs.currency).symbol}${"%.2f".format(safeArgs.taxAmount)}" rootView.totalAmountLayout.isVisible = safeArgs.amount > 0f - rootView.totalPrice.text = "${safeArgs.currency}${"%.2f".format(safeArgs.amount)}" + rootView.totalPrice.text = "${Currency.getInstance(safeArgs.currency).symbol}${"%.2f".format(safeArgs.amount)}" rootView.ticketTableDetails.setOnClickListener { attendeeViewModel.ticketDetailsVisible = !attendeeViewModel.ticketDetailsVisible @@ -615,6 +628,12 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { if (it && this::card.isInitialized) sendToken(card) }) + + attendeeViewModel.paypalOrderMade + .nonNull() + .observe(viewLifecycleOwner, Observer { + startPaypalPayment() + }) } private fun setupMonthOptions() { @@ -683,10 +702,6 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { private fun checkPaymentOptions(): Boolean = when (attendeeViewModel.selectedPaymentMode) { - PAYMENT_MODE_PAYPAL -> { - rootView.attendeeScrollView.longSnackbar(getString(R.string.paypal_payment_not_available)) - false - } PAYMENT_MODE_STRIPE -> { card = Card.create(rootView.cardNumber.text.toString(), attendeeViewModel.monthSelectedPosition, rootView.year.selectedItem.toString().toInt(), rootView.cvc.text.toString()) @@ -698,13 +713,63 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { true } } - PAYMENT_MODE_CHEQUE, PAYMENT_MODE_ONSITE, PAYMENT_MODE_FREE, PAYMENT_MODE_BANK -> true + PAYMENT_MODE_CHEQUE, PAYMENT_MODE_ONSITE, PAYMENT_MODE_FREE, PAYMENT_MODE_BANK, PAYMENT_MODE_PAYPAL -> true else -> { rootView.snackbar(getString(R.string.select_payment_option_message)) false } } + private fun startPaypalPayment() { + val paypalEnvironment = if (BuildConfig.DEBUG) PayPalConfiguration.ENVIRONMENT_SANDBOX + else PayPalConfiguration.ENVIRONMENT_PRODUCTION + val paypalConfig = PayPalConfiguration() + .environment(paypalEnvironment) + .clientId(BuildConfig.PAYPAL_CLIENT_ID) + val paypalIntent = Intent(activity, PaymentActivity::class.java) + paypalIntent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, paypalConfig) + activity?.startService(paypalIntent) + + val paypalPayment = paypalThingsToBuy(PayPalPayment.PAYMENT_INTENT_SALE) + val payeeEmail = attendeeViewModel.event.value?.paypalEmail ?: "" + paypalPayment.payeeEmail(payeeEmail) + addShippingAddress(paypalPayment) + val intent = Intent(activity, PaymentActivity::class.java) + intent.putExtra(PayPalService.EXTRA_PAYPAL_CONFIGURATION, paypalConfig) + intent.putExtra(PaymentActivity.EXTRA_PAYMENT, paypalPayment) + startActivityForResult(intent, PAYPAL_REQUEST_CODE) + } + + private fun addShippingAddress(paypalPayment: PayPalPayment) { + if (rootView.billingEnabledCheckbox.isChecked) { + val shippingAddress = ShippingAddress() + .recipientName("${rootView.firstName.text} ${rootView.lastName.text}") + .line1(rootView.billingAddress.text.toString()) + .city(rootView.billingCity.text.toString()) + .state(rootView.billingState.text.toString()) + .postalCode(rootView.billingPostalCode.text.toString()) + .countryCode(getCountryCodes(rootView.countryPicker.selectedItem.toString())) + paypalPayment.providedShippingAddress(shippingAddress) + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (requestCode == PAYPAL_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + val paymentConfirm = + data?.getParcelableExtra(PaymentActivity.EXTRA_RESULT_CONFIRMATION) + if (paymentConfirm != null) { + val paymentId = paymentConfirm.proofOfPayment.paymentId + attendeeViewModel.sendPaypalConfirm(paymentId) + } + } + } + + private fun paypalThingsToBuy(paymentIntent: String): PayPalPayment = + PayPalPayment(BigDecimal(safeArgs.amount.toString()), + Currency.getInstance(safeArgs.currency).currencyCode, + getString(R.string.tickets_for, attendeeViewModel.event.value?.name), paymentIntent) + private fun checkRequiredFields(): Boolean { val checkBasicInfo = rootView.firstName.checkEmpty(rootView.firstNameLayout) && rootView.lastName.checkEmpty(rootView.lastNameLayout) && @@ -771,11 +836,12 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { else PAYMENT_MODE_FREE val company = rootView.billingCompany.text.toString() val city = rootView.billingCity.text.toString() + val state = rootView.billingState.text.toString() val taxId = rootView.taxId.text.toString() val address = rootView.billingAddress.text.toString() val postalCode = rootView.billingPostalCode.text.toString() attendeeViewModel.createAttendees(attendees, country, company, taxId, address, - city, postalCode, paymentOption) + city, postalCode, state, paymentOption) } else { rootView.attendeeScrollView.longSnackbar(getString(R.string.invalid_email_address_message)) } @@ -805,8 +871,8 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { } private fun sendToken(card: Card) { - Stripe(requireContext()) - .createToken(card, BuildConfig.STRIPE_API_KEY, object : TokenCallback { + Stripe(requireContext(), BuildConfig.STRIPE_API_KEY) + .createToken(card, object : ApiResultCallback { override fun onSuccess(token: Token) { val charge = Charge(attendeeViewModel.getId().toInt(), token.id, null) attendeeViewModel.chargeOrder(charge) @@ -876,4 +942,11 @@ class AttendeeFragment : Fragment(), ComplexBackPressFragment { val countryIndex = countryCodes.indexOf(currentCountryCode.toUpperCase()) if (countryIndex != -1) rootView.countryPicker.setSelection(countryIndex) } + + private fun getCountryCodes(countryName: String): String { + val countryCodes = resources.getStringArray(R.array.country_code_arrays) + val countryList = resources.getStringArray(R.array.country_arrays) + val index = countryList.indexOf(countryName) + return countryCodes[index] + } } diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeId.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeId.kt deleted file mode 100644 index 2393543ad2..0000000000 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeId.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.fossasia.openevent.general.attendees - -import com.github.jasminb.jsonapi.LongIdHandler -import com.github.jasminb.jsonapi.annotations.Id -import com.github.jasminb.jsonapi.annotations.Type - -@Type("attendee") -data class AttendeeId( - @Id(LongIdHandler::class) - val id: Long -) diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeIdConverter.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeIdConverter.kt deleted file mode 100644 index 02497586fc..0000000000 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/AttendeeIdConverter.kt +++ /dev/null @@ -1,16 +0,0 @@ -package org.fossasia.openevent.general.attendees - -import androidx.room.TypeConverter - -class AttendeeIdConverter { - - @TypeConverter - fun fromAttendeeId(attendeeId: AttendeeId): Long { - return attendeeId.id - } - - @TypeConverter - fun toAttendeeId(id: Long): AttendeeId { - return AttendeeId(id) - } -} 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 f9a3e49ad9..b4ea404a16 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 @@ -42,7 +42,7 @@ const val PAYMENT_MODE_PAYPAL = "paypal" const val PAYMENT_MODE_STRIPE = "stripe" private const val ERRORS = "errors" private const val DETAIL = "detail" -private const val UNVERIFIED_USER = "unverified user" +private const val UNVERIFIED_USER = "unverified-user" private const val ORDER_EXPIRY_TIME = 15 class AttendeeViewModel( @@ -82,6 +82,8 @@ class AttendeeViewModel( val orderExpiryTime: LiveData = mutableOrderExpiryTime private val mutableRedirectToProfile = SingleLiveEvent() val redirectToProfile = mutableRedirectToProfile + private val mutablePaypalOrderMade = MutableLiveData() + val paypalOrderMade: LiveData = mutablePaypalOrderMade val attendees = ArrayList() private val attendeesForOrder = ArrayList() @@ -93,6 +95,7 @@ class AttendeeViewModel( private var addressForOrder: String = "" private var cityForOrder: String = "" private var postalCodeForOrder: String = "" + private var stateForOrder: String = "" private var createAttendeeIterations = 0 var orderIdentifier: String? = null @@ -169,7 +172,7 @@ class AttendeeViewModel( orderIdentifier = it.identifier.toString() }, { if (it is HttpException) { - if (ErrorUtils.getErrorDetails(it).detail?.contains(UNVERIFIED_USER, true) == true) { + if (ErrorUtils.getErrorDetails(it).code == UNVERIFIED_USER) { mutableRedirectToProfile.value = true } } @@ -211,6 +214,7 @@ class AttendeeViewModel( address: String, city: String, postalCode: String, + state: String, paymentMode: String ) { attendeesForOrder.clear() @@ -220,6 +224,7 @@ class AttendeeViewModel( addressForOrder = address cityForOrder = city postalCodeForOrder = postalCode + stateForOrder = state paymentModeForOrder = paymentMode var isAllDetailsFilled = true createAttendeeIterations = 0 @@ -270,15 +275,15 @@ class AttendeeViewModel( mutableMessage.value = resource.getString(R.string.order_fail_message) return } - val attendeeList = attendeesForOrder.map { AttendeeId(it.id) }.toList() val amount: Float = totalAmount.value ?: 0F if (amount <= 0) { paymentModeForOrder = PAYMENT_MODE_FREE } - order = order.copy(attendees = attendeeList, paymentMode = paymentModeForOrder, amount = amount) + order = order.copy(attendees = attendeesForOrder, paymentMode = paymentModeForOrder, amount = amount) if (billingEnabled) { order = order.copy(isBillingEnabled = true, company = companyForOrder, taxBusinessInfo = taxIdForOrder, - address = addressForOrder, city = cityForOrder, zipcode = postalCodeForOrder, country = countryForOrder) + address = addressForOrder, city = cityForOrder, zipcode = postalCodeForOrder, country = countryForOrder, + state = stateForOrder) } compositeDisposable += orderService.placeOrder(order) .withDefaultSchedulers() @@ -297,6 +302,11 @@ class AttendeeViewModel( PAYMENT_MODE_STRIPE -> { mutableStripeOrderMade.value = true } + PAYMENT_MODE_PAYPAL -> { + mutablePendingOrder.value = it + mutablePaypalOrderMade.value = true + mutableProgress.value = false + } else -> mutableMessage.value = resource.getString(R.string.order_success_message) } }, { @@ -307,6 +317,28 @@ class AttendeeViewModel( }) } + fun sendPaypalConfirm(paymentId: String) { + pendingOrder.value?.let { order -> + compositeDisposable += orderService.verifyPaypalPayment(order.identifier.toString(), paymentId) + .withDefaultSchedulers() + .doOnSubscribe { + mutableProgress.value = true + }.subscribe({ + if (it.status) { + confirmOrder = ConfirmOrder(order.id.toString(), ORDER_STATUS_COMPLETED) + confirmOrderStatus(order.identifier.toString(), confirmOrder) + } else { + mutableMessage.value = it.error + mutableProgress.value = false + } + }, { + Timber.e(it, "Error verifying paypal payment") + mutableMessage.value = resource.getString(R.string.error_making_paypal_payment_message) + mutableProgress.value = false + }) + } + } + private fun confirmOrderStatus(identifier: String, order: ConfirmOrder) { compositeDisposable += orderService.confirmOrder(identifier, order) .withDefaultSchedulers() @@ -336,12 +368,12 @@ class AttendeeViewModel( }) } - private fun deleteAttendees(attendeeIds: List?) { - attendeeIds?.forEach { attendeeId -> - compositeDisposable += attendeeService.deleteAttendee(attendeeId.id) + private fun deleteAttendees(attendees: List?) { + attendees?.forEach { attendee -> + compositeDisposable += attendeeService.deleteAttendee(attendee.id) .withDefaultSchedulers() .subscribe({ - Timber.d("Deleted attendee $attendeeId.id") + Timber.d("Deleted attendee ${attendee.id}") }, { Timber.d("Failed to delete attendee $it.id") }) diff --git a/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeIdConverter.kt b/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeConverter.kt similarity index 52% rename from app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeIdConverter.kt rename to app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeConverter.kt index 0f1bbcfaa5..3cb377b51a 100644 --- a/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeIdConverter.kt +++ b/app/src/main/java/org/fossasia/openevent/general/attendees/ListAttendeeConverter.kt @@ -5,18 +5,18 @@ import com.fasterxml.jackson.core.type.TypeReference import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper -class ListAttendeeIdConverter { +class ListAttendeeConverter { @TypeConverter - fun fromListAttendeeId(attendeeIdList: List): String { + fun fromListAttendee(attendees: List): String { val objectMapper = ObjectMapper() - return objectMapper.writeValueAsString(attendeeIdList) + return objectMapper.writeValueAsString(attendees) } @TypeConverter - fun toListAttendeeId(attendeeList: String): List { + fun toListAttendee(attendees: String): List { val objectMapper = jacksonObjectMapper() - val mapType = object : TypeReference>() {} - return objectMapper.readValue(attendeeList, mapType) + val mapType = object : TypeReference>() {} + return objectMapper.readValue(attendees, mapType) } } 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 baf0b84405..42a671e760 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 @@ -17,7 +17,6 @@ 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 import androidx.navigation.Navigation.findNavController @@ -39,16 +38,15 @@ import kotlinx.android.synthetic.main.dialog_edit_profile_image.view.replaceImag import kotlinx.android.synthetic.main.dialog_edit_profile_image.view.removeImage import kotlinx.android.synthetic.main.fragment_edit_profile.view.lastName import kotlinx.android.synthetic.main.fragment_edit_profile.view.profilePhoto -import kotlinx.android.synthetic.main.fragment_edit_profile.view.progressBar import kotlinx.android.synthetic.main.fragment_edit_profile.view.profilePhotoFab -import kotlinx.android.synthetic.main.fragment_edit_profile.view.firstNameLayout -import kotlinx.android.synthetic.main.fragment_edit_profile.view.lastNameLayout import org.fossasia.openevent.general.CircleTransform import org.fossasia.openevent.general.MainActivity import org.fossasia.openevent.general.R import org.fossasia.openevent.general.RotateBitmap import org.fossasia.openevent.general.ComplexBackPressFragment +import org.fossasia.openevent.general.utils.Utils.show import org.fossasia.openevent.general.utils.Utils.hideSoftKeyboard +import org.fossasia.openevent.general.utils.Utils.progressDialog import org.fossasia.openevent.general.utils.Utils.requireDrawable import org.fossasia.openevent.general.utils.extensions.nonNull import org.fossasia.openevent.general.utils.nullToEmpty @@ -61,7 +59,6 @@ import java.io.IOException import java.io.FileNotFoundException import org.fossasia.openevent.general.utils.Utils.setToolbar import org.fossasia.openevent.general.utils.emptyToNull -import org.fossasia.openevent.general.utils.setRequired import org.jetbrains.anko.design.snackbar class EditProfileFragment : Fragment(), ComplexBackPressFragment { @@ -109,10 +106,11 @@ class EditProfileFragment : Fragment(), ComplexBackPressFragment { val currentUser = editProfileViewModel.user.value if (currentUser == null) profileViewModel.getProfile() else loadUserUI(currentUser) + val progress = progressDialog(context) editProfileViewModel.progress .nonNull() .observe(viewLifecycleOwner, Observer { - rootView.progressBar.isVisible = it + progress.show(it) }) editProfileViewModel.getUpdatedTempFile() @@ -156,9 +154,6 @@ class EditProfileFragment : Fragment(), ComplexBackPressFragment { showEditPhotoDialog() } - rootView.firstNameLayout.setRequired() - rootView.lastNameLayout.setRequired() - return rootView } @@ -187,14 +182,6 @@ class EditProfileFragment : Fragment(), ComplexBackPressFragment { private fun isValidInput(): Boolean { var valid = true - if (rootView.firstName.text.isNullOrBlank()) { - rootView.firstName.error = getString(R.string.empty_field_error_message) - valid = false - } - if (rootView.lastName.text.isNullOrBlank()) { - rootView.lastName.error = getString(R.string.empty_field_error_message) - valid = false - } if (!rootView.instagram.text.isNullOrEmpty() && !Patterns.WEB_URL.matcher(rootView.instagram.text).matches()) { rootView.instagram.error = getString(R.string.invalid_url_message) valid = false 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 6ec6dd4cdb..80e70b4097 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 @@ -16,7 +16,6 @@ import org.fossasia.openevent.general.StartupViewModel import org.fossasia.openevent.general.about.AboutEventViewModel import org.fossasia.openevent.general.attendees.Attendee import org.fossasia.openevent.general.attendees.AttendeeApi -import org.fossasia.openevent.general.attendees.AttendeeId import org.fossasia.openevent.general.attendees.AttendeeService import org.fossasia.openevent.general.attendees.AttendeeViewModel import org.fossasia.openevent.general.attendees.forms.CustomForm @@ -226,7 +225,7 @@ val apiModule = module { factory { TicketService(get(), get(), get()) } factory { SocialLinksService(get(), get()) } factory { AttendeeService(get(), get(), get()) } - factory { OrderService(get(), get(), get()) } + factory { OrderService(get(), get(), get(), get(), get()) } factory { SessionService(get(), get()) } factory { NotificationService(get(), get()) } factory { FeedbackService(get(), get()) } @@ -313,7 +312,7 @@ val networkModule = module { objectMapper, Event::class.java, User::class.java, SignUp::class.java, Ticket::class.java, SocialLink::class.java, EventId::class.java, EventTopic::class.java, Attendee::class.java, TicketId::class.java, Order::class.java, - AttendeeId::class.java, Charge::class.java, Paypal::class.java, ConfirmOrder::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, FavoriteEvent::class.java, Session::class.java, SessionType::class.java, MicroLocation::class.java, SpeakersCall::class.java, 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 e733b396cd..084ed9dbcc 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 @@ -25,6 +25,9 @@ interface EventDao { @Query("SELECT * from Event WHERE id = :id") fun getEvent(id: Long): Flowable + @Query("SELECT * FROM Event WHERE id = :id") + fun getEventObjectById(id: Long): Event + @Query("SELECT * FROM event WHERE id = :eventId") fun getEventById(eventId: Long): 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 32b5cb342b..2e49a5df7c 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 @@ -87,7 +87,6 @@ import org.fossasia.openevent.general.utils.Utils.progressDialog import org.fossasia.openevent.general.utils.Utils.show import org.koin.androidx.viewmodel.ext.android.viewModel import timber.log.Timber -import java.util.Currency import org.fossasia.openevent.general.utils.Utils.setToolbar import org.fossasia.openevent.general.utils.extensions.setSharedElementEnterTransition import org.jetbrains.anko.design.longSnackbar @@ -683,7 +682,7 @@ class EventDetailsFragment : Fragment() { } private fun loadTicketFragment() { - val currency = Currency.getInstance(currentEvent?.paymentCurrency ?: "USD").symbol + val currency = currentEvent?.paymentCurrency ?: "USD" currentEvent?.let { findNavController(rootView).navigate(EventDetailsFragmentDirections .actionEventDetailsToTickets(it.id, currency)) 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 4ad43c1db0..d562d0c6fa 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 @@ -148,7 +148,7 @@ class EventService( } fun removeFavorite(favoriteEvent: FavoriteEvent, event: Event): Completable = - favoriteEventApi.removeFavorite(favoriteEvent.id).andThen { + favoriteEventApi.removeFavorite(event.id).andThen { event.favorite = false event.favoriteEventId = null eventDao.insertEvent(event) diff --git a/app/src/main/java/org/fossasia/openevent/general/order/ExpiredOrderFragment.kt b/app/src/main/java/org/fossasia/openevent/general/order/ExpiredOrderFragment.kt index e0d41b5400..21a6285df9 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/ExpiredOrderFragment.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/ExpiredOrderFragment.kt @@ -1,5 +1,6 @@ package org.fossasia.openevent.general.order +import android.graphics.Color import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -22,6 +23,7 @@ import kotlinx.android.synthetic.main.fragment_expired_order.view.noTicketsScree import kotlinx.android.synthetic.main.fragment_expired_order.view.shimmerSearch import kotlinx.android.synthetic.main.fragment_expired_order.view.filterToolbar import kotlinx.android.synthetic.main.fragment_expired_order.view.toolbar +import kotlinx.android.synthetic.main.fragment_expired_order.view.swipeRefresh import org.fossasia.openevent.general.R import org.fossasia.openevent.general.utils.Utils.setToolbar import org.fossasia.openevent.general.utils.extensions.nonNull @@ -57,7 +59,7 @@ class ExpiredOrderFragment : Fragment() { showNoInternetScreen(false) } else { rootView.shimmerSearch.stopShimmer() - showNoTicketsScreen(ordersPagedListAdapter.currentList?.isEmpty() ?: true) + rootView.swipeRefresh.isRefreshing = false } }) @@ -76,20 +78,32 @@ class ExpiredOrderFragment : Fragment() { showNoTicketsScreen(currentItems.size == 0) ordersPagedListAdapter.submitList(currentItems) } else { - if (isConnected) { - ordersUnderUserVM.getOrdersAndEventsOfUser(true) - } else { - showNoInternetScreen(true) - } + ordersUnderUserVM.getOrdersAndEventsOfUser(true, true) } }) + ordersUnderUserVM.numOfTickets + .nonNull() + .observe(viewLifecycleOwner, Observer { + showNoTicketsScreen(it == 0 && !rootView.shimmerSearch.isVisible) + }) + ordersUnderUserVM.eventAndOrderPaged .nonNull() .observe(viewLifecycleOwner, Observer { ordersPagedListAdapter.submitList(it) }) + rootView.swipeRefresh.setColorSchemeColors(Color.BLUE) + rootView.swipeRefresh.setOnRefreshListener { + if (ordersUnderUserVM.isConnected()) { + ordersUnderUserVM.clearOrders() + ordersUnderUserVM.getOrdersAndEventsOfUser(showExpired = true, fromDb = false) + } else { + rootView.swipeRefresh.isRefreshing = false + } + } + return rootView } @@ -134,7 +148,7 @@ class ExpiredOrderFragment : Fragment() { ordersUnderUserVM.clearOrders() ordersPagedListAdapter.clear() if (ordersUnderUserVM.isConnected()) { - ordersUnderUserVM.getOrdersAndEventsOfUser(true) + ordersUnderUserVM.getOrdersAndEventsOfUser(true, true) } else { showNoInternetScreen(true) } 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 dc8913a78c..4cc17d9f44 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 @@ -10,7 +10,7 @@ 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.AttendeeId +import org.fossasia.openevent.general.attendees.Attendee import org.fossasia.openevent.general.event.EventId @Type("order") @@ -28,6 +28,7 @@ data class Order( val identifier: String? = null, val orderNotes: String? = null, val completedAt: String? = null, + val state: String? = null, val city: String? = null, val address: String? = null, val createdAt: String? = null, @@ -39,9 +40,10 @@ data class Order( val isBillingEnabled: Boolean = false, val taxBusinessInfo: String? = null, val company: String? = null, + var isExpired: Boolean? = null, @ColumnInfo(index = true) @Relationship("event") var event: EventId? = null, @Relationship("attendees") - var attendees: List = emptyList() + var attendees: List = emptyList() ) diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderApi.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderApi.kt index 54750b4f36..0438bfab10 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderApi.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderApi.kt @@ -1,7 +1,6 @@ package org.fossasia.openevent.general.order import io.reactivex.Single -import org.fossasia.openevent.general.attendees.Attendee import retrofit2.http.Body import retrofit2.http.GET import retrofit2.http.PATCH @@ -24,14 +23,11 @@ interface OrderApi { "\"placed\",\"pending\"]}]&include=event,attendees&fields[attendees]=id") fun ordersUnderUser(@Path("userId") userId: Long): Single> - @GET("/v1/users/{userId}/orders?include=event,attendees&fields[attendees]=id") + @GET("/v1/users/{userId}/orders?include=event,attendees") fun ordersUnderUserPaged( @Path("userId") userId: Long, @Query("filter") filter: String, @Query("page[number]") page: Int, @Query("page[size]") pageSize: Int = 5 ): Single> - - @GET("/v1/orders/{orderIdentifier}/attendees") - fun attendeesUnderOrder(@Path("orderIdentifier") orderIdentifier: String): Single> } 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 2214378142..5579ec1fe3 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 @@ -17,6 +17,9 @@ interface OrderDao { @Query("SELECT * FROM `order`") fun getAllOrders(): Single> + @Query("SELECT * FROM `order` WHERE isExpired = :expired") + fun getOrders(expired: Boolean): Single> + @Query("DELETE FROM `order`") fun deleteAllOrders() diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSource.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSource.kt index 8dc669941d..280dbeb18f 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSource.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSource.kt @@ -6,6 +6,9 @@ import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable import io.reactivex.rxkotlin.plusAssign import org.fossasia.openevent.general.R +import org.fossasia.openevent.general.attendees.ORDER_STATUS_COMPLETED +import org.fossasia.openevent.general.attendees.ORDER_STATUS_PENDING +import org.fossasia.openevent.general.attendees.ORDER_STATUS_PLACED import org.fossasia.openevent.general.common.SingleLiveEvent import org.fossasia.openevent.general.data.Resource import org.fossasia.openevent.general.event.Event @@ -25,12 +28,16 @@ class OrderDataSource( private val mutableProgress: MutableLiveData, private val mutableNumOfTickets: MutableLiveData, private val mutableMessage: SingleLiveEvent, - private val filter: OrderFilter + private val filter: OrderFilter, + private val fromDb: Boolean ) : PageKeyedDataSource>() { private val resource = Resource() override fun loadInitial(params: LoadInitialParams, callback: LoadInitialCallback>) { - createObservable(1, 2, callback, null) + if (fromDb) + getOrdersFromDb(callback) + else + createObservable(1, 2, callback, null) } override fun loadAfter(params: LoadParams, callback: LoadCallback>) { @@ -43,6 +50,31 @@ class OrderDataSource( createObservable(page, page - 1, null, callback) } + private fun getOrdersFromDb(callback: LoadInitialCallback>) { + compositeDisposable += orderService.getOrderAndEventSourceFactoryFromDb(showExpired) + .withDefaultSchedulers() + .subscribe({ + val filteredList = it.filterNotNull().filter { + if (filter.isShowingCompletedOrders) true else it.second.status != ORDER_STATUS_COMPLETED + }.filter { + if (filter.isShowingPendingOrders) true else it.second.status != ORDER_STATUS_PENDING + }.filter { + if (filter.isShowingPlacedOrders) true else it.second.status != ORDER_STATUS_PLACED + } + if (filteredList.isEmpty()) { + createObservable(1, 2, callback, null) + } else { + mutableNumOfTickets.value = filteredList.size + callback.onResult(filteredList, null, null) + mutableProgress.value = false + } + }, { + mutableMessage.value = resource.getString(R.string.list_events_fail_message) + Timber.e(it, "Fail on fetching orders ") + mutableProgress.value = false + }) + } + private fun createObservable( requestedPage: Int, adjacentPage: Int, @@ -100,13 +132,13 @@ class OrderDataSource( | 'name':'event', | 'op':'has', | 'val': { - | 'name':'starts-at', + | 'name':'ends-at', | 'op':'$operator', | 'val':'%${EventUtils.getTimeInISO8601(Date())}%' | } | }] |}]""".trimMargin().replace("'", "\"") - val ordersList = orderService.getOrdersOfUserPaged(userId, ordersQuery, page) + val ordersList = orderService.getOrdersOfUserPaged(userId, ordersQuery, page, showExpired) return ordersList.flatMap { orders -> val ids = orders.map { it.event?.id }.distinct() val eventsQuery = """[{ diff --git a/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSourceFactory.kt b/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSourceFactory.kt index b4999e3446..db481f61f5 100644 --- a/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSourceFactory.kt +++ b/app/src/main/java/org/fossasia/openevent/general/order/OrderDataSourceFactory.kt @@ -16,7 +16,8 @@ class OrderDataSourceFactory( private val mutableNumOfTicket: MutableLiveData, private val mutableMessage: SingleLiveEvent, private val userId: Long, - private val orderFilter: OrderFilter + private val orderFilter: OrderFilter, + private val fromDb: Boolean ) : DataSource.Factory>() { override fun create(): DataSource> { return OrderDataSource( @@ -28,7 +29,8 @@ class OrderDataSourceFactory( mutableProgress, mutableNumOfTicket, mutableMessage, - orderFilter + orderFilter, + fromDb ) } } 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 7d26ba2078..db7f630536 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 @@ -15,7 +15,6 @@ import android.view.View import android.view.ViewGroup import android.view.Menu import android.view.MenuInflater -import android.widget.Toast import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.fragment.app.Fragment @@ -38,7 +37,6 @@ import org.fossasia.openevent.general.utils.Utils.progressDialog import org.fossasia.openevent.general.utils.Utils.show import org.fossasia.openevent.general.utils.extensions.nonNull import org.koin.androidx.viewmodel.ext.android.viewModel -import timber.log.Timber import org.fossasia.openevent.general.utils.Utils.setToolbar import org.jetbrains.anko.design.longSnackbar import org.jetbrains.anko.design.snackbar @@ -60,28 +58,6 @@ class OrderDetailsFragment : Fragment() { super.onCreate(savedInstanceState) ordersRecyclerAdapter.setOrderIdentifier(safeArgs.orderIdentifier) - - orderDetailsViewModel.event - .nonNull() - .observe(this, Observer { - ordersRecyclerAdapter.setEvent(it) - Picasso.get() - .load(it.originalImageUrl) - .error(R.drawable.header) - .placeholder(R.drawable.header) - .into(rootView.backgroundImage) - }) - - orderDetailsViewModel.attendees - .nonNull() - .observe(this, Observer { - if (it.isEmpty()) { - Toast.makeText(context, getString(R.string.error_fetching_attendees), Toast.LENGTH_SHORT).show() - activity?.onBackPressed() - } - ordersRecyclerAdapter.addAll(it) - Timber.d("Fetched attendees of size %s", ordersRecyclerAdapter.itemCount) - }) } override fun onCreateView( @@ -144,8 +120,40 @@ class OrderDetailsFragment : Fragment() { rootView.orderDetailCoordinatorLayout.longSnackbar(it) }) - orderDetailsViewModel.loadEvent(safeArgs.eventId) - orderDetailsViewModel.loadAttendeeDetails(safeArgs.orderId) + orderDetailsViewModel.event + .nonNull() + .observe(viewLifecycleOwner, Observer { + ordersRecyclerAdapter.setEvent(it) + Picasso.get() + .load(it.originalImageUrl) + .error(R.drawable.header) + .placeholder(R.drawable.header) + .into(rootView.backgroundImage) + }) + + orderDetailsViewModel.attendees + .nonNull() + .observe(viewLifecycleOwner, Observer { + ordersRecyclerAdapter.addAll(it) + }) + + val currentEvent = orderDetailsViewModel.event.value + if (currentEvent == null) { + orderDetailsViewModel.loadEvent(safeArgs.eventId) + } else { + ordersRecyclerAdapter.setEvent(currentEvent) + Picasso.get() + .load(currentEvent.originalImageUrl) + .error(R.drawable.header) + .placeholder(R.drawable.header) + .into(rootView.backgroundImage) + } + + val currentAttendees = orderDetailsViewModel.attendees.value + if (currentAttendees == null) + orderDetailsViewModel.loadAttendeeDetails(safeArgs.orderId) + else + ordersRecyclerAdapter.addAll(currentAttendees) writePermissionGranted = (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED) 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 fd0826f1a5..14cc92ed07 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 @@ -5,14 +5,11 @@ import android.content.Intent import android.net.Uri import android.provider.CalendarContract import android.view.View -import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import android.widget.FrameLayout import androidx.core.view.isVisible import androidx.recyclerview.widget.RecyclerView import kotlinx.android.synthetic.main.item_card_order_details.view.calendar import kotlinx.android.synthetic.main.item_card_order_details.view.eventDetails import kotlinx.android.synthetic.main.item_card_order_details.view.map -import kotlinx.android.synthetic.main.item_card_order_details.view.mainLayout import kotlinx.android.synthetic.main.item_card_order_details.view.qrCodeView import kotlinx.android.synthetic.main.item_card_order_details.view.downloadButton import kotlinx.android.synthetic.main.item_card_order_details.view.checkedInLayout @@ -57,18 +54,6 @@ class OrderDetailsViewHolder(private val binding: ItemCardOrderDetailsBinding) : identifier = ticketIdentifier } - if (position == 0) { - val params: FrameLayout.LayoutParams = - FrameLayout.LayoutParams(resources.getDimension(R.dimen.ticket_width).toInt(), MATCH_PARENT) - params.leftMargin = resources.getDimension(R.dimen.layout_margin_large).toInt() - itemView.mainLayout.layoutParams = params - } else if (position + 1 == count) { - val params: FrameLayout.LayoutParams = - FrameLayout.LayoutParams(resources.getDimension(R.dimen.ticket_width).toInt(), MATCH_PARENT) - params.rightMargin = resources.getDimension(R.dimen.layout_margin_large).toInt() - itemView.mainLayout.layoutParams = params - } - if (attendee.isCheckedIn != null) { itemView.checkedInLayout.isVisible = attendee.isCheckedIn itemView.notCheckedInLayout.isVisible = !attendee.isCheckedIn 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 f7f2176cb9..58ca14f9c6 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 @@ -57,7 +57,7 @@ class OrderDetailsViewModel( compositeDisposable += orderService .getOrderById(orderId) .flatMap { order -> - orderService.getAttendeesUnderOrder(order.identifier ?: "", order.attendees.map { it.id }) + orderService.getAttendeesUnderOrder(order.attendees.map { it.id }) } .withDefaultSchedulers() .doOnSubscribe { @@ -66,6 +66,7 @@ class OrderDetailsViewModel( mutableProgress.value = false }.subscribe({ mutableAttendees.value = it + Timber.d("Fetched attendees of size %s", it) }, { Timber.e(it, "Error fetching attendee details") 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 d27443ebb7..d0bd867f6a 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 @@ -3,12 +3,34 @@ package org.fossasia.openevent.general.order import io.reactivex.Single import org.fossasia.openevent.general.attendees.Attendee import org.fossasia.openevent.general.attendees.AttendeeDao +import org.fossasia.openevent.general.paypal.Paypal +import org.fossasia.openevent.general.paypal.PaypalApi +import org.fossasia.openevent.general.paypal.PaypalPaymentResponse +import org.fossasia.openevent.general.event.Event +import org.fossasia.openevent.general.event.EventDao class OrderService( private val orderApi: OrderApi, private val orderDao: OrderDao, - private val attendeeDao: AttendeeDao + private val attendeeDao: AttendeeDao, + private val paypalApi: PaypalApi, + private val eventDao: EventDao ) { + fun verifyPaypalPayment(orderIdentifier: String, paymentId: String): Single = + paypalApi.verifyPaypalPayment(orderIdentifier, Paypal(paymentId = paymentId)) + + fun getOrderAndEventSourceFactoryFromDb(showExpired: Boolean): Single?>> { + return orderDao.getOrders(showExpired) + .map { + it.map { order -> + order.event?.id?.let { eventId -> + val event = eventDao.getEventObjectById(eventId) + Pair(event, order) + } + } + } + } + fun placeOrder(order: Order): Single { return orderApi.placeOrder(order) .map { order -> @@ -40,10 +62,15 @@ class OrderService( } } - fun getOrdersOfUserPaged(userId: Long, query: String, page: Int): Single> { + fun getOrdersOfUserPaged(userId: Long, query: String, page: Int, isExpired: Boolean): Single> { return orderApi.ordersUnderUserPaged(userId, query, page).map { - orderDao.insertOrders(it) - it + val updatedOrdersList = it.map { + it.isExpired = isExpired + it + } + orderDao.insertOrders(updatedOrdersList) + attendeeDao.insertAttendees(updatedOrdersList.map { it.attendees }.flatten()) + updatedOrdersList } } @@ -51,13 +78,7 @@ class OrderService( return orderDao.getOrderById(orderId) } - fun getAttendeesUnderOrder(orderIdentifier: String, attendeesIds: List): Single> { - return orderApi.attendeesUnderOrder(orderIdentifier) - .map { - attendeeDao.insertAttendees(it) - it - }.onErrorResumeNext { - attendeeDao.getAttendeesWithIds(attendeesIds) - } + fun getAttendeesUnderOrder(attendeesIds: List): Single> { + return attendeeDao.getAttendeesWithIds(attendeesIds) } } 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 fb600b5886..aabd441697 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 @@ -1,5 +1,6 @@ package org.fossasia.openevent.general.order +import android.graphics.Color import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem @@ -23,6 +24,7 @@ import kotlinx.android.synthetic.main.fragment_orders_under_user.view.findMyTick import kotlinx.android.synthetic.main.fragment_orders_under_user.view.noTicketsScreen import kotlinx.android.synthetic.main.fragment_orders_under_user.view.ordersRecycler import kotlinx.android.synthetic.main.fragment_orders_under_user.view.shimmerSearch +import kotlinx.android.synthetic.main.fragment_orders_under_user.view.swipeRefresh import kotlinx.android.synthetic.main.fragment_orders_under_user.view.scrollView import kotlinx.android.synthetic.main.fragment_orders_under_user.view.pastEvent import kotlinx.android.synthetic.main.fragment_orders_under_user.view.ticketsNumber @@ -80,11 +82,7 @@ class OrdersUnderUserFragment : Fragment(), BottomIconDoubleClick { showNoTicketsScreen(currentItems.size == 0) ordersPagedListAdapter.submitList(currentItems) } else { - if (isConnected) { - ordersUnderUserVM.getOrdersAndEventsOfUser(false) - } else { - showNoInternetScreen(true) - } + ordersUnderUserVM.getOrdersAndEventsOfUser(showExpired = false, fromDb = true) } }) @@ -92,6 +90,7 @@ class OrdersUnderUserFragment : Fragment(), BottomIconDoubleClick { .nonNull() .observe(viewLifecycleOwner, Observer { rootView.ticketsNumber.text = resources.getQuantityString(R.plurals.numOfOrders, it, it) + showNoTicketsScreen(it == 0 && !rootView.shimmerSearch.isVisible) }) ordersUnderUserVM.showShimmerResults @@ -103,7 +102,7 @@ class OrdersUnderUserFragment : Fragment(), BottomIconDoubleClick { showNoInternetScreen(false) } else { rootView.shimmerSearch.stopShimmer() - showNoTicketsScreen(ordersPagedListAdapter.currentList?.isEmpty() ?: true) + rootView.swipeRefresh.isRefreshing = false } rootView.shimmerSearch.isVisible = it }) @@ -120,6 +119,16 @@ class OrdersUnderUserFragment : Fragment(), BottomIconDoubleClick { ordersPagedListAdapter.submitList(it) }) + rootView.swipeRefresh.setColorSchemeColors(Color.BLUE) + rootView.swipeRefresh.setOnRefreshListener { + if (ordersUnderUserVM.isConnected()) { + ordersUnderUserVM.clearOrders() + ordersUnderUserVM.getOrdersAndEventsOfUser(showExpired = false, fromDb = false) + } else { + rootView.swipeRefresh.isRefreshing = false + } + } + return rootView } @@ -182,7 +191,7 @@ class OrdersUnderUserFragment : Fragment(), BottomIconDoubleClick { ordersUnderUserVM.clearOrders() ordersPagedListAdapter.clear() if (ordersUnderUserVM.isConnected()) { - ordersUnderUserVM.getOrdersAndEventsOfUser(false) + ordersUnderUserVM.getOrdersAndEventsOfUser(showExpired = false, fromDb = false) } else { showNoInternetScreen(true) } @@ -190,6 +199,7 @@ class OrdersUnderUserFragment : Fragment(), BottomIconDoubleClick { override fun onDestroyView() { super.onDestroyView() + rootView.swipeRefresh.setOnRefreshListener(null) ordersPagedListAdapter.setListener(null) } 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 f9e02bb32c..739dd616e3 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 @@ -43,7 +43,7 @@ class OrdersUnderUserViewModel( fun isLoggedIn() = authHolder.isLoggedIn() - fun getOrdersAndEventsOfUser(showExpired: Boolean) { + fun getOrdersAndEventsOfUser(showExpired: Boolean, fromDb: Boolean) { val sourceFactory = OrderDataSourceFactory( orderService, @@ -54,7 +54,8 @@ class OrdersUnderUserViewModel( mutableNumOfTickets, mutableMessage, getId(), - filter + filter, + fromDb ) val ordersAndEventsPagedList = RxPagedListBuilder(sourceFactory, config) diff --git a/app/src/main/java/org/fossasia/openevent/general/paypal/Paypal.kt b/app/src/main/java/org/fossasia/openevent/general/paypal/Paypal.kt index 6404569514..6cfbb83312 100644 --- a/app/src/main/java/org/fossasia/openevent/general/paypal/Paypal.kt +++ b/app/src/main/java/org/fossasia/openevent/general/paypal/Paypal.kt @@ -1,13 +1,14 @@ package org.fossasia.openevent.general.paypal -import com.github.jasminb.jsonapi.IntegerIdHandler +import com.fasterxml.jackson.databind.PropertyNamingStrategy +import com.fasterxml.jackson.databind.annotation.JsonNaming import com.github.jasminb.jsonapi.annotations.Id import com.github.jasminb.jsonapi.annotations.Type -@Type("paypal-payment") -data class Paypal( - @Id(IntegerIdHandler::class) - val id: Int, - val cancelUrl: String? = null, - val returnUrl: String? = null +@Type("order") +@JsonNaming(PropertyNamingStrategy.KebabCaseStrategy::class) +class Paypal( + @Id + val id: Int? = null, + val paymentId: String ) diff --git a/app/src/main/java/org/fossasia/openevent/general/paypal/PaypalApi.kt b/app/src/main/java/org/fossasia/openevent/general/paypal/PaypalApi.kt index 6513a4d771..1608d536c7 100644 --- a/app/src/main/java/org/fossasia/openevent/general/paypal/PaypalApi.kt +++ b/app/src/main/java/org/fossasia/openevent/general/paypal/PaypalApi.kt @@ -1,11 +1,15 @@ package org.fossasia.openevent.general.paypal +import io.reactivex.Single import retrofit2.http.Body import retrofit2.http.POST import retrofit2.http.Path interface PaypalApi { - @POST("orders/{orderIdentifier}/create-paypal-payment") - fun createPaypalPayment(@Path("orderIdentifier") orderIdentifier: String, @Body paypal: Paypal) + @POST("orders/{orderIdentifier}/verify-mobile-paypal-payment") + fun verifyPaypalPayment( + @Path("orderIdentifier") orderIdentifier: String, + @Body paypal: Paypal + ): Single } diff --git a/app/src/main/java/org/fossasia/openevent/general/paypal/CreatePaypalPaymentResponse.kt b/app/src/main/java/org/fossasia/openevent/general/paypal/PaypalPaymentResponse.kt similarity index 74% rename from app/src/main/java/org/fossasia/openevent/general/paypal/CreatePaypalPaymentResponse.kt rename to app/src/main/java/org/fossasia/openevent/general/paypal/PaypalPaymentResponse.kt index 371fed4f4f..e7abd4d5e4 100644 --- a/app/src/main/java/org/fossasia/openevent/general/paypal/CreatePaypalPaymentResponse.kt +++ b/app/src/main/java/org/fossasia/openevent/general/paypal/PaypalPaymentResponse.kt @@ -4,8 +4,7 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategy import com.fasterxml.jackson.databind.annotation.JsonNaming @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) -data class CreatePaypalPaymentResponse( +data class PaypalPaymentResponse( val status: Boolean, - val paymentId: String, - val error: String + val error: String? = null ) 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 0ebe9cff88..0e2214911d 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 @@ -46,8 +46,7 @@ import org.fossasia.openevent.general.utils.extensions.nonNull const val LOCAL_TIMEZONE = "localTimeZone" class SettingsFragment : PreferenceFragmentCompat(), PreferenceChangeListener { - private val FORM_LINK: String = "https://docs.google.com/forms/d/e/" + - "1FAIpQLSd7Y1T1xoXeYaAG_b6Tu1YYK-jZssoC5ltmQbkUX0kmDZaKYw/viewform" + private val PRIVACY_LINK: String = "https://eventyay.com/privacy-policy" private val TERMS_OF_SERVICE_LINK: String = "https://eventyay.com/terms" private val REFUND_POLICY_LINK: String = "https://eventyay.com/refunds" @@ -125,11 +124,7 @@ class SettingsFragment : PreferenceFragmentCompat(), PreferenceChangeListener { startAppPlayStore(activity?.packageName.nullToEmpty()) return true } - if (preference?.key == getString(R.string.key_suggestion)) { - // Links to suggestion form - Utils.openUrl(requireContext(), FORM_LINK) - return true - } + if (preference?.key == getString(R.string.key_profile)) { showLogoutDialog() return true 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 7b9cae6a6e..727b76a359 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 @@ -42,6 +42,8 @@ import org.fossasia.openevent.general.utils.extensions.nonNull import org.koin.androidx.viewmodel.ext.android.viewModel import org.fossasia.openevent.general.utils.Utils.setToolbar import org.jetbrains.anko.design.longSnackbar +import java.util.Currency +import kotlin.collections.ArrayList const val TICKETS_FRAGMENT = "ticketsFragment" const val APPLY_DISCOUNT_CODE = 1 @@ -58,7 +60,7 @@ class TicketsFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - ticketsRecyclerAdapter.setCurrency(safeArgs.currency) + ticketsRecyclerAdapter.setCurrency(Currency.getInstance(safeArgs.currency).symbol) } override fun onCreateView( diff --git a/app/src/main/java/org/fossasia/openevent/general/utils/Error.kt b/app/src/main/java/org/fossasia/openevent/general/utils/Error.kt index e6ce71fb8d..aa7a96bf46 100644 --- a/app/src/main/java/org/fossasia/openevent/general/utils/Error.kt +++ b/app/src/main/java/org/fossasia/openevent/general/utils/Error.kt @@ -7,6 +7,7 @@ class Error { var title: String? = null var detail: String? = null var pointer: String? = null + var code: String? = null override fun toString(): String { diff --git a/app/src/main/java/org/fossasia/openevent/general/utils/ErrorUtils.kt b/app/src/main/java/org/fossasia/openevent/general/utils/ErrorUtils.kt index 7a7a672052..b946ee8693 100644 --- a/app/src/main/java/org/fossasia/openevent/general/utils/ErrorUtils.kt +++ b/app/src/main/java/org/fossasia/openevent/general/utils/ErrorUtils.kt @@ -18,6 +18,7 @@ const val SOURCE = "source" const val POINTER = "pointer" const val DETAIL = "detail" const val TITLE = "title" +const val CODE = "code" const val POINTER_LENGTH = 3 @@ -62,6 +63,7 @@ object ErrorUtils { } else { error.pointer = pointedField error.detail = jsonArray.get(DETAIL).toString().replace(".", "") + error.code = errorSource.get(CODE).toString() } } catch (e: Exception) { error.detail = jsonArray.get(DETAIL).toString() @@ -91,6 +93,7 @@ object ErrorUtils { } else { error.pointer = pointedField error.detail = jsonArray.get(DETAIL).toString().replace(".", "") + error.code = errorSource.get(CODE).toString() } error.title = jsonArray.get(TITLE).toString() } catch (e: Exception) { 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 da851ee0fc..706bb0c614 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 @@ -157,6 +157,17 @@ android:background="@drawable/circle_shape"/> + + + + + + + - - diff --git a/app/src/main/res/layout/fragment_expired_order.xml b/app/src/main/res/layout/fragment_expired_order.xml index 5c2e9da368..08f067ee89 100644 --- a/app/src/main/res/layout/fragment_expired_order.xml +++ b/app/src/main/res/layout/fragment_expired_order.xml @@ -22,6 +22,10 @@ android:background="?selectableItemBackground" android:src="@drawable/ic_filter" /> + + diff --git a/app/src/main/res/layout/fragment_order_details.xml b/app/src/main/res/layout/fragment_order_details.xml index db62dd8c94..cf249000b6 100644 --- a/app/src/main/res/layout/fragment_order_details.xml +++ b/app/src/main/res/layout/fragment_order_details.xml @@ -14,7 +14,7 @@ diff --git a/app/src/main/res/layout/fragment_orders_under_user.xml b/app/src/main/res/layout/fragment_orders_under_user.xml index ac6c7b7f10..a781aeaf24 100644 --- a/app/src/main/res/layout/fragment_orders_under_user.xml +++ b/app/src/main/res/layout/fragment_orders_under_user.xml @@ -4,6 +4,10 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"> + - + diff --git a/app/src/main/res/values-bn-rIN/strings.xml b/app/src/main/res/values-bn-rIN/strings.xml index c863ef7030..dd5387f9fb 100644 --- a/app/src/main/res/values-bn-rIN/strings.xml +++ b/app/src/main/res/values-bn-rIN/strings.xml @@ -119,8 +119,6 @@ এবাউট রেটিং রেটিং দিন - পরামর্শ দিন - পরামর্শ প্রোফাইল একাউন্ট ইভেন্টের সময় অঞ্চল ব্যবহার করুন diff --git a/app/src/main/res/values-hi-rIN/strings.xml b/app/src/main/res/values-hi-rIN/strings.xml index 29101939aa..1efa06b532 100644 --- a/app/src/main/res/values-hi-rIN/strings.xml +++ b/app/src/main/res/values-hi-rIN/strings.xml @@ -118,8 +118,6 @@ अबाउट रेटिंग रेट उस - सुझाव में सुधार - सुझाव प्रोफ़ाइल अकाउंट ईवेंट के समय-क्षेत्र का उपयोग करें diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index 62b4e2fc3e..c42a1fce69 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -90,8 +90,6 @@ Thông tin đánh giá Đánh Giá Chúng Tôi - Gợi Ý Cải Thiện - gợi ý thông tin Tài Khoản Bảo Mật diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5caea59dbb..8e19cef4a7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -181,9 +181,7 @@ account Rate Us Open Event Android - Suggest Improvement key_acknowledgements - suggestion profile Account timeZoneSwitch @@ -532,5 +530,7 @@ Incorrect old password provided! Unable to change password! Password changed successfully! + Ticket(s) for %1$s + Fail on making Paypal payment diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml index 1e00ead312..539a66ef45 100644 --- a/app/src/main/res/xml/settings.xml +++ b/app/src/main/res/xml/settings.xml @@ -51,11 +51,6 @@ android:title="@string/rating_settings" app:iconSpaceReserved="false" /> - -