Skip to content

Commit 05e0d50

Browse files
committed
Merge branch 'ADP-4954' into develop
# Conflicts: # versioned_docs/version-3.0/react-native-handling-onboarding-events.md # versioned_docs/version-3.0/react-native-present-paywalls.md # versioned_docs/version-3.0/react-native-quickstart-paywalls.md # versioned_docs/version-3.0/sdk-installation-reactnative.md # versioned_sidebars/version-3.0-sidebars.json
2 parents 846d083 + 804b662 commit 05e0d50

39 files changed

Lines changed: 1946 additions & 1113 deletions

docusaurus.config.js

Lines changed: 20 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -224,41 +224,41 @@ const config = {
224224
title: "Adapty SDK Sample Apps",
225225
items: [
226226
{
227-
label: "iOS",
228-
href: "https://github.com/adaptyteam/AdaptySDK-iOS/tree/master/Examples",
229-
},
230-
{
231-
label: "Android",
232-
href: "https://github.com/adaptyteam/AdaptySDK-Android",
233-
},
234-
{
235-
label: "Flutter",
236-
href: "https://github.com/adaptyteam/AdaptySDK-Flutter/tree/master/example",
237-
},
238-
{
239-
label: "Unity",
240-
href: "https://github.com/adaptyteam/AdaptySDK-Unity",
241-
},
242-
],
227+
"label": "Sample apps",
228+
"to": "/sample-apps"
229+
}
230+
]
243231
},
244232
{
245233
title: "SDK Reference",
246234
items: [
247235
{
248236
label: "iOS",
249-
href: "/sdk-installation-ios",
237+
href: "https://swift.adapty.io",
250238
},
251239
{
252240
label: "Android",
253-
href: "/sdk-installation-android",
241+
href: "https://android.adapty.io",
254242
},
255243
{
256244
label: "Flutter",
257-
href: "/sdk-installation-flutter",
245+
href: "https://pub.dev/documentation/adapty_flutter/latest/adapty_flutter/#classes",
258246
},
259247
{
260248
label: "React Native",
261-
href: "/sdk-installation-reactnative",
249+
href: "https://react-native.adapty.io",
250+
},
251+
{
252+
label: "Unity",
253+
href: "https://unity.adapty.io",
254+
},
255+
{
256+
label: "Kotlin Multiplatform",
257+
href: "https://kmp.adapty.io",
258+
},
259+
{
260+
label: "Capacitor",
261+
href: "https://capacitor.adapty.io",
262262
}
263263
],
264264
},
@@ -267,10 +267,6 @@ const config = {
267267
items: [
268268
{
269269
label: "Server-side API",
270-
href: "/docs/getting-started-with-server-side-api",
271-
},
272-
{
273-
label: "Adapty API",
274270
href: "/api-adapty",
275271
},
276272
{

src/components/reusable/WebhookEvents.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
| subscription_renewal_reactivated | Triggered when a user reactivates subscription auto-renewal. |
99
| subscription_expired | Triggered when a subscription fully ends after being canceled. For instance, if a user cancels a subscription on December 12th but it remains active until December 31st, the event is recorded on December 31st when the subscription expires. |
1010
| subscription_paused | Occurs when a user activates [subscription pause](https://developer.android.com/google/play/billing/subs#pause) (Android only). |
11-
| subscription_deferred | Triggered when a subscription purchase is [deferred](https://adapty.io/glossary/subscription-purchase-deferral/), allowing users to delay payment while maintaining access to premium features. This feature is available through the Google Play Developer APIand can be used for free trials or to accommodate users facing financial challenges. |
11+
| subscription_deferred | Triggered when a subscription purchase is [deferred](https://adapty.io/glossary/subscription-purchase-deferral/), allowing users to delay payment while maintaining access to premium features. This feature is available through the Google Play Developer API and can be used for free trials or to accommodate users facing financial challenges. |
1212
| non_subscription_purchase | Any non-subscription purchase, such as lifetime access or consumable products like in-game coins. |
1313
| trial_started | Triggered when a user activates a trial subscription. |
1414
| trial_converted | Occurs when a trial ends and the user is billed (first purchase). For example, if a user has a trial until January 14th but is billed on January 7th, this event is recorded on January 7th. |
@@ -19,4 +19,4 @@
1919
| billing_issue_detected | Triggered when a billing issue occurs during a charge attempt (e.g., insufficient card balance). |
2020
| subscription_refunded | Triggered when a subscription is refunded (e.g., by Apple Support). |
2121
| non_subscription_purchase_refunded | Triggered when a non-subscription purchase is refunded. |
22-
| access_level_updated | Occurs when a user's access level is updated. |
22+
| access_level_updated | Occurs when a user's access level is updated. |
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
import React, { useState, useEffect, useRef } from 'react';
8+
import clsx from 'clsx';
9+
import { ThemeClassNames } from '@docusaurus/theme-common';
10+
import {
11+
useAnnouncementBar,
12+
useScrollPosition,
13+
} from '@docusaurus/theme-common/internal';
14+
import { translate } from '@docusaurus/Translate';
15+
import DocSidebarItems from '@theme/DocSidebarItems';
16+
import styles from './styles.module.css';
17+
18+
function useShowAnnouncementBar() {
19+
const { isActive } = useAnnouncementBar();
20+
const [showAnnouncementBar, setShowAnnouncementBar] = useState(isActive);
21+
useScrollPosition(
22+
({ scrollY }) => {
23+
if (isActive) {
24+
setShowAnnouncementBar(scrollY === 0);
25+
}
26+
},
27+
[isActive],
28+
);
29+
return isActive && showAnnouncementBar;
30+
}
31+
32+
export default function DocSidebarDesktopContent({ path, sidebar, className }) {
33+
console.log('DocSidebarDesktopContent mounted', { path });
34+
const showAnnouncementBar = useShowAnnouncementBar();
35+
const menuRef = useRef(null);
36+
37+
useEffect(() => {
38+
// Function to handle active item scrolling and duplicate cleanup
39+
const handleActiveItem = () => {
40+
if (!menuRef.current) return;
41+
42+
// Find all active links
43+
// Docusaurus uses 'menu__link--active' for the active item
44+
const activeLinks = menuRef.current.querySelectorAll('.menu__link--active');
45+
46+
if (activeLinks.length > 0) {
47+
// If there are duplicates, keep only the first one active
48+
// We check href to ensure we don't un-highlight distinct active items (like parent categories)
49+
const seenHrefs = new Set();
50+
51+
activeLinks.forEach((link) => {
52+
const href = link.getAttribute('href');
53+
if (seenHrefs.has(href)) {
54+
link.classList.remove('menu__link--active');
55+
link.removeAttribute('aria-current');
56+
} else {
57+
seenHrefs.add(href);
58+
}
59+
});
60+
61+
const activeItem = activeLinks[0];
62+
63+
// Check if the item is already visible in the viewport
64+
const rect = activeItem.getBoundingClientRect();
65+
const isVisible = (
66+
rect.top >= 0 &&
67+
rect.left >= 0 &&
68+
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
69+
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
70+
);
71+
72+
// Only scroll if not fully visible
73+
if (!isVisible) {
74+
activeItem.scrollIntoView({
75+
behavior: 'auto',
76+
block: 'center',
77+
inline: 'nearest'
78+
});
79+
}
80+
}
81+
};
82+
83+
// Initial check
84+
handleActiveItem();
85+
86+
// Use MutationObserver to detect changes in the sidebar (e.g. expansion, loading)
87+
const observer = new MutationObserver((mutations) => {
88+
handleActiveItem();
89+
});
90+
91+
if (menuRef.current) {
92+
observer.observe(menuRef.current, {
93+
childList: true,
94+
subtree: true,
95+
attributes: true,
96+
attributeFilter: ['class']
97+
});
98+
}
99+
100+
return () => {
101+
observer.disconnect();
102+
};
103+
}, [path, sidebar]); // Re-run when path or sidebar changes
104+
105+
return (
106+
<nav
107+
ref={menuRef}
108+
aria-label={translate({
109+
id: 'theme.docs.sidebar.navAriaLabel',
110+
message: 'Docs sidebar',
111+
description: 'The ARIA label for the sidebar navigation',
112+
})}
113+
className={clsx(
114+
'menu',
115+
styles.menu,
116+
showAnnouncementBar && styles.menuWithAnnouncementBar,
117+
className,
118+
)}>
119+
<ul className={clsx(ThemeClassNames.docs.docSidebarMenu, 'menu__list')}>
120+
<DocSidebarItems items={sidebar} activePath={path} level={1} />
121+
</ul>
122+
</nav>
123+
);
124+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/**
2+
* Copyright (c) Facebook, Inc. and its affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
8+
@media (min-width: 997px) {
9+
.menu {
10+
flex-grow: 1;
11+
padding: 0.5rem;
12+
}
13+
@supports (scrollbar-gutter: stable) {
14+
.menu {
15+
padding: 0.5rem 0 0.5rem 0.5rem;
16+
scrollbar-gutter: stable;
17+
}
18+
}
19+
20+
.menuWithAnnouncementBar {
21+
margin-bottom: var(--docusaurus-announcement-bar-height);
22+
}
23+
}

src/theme/DocSidebar/Desktop/index.jsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import clsx from 'clsx';
33
import { useThemeConfig } from '@docusaurus/theme-common';
44
import Logo from '@theme/Logo';
55
import CollapseButton from '@theme/DocSidebar/Desktop/CollapseButton';
6-
import Content from '@theme/DocSidebar/Desktop/Content';
6+
import Content from './Content';
77
import styles from './styles.module.css';
88
import SidebarMenu from '../../../components/SidebarMenu';
99

1010
function DocSidebarDesktop({ path, sidebar, onCollapse, isHidden }) {
11+
console.log('DocSidebarDesktop mounted');
1112
const {
1213
navbar: { hideOnScroll },
1314
docs: {

versioned_docs/version-3.0/app-store-connection-configuration.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ into the **Private key (.p8 file)** field in the Adapty Dashboard.
101101
## Step 4. For trials and special offers – set up promotional offers
102102

103103
:::important
104-
This step is required if your app has trials or other promotional offers.
104+
This step is required if your app has [trials or other promotional offers](offers).
105105
:::
106106

107107
1. Copy the same key ID you used in [Step 2](#step-2-provide-issuer-id-and-key-id) to the **Subscription key ID** field in the **App Store promotional offers** section.
@@ -287,4 +287,4 @@ Generate an App Store Connect API key and add it to Adapty to be able to [manage
287287

288288
**What's next**
289289

290-
- [Enable App Store server notifications](enable-app-store-server-notifications)
290+
- [Enable App Store server notifications](enable-app-store-server-notifications)

versioned_docs/version-3.0/autopilot.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ Before you start, consider the following:
4343

4444
To create a report on your app:
4545

46-
1. Go to **A/B tests** from the dashboard sidebar and click **Start Pricing Analysis**.
46+
1. Go to **A/B tests** from the dashboard sidebar and click **Adapty autopilot**.
4747

4848
<Zoom>
4949
<img src={require('./img/pricing-analysis.webp').default}
@@ -65,12 +65,12 @@ If the analysis doesn't start, it may be due to one of the following reasons:
6565
- Your top-performing paywall has two or more products with the same period.
6666
:::
6767

68-
2. We automatically detect your top-performing placement based on revenue data. If you'd like to analyze a different placement, select one from the **Placement** dropdown. Then, click **Analyze this app**. After that, Adapty will need some time to analyze your app, but don't leave the page – it won't take long.
68+
2. We automatically detect your top-performing placement based on revenue data. If you'd like to analyze a different placement, select one from the **Placement** dropdown. Then, click **Analyze this placement**. After that, Adapty will need some time to analyze your app, but don't leave the page – it won't take long.
6969
<Zoom>
7070
<img src={require('./img/select-placement.webp').default}
7171
style={{
7272
border: '1px solid #727272', /* border width and color */
73-
width: '700px', /* image width */
73+
width: '500px', /* image width */
7474
display: 'block', /* for alignment */
7575
margin: '0 auto' /* center alignment */
7676
}}

versioned_docs/version-3.0/capacitor-handling-onboarding-events.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ try {
4040
},
4141
onPaywall(actionId, meta) {
4242
console.log('Paywall action:', actionId);
43+
view.dismiss().then(() => {
44+
openPaywall(actionId);
45+
});
4346
},
4447
onStateUpdated(action, meta) {
4548
console.log('State updated:', action);
@@ -190,15 +193,23 @@ view.setEventHandlers({
190193
Handle this event to open a paywall if you want to open it inside the onboarding. If you want to open a paywall after it is closed, there is a more straightforward way to do it – handle the close action and open a paywall without relying on the event data.
191194
:::
192195

193-
If a user clicks a button that opens a paywall, you will get a button action ID that you [set up manually](get-paid-in-onboardings.md). The most seamless way to work with paywalls in onboardings is to make the action ID equal to a paywall placement ID:
196+
If a user clicks a button that opens a paywall, you will get a button action ID that you [set up manually](get-paid-in-onboardings.md). The most seamless way to work with paywalls in onboardings is to make the action ID equal to a paywall placement ID.
197+
198+
Note that, for iOS, only one view (paywall or onboarding) can be displayed on screen at a time. If you present a paywall on top of an onboarding, you cannot programmatically control the onboarding in the background. Attempting to dismiss the onboarding will close the paywall instead, leaving the onboarding visible. To avoid this, always dismiss the onboarding view before presenting the paywall.
194199

195200
```typescript showLineNumbers
196201
view.setEventHandlers({
197202
onPaywall(actionId, meta) {
198-
console.log('Paywall action triggered:', actionId);
199-
// Implement your paywall opening logic here
203+
// Dismiss onboarding before presenting paywall
204+
view.dismiss().then(() => {
205+
openPaywall(actionId);
206+
});
200207
},
201208
});
209+
210+
async function openPaywall(placementId: string) {
211+
// Implement your paywall opening logic here
212+
}
202213
```
203214

204215
<Details>

versioned_docs/version-3.0/flutter-handling-onboarding-events.md

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@ void onboardingViewOnPaywallAction(
7171
AdaptyUIOnboardingMeta meta,
7272
String actionId,
7373
) {
74-
_openPaywall(actionId);
74+
// Dismiss onboarding before presenting paywall
75+
view.dismiss().then((_) {
76+
_openPaywall(actionId);
77+
});
7578
}
7679

7780
void onboardingViewOnCustomAction(
@@ -286,14 +289,19 @@ Handle this event to open a paywall if you want to open it inside the onboarding
286289

287290
If a user clicks a button that opens a paywall, you will get a button action ID that you [set up manually](get-paid-in-onboardings.md). The most seamless way to work with paywalls in onboardings is to make the action ID equal to a paywall placement ID:
288291

292+
Note that, for iOS, only one view (paywall or onboarding) can be displayed on screen at a time. If you present a paywall on top of an onboarding, you cannot programmatically control the onboarding in the background. Attempting to dismiss the onboarding will close the paywall instead, leaving the onboarding visible. To avoid this, always dismiss the onboarding view before presenting the paywall.
293+
289294
```javascript showLineNumbers title="Flutter"
290295
// Full-screen presentation
291296
void onboardingViewOnPaywallAction(
292297
AdaptyUIOnboardingView view,
293298
AdaptyUIOnboardingMeta meta,
294299
String actionId,
295300
) {
296-
_openPaywall(actionId);
301+
// Dismiss onboarding before presenting paywall
302+
view.dismiss().then((_) {
303+
_openPaywall(actionId);
304+
});
297305
}
298306

299307
Future<void> _openPaywall(String actionId) async {
43.6 KB
Loading

0 commit comments

Comments
 (0)