From 678bca87ebbb61d3112d5b043917f85ee1604fc3 Mon Sep 17 00:00:00 2001 From: SimonLaux Date: Sun, 8 Jan 2023 21:15:12 +0100 Subject: [PATCH 1/2] start with the new donation screen --- _locales/_untranslated_en.json | 21 ++++++- scss/dialogs/_settings.scss | 58 +++++++++++++++++++ .../components/dialogs/Settings-Donate.tsx | 55 ++++++++++++++++++ src/renderer/components/dialogs/Settings.tsx | 17 ++++++ src/shared/constants.ts | 1 + 5 files changed, 149 insertions(+), 3 deletions(-) create mode 100644 src/renderer/components/dialogs/Settings-Donate.tsx diff --git a/_locales/_untranslated_en.json b/_locales/_untranslated_en.json index 89b10c755a..852dd3aeca 100644 --- a/_locales/_untranslated_en.json +++ b/_locales/_untranslated_en.json @@ -29,9 +29,6 @@ "broadcast_list_warning": { "message": "Broadcast Lists are always unencrypted because they use blind copy (BCC)" }, - "pref_donate": { - "message": "Donate" - }, "clear_chat": { "message": "Clear Chat" }, @@ -61,5 +58,23 @@ }, "save_sticker": { "message": "Save Sticker" + }, + "pref_support_deltachat": { + "message": "Donate Money or Time" + }, + "contribute_to_deltachat": { + "message": "Contribute to DeltaChat" + }, + "donate_money": { + "message": "Donate Money" + }, + "other_ways_to_contribute": { + "message": "Other ways to contribute" + }, + "other_ways_to_contribute_description": { + "message": "If giving money is not your thing, you can find other ways to help here, like translating Delta Chat into your language, testing nightly versions, giving feedback on in our forum or even getting involved with coding, there are many ways in which you can contribute to the project." + }, + "donate_money_reason":{ + "message": "Your support helps us to continue to improve Delta Chat." } } diff --git a/scss/dialogs/_settings.scss b/scss/dialogs/_settings.scss index 8fb82ef84f..2660334d23 100644 --- a/scss/dialogs/_settings.scss +++ b/scss/dialogs/_settings.scss @@ -208,3 +208,61 @@ padding-top: 0px; } } + +div.donate-page { + margin: 4px; + display: flex; + flex-direction: column; + height: 100%; + max-height: 395px; + + p.donate-reason { + font-size: 23px; + line-height: 30px; + } + + .donate-money-section { + display: flex; + flex-grow: 1; + align-items: center; + } + + .donate-time-section { + .other-ways { + font-size: 18px; + line-height: 23px; + margin-bottom: 17px; + } + } + + .spacer { + flex-grow: 1; + } + + button { + &:hover { + background-color: #5c85ff; + } + + background-color: #7396ff; + color: black; + border: none; + border-radius: 5px; + padding: 10px 15px; + font-size: 36px; + display: flex; + font-size: 23px; + text-align: start; + width: 100%; + .label { + flex-grow: 1; + } + .Icon { + width: 24px; + height: 24px; + mask-size: 80%; + -webkit-mask-size: 80%; + background-color: black; + } + } +} diff --git a/src/renderer/components/dialogs/Settings-Donate.tsx b/src/renderer/components/dialogs/Settings-Donate.tsx new file mode 100644 index 0000000000..b9160b7064 --- /dev/null +++ b/src/renderer/components/dialogs/Settings-Donate.tsx @@ -0,0 +1,55 @@ +import React from 'react' +import { runtime } from '../../runtime' +import { contributeUrl, donationUrl } from '../../../shared/constants' + +export function SettingsDonate({}: {}) { + const tx = window.static_translate + + const MoneySection = false /* isMacAppstoreBuild */ + ? DonateMacOSAppstore + : DonateMoney + + return ( +
+

{tx('donate_money_reason')}

+
+ +
+
+
+

+ {tx('other_ways_to_contribute_description')} +

+ +
+
+ ) +} + +function DonateMoney() { + const tx = window.static_translate + return ( + + ) +} + +function DonateMacOSAppstore() { + return
options
+} diff --git a/src/renderer/components/dialogs/Settings.tsx b/src/renderer/components/dialogs/Settings.tsx index d3b72b61bb..5fba530cca 100644 --- a/src/renderer/components/dialogs/Settings.tsx +++ b/src/renderer/components/dialogs/Settings.tsx @@ -23,6 +23,7 @@ import SettingsStoreInstance, { } from '../../stores/settings' import { runtime } from '../../runtime' import { donationUrl } from '../../../shared/constants' +import { SettingsDonate } from './Settings-Donate' export function flipDeltaBoolean(value: string) { return value === '1' ? '0' : '1' @@ -357,6 +358,22 @@ export default function Settings(props: DialogProps) { )} + {settingsMode === 'donation' && ( + <> + setSettingsMode('main')} + showCloseButton={true} + onClose={onClose} + /> + + + + + + + )} ) } diff --git a/src/shared/constants.ts b/src/shared/constants.ts index 6e7ac9835b..c45f37e3ae 100644 --- a/src/shared/constants.ts +++ b/src/shared/constants.ts @@ -4,6 +4,7 @@ export const gitHubUrl = 'https://github.com/deltachat/deltachat-desktop' export const gitHubIssuesUrl = gitHubUrl + '/issues' export const gitHubLicenseUrl = gitHubUrl + '/blob/master/LICENSE' export const donationUrl = 'https://delta.chat/donate' +export const contributeUrl = 'https://delta.chat/contribute' export const appWindowTitle = appName From 46af766f591efbfa7b80af992182c41da82fe37c Mon Sep 17 00:00:00 2001 From: SimonLaux Date: Fri, 13 Jan 2023 17:02:36 +0100 Subject: [PATCH 2/2] start with apple stuff --- _locales/_untranslated_en.json | 6 + docs/DEVELOPMENT.md | 16 ++ src/main/inAppPurchases.ts | 152 ++++++++++++++++++ .../components/dialogs/Settings-Donate.tsx | 17 +- 4 files changed, 189 insertions(+), 2 deletions(-) create mode 100644 src/main/inAppPurchases.ts diff --git a/_locales/_untranslated_en.json b/_locales/_untranslated_en.json index 852dd3aeca..36e26276ea 100644 --- a/_locales/_untranslated_en.json +++ b/_locales/_untranslated_en.json @@ -76,5 +76,11 @@ }, "donate_money_reason":{ "message": "Your support helps us to continue to improve Delta Chat." + }, + "in_app_donate_no_permission": { + "message": "The user is not allowed to make in-app purchase." + }, + "in_app_donate_not_availible": { + "message": "in-app donation failed to load, please conect to the internet" } } diff --git a/docs/DEVELOPMENT.md b/docs/DEVELOPMENT.md index 2423c1c0a1..fddacc7ead 100644 --- a/docs/DEVELOPMENT.md +++ b/docs/DEVELOPMENT.md @@ -267,3 +267,19 @@ https://developer.apple.com/library/archive/documentation/Miscellaneous/Referenc If you want to debug how many jsonrpc calls were made you can run `exp.printCallCounterResult()` in the devConsole when you have debug logging enabled. This can be useful if you want to test your debouncing logic or compare a branch against another branch, to see if you change reduced overall jsonrpc calls. + +### MacOS in app purchase stuff for donations on MacOS: + +To test it you need to replace the bundle id: + +``` +sed -i s/com.github.Electron/chat.delta.desktop.electron/ node_modules/electron/dist/Electron.app/Contents/Info.plist +``` + +> note: that the `-i` option does only work with the gnu version of `sed`. + + +#### Useful links: + +- https://help.apple.com/app-store-connect/#/devb57be10e7 +- Create a Sandbox Apple ID - https://help.apple.com/app-store-connect/#/dev8b997bee1 \ No newline at end of file diff --git a/src/main/inAppPurchases.ts b/src/main/inAppPurchases.ts new file mode 100644 index 0000000000..7cdb800d31 --- /dev/null +++ b/src/main/inAppPurchases.ts @@ -0,0 +1,152 @@ +import { inAppPurchase } from 'electron' +import { platform } from 'os' +import { getLogger } from '../shared/logger' + +const log = getLogger('main/inAppDonations') + +// Listen for transactions as soon as possible. +inAppPurchase.on( + 'transactions-updated', + async (_event: any, transactions: Electron.Transaction[]) => { + if (!Array.isArray(transactions)) { + log.info('No Transactions to process') + return + } + + // Check each transaction. + transactions.forEach(transaction => { + const payment = transaction.payment + + switch (transaction.transactionState) { + case 'purchasing': + log.info(`Purchasing ${payment.productIdentifier}...`) + break + + case 'purchased': { + log.info(`${payment.productIdentifier} purchased.`) + + // Get the receipt url. + const receiptURL = inAppPurchase.getReceiptURL() + + log.info(`Receipt URL: ${receiptURL}`) + + // just assume that the receipt is valid, there is no reason to fake it. + + // TODO try to load product information if not loaded yet, if not availible and failed return and let app try again later + + // TODO send thank you device message over jsonrpc -> to active account + + // Finish the transaction. + inAppPurchase.finishTransactionByDate(transaction.transactionDate) + + break + } + + case 'failed': + log.info(`Failed to purchase ${payment.productIdentifier}.`) + + // Finish the transaction. + inAppPurchase.finishTransactionByDate(transaction.transactionDate) + + break + case 'restored': + log.info( + `The purchase of ${payment.productIdentifier} has been restored.` + ) + + break + case 'deferred': + log.info( + `The purchase of ${payment.productIdentifier} has been deferred.` + ) + + break + default: + break + } + }) + } +) + +const PRODUCT_IDS = ['donation.small', 'default.monthly'] + +let loaded_products: Electron.Product[] | null = null + +async function loadProducts(): Promise { + if (!loaded_products) { + // Retrieve and display the product descriptions. + const products = await inAppPurchase.getProducts(PRODUCT_IDS) + + // Check the parameters. + if (!Array.isArray(products) || products.length <= 0) { + throw new Error('Unable to retrieve the product informations.') + } + + products.forEach(product => { + log.info( + `The price of ${product.localizedTitle} is ${product.formattedPrice}.` + ) + product.subscriptionPeriod + }) + + loaded_products = products + return products + } else { + return loaded_products + } +} + +async function setupInAppPurchases(): Promise< + | { inAppDonationAvailible: false } + | { inAppDonationAvailible: true; paymentsAllowed: false } + | { inAppDonationAvailible: true; paymentsAllowed: true; error: string } + | { + inAppDonationAvailible: true + paymentsAllowed: true + } +> { + // TODO check if appstore build + if (platform() !== 'darwin') { + // if not mac or not appstore build + return { + inAppDonationAvailible: false, + } + } + if (!inAppPurchase.canMakePayments()) { + return { + inAppDonationAvailible: true, + paymentsAllowed: false, + } + } else { + return { + inAppDonationAvailible: true, + paymentsAllowed: true, + } + } +} + +async function purchaseSingle( + productIdentifier: Electron.Product['productIdentifier'] +) { + if (await inAppPurchase.purchaseProduct(productIdentifier, 1)) { + log.info('The payment has been added to the payment queue.') + } else { + log.info('The product is not valid:', { productIdentifier }) + throw new Error('The product is not valid.') + } +} + +// TODO: +// - get availible products +// - show thank you device message you when done + +const THANK_YOU_MESSAGE = 'Thank you for donating $1!' +const THANK_YOU_MESSAGE_SUBSCRIPTION = + 'Thank you for your monthly support of $1.\n\n Remember that you can cancel anytime on https://apps.apple.com/account/subscriptions' + +const FAILED_MESSAGE = 'Your donation of $1 failed. :(' + + + +loadProducts().then(console.log) +setupInAppPurchases().then(console.log) \ No newline at end of file diff --git a/src/renderer/components/dialogs/Settings-Donate.tsx b/src/renderer/components/dialogs/Settings-Donate.tsx index b9160b7064..a58ee65b81 100644 --- a/src/renderer/components/dialogs/Settings-Donate.tsx +++ b/src/renderer/components/dialogs/Settings-Donate.tsx @@ -5,7 +5,7 @@ import { contributeUrl, donationUrl } from '../../../shared/constants' export function SettingsDonate({}: {}) { const tx = window.static_translate - const MoneySection = false /* isMacAppstoreBuild */ + const MoneySection = true /* isMacAppstoreBuild */ ? DonateMacOSAppstore : DonateMoney @@ -51,5 +51,18 @@ function DonateMoney() { } function DonateMacOSAppstore() { - return
options
+ + // only one recuring donation option, because otherwise we'd need to track if the user is already subscribed + return ( +
+ + +
+ ) }