feat(ios): upgrade button in setting (#13645)
<!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - New Features - Added a Subscription section in Mobile Settings (for signed-in users) with plan info and an Upgrade button that opens the native paywall. - Supports showing “Pro” and “AI” paywalls. - Integrated native paywall provider on iOS. - Style - Introduced new styling for the subscription card, content, and button. - Localization - Added English strings for subscription title, description, and button. - Chores - Minor iOS project cleanup and internal wiring to enable the paywall module. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -88,6 +88,8 @@
|
|||||||
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
/* Begin PBXFileSystemSynchronizedRootGroup section */
|
||||||
9DAE85B72E7BAC3B00DB9F1D /* Plugins */ = {
|
9DAE85B72E7BAC3B00DB9F1D /* Plugins */ = {
|
||||||
isa = PBXFileSystemSynchronizedRootGroup;
|
isa = PBXFileSystemSynchronizedRootGroup;
|
||||||
|
exceptions = (
|
||||||
|
);
|
||||||
path = Plugins;
|
path = Plugins;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
@@ -307,13 +309,9 @@
|
|||||||
);
|
);
|
||||||
inputFileListPaths = (
|
inputFileListPaths = (
|
||||||
);
|
);
|
||||||
inputPaths = (
|
|
||||||
);
|
|
||||||
name = "[CP] Embed Pods Frameworks";
|
name = "[CP] Embed Pods Frameworks";
|
||||||
outputFileListPaths = (
|
outputFileListPaths = (
|
||||||
);
|
);
|
||||||
outputPaths = (
|
|
||||||
);
|
|
||||||
runOnlyForDeploymentPostprocessing = 0;
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
shellPath = /bin/sh;
|
shellPath = /bin/sh;
|
||||||
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AFFiNE/Pods-AFFiNE-frameworks.sh\"\n";
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-AFFiNE/Pods-AFFiNE-frameworks.sh\"\n";
|
||||||
|
|||||||
@@ -45,13 +45,13 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../../../../../node_modules/capacitor-plugin-app-tracking-transparency"
|
:path: "../../../../../node_modules/capacitor-plugin-app-tracking-transparency"
|
||||||
|
|
||||||
SPEC CHECKSUMS:
|
SPEC CHECKSUMS:
|
||||||
Capacitor: 03bc7cbdde6a629a8b910a9d7d78c3cc7ed09ea7
|
Capacitor: 106e7a4205f4618d582b886a975657c61179138d
|
||||||
CapacitorApp: febecbb9582cb353aed037e18ec765141f880fe9
|
CapacitorApp: d63334c052278caf5d81585d80b21905c6f93f39
|
||||||
CapacitorBrowser: 6299776d496e968505464884d565992faa20444a
|
CapacitorBrowser: 081852cf532acf77b9d2953f3a88fe5b9711fb06
|
||||||
CapacitorCordova: 5967b9ba03915ef1d585469d6e31f31dc49be96f
|
CapacitorCordova: 5967b9ba03915ef1d585469d6e31f31dc49be96f
|
||||||
CapacitorHaptics: 1f1e17041f435d8ead9ff2a34edd592c6aa6a8d6
|
CapacitorHaptics: 70e47470fa1a6bd6338cd102552e3846b7f9a1b3
|
||||||
CapacitorKeyboard: 09fd91dcde4f8a37313e7f11bde553ad1ed52036
|
CapacitorKeyboard: 969647d0ca2e5c737d7300088e2517aa832434e2
|
||||||
CapacitorPluginAppTrackingTransparency: 92ae9c1cfb5cf477753db9269689332a686f675a
|
CapacitorPluginAppTrackingTransparency: 2a2792623a5a72795f2e8f9ab3f1147573732fd8
|
||||||
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483
|
||||||
|
|
||||||
PODFILE CHECKSUM: 2c1e4be82121f2d9724ecf7e31dd14e165aeb082
|
PODFILE CHECKSUM: 2c1e4be82121f2d9724ecf7e31dd14e165aeb082
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { FeatureFlagService } from '@affine/core/modules/feature-flag';
|
|||||||
import { GlobalContextService } from '@affine/core/modules/global-context';
|
import { GlobalContextService } from '@affine/core/modules/global-context';
|
||||||
import { I18nProvider } from '@affine/core/modules/i18n';
|
import { I18nProvider } from '@affine/core/modules/i18n';
|
||||||
import { LifecycleService } from '@affine/core/modules/lifecycle';
|
import { LifecycleService } from '@affine/core/modules/lifecycle';
|
||||||
|
import { NativePaywallProvider } from '@affine/core/modules/paywall';
|
||||||
import {
|
import {
|
||||||
configureLocalStorageStateStorageImpls,
|
configureLocalStorageStateStorageImpls,
|
||||||
NbstoreProvider,
|
NbstoreProvider,
|
||||||
@@ -63,6 +64,7 @@ import { ModalConfigProvider } from './modal-config';
|
|||||||
import { Auth } from './plugins/auth';
|
import { Auth } from './plugins/auth';
|
||||||
import { Hashcash } from './plugins/hashcash';
|
import { Hashcash } from './plugins/hashcash';
|
||||||
import { NbStoreNativeDBApis } from './plugins/nbstore';
|
import { NbStoreNativeDBApis } from './plugins/nbstore';
|
||||||
|
import { PayWall } from './plugins/paywall';
|
||||||
import { writeEndpointToken } from './proxy';
|
import { writeEndpointToken } from './proxy';
|
||||||
import { enableNavigationGesture$ } from './web-navigation-control';
|
import { enableNavigationGesture$ } from './web-navigation-control';
|
||||||
|
|
||||||
@@ -198,6 +200,11 @@ framework.scope(ServerScope).override(AuthProvider, resolver => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
framework.impl(NativePaywallProvider, {
|
||||||
|
showPaywall: async (type: 'Pro' | 'AI') => {
|
||||||
|
await PayWall.showPayWall({ type });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const frameworkProvider = framework.provider();
|
const frameworkProvider = framework.provider();
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface PayWallPlugin {
|
export interface PayWallPlugin {
|
||||||
showPayWall(options: {
|
showPayWall(options: {
|
||||||
type: string;
|
type: 'Pro' | 'AI';
|
||||||
}): Promise<{ success: boolean; type: string }>;
|
}): Promise<{ success: boolean; type: string }>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import { AppearanceGroup } from './appearance';
|
|||||||
import { ExperimentalFeatureSetting } from './experimental';
|
import { ExperimentalFeatureSetting } from './experimental';
|
||||||
import { OthersGroup } from './others';
|
import { OthersGroup } from './others';
|
||||||
import * as styles from './style.css';
|
import * as styles from './style.css';
|
||||||
|
import { UserSubscription } from './subscription';
|
||||||
import { SwipeDialog } from './swipe-dialog';
|
import { SwipeDialog } from './swipe-dialog';
|
||||||
import { UserProfile } from './user-profile';
|
import { UserProfile } from './user-profile';
|
||||||
import { UserUsage } from './user-usage';
|
import { UserUsage } from './user-usage';
|
||||||
@@ -23,6 +24,7 @@ const MobileSetting = () => {
|
|||||||
return (
|
return (
|
||||||
<div className={styles.root}>
|
<div className={styles.root}>
|
||||||
<UserProfile />
|
<UserProfile />
|
||||||
|
<UserSubscription />
|
||||||
<UserUsage />
|
<UserUsage />
|
||||||
<AppearanceGroup />
|
<AppearanceGroup />
|
||||||
<AboutGroup />
|
<AboutGroup />
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { Button } from '@affine/component';
|
||||||
|
import { AuthService, ServerService } from '@affine/core/modules/cloud';
|
||||||
|
import { NativePaywallService } from '@affine/core/modules/paywall';
|
||||||
|
import { useI18n } from '@affine/i18n';
|
||||||
|
import { useLiveData, useService } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import * as styles from './styles.css';
|
||||||
|
|
||||||
|
export const UserSubscription = () => {
|
||||||
|
const serverService = useService(ServerService);
|
||||||
|
const authService = useService(AuthService);
|
||||||
|
const nativePaywallProvider =
|
||||||
|
useService(NativePaywallService).getNativePaywallProvider();
|
||||||
|
const t = useI18n();
|
||||||
|
|
||||||
|
const supported = useLiveData(
|
||||||
|
serverService.server.features$.map(f => f.payment)
|
||||||
|
);
|
||||||
|
|
||||||
|
const loggedIn = useLiveData(authService.session.status$) === 'authenticated';
|
||||||
|
|
||||||
|
if (!loggedIn) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!supported) {
|
||||||
|
// TODO: enable this
|
||||||
|
// return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!nativePaywallProvider) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={styles.root}>
|
||||||
|
<div className={styles.content}>
|
||||||
|
<div className={styles.title}>
|
||||||
|
{t['com.affine.payment.subscription.title']()}
|
||||||
|
</div>
|
||||||
|
<div className={styles.description}>
|
||||||
|
{t['com.affine.payment.subscription.description']()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<Button
|
||||||
|
className={styles.button}
|
||||||
|
variant="primary"
|
||||||
|
onClick={() =>
|
||||||
|
void nativePaywallProvider.showPaywall('Pro').catch(console.error)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t['com.affine.payment.subscription.button']()}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { cssVarV2 } from '@toeverything/theme/v2';
|
||||||
|
import { style } from '@vanilla-extract/css';
|
||||||
|
|
||||||
|
export const root = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 16,
|
||||||
|
border: `1px solid ${cssVarV2('database/border')}`,
|
||||||
|
borderRadius: '12px',
|
||||||
|
padding: '10px 16px',
|
||||||
|
backgroundColor: cssVarV2('edgeless/selection/selectionMarqueeBackground'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const content = style({
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 4,
|
||||||
|
});
|
||||||
|
|
||||||
|
export const title = style({
|
||||||
|
fontSize: '17px',
|
||||||
|
lineHeight: '22px',
|
||||||
|
fontWeight: 600,
|
||||||
|
color: cssVarV2('text/primary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const description = style({
|
||||||
|
fontSize: '13px',
|
||||||
|
lineHeight: '18px',
|
||||||
|
fontWeight: 400,
|
||||||
|
color: cssVarV2('text/secondary'),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const button = style({
|
||||||
|
fontSize: '15px',
|
||||||
|
});
|
||||||
@@ -44,6 +44,7 @@ import { configureNavigationPanelModule } from './navigation-panel';
|
|||||||
import { configureNotificationModule } from './notification';
|
import { configureNotificationModule } from './notification';
|
||||||
import { configureOpenInApp } from './open-in-app';
|
import { configureOpenInApp } from './open-in-app';
|
||||||
import { configureOrganizeModule } from './organize';
|
import { configureOrganizeModule } from './organize';
|
||||||
|
import { configurePaywallModule } from './paywall';
|
||||||
import { configurePDFModule } from './pdf';
|
import { configurePDFModule } from './pdf';
|
||||||
import { configurePeekViewModule } from './peek-view';
|
import { configurePeekViewModule } from './peek-view';
|
||||||
import { configurePermissionsModule } from './permissions';
|
import { configurePermissionsModule } from './permissions';
|
||||||
@@ -130,4 +131,5 @@ export function configureCommonModules(framework: Framework) {
|
|||||||
configureIndexerEmbeddingModule(framework);
|
configureIndexerEmbeddingModule(framework);
|
||||||
configureCommentModule(framework);
|
configureCommentModule(framework);
|
||||||
configureDocSummaryModule(framework);
|
configureDocSummaryModule(framework);
|
||||||
|
configurePaywallModule(framework);
|
||||||
}
|
}
|
||||||
|
|||||||
10
packages/frontend/core/src/modules/paywall/index.ts
Normal file
10
packages/frontend/core/src/modules/paywall/index.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Framework } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { NativePaywallService } from './services/native-paywall';
|
||||||
|
|
||||||
|
export { NativePaywallProvider } from './providers/native-paywall';
|
||||||
|
export { NativePaywallService } from './services/native-paywall';
|
||||||
|
|
||||||
|
export function configurePaywallModule(framework: Framework) {
|
||||||
|
framework.service(NativePaywallService);
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import { createIdentifier } from '@toeverything/infra';
|
||||||
|
|
||||||
|
export interface NativePaywallProvider {
|
||||||
|
showPaywall(type: 'Pro' | 'AI'): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const NativePaywallProvider = createIdentifier<NativePaywallProvider>(
|
||||||
|
'NativePaywallProvider'
|
||||||
|
);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Service } from '@toeverything/infra';
|
||||||
|
|
||||||
|
import { NativePaywallProvider } from '../providers/native-paywall';
|
||||||
|
|
||||||
|
export class NativePaywallService extends Service {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
getNativePaywallProvider() {
|
||||||
|
return this.framework.getOptional(NativePaywallProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
"it-IT": 100,
|
"it-IT": 100,
|
||||||
"it": 1,
|
"it": 1,
|
||||||
"ja": 100,
|
"ja": 100,
|
||||||
"ko": 53,
|
"ko": 52,
|
||||||
"pl": 100,
|
"pl": 100,
|
||||||
"pt-BR": 100,
|
"pt-BR": 100,
|
||||||
"ru": 100,
|
"ru": 100,
|
||||||
|
|||||||
@@ -8306,6 +8306,18 @@ export function useAFFiNEI18N(): {
|
|||||||
* `Only current mode`
|
* `Only current mode`
|
||||||
*/
|
*/
|
||||||
["com.affine.comment.filter.only-current-mode"](): string;
|
["com.affine.comment.filter.only-current-mode"](): string;
|
||||||
|
/**
|
||||||
|
* `Unlock more features`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.subscription.title"](): string;
|
||||||
|
/**
|
||||||
|
* `The universal editor that lets you work, play, present or create just about anything.`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.subscription.description"](): string;
|
||||||
|
/**
|
||||||
|
* `Upgrade`
|
||||||
|
*/
|
||||||
|
["com.affine.payment.subscription.button"](): string;
|
||||||
/**
|
/**
|
||||||
* `Reply`
|
* `Reply`
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -2084,6 +2084,9 @@
|
|||||||
"com.affine.comment.filter.show-resolved": "Show resolved comments",
|
"com.affine.comment.filter.show-resolved": "Show resolved comments",
|
||||||
"com.affine.comment.filter.only-my-replies": "Only my replies and mentions",
|
"com.affine.comment.filter.only-my-replies": "Only my replies and mentions",
|
||||||
"com.affine.comment.filter.only-current-mode": "Only current mode",
|
"com.affine.comment.filter.only-current-mode": "Only current mode",
|
||||||
|
"com.affine.payment.subscription.title": "Unlock more features",
|
||||||
|
"com.affine.payment.subscription.description": "The universal editor that lets you work, play, present or create just about anything.",
|
||||||
|
"com.affine.payment.subscription.button": "Upgrade",
|
||||||
"com.affine.comment.reply": "Reply",
|
"com.affine.comment.reply": "Reply",
|
||||||
"com.affine.comment.copy-link": "Copy link",
|
"com.affine.comment.copy-link": "Copy link",
|
||||||
"com.affine.context-menu.copy": "Copy",
|
"com.affine.context-menu.copy": "Copy",
|
||||||
|
|||||||
Reference in New Issue
Block a user