diff --git a/app/src/main/graphql/fragments.graphql b/app/src/main/graphql/fragments.graphql index 144fa2ceac..7412b36b16 100644 --- a/app/src/main/graphql/fragments.graphql +++ b/app/src/main/graphql/fragments.graphql @@ -2,6 +2,8 @@ fragment fullProject on Project { availableCardTypes backersCount description + minPledge + isLaunched backing { ... backing } @@ -143,10 +145,12 @@ fragment updates on PostConnection { fragment category on Category { + analyticsName id name slug parentCategory { + analyticsName id name slug @@ -176,6 +180,13 @@ fragment reward on Reward { description estimatedDeliveryOn available + allowedAddons { + edges { + node { + id + } + } + } items { edges { quantity @@ -235,6 +246,7 @@ fragment user on User { id imageUrl(blur: false, width: 54), isCreator + chosenCurrency } fragment amount on Money { diff --git a/app/src/main/graphql/schema.json b/app/src/main/graphql/schema.json index b5b7f54422..a7cb75e394 100644 --- a/app/src/main/graphql/schema.json +++ b/app/src/main/graphql/schema.json @@ -1602,6 +1602,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "fulfillmentStatus", + "description": "The fulfillment status of a backing.", + "args": [], + "type": { + "kind": "ENUM", + "name": "FulfillmentStatusSelectOptions", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "id", "description": "", @@ -1618,6 +1630,18 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "latestSetupIntent", + "description": "If present, the most recent setup_intent data from Stripe.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "SetupIntent", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "location", "description": "The backing location.", @@ -2171,6 +2195,63 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "allowedRewards", + "description": "Base rewards which can be combined with this addon.\nUses creator preferences and shipping rules to determine allow-ability.\nInclusion in this list does not necessarily indicate that the reward is available for backing.", + "args": [ + { + "name": "after", + "description": "Returns the elements in the list that come after the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "before", + "description": "Returns the elements in the list that come before the specified cursor.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "first", + "description": "Returns the first _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "last", + "description": "Returns the last _n_ elements from the list.", + "type": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + }, + "defaultValue": null + } + ], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "OBJECT", + "name": "RewardConnection", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "amount", "description": "Amount for claiming this reward.", @@ -2430,6 +2511,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "maxPledgedInSingleBacking", + "description": "The maximum amount of this add-on in a single pledge selected by any pledged backer.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "name", "description": "A reward title.", @@ -3217,6 +3314,22 @@ "name": "RewardItem", "description": "A reward item.", "fields": [ + { + "name": "addOnsCount", + "description": "The numer of add-ons that the item is included in.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "hasBackers", "description": "Whether backers have backed rewards this item belongs to", @@ -3364,6 +3477,16 @@ }, "defaultValue": null }, + { + "name": "forLocation", + "description": "Filters available add ons by given location", + "type": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + }, + "defaultValue": null + }, { "name": "last", "description": "Returns the last _n_ elements from the list.", @@ -3373,6 +3496,16 @@ "ofType": null }, "defaultValue": null + }, + { + "name": "sort", + "description": "Enables/disables add-ons sort by cost and title, with sorting enabled by default", + "type": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + }, + "defaultValue": null } ], "type": { @@ -3931,6 +4064,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "defaultPledge", + "description": "The default no reward pledge amount based on the project's currency.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Int", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "description", "description": "A short description of the project.", @@ -4904,6 +5053,22 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "showRisksTab", + "description": "Whether to show risks under it's own tab instead of campaign tab", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "slug", "description": "The project's unique URL identifier.", @@ -7033,18 +7198,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "optedIntoAddOnBetaTest", - "description": "Has the user opted into the add on beta test", - "args": [], - "type": { - "kind": "SCALAR", - "name": "Boolean", - "ofType": null - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "optedOutOfRecommendations", "description": "Is the user opted out from receiving recommendations", @@ -7880,6 +8033,54 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "projectsWithUpdatableSurveyResponses", + "description": "The title of projects with updatable survey responses", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "projectsWithoutUpdatableSurveyResponses", + "description": "The title of projects with non updatable survey responses", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "recipientName", "description": "Address recipient name", @@ -10526,31 +10727,25 @@ "deprecationReason": null }, { - "name": "PL", + "name": "IBAN_flexibility", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "SI", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "accounts_upgrade", + "name": "PL", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "add_on_creator_beta_test", + "name": "SI", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "addons_in_build", + "name": "accounts_upgrade", "description": "", "isDeprecated": false, "deprecationReason": null @@ -10562,43 +10757,43 @@ "deprecationReason": null }, { - "name": "android_creator_view", + "name": "android_braze", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "android_go_rewardless_2", + "name": "android_segment", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "android_native_checkout", + "name": "budget_module", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "budget_module", + "name": "ch_currency_selector", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "budget_viz_arts", + "name": "creator_demographics_survey", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "budget_viz_fashion", + "name": "creator_onboarding_flow_2021", "description": "", "isDeprecated": false, "deprecationReason": null }, { - "name": "ch_currency_selector", + "name": "ct_2021_migration_tool", "description": "", "isDeprecated": false, "deprecationReason": null @@ -10705,12 +10900,30 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "ios_braze", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "ios_crashlytics", "description": "", "isDeprecated": false, "deprecationReason": null }, + { + "name": "ios_email_verification_flow", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "ios_email_verification_skip", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "ios_favorite_categories", "description": "", @@ -10795,6 +11008,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "ios_segment", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "ios_tappable_category_location", "description": "", @@ -10981,6 +11200,12 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "segment_tracking_events", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "show_posts_feed", "description": "", @@ -10993,17 +11218,47 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "stripe_connect_onboarding_only_for_kyc", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "stripe_connect_onboarding_skeleton_poc", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "track_define_namespace", "description": "", "isDeprecated": false, "deprecationReason": null }, + { + "name": "uk_shipping_rule_tip", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updated_risks_flow", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, { "name": "user_menu_draft_project", "description": "", "isDeprecated": false, "deprecationReason": null + }, + { + "name": "web_error_on_retry_of_failed_3ds_backing", + "description": "", + "isDeprecated": false, + "deprecationReason": null } ], "possibleTypes": null @@ -12006,18 +12261,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "campus_digest", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "campus_posts", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, { "name": "comment_replies", "description": "", @@ -12072,12 +12315,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "post_likes", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, { "name": "project_launch", "description": "", @@ -14947,6 +15184,22 @@ }, "isDeprecated": false, "deprecationReason": null + }, + { + "name": "showExternalSurvey", + "description": "Whether or not the external survey should be displayed", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null } ], "inputFields": null, @@ -15109,6 +15362,22 @@ "name": "Category", "description": "A project category.", "fields": [ + { + "name": "analyticsName", + "description": "Category name in English for analytics use.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "id", "description": "", @@ -22881,6 +23150,277 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "ENUM", + "name": "FulfillmentStatusSelectOptions", + "description": "Values for backing fulfillment status", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "delayed", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "in_progress", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "not_started", + "description": "", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "shipped", + "description": "", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SetupIntent", + "description": "Selected fields on a SetupIntent from Stripe for a given backing.", + "fields": [ + { + "name": "id", + "description": "Stripe ID of the SetupIntent.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "lastSetupError", + "description": "The error encountered in the previous SetupIntent confirmation.", + "args": [], + "type": { + "kind": "OBJECT", + "name": "SetupIntentError", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "status", + "description": "Status of the SetupIntent.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "SetupIntentStatus", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SetupIntentError", + "description": "", + "fields": [ + { + "name": "code", + "description": "For some errors that could be handled programmatically, a short string\nindicating the error code reported. https://stripe.com/docs/error-codes", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "declineCode", + "description": "A short string indicating the card issuer’s reason for the decline if they provide one.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "message", + "description": "A human-readable message providing more details about the error. For card\nerrors, these messages can be shown to your users.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "String", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "type", + "description": "The type of error returned.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "SetupIntentErrorType", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "SetupIntentErrorType", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "API_CONNECTION_ERROR", + "description": "Failure to connect to Stripe's API.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "API_ERROR", + "description": "API errors cover any other type of problem (e.g., a temporary problem with Stripe's servers), and are extremely uncommon.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "AUTHENTICATION_ERROR", + "description": "Failure to properly authenticate in the request.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "CARD_ERROR", + "description": "Card errors are very common and they result when the user enters a card that can't be charged for some reason.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "IDEMPOTENCY_ERROR", + "description": "Idempotency errors occur when an Idempotency-Key is re-used on a request that\ndoes not match the first request's API endpoint and parameters.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "INVALID_REQUEST_ERROR", + "description": "Invalid request errors arise when your request has invalid parameters eg., 3DS authentication failed.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "RATE_LIMIT_ERROR", + "description": "Too many requests hit the Stripe API too quickly.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "VALIDATION_ERROR", + "description": "Errors triggered by Stripe's client-side libraries when failing to validate\nfields (e.g., when a card number or expiration date is invalid or incomplete).", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, + { + "kind": "ENUM", + "name": "SetupIntentStatus", + "description": "", + "fields": null, + "inputFields": null, + "interfaces": null, + "enumValues": [ + { + "name": "CANCELED", + "description": "SetupIntent can be canceled at any point before it is processing or succeeded.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "PROCESSING", + "description": "Once required actions are handled, the SetupIntent moves to this status, which\ncan be brief or take a few days depending on the payment method.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "REQUIRES_ACTION", + "description": "If the setup requires additional actions, such as authenticating with 3D Secure", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "REQUIRES_CONFIRMATION", + "description": "After the customer provides their payment method information, the SetupIntent is ready to be confirmed.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "REQUIRES_PAYMENT_METHOD", + "description": "When the SetupIntent is created, it has this status until a payment method is\nattached. If a SetupIntent fails, it will revert to this status.", + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "SUCCEEDED", + "description": "Setup of payment source was successful.", + "isDeprecated": false, + "deprecationReason": null + } + ], + "possibleTypes": null + }, { "kind": "UNION", "name": "PaymentSource", @@ -29834,6 +30374,33 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "migrateToFulfillmentStatus", + "description": "Migrate's a project's eligible backings to new fulfillment status tool.", + "args": [ + { + "name": "input", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "MigrateToFulfillmentStatusInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "MigrateToFulfillmentStatusPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "paymentSourceDelete", "description": "Delete a user's payment source", @@ -30131,6 +30698,33 @@ "isDeprecated": false, "deprecationReason": null }, + { + "name": "setBackingFulfillmentStatuses", + "description": "Sets the fulfillment status of multiple backings at once.", + "args": [ + { + "name": "input", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "INPUT_OBJECT", + "name": "SetBackingFulfillmentStatusesInput", + "ofType": null + } + }, + "defaultValue": null + } + ], + "type": { + "kind": "OBJECT", + "name": "SetBackingFulfillmentStatusesPayload", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, { "name": "setProjectSlug", "description": "Set a project slug.", @@ -34945,13 +35539,9 @@ "name": "description", "description": "", "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { - "kind": "SCALAR", - "name": "String", - "ofType": null - } + "kind": "SCALAR", + "name": "String", + "ofType": null }, "defaultValue": null }, @@ -38522,6 +39112,80 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "MigrateToFulfillmentStatusInput", + "description": "Autogenerated input type of MigrateToFulfillmentStatus", + "fields": null, + "inputFields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "projectId", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "MigrateToFulfillmentStatusPayload", + "description": "Autogenerated return type of MigrateToFulfillmentStatus", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "success", + "description": "Succeeds if backings are being updated in backend.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "Boolean", + "ofType": null + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "PaymentSourceDeleteInput", @@ -39390,6 +40054,124 @@ "enumValues": null, "possibleTypes": null }, + { + "kind": "INPUT_OBJECT", + "name": "SetBackingFulfillmentStatusesInput", + "description": "Autogenerated input type of SetBackingFulfillmentStatuses", + "fields": null, + "inputFields": [ + { + "name": "backingIds", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + }, + "defaultValue": null + }, + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "defaultValue": null + }, + { + "name": "projectId", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + }, + "defaultValue": null + }, + { + "name": "status", + "description": "", + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "ENUM", + "name": "FulfillmentStatusSelectOptions", + "ofType": null + } + }, + "defaultValue": null + } + ], + "interfaces": null, + "enumValues": null, + "possibleTypes": null + }, + { + "kind": "OBJECT", + "name": "SetBackingFulfillmentStatusesPayload", + "description": "Autogenerated return type of SetBackingFulfillmentStatuses", + "fields": [ + { + "name": "clientMutationId", + "description": "A unique identifier for the client performing the mutation.", + "args": [], + "type": { + "kind": "SCALAR", + "name": "String", + "ofType": null + }, + "isDeprecated": false, + "deprecationReason": null + }, + { + "name": "updatedBackingIds", + "description": "Lists the ids of all successfully updated backings.", + "args": [], + "type": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "LIST", + "name": null, + "ofType": { + "kind": "NON_NULL", + "name": null, + "ofType": { + "kind": "SCALAR", + "name": "ID", + "ofType": null + } + } + } + }, + "isDeprecated": false, + "deprecationReason": null + } + ], + "inputFields": null, + "interfaces": [], + "enumValues": null, + "possibleTypes": null + }, { "kind": "INPUT_OBJECT", "name": "SetProjectSlugInput", @@ -43384,12 +44166,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "opted_into_add_on_beta_test", - "description": "", - "isDeprecated": false, - "deprecationReason": null - }, { "name": "opted_out_of_recommendations", "description": "", diff --git a/app/src/main/java/com/kickstarter/libs/utils/RewardUtils.kt b/app/src/main/java/com/kickstarter/libs/utils/RewardUtils.kt index 80d5a1200e..e64d4f2775 100644 --- a/app/src/main/java/com/kickstarter/libs/utils/RewardUtils.kt +++ b/app/src/main/java/com/kickstarter/libs/utils/RewardUtils.kt @@ -162,6 +162,8 @@ object RewardUtils { } return when (shippingType) { + Reward.ShippingPreference.UNRESTRICTED.name.lowercase() -> Pair.create(R.string.Ships_worldwide, null) + Reward.ShippingPreference.RESTRICTED.name.lowercase() -> Pair.create(R.string.Limited_shipping, "") Reward.SHIPPING_TYPE_ANYWHERE -> Pair.create(R.string.Ships_worldwide, null) Reward.SHIPPING_TYPE_MULTIPLE_LOCATIONS -> Pair.create(R.string.Limited_shipping, null) Reward.SHIPPING_TYPE_SINGLE_LOCATION -> { diff --git a/app/src/main/java/com/kickstarter/libs/utils/extensions/ProjectExt.kt b/app/src/main/java/com/kickstarter/libs/utils/extensions/ProjectExt.kt new file mode 100644 index 0000000000..f60bfecee3 --- /dev/null +++ b/app/src/main/java/com/kickstarter/libs/utils/extensions/ProjectExt.kt @@ -0,0 +1,37 @@ +@file:JvmName("ProjectExt") +package com.kickstarter.libs.utils.extensions + +import com.kickstarter.libs.Config +import com.kickstarter.models.Project +import com.kickstarter.models.User + +/** + * When fetching a project from GraphQL, we need to populate the next fields + * Project.currentCurrency() + * Project.currencyTrailingCode() + * Project.currencySymbol() + * + * - with the country code on configuration in case no user logged in + * - with the selected currency by the user in case the is logged in user. + * + * Note: And user logged in can change it's currency at any time. + */ +fun Project.updateProjectWith(config: Config, user: User?): Project { + val currentCountry = config.launchedCountries().find { + it.name().equals(config.countryCode()) + } + + val currentCurrency = user?.let { + it.chosenCurrency() + } ?: currentCountry?.currencyCode() ?: currency() + + val countryOfCurrency = config.launchedCountries().first { it.currencyCode() == currentCurrency } + val currencySymbol = countryOfCurrency.currencySymbol() + val trailingCode = countryOfCurrency.trailingCode() + + return this.toBuilder() + .currentCurrency(currentCurrency) + .currencyTrailingCode(trailingCode ?: false) + .currencySymbol(currencySymbol) + .build() +} diff --git a/app/src/main/java/com/kickstarter/mock/factories/ConfigFactory.java b/app/src/main/java/com/kickstarter/mock/factories/ConfigFactory.java index dcc9e1f3e9..425d9ae510 100644 --- a/app/src/main/java/com/kickstarter/mock/factories/ConfigFactory.java +++ b/app/src/main/java/com/kickstarter/mock/factories/ConfigFactory.java @@ -33,10 +33,18 @@ private ConfigFactory() {} .trailingCode(true) .build(); + + final Config.LaunchedCountry JP = Config.LaunchedCountry.builder() + .name("JP") + .currencyCode("JPY") + .currencySymbol("¥") + .trailingCode(false) + .build(); + return Config.builder() .countryCode("US") .features(Collections.emptyMap()) - .launchedCountries(Arrays.asList(US, GB, CA)) + .launchedCountries(Arrays.asList(US, GB, CA, JP)) .build(); } @@ -44,7 +52,13 @@ private ConfigFactory() {} return config(); } - public static @NonNull Config configForCAUser() { + public static @NonNull Config configFor() { + return config().toBuilder() + .countryCode("CA") + .build(); + } + + public static @NonNull Config configForCA() { return config().toBuilder() .countryCode("CA") .build(); diff --git a/app/src/main/java/com/kickstarter/mock/factories/ProjectFactory.java b/app/src/main/java/com/kickstarter/mock/factories/ProjectFactory.java index ca214dea4d..2371d36749 100644 --- a/app/src/main/java/com/kickstarter/mock/factories/ProjectFactory.java +++ b/app/src/main/java/com/kickstarter/mock/factories/ProjectFactory.java @@ -95,6 +95,15 @@ private ProjectFactory() {} .build(); } + public static @NonNull Project britishProject() { + return project() + .toBuilder() + .country("GB") + .currency("GBP") + .currencySymbol("£") + .build(); + } + public static @NonNull Project backedProject() { final Project project = project(); diff --git a/app/src/main/java/com/kickstarter/mock/factories/UserFactory.java b/app/src/main/java/com/kickstarter/mock/factories/UserFactory.java index 8b0105b3ce..593ab6f773 100644 --- a/app/src/main/java/com/kickstarter/mock/factories/UserFactory.java +++ b/app/src/main/java/com/kickstarter/mock/factories/UserFactory.java @@ -54,6 +54,13 @@ public static User germanUser() { .build(); } + public static User canadianUser() { + return user() + .toBuilder() + .location(LocationFactory.germany()) + .build(); + } + public static User mexicanUser() { return user() .toBuilder() diff --git a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt index d4bc9bd2d2..8dea812cc6 100644 --- a/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt +++ b/app/src/main/java/com/kickstarter/mock/services/MockApolloClient.kt @@ -43,8 +43,17 @@ open class MockApolloClient : ApolloClientType { return Observable.just(BackingFactory.backing()) } + override fun getProject(project: Project): Observable { + return return Observable.just(project) + } + override fun getProject(slug: String): Observable { - return Observable.just(ProjectFactory.project()) + return return Observable.just( + ProjectFactory.project() + .toBuilder() + .slug(slug) + .build() + ) } override fun getProjectAddOns(slug: String, location: Location): Observable> { diff --git a/app/src/main/java/com/kickstarter/models/User.java b/app/src/main/java/com/kickstarter/models/User.java index 3fc348eff4..b7713f83fd 100644 --- a/app/src/main/java/com/kickstarter/models/User.java +++ b/app/src/main/java/com/kickstarter/models/User.java @@ -33,6 +33,7 @@ public abstract class User implements Parcelable, Relay { public abstract @Nullable Boolean inventNewsletter(); public abstract @Nullable Boolean isAdmin(); public abstract @Nullable Boolean isEmailVerified(); + public abstract @Nullable String chosenCurrency(); public abstract @Nullable Location location(); public abstract @Nullable Integer memberProjectsCount(); public abstract @Nullable Boolean musicNewsletter(); @@ -81,6 +82,7 @@ public abstract static class Builder { public abstract Builder id(long __); public abstract Builder isAdmin(Boolean __); public abstract Builder isEmailVerified(Boolean __); + public abstract Builder chosenCurrency(String __); public abstract Builder inventNewsletter(Boolean __); public abstract Builder location(Location __); public abstract Builder memberProjectsCount(Integer __); diff --git a/app/src/main/java/com/kickstarter/services/ApolloClientType.kt b/app/src/main/java/com/kickstarter/services/ApolloClientType.kt index 8fd66e4039..94d009229c 100644 --- a/app/src/main/java/com/kickstarter/services/ApolloClientType.kt +++ b/app/src/main/java/com/kickstarter/services/ApolloClientType.kt @@ -36,6 +36,8 @@ interface ApolloClientType { fun getProject(slug: String): Observable + fun getProject(project: Project): Observable + fun getProjectComments(slug: String, cursor: String?, limit: Int = PAGE_SIZE): Observable fun getProjectUpdateComments(updateId: String, cursor: String?, limit: Int = PAGE_SIZE): Observable diff --git a/app/src/main/java/com/kickstarter/services/KSApolloClient.kt b/app/src/main/java/com/kickstarter/services/KSApolloClient.kt index 18db78c6d7..fad599b2a2 100644 --- a/app/src/main/java/com/kickstarter/services/KSApolloClient.kt +++ b/app/src/main/java/com/kickstarter/services/KSApolloClient.kt @@ -23,7 +23,9 @@ import com.apollographql.apollo.api.Response import com.apollographql.apollo.exception.ApolloException import com.google.android.gms.common.util.Base64Utils import com.kickstarter.libs.Permission +import com.kickstarter.libs.utils.BooleanUtils import com.kickstarter.libs.utils.ObjectUtils +import com.kickstarter.mock.factories.RewardFactory import com.kickstarter.models.Avatar import com.kickstarter.models.Backing import com.kickstarter.models.Category @@ -317,6 +319,10 @@ class KSApolloClient(val service: ApolloClient) : ApolloClientType { }.subscribeOn(Schedulers.io()) } + override fun getProject(project: Project): Observable { + return getProject(project.slug() ?: "") + } + override fun getProject(slug: String): Observable { return Observable.defer { val ps = PublishSubject.create() @@ -1091,15 +1097,26 @@ private fun projectTransformer(projectFragment: FullProject?): Project { val tags = mutableListOf() projectFragment?.fragments()?.tagsCreative()?.tags()?.map { tags.add(it.id()) } projectFragment?.fragments()?.tagsDiscovery()?.tags()?.map { tags.add(it.id()) } + + val minPledge = projectFragment?.minPledge()?.toDouble() ?: 1.0 val rewards = projectFragment?.rewards()?.nodes()?.map { rewardTransformer(it.fragments().reward()) } + + // - GraphQL does not provide the Reward no reward, we need to add it first + val modifiedRewards = rewards?.toMutableList() + modifiedRewards?.add(0, RewardFactory.noReward().toBuilder().minimum(minPledge).build()) + modifiedRewards?.toList() + val slug = projectFragment?.slug() val staffPicked = projectFragment?.isProjectWeLove ?: false - val state = projectFragment?.state()?.name + val state = projectFragment?.state()?.name?.lowercase() val stateChangedAt = projectFragment?.stateChangedAt() val staticUSDRate = projectFragment?.usdExchangeRate()?.toFloat() val usdExchangeRate = projectFragment?.usdExchangeRate()?.toFloat() - val updatedAt = projectFragment?.posts()?.fragments()?.updates()?.nodes()?.first()?.updatedAt() + val updatedAt = projectFragment?.posts()?.fragments()?.updates()?.nodes()?.let { + if (it.isNotEmpty()) return@let it.first()?.updatedAt() + else null + } val updatesCount = projectFragment?.posts()?.fragments()?.updates()?.nodes()?.size val url = projectFragment?.url() val urlsWeb = Project.Urls.Web.builder() @@ -1110,6 +1127,7 @@ private fun projectTransformer(projectFragment: FullProject?): Project { val video = if (projectFragment?.video()?.fragments()?.video() != null) { videoTransformer(projectFragment?.video()?.fragments()?.video()) } else null + val displayPrelaunch = BooleanUtils.negate(projectFragment?.isLaunched ?: false) return Project.builder() .availableCardTypes(availableCards.map { it.name }) @@ -1123,9 +1141,9 @@ private fun projectTransformer(projectFragment: FullProject?): Project { .creator(creator) .currency(currency) .currencySymbol(currencySymbol) - .currentCurrency(currency) // TODO: selected currency can be fetched form the User Object + .currentCurrency(currency) // TODO: selected currency can be fetched form the User/Configuration Object .currencyTrailingCode(false) // TODO: This field is available on V1 Configuration Object - .displayPrelaunch(prelaunchActivated) + .displayPrelaunch(displayPrelaunch) .featuredAt(featuredAt) .friends(friends) .fxRate(fxRate) @@ -1143,7 +1161,7 @@ private fun projectTransformer(projectFragment: FullProject?): Project { .photo(photo) // TODO: now we get the full size for everything same as iOS, but V1 provided several image sizes .prelaunchActivated(prelaunchActivated) .tags(tags) - .rewards(rewards) + .rewards(modifiedRewards) .slug(slug) .staffPick(staffPicked) .state(state) @@ -1190,11 +1208,13 @@ private fun userTransformer(user: fragment.User?): User { val avatar = Avatar.builder() .medium(user?.imageUrl()) .build() + val chosenCurrency = user?.chosenCurrency() return User.builder() .id(id) .name(name) .avatar(avatar) + .chosenCurrency(chosenCurrency) .build() } @@ -1204,17 +1224,19 @@ private fun userTransformer(user: fragment.User?): User { * @return Project */ private fun categoryTransformer(categoryFragment: fragment.Category?): Category { + val analyticsName = categoryFragment?.analyticsName() ?: "" val name = categoryFragment?.name() ?: "" val id = decodeRelayId(categoryFragment?.id()) ?: -1 val slug = categoryFragment?.slug() val parentId = decodeRelayId(categoryFragment?.parentCategory()?.id()) ?: -1 val parentName = categoryFragment?.parentCategory()?.name() val parentSlug = categoryFragment?.parentCategory()?.slug() + val parentAnalyticName = categoryFragment?.parentCategory()?.analyticsName() ?: "" val parentCategory = if (parentId > 0) { Category.builder() .slug(parentSlug) - .analyticsName(parentName) + .analyticsName(parentAnalyticName) .id(parentId) .name(parentName) .build() @@ -1225,7 +1247,7 @@ private fun categoryTransformer(categoryFragment: fragment.Category?): Category .id(id) .name(name) .slug(slug) - .parent(parentCategory) // TODO: seems we can skip the entire parent category structure + .parent(parentCategory) .parentId(parentId) .parentName(parentName) .build() @@ -1242,14 +1264,14 @@ private fun rewardTransformer(rewardGr: fragment.Reward, shippingRulesExpanded: val desc = rewardGr.description() val title = rewardGr.name() val estimatedDelivery = rewardGr.estimatedDeliveryOn()?.let { DateTime(it) } - val limit = chooseLimit(rewardGr.limit(), rewardGr.limitPerBacker()) val remaining = rewardGr.remainingQuantity() val endsAt = rewardGr.endsAt()?.let { DateTime(it) } val startsAt = rewardGr.startsAt()?.let { DateTime(it) } val rewardId = decodeRelayId(rewardGr.id()) ?: -1 val available = rewardGr.available() val isAddOn = rewardGr.rewardType() == RewardType.ADDON - + val isReward = rewardGr.rewardType() == RewardType.BASE + val backersCount = rewardGr.backersCount() val shippingPreference = when (rewardGr.shippingPreference()) { ShippingPreference.NONE -> Reward.ShippingPreference.NONE ShippingPreference.RESTRICTED -> Reward.ShippingPreference.RESTRICTED @@ -1257,9 +1279,24 @@ private fun rewardTransformer(rewardGr: fragment.Reward, shippingRulesExpanded: else -> Reward.ShippingPreference.UNKNOWN } - val items = rewardGr.items()?.let { - rewardItemsTransformer(it) - } + val limit = if (isAddOn) chooseLimit(rewardGr.limit(), rewardGr.limitPerBacker()) + else rewardGr.limit() + + val addonItems = if (isAddOn) { + rewardGr.items()?.let { + rewardItemsTransformer(it) + } + } else emptyList() + + val rewardItems = if (isReward) { + rewardGr.items()?.let { + rewardItemsTransformer(it) + } + } else emptyList() + + val hasAddons = if (isReward) { + rewardGr.allowedAddons().edges()?.isNotEmpty() ?: false + } else false val shippingRules = shippingRulesExpanded.map { shippingRuleTransformer(it) @@ -1276,13 +1313,16 @@ private fun rewardTransformer(rewardGr: fragment.Reward, shippingRulesExpanded: .description(desc) .estimatedDeliveryOn(estimatedDelivery) .isAddOn(isAddOn) - .addOnsItems(items) + .addOnsItems(addonItems) + .hasAddons(hasAddons) + .rewardsItems(rewardItems) .id(rewardId) - .shippingPreference(shippingPreference.name) + .shippingPreference(shippingPreference.name.lowercase()) .shippingPreferenceType(shippingPreference) - .shippingType(shippingPreference.name) + .shippingType(shippingPreference.name.lowercase()) .shippingRules(shippingRules) .isAvailable(available) + .backersCount(backersCount) .build() } diff --git a/app/src/main/java/com/kickstarter/ui/intentmappers/ProjectIntentMapper.java b/app/src/main/java/com/kickstarter/ui/intentmappers/ProjectIntentMapper.java index 220a798f4e..932c108029 100644 --- a/app/src/main/java/com/kickstarter/ui/intentmappers/ProjectIntentMapper.java +++ b/app/src/main/java/com/kickstarter/ui/intentmappers/ProjectIntentMapper.java @@ -7,6 +7,7 @@ import com.kickstarter.libs.utils.ObjectUtils; import com.kickstarter.models.Project; import com.kickstarter.services.ApiClientType; +import com.kickstarter.services.ApolloClientType; import com.kickstarter.services.apiresponses.PushNotificationEnvelope; import com.kickstarter.ui.IntentKey; @@ -25,6 +26,23 @@ private ProjectIntentMapper() {} // /projects/param-1/param-2* final static Pattern PROJECT_PATTERN = Pattern.compile("\\A\\/projects\\/([a-zA-Z0-9_-]+)(\\/([a-zA-Z0-9_-]+)).*"); + public static @NonNull Observable project(final @NonNull Intent intent, final @NonNull ApolloClientType apolloClient) { + + final Project intentProject = projectFromIntent(intent); + final Observable projectFromParceledProject = intentProject == null ? Observable.empty() : Observable.just(intentProject) + .flatMap(apolloClient::getProject) + .startWith(intentProject) + .retry(3); + + final Observable projectFromParceledParam = Observable.just(paramFromIntent(intent)) + .filter(ObjectUtils::isNotNull) + .flatMap(apolloClient::getProject) + .retry(3); + + return projectFromParceledProject + .mergeWith(projectFromParceledParam); + } + /** * Returns an observable of projects retrieved from intent data. May hit the API if the intent only contains a project * param rather than a parceled project. diff --git a/app/src/main/java/com/kickstarter/viewmodels/ProjectViewModel.kt b/app/src/main/java/com/kickstarter/viewmodels/ProjectViewModel.kt index 2eca4bd1ff..1383746bc3 100644 --- a/app/src/main/java/com/kickstarter/viewmodels/ProjectViewModel.kt +++ b/app/src/main/java/com/kickstarter/viewmodels/ProjectViewModel.kt @@ -31,6 +31,7 @@ import com.kickstarter.libs.utils.RefTagUtils import com.kickstarter.libs.utils.UrlUtils import com.kickstarter.libs.utils.extensions.backedReward import com.kickstarter.libs.utils.extensions.isErrored +import com.kickstarter.libs.utils.extensions.updateProjectWith import com.kickstarter.models.Backing import com.kickstarter.models.Project import com.kickstarter.models.Reward @@ -261,6 +262,7 @@ interface ProjectViewModel { private val optimizely: ExperimentsClientType = environment.optimizely() private val sharedPreferences: SharedPreferences = environment.sharedPreferences() private val apolloClient = environment.apolloClient() + private val currentConfig = environment.currentConfig() private val blurbTextViewClicked = PublishSubject.create() private val blurbVariantClicked = PublishSubject.create() @@ -343,13 +345,16 @@ interface ProjectViewModel { .compose(takeWhen(this.reloadProjectContainerClicked)) ) .flatMap { - ProjectIntentMapper.project(it, this.client) + ProjectIntentMapper.project(it, this.apolloClient) .doOnSubscribe { progressBarIsGone.onNext(false) } .doAfterTerminate { progressBarIsGone.onNext(true) } + .withLatestFrom(currentConfig.observable(), currentUser.observable()) { project, config, user -> + return@withLatestFrom project.updateProjectWith(config, user) + } .materialize() } .share() @@ -429,13 +434,16 @@ interface ProjectViewModel { .compose(takeWhen(refreshProjectEvent)) .switchMap { it.slug()?.let { slug -> - this.client.fetchProject(slug) + this.apolloClient.getProject(slug) .doOnSubscribe { progressBarIsGone.onNext(false) } .doAfterTerminate { progressBarIsGone.onNext(true) } + .withLatestFrom(currentConfig.observable(), currentUser.observable()) { project, config, user -> + return@withLatestFrom project.updateProjectWith(config, user) + } .materialize() } } diff --git a/app/src/test/java/com/kickstarter/libs/utils/extensions/ProjectExtTest.kt b/app/src/test/java/com/kickstarter/libs/utils/extensions/ProjectExtTest.kt new file mode 100644 index 0000000000..da117c7c81 --- /dev/null +++ b/app/src/test/java/com/kickstarter/libs/utils/extensions/ProjectExtTest.kt @@ -0,0 +1,55 @@ +package com.kickstarter.libs.utils.extensions + +import com.kickstarter.mock.factories.ConfigFactory +import com.kickstarter.mock.factories.ProjectFactory +import com.kickstarter.mock.factories.UserFactory +import junit.framework.TestCase +import org.junit.Test + +class ProjectExtTest : TestCase() { + + @Test + fun testBritishProject_WhenNoUserAndCanadaConfig() { + val user = null + val config = ConfigFactory.configForCA() + val project = ProjectFactory.britishProject() + + val updatedProject = project.updateProjectWith(config, user = user) + + assertFalse(updatedProject.currentCurrency() == "GBP") + assertFalse(updatedProject.currencySymbol() == "£") + assertTrue(updatedProject.currentCurrency() == "CAD") + assertTrue(updatedProject.currencyTrailingCode()) + assertTrue(updatedProject.currencySymbol() == "$") + } + + @Test + fun testBritishProject_WhenUserCanadaCurrencyAndCanadaConfig() { + val user = UserFactory.germanUser().toBuilder().chosenCurrency("CAD").build() + val config = ConfigFactory.configForCA() + val project = ProjectFactory.britishProject() + + val updatedProject = project.updateProjectWith(config, user = user) + + assertFalse(updatedProject.currentCurrency() == "GBP") + assertFalse(updatedProject.currencySymbol() == "£") + assertTrue(updatedProject.currentCurrency() == "CAD") + assertTrue(updatedProject.currencyTrailingCode()) + assertTrue(updatedProject.currencySymbol() == "$") + } + + @Test + fun testBritishProject_WhenUserJapaneseCurrencyAndCanadaConfig() { + val user = UserFactory.germanUser().toBuilder().chosenCurrency("JPY").build() + val config = ConfigFactory.configForCA() + val project = ProjectFactory.britishProject() + + val updatedProject = project.updateProjectWith(config, user = user) + + assertFalse(updatedProject.currentCurrency() == "GBP") + assertFalse(updatedProject.currencySymbol() == "£") + assertTrue(updatedProject.currentCurrency() == "JPY") + assertFalse(updatedProject.currencyTrailingCode()) + assertTrue(updatedProject.currencySymbol() == "¥") + } +} diff --git a/app/src/test/java/com/kickstarter/ui/intents/ProjectIntentMapperTest.java b/app/src/test/java/com/kickstarter/ui/intents/ProjectIntentMapperTest.java index bdc9dc5359..f84e35a292 100644 --- a/app/src/test/java/com/kickstarter/ui/intents/ProjectIntentMapperTest.java +++ b/app/src/test/java/com/kickstarter/ui/intents/ProjectIntentMapperTest.java @@ -7,6 +7,7 @@ import com.kickstarter.mock.factories.ProjectFactory; import com.kickstarter.mock.factories.PushNotificationEnvelopeFactory; import com.kickstarter.libs.RefTag; +import com.kickstarter.mock.services.MockApolloClient; import com.kickstarter.models.Project; import com.kickstarter.mock.services.MockApiClient; import com.kickstarter.services.apiresponses.PushNotificationEnvelope; @@ -29,6 +30,17 @@ public void testProject_emitsFromProjectParamExtra() { resultTest.assertValueCount(1); } + @Test + public void testProject_emitsFromProjectParamExtraApollo() { + final Intent intent = new Intent().putExtra(IntentKey.PROJECT_PARAM, "skull-graphic-tee"); + + final TestSubscriber resultTest = TestSubscriber.create(); + ProjectIntentMapper.project(intent, new MockApolloClient()) + .subscribe(resultTest); + + resultTest.assertValueCount(1); + } + @Test public void testProject_emitsTwiceFromProjectExtra() { final Project project = ProjectFactory.project(); @@ -41,6 +53,18 @@ public void testProject_emitsTwiceFromProjectExtra() { resultTest.assertValueCount(2); } + @Test + public void testProject_emitsTwiceFromProjectExtraApollo() { + final Project project = ProjectFactory.project(); + final Intent intent = new Intent().putExtra(IntentKey.PROJECT, project); + + final TestSubscriber resultTest = TestSubscriber.create(); + ProjectIntentMapper.project(intent, new MockApolloClient()) + .subscribe(resultTest); + + resultTest.assertValueCount(2); + } + @Test public void testProject_emitsFromKsrProjectUri() { final Uri uri = Uri.parse("ksr://www.kickstarter.com/projects/1186238668/skull-graphic-tee"); @@ -53,6 +77,18 @@ public void testProject_emitsFromKsrProjectUri() { resultTest.assertValueCount(1); } + @Test + public void testProject_emitsFromKsrProjectUriApollo() { + final Uri uri = Uri.parse("ksr://www.kickstarter.com/projects/1186238668/skull-graphic-tee"); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + + final TestSubscriber resultTest = TestSubscriber.create(); + ProjectIntentMapper.project(intent, new MockApolloClient()) + .subscribe(resultTest); + + resultTest.assertValueCount(1); + } + @Test public void testProject_emitsFromHttpsProjectUri() { final Uri uri = Uri.parse("https://www.kickstarter.com/projects/1186238668/skull-graphic-tee"); @@ -65,6 +101,18 @@ public void testProject_emitsFromHttpsProjectUri() { resultTest.assertValueCount(1); } + @Test + public void testProject_emitsFromHttpsProjectUriApollo() { + final Uri uri = Uri.parse("https://www.kickstarter.com/projects/1186238668/skull-graphic-tee"); + final Intent intent = new Intent(Intent.ACTION_VIEW, uri); + + final TestSubscriber resultTest = TestSubscriber.create(); + ProjectIntentMapper.project(intent, new MockApolloClient()) + .subscribe(resultTest); + + resultTest.assertValueCount(1); + } + @Test public void testRefTag_emitsFromRefTag() { final RefTag refTag = RefTag.from("test"); diff --git a/app/src/test/java/com/kickstarter/viewmodels/ProjectViewModelTest.kt b/app/src/test/java/com/kickstarter/viewmodels/ProjectViewModelTest.kt index 9dba12f258..3b91a5be2f 100644 --- a/app/src/test/java/com/kickstarter/viewmodels/ProjectViewModelTest.kt +++ b/app/src/test/java/com/kickstarter/viewmodels/ProjectViewModelTest.kt @@ -20,7 +20,7 @@ import com.kickstarter.mock.factories.ProjectDataFactory import com.kickstarter.mock.factories.ProjectFactory import com.kickstarter.mock.factories.RewardFactory import com.kickstarter.mock.factories.UserFactory -import com.kickstarter.mock.services.MockApiClient +import com.kickstarter.mock.services.MockApolloClient import com.kickstarter.models.Backing import com.kickstarter.models.Project import com.kickstarter.ui.IntentKey @@ -130,7 +130,7 @@ class ProjectViewModelTest : KSRobolectricTestCase() { val refreshedProject = ProjectFactory.project() val environment = environment() .toBuilder() - .apiClient(apiClientWithSuccessFetchingProject(refreshedProject)) + .apolloClient(apiClientWithSuccessFetchingProject(refreshedProject)) .build() setUpEnvironment(environment) @@ -154,8 +154,8 @@ class ProjectViewModelTest : KSRobolectricTestCase() { val environment = environment() .toBuilder() - .apiClient(object : MockApiClient() { - override fun fetchProject(project: Project): Observable { + .apolloClient(object : MockApolloClient() { + override fun getProject(project: Project): Observable { val observable = when { error -> Observable.error(Throwable("boop")) else -> { @@ -199,8 +199,8 @@ class ProjectViewModelTest : KSRobolectricTestCase() { val environment = environment() .toBuilder() - .apiClient(object : MockApiClient() { - override fun fetchProject(param: String): Observable { + .apolloClient(object : MockApolloClient() { + override fun getProject(param: String): Observable { return Observable.just(project) } }) @@ -225,8 +225,8 @@ class ProjectViewModelTest : KSRobolectricTestCase() { val environment = environment() .toBuilder() - .apiClient(object : MockApiClient() { - override fun fetchProject(param: String): Observable { + .apolloClient(object : MockApolloClient() { + override fun getProject(slug: String): Observable { val observable = when { error -> Observable.error(Throwable("boop")) else -> Observable.just(refreshedProject) @@ -265,8 +265,8 @@ class ProjectViewModelTest : KSRobolectricTestCase() { val environment = environment() .toBuilder() - .apiClient(object : MockApiClient() { - override fun fetchProject(param: String): Observable { + .apolloClient(object : MockApolloClient() { + override fun getProject(param: String): Observable { return Observable.just(project) } }) @@ -347,7 +347,11 @@ class ProjectViewModelTest : KSRobolectricTestCase() { this.vm.inputs.shareButtonClicked() val expectedName = "Best Project 2K19" val expectedShareUrl = "https://www.kck.str/projects/" + creator.id().toString() + "/" + slug + "?ref=android_project_share" - this.showShareSheet.assertValues(Pair(expectedName, expectedShareUrl)) + + this.vm.outputs.showShareSheet().subscribe { + assertTrue(it.first == expectedName) + assertTrue(it.second == expectedShareUrl) + } this.segmentTrack.assertValue(EventName.PAGE_VIEWED.eventName) } @@ -1588,7 +1592,7 @@ class ProjectViewModelTest : KSRobolectricTestCase() { val refreshedProject = ProjectFactory.backedProject() val environment = environment() .toBuilder() - .apiClient(apiClientWithSuccessFetchingProjectFromSlug(refreshedProject)) + .apolloClient(apiClientWithSuccessFetchingProjectFromSlug(refreshedProject)) .build() setUpEnvironment(environment) @@ -1618,7 +1622,7 @@ class ProjectViewModelTest : KSRobolectricTestCase() { val refreshedProject = ProjectFactory.backedProject() val environment = environment() .toBuilder() - .apiClient(apiClientWithSuccessFetchingProjectFromSlug(refreshedProject)) + .apolloClient(apiClientWithSuccessFetchingProjectFromSlug(refreshedProject)) .build() setUpEnvironment(environment) @@ -1673,17 +1677,17 @@ class ProjectViewModelTest : KSRobolectricTestCase() { this.projectData.assertValueCount(2) } - private fun apiClientWithSuccessFetchingProject(refreshedProject: Project): MockApiClient { - return object : MockApiClient() { - override fun fetchProject(project: Project): Observable { + private fun apiClientWithSuccessFetchingProject(refreshedProject: Project): MockApolloClient { + return object : MockApolloClient() { + override fun getProject(project: Project): Observable { return Observable.just(refreshedProject) } } } - private fun apiClientWithSuccessFetchingProjectFromSlug(refreshedProject: Project): MockApiClient { - return object : MockApiClient() { - override fun fetchProject(slug: String): Observable { + private fun apiClientWithSuccessFetchingProjectFromSlug(refreshedProject: Project): MockApolloClient { + return object : MockApolloClient() { + override fun getProject(slug: String): Observable { return Observable.just(refreshedProject) } }