Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add PaywallFooterView listeners #1012

Merged
merged 2 commits into from
Mar 14, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions api_tester/lib/api_tests/purchases_ui_flutter_api_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,51 @@ class _PurchasesFlutterApiTest {
);
}

Widget _checkPaywallFooterView() {
return Scaffold(
body: Center(
child: PaywallFooterView(
contentCreator: (double bottomPadding) {
return Container();
},
),
),
);
}

Widget _checkPaywallFooterViewWithOffering(Offering offering) {
return Scaffold(
body: Center(
child: PaywallFooterView(
offering: offering,
contentCreator: (double bottomPadding) {
return Container();
},
),
),
);
}

Widget _checkPaywallFooterViewWithListeners(Offering offering) {
return Scaffold(
body: Center(
child: PaywallFooterView(
onPurchaseStarted: (Package rcPackage) {
},
onPurchaseCompleted:
(CustomerInfo customerInfo, StoreTransaction storeTransaction) {
},
onPurchaseError: (PurchasesError error) {
},
onRestoreCompleted: (CustomerInfo customerInfo) {
},
onRestoreError: (PurchasesError error) {
},
contentCreator: (double bottomPadding) {
return Container();
},
),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.view.Gravity
import android.view.View
import android.widget.FrameLayout
import androidx.core.view.children
import com.revenuecat.purchases.hybridcommon.ui.PaywallListenerWrapper
import com.revenuecat.purchases.ui.revenuecatui.ExperimentalPreviewRevenueCatUIPurchasesAPI
import com.revenuecat.purchases.ui.revenuecatui.views.PaywallFooterView as NativePaywallFooterView
import io.flutter.plugin.common.BinaryMessenger
Expand All @@ -32,7 +33,7 @@ internal class PaywallFooterView(
override fun dispose() {}

init {
methodChannel = MethodChannel(messenger, "purchases_ui_flutter/PaywallFooterView/${id}")
methodChannel = MethodChannel(messenger, "com.revenuecat.purchasesui/PaywallFooterView/${id}")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed the channel name to be consistent with the full screen version

val offeringIdentifier = creationParams["offeringIdentifier"] as String?
nativePaywallFooterView = object : NativePaywallFooterView(context) {
public override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
Expand All @@ -51,6 +52,30 @@ internal class PaywallFooterView(
updateHeight(finalHeight.toDouble())
}
}
nativePaywallFooterView.setPaywallListener(object : PaywallListenerWrapper() {
override fun onPurchaseStarted(rcPackage: Map<String, Any?>) {
methodChannel.invokeMethod("onPurchaseStarted", rcPackage)
}

override fun onPurchaseCompleted(customerInfo: Map<String, Any?>, storeTransaction: Map<String, Any?>) {
methodChannel.invokeMethod(
"onPurchaseCompleted",
mapOf("customerInfo" to customerInfo, "storeTransaction" to storeTransaction)
)
}

override fun onPurchaseError(error: Map<String, Any?>) {
methodChannel.invokeMethod("onPurchaseError", error)
}

override fun onRestoreCompleted(customerInfo: Map<String, Any?>) {
methodChannel.invokeMethod("onRestoreCompleted", customerInfo)
}

override fun onRestoreError(error: Map<String, Any?>) {
methodChannel.invokeMethod("onRestoreError", error)
}
})
nativePaywallFooterView.layoutParams = FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
FrameLayout.LayoutParams.MATCH_PARENT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class PurchasesUiPaywallFooterViewFactory: NSObject, FlutterPlatformViewFactory
class PurchasesUiPaywallFooterView: NSObject, FlutterPlatformView {
private let _view: UIView
private var _paywallProxy: PaywallProxy?
// Need to keep the controller in memory while this view is alive otherwise the delegate is dealocated
private var _paywallFooterViewController: PaywallFooterViewController
private let channel: FlutterMethodChannel

Expand All @@ -47,7 +48,7 @@ class PurchasesUiPaywallFooterView: NSObject, FlutterPlatformView {
arguments args: Any?,
binaryMessenger messenger: FlutterBinaryMessenger
) {
channel = FlutterMethodChannel(name: "purchases_ui_flutter/PaywallFooterView/\(viewId)",
channel = FlutterMethodChannel(name: "com.revenuecat.purchasesui/PaywallFooterView/\(viewId)",
binaryMessenger: messenger)
let paywallProxy = PaywallProxy()
_paywallProxy = paywallProxy
Expand All @@ -72,7 +73,6 @@ class PurchasesUiPaywallFooterView: NSObject, FlutterPlatformView {
func view() -> UIView {
return _view
}

}


Expand All @@ -84,4 +84,32 @@ extension PurchasesUiPaywallFooterView: PaywallViewControllerDelegateWrapper {
channel.invokeMethod("onHeightChanged", arguments: newHeight)
}

func paywallViewController(_ controller: PaywallViewController,
didStartPurchaseWith packageDictionary: [String : Any]) {
channel.invokeMethod("onPurchaseStarted", arguments: packageDictionary)
}

func paywallViewController(_ controller: PaywallViewController,
didFinishPurchasingWith customerInfoDictionary: [String : Any],
transaction transactionDictionary: [String : Any]?) {
channel.invokeMethod("onPurchaseCompleted", arguments: [
"customerInfo":customerInfoDictionary,
"storeTransaction":transactionDictionary
])
}

func paywallViewController(_ controller: PaywallViewController,
didFailPurchasingWith errorDictionary: [String : Any]) {
channel.invokeMethod("onPurchaseError", arguments: errorDictionary)
}

func paywallViewController(_ controller: PaywallViewController,
didFinishRestoringWith customerInfoDictionary: [String : Any]) {
channel.invokeMethod("onRestoreCompleted", arguments: customerInfoDictionary)
}

func paywallViewController(_ controller: PaywallViewController,
didFailRestoringWith errorDictionary: [String : Any]) {
channel.invokeMethod("onRestoreError", arguments: errorDictionary)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ class PurchasesUiPaywallView: NSObject, FlutterPlatformView {
private var _view: UIView
private var _paywallProxy: PaywallProxy?
private var _methodChannel: FlutterMethodChannel
// Need to keep the controller in memory otherwise while this view is alive otherwise the delegate is dealocated
// Need to keep the controller in memory while this view is alive otherwise the delegate is dealocated
private var _paywallViewController: PaywallViewController

init(
Expand Down
78 changes: 44 additions & 34 deletions purchases_ui_flutter/lib/views/internal_paywall_footer_view.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,31 +5,41 @@ import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:purchases_flutter/models/customer_info_wrapper.dart';
import 'package:purchases_flutter/models/offering_wrapper.dart';
import 'package:purchases_flutter/models/package_wrapper.dart';
import 'package:purchases_flutter/models/purchases_error.dart';
import 'package:purchases_flutter/models/store_transaction.dart';

class InternalPaywallFooterView extends StatefulWidget {
import 'paywall_view_method_handler.dart';

class InternalPaywallFooterView extends StatelessWidget {
final Offering? offering;
final Function(Package rcPackage)? onPurchaseStarted;
final Function(CustomerInfo customerInfo, StoreTransaction storeTransaction)?
onPurchaseCompleted;
final Function(PurchasesError)? onPurchaseError;
final Function(CustomerInfo customerInfo)? onRestoreCompleted;
final Function(PurchasesError)? onRestoreError;
final Function(double) onHeightChanged;

const InternalPaywallFooterView({
Key? key,
this.offering,
this.onPurchaseStarted,
this.onPurchaseCompleted,
this.onPurchaseError,
this.onRestoreCompleted,
this.onRestoreError,
required this.onHeightChanged,
}) : super(key: key);

static const String _viewType = 'com.revenuecat.purchasesui/PaywallFooterView';

@override
State<InternalPaywallFooterView> createState() => _InternalPaywallFooterViewState();
}

class _InternalPaywallFooterViewState extends State<InternalPaywallFooterView> {
MethodChannel? _channel;

@override
Widget build(BuildContext context) {
final creationParams = <String, dynamic>{
'offeringIdentifier': widget.offering?.identifier,
'offeringIdentifier': offering?.identifier,
};

return Platform.isAndroid
Expand All @@ -38,44 +48,26 @@ class _InternalPaywallFooterViewState extends State<InternalPaywallFooterView> {
}

UiKitView _buildUiKitView(Map<String, dynamic> creationParams) => UiKitView(
viewType: InternalPaywallFooterView._viewType,
viewType: _viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
onPlatformViewCreated: (id) {
_channel = MethodChannel(
'purchases_ui_flutter/PaywallFooterView/$id',
);
_channel?.setMethodCallHandler((call) async {
if (call.method == 'onHeightChanged') {
widget.onHeightChanged(call.arguments as double);
}
});
},
onPlatformViewCreated: _buildListenerChannel,
);

PlatformViewLink _buildAndroidPlatformViewLink(
Map<String, dynamic> creationParams,
) =>
PlatformViewLink(
viewType: InternalPaywallFooterView._viewType,
viewType: _viewType,
surfaceFactory: (context, controller) => AndroidViewSurface(
controller: controller as AndroidViewController,
gestureRecognizers: const <Factory<OneSequenceGestureRecognizer>>{},
hitTestBehavior: PlatformViewHitTestBehavior.opaque,
),
onCreatePlatformView: (params) {
_channel = MethodChannel(
'purchases_ui_flutter/PaywallFooterView/${params.id}',
);
_channel?.setMethodCallHandler((call) async {
if (call.method == 'onHeightChanged') {
widget.onHeightChanged(call.arguments as double);
}
});
return PlatformViewsService.initSurfaceAndroidView(
onCreatePlatformView: (params) => PlatformViewsService.initSurfaceAndroidView(
id: params.id,
viewType: InternalPaywallFooterView._viewType,
viewType: _viewType,
layoutDirection: TextDirection.ltr,
creationParams: creationParams,
creationParamsCodec: const StandardMessageCodec(),
Expand All @@ -84,7 +76,25 @@ class _InternalPaywallFooterViewState extends State<InternalPaywallFooterView> {
},
)
..addOnPlatformViewCreatedListener(params.onPlatformViewCreated)
..create();
},
..addOnPlatformViewCreatedListener(_buildListenerChannel)
..create(),
);

void _buildListenerChannel(int id) {
final handler = PaywallViewMethodHandler(
onPurchaseStarted,
onPurchaseCompleted,
onPurchaseError,
onRestoreCompleted,
onRestoreError,
);
MethodChannel('com.revenuecat.purchasesui/PaywallFooterView/$id')
.setMethodCallHandler((call) async {
if (call.method == 'onHeightChanged') {
onHeightChanged(call.arguments as double);
} else {
handler.handleMethodCall(call);
}
});
}
}
36 changes: 36 additions & 0 deletions purchases_ui_flutter/lib/views/paywall_footer_view.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:purchases_flutter/models/customer_info_wrapper.dart';
import 'package:purchases_flutter/models/offering_wrapper.dart';
import 'package:purchases_flutter/models/package_wrapper.dart';
import 'package:purchases_flutter/models/purchases_error.dart';
import 'package:purchases_flutter/models/store_transaction.dart';

import 'internal_paywall_footer_view.dart';

Expand All @@ -11,17 +15,44 @@ import 'internal_paywall_footer_view.dart';
/// [offering] (Optional) The offering object to be displayed in the paywall.
/// Obtained from [Purchases.getOfferings].
///
/// [onPurchaseStarted] (Optional) Callback that gets called when a purchase
/// is started.
///
/// [onPurchaseCompleted] (Optional) Callback that gets called when a purchase
/// is completed.
///
/// [onPurchaseError] (Optional) Callback that gets called when a purchase
/// fails.
///
/// [onRestoreCompleted] (Optional) Callback that gets called when a restore
/// is completed. Note that this may get called even if no entitlements have
/// been granted in case no relevant purchases were found.
///
/// [onRestoreError] (Optional) Callback that gets called when a restore
/// fails.
///
/// [contentCreator] A function that creates the content to be displayed above
/// the paywall. Make sure you apply the given padding to the bottom of your
/// content to avoid overlap.
class PaywallFooterView extends StatefulWidget {

final Offering? offering;
final Function(Package rcPackage)? onPurchaseStarted;
final Function(CustomerInfo customerInfo, StoreTransaction storeTransaction)?
onPurchaseCompleted;
final Function(PurchasesError)? onPurchaseError;
final Function(CustomerInfo customerInfo)? onRestoreCompleted;
final Function(PurchasesError)? onRestoreError;
final Widget Function(double bottomPadding) contentCreator;

const PaywallFooterView({
Key? key,
this.offering,
this.onPurchaseStarted,
this.onPurchaseCompleted,
this.onPurchaseError,
this.onRestoreCompleted,
this.onRestoreError,
required this.contentCreator,
}) : super(key: key);

Expand Down Expand Up @@ -61,6 +92,11 @@ class _PaywallFooterViewState extends State<PaywallFooterView> {
height: _height,
child: InternalPaywallFooterView(
offering: widget.offering,
onPurchaseStarted: widget.onPurchaseStarted,
onPurchaseCompleted: widget.onPurchaseCompleted,
onPurchaseError: widget.onPurchaseError,
onRestoreCompleted: widget.onRestoreCompleted,
onRestoreError: widget.onRestoreError,
onHeightChanged: _updateHeight,
),
),
Expand Down
Loading