join_stories_flutter 1.0.0
join_stories_flutter: ^1.0.0 copied to clipboard
JOIN Stories Flutter SDK - Monolithic plugin for JOIN Stories
example/lib/main.dart
import 'package:flutter/material.dart';
import 'package:join_stories_flutter/join_stories_flutter.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'JOIN Demo',
theme: ThemeData(primarySwatch: Colors.blue, fontFamily: 'Parisienne'),
home: const SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
const SplashScreen({super.key});
@override
State<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
@override
void initState() {
super.initState();
_initSdk();
}
Future<void> _initSdk() async {
try {
await JOINStories.initialize(teamId: 'join-test-sdk-new');
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
}
} catch (e) {
if (mounted) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (_) => const HomeScreen()),
);
}
}
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
CircularProgressIndicator(),
SizedBox(height: 20),
Text('Initializing JOIN SDK...'),
],
),
),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final _bubbleCtrl = BubbleController();
final _cardListCtrl = CardController();
final _cardGridCtrl = CardController();
Future<void> _startPlayer(String alias, {String? standaloneOrigin}) async {
try {
await JOINStories.startPlayer(
alias,
standaloneOrigin: standaloneOrigin,
onPlayerFetchSuccess: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Standalone: Player fetch success')),
),
onPlayerLoaded: () => ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Standalone: Player started playing')),
),
onPlayerFetchError: (msg) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Standalone: Player fetch error: $msg')),
),
onPlayerDismissed: (type) => ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Standalone: Player dismissed ($type)')),
),
onContentLinkClick: (link) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Standalone: Link clicked - $link')),
),
onAnalyticsEvent: (eventName, payload) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Standalone Analytics: $eventName')),
),
onSeeToCart: () async {
await JOINStories.dismissPlayer();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Cart opened (demo simulation)')),
);
}
},
onShoppingRedirect: (offerId) async {
await JOINStories.dismissPlayer();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Redirect to product: $offerId (demo)')),
);
}
},
);
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error starting player: $e')),
);
}
}
}
Future<void> _startPlayerWithTheme(String alias, String theme) async {
try {
switch (theme) {
case 'blue':
await JOINStories.startPlayer(
alias,
standaloneOrigin: 'top',
playerBackgroundColor: 0xFF2C3E50,
playerVerticalAnchor: 'center',
playerHorizontalMargins: 20.0,
playerCornerRadius: 12.0,
playerProgressBarDefaultColor: 0xFF7F8C8D,
playerProgressBarFillColor: 0xFF3498DB,
playerProgressBarThickness: 4.0,
playerProgressBarRadius: 8.0,
onPlayerFetchSuccess: () =>
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Standalone: Player fetch success')),
),
onPlayerFetchError: (msg) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Standalone: Player fetch error: $msg')),
),
);
break;
case 'red':
await JOINStories.startPlayer(
alias,
standaloneOrigin: 'bottomLeft',
playerBackgroundColor: 0xFFE74C3C,
playerVerticalAnchor: 'bottom',
playerHorizontalMargins: 15.0,
playerCornerRadius: 8.0,
playerProgressBarDefaultColor: 0xFF95A5A6,
playerProgressBarFillColor: 0xFFE74C3C,
playerProgressBarThickness: 3.0,
playerProgressBarRadius: 6.0,
onPlayerFetchSuccess: () =>
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Standalone: Player fetch success')),
),
onPlayerFetchError: (msg) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Standalone: Player fetch error: $msg')),
),
);
break;
case 'green':
await JOINStories.startPlayer(
alias,
standaloneOrigin: 'topRight',
playerBackgroundColor: 0xFF27AE60,
playerVerticalAnchor: 'top',
playerHorizontalMargins: 25.0,
playerCornerRadius: 15.0,
playerProgressBarDefaultColor: 0xFFBDC3C7,
playerProgressBarFillColor: 0xFFF39C12,
playerProgressBarThickness: 5.0,
playerProgressBarRadius: 10.0,
onPlayerFetchSuccess: () =>
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Standalone: Player fetch success')),
),
onPlayerFetchError: (msg) =>
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Standalone: Player fetch error: $msg')),
),
);
break;
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error starting player: $e')),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('JOIN Stories Flutter Demo'),
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
),
body: RefreshIndicator(
onRefresh: () async {
await _bubbleCtrl.refresh();
await _cardListCtrl.refresh();
await _cardGridCtrl.refresh();
},
child: SingleChildScrollView(
physics: const AlwaysScrollableScrollPhysics(),
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const Text(
'JOIN Stories Widgets',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
BubbleWidget(
alias: 'test-sdk-shopping',
controller: _bubbleCtrl,
onNativeViewCreated: (id) => _bubbleCtrl.attach(id),
configuration: const BubbleConfiguration(
showLabel: true,
labelColor: Color(0xFF2C3E50),
labelFontSize: 14,
fontName: 'Parisienne-Regular',
showPlayButton: true,
thumbViewSpacing: 8,
thumbViewSize: 100,
storyViewedIndicatorColor: Color(0xFFE74C3C),
loaderColors: [Color(0xFF3498DB), Color(0xFF2ECC71)],
loaderWidth: 3,
animationType: BubbleAnimationType.pulse,
reorderedReadStories: true,
maxStories: 5,
horizontalMargin: 16,
horizontalPadding: 8,
playerBackgroundColor: Color(0xFF2C3E50),
playerVerticalAnchor: "center",
playerHorizontalMargins: 20.0,
playerCornerRadius: 12.0,
playerProgressBarDefaultColor: Color(0xFF7F8C8D),
playerProgressBarFillColor: Color(0xFF3498DB),
//playerProgressBarThickness: 4.0,
//playerProgressBarRadius: 8.0,
playerStandaloneAnimationOrigin: "top",
),
onTriggerFetchSuccess: (itemCount) {
print(
'🎉 Bubble: Trigger fetch success with $itemCount items');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Bubble: Loaded $itemCount stories')),
);
},
onTriggerFetchEmpty: () {
print('⚠️ Bubble: No stories available');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Bubble: No stories available')),
);
},
onTriggerFetchError: (error) {
print('❌ Bubble: Trigger fetch error: $error');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Bubble Error: $error')),
);
},
onPlayerLoaded: () {
print('▶️ Bubble: Player loaded and playing');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Bubble: Player started playing')),
);
},
onPlayerDismissed: (type) {
print('🔚 Bubble: Player dismissed ($type)');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Bubble: Player dismissed ($type)')),
);
},
onContentLinkClick: (link) {
print('🔗 Bubble: Content link clicked: $link');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Bubble: Link clicked - $link')),
);
},
onAnalyticsEvent: (eventName, payload) {
print('📊 Bubble Analytics: $eventName - $payload');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Bubble Analytics: $eventName')),
);
},
onSeeToCart: () async {
await JOINStories.dismissPlayer();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Cart opened (demo simulation)')),
);
}
},
onShoppingRedirect: (offerId) async {
await JOINStories.dismissPlayer();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Redirect to product: $offerId (demo)')),
);
}
},
),
const SizedBox(height: 16),
CardListWidget(
alias: 'widget-fft',
onNativeViewCreated: (id) => _cardListCtrl.attach(id),
configuration: const CardConfiguration(
showLabel: true,
labelColor: Color(0xFF8E44AD),
labelFontSize: 16,
fontName: 'DynaPuff-Regular',
showPlayButton: true,
cardElevation: 4,
cardRadius: 12,
showOverlay: true,
spacing: 12,
borderWidth: 2,
borderColor: Color(0xFF9B59B6),
storyViewedBorderColor: Color(0xFFE67E22),
animationType: CardAnimationType.pulse,
reorderedReadStories: false,
maxStories: 3,
playerBackgroundColor: Color(0xFF8E44AD),
playerVerticalAnchor: "bottom",
playerHorizontalMargins: 15.0,
playerCornerRadius: 8.0,
playerProgressBarDefaultColor: Color(0xFF95A5A6),
playerProgressBarFillColor: Color(0xFFE74C3C),
playerProgressBarThickness: 3.0,
playerProgressBarRadius: 6.0,
playerStandaloneAnimationOrigin: "bottomRight",
),
onTriggerFetchSuccess: (itemCount) {
print(
'🎉 CardList: Trigger fetch success with $itemCount items');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('CardList: Loaded $itemCount stories')),
);
},
onTriggerFetchEmpty: () {
print('⚠️ CardList: No stories available');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('CardList: No stories available')),
);
},
onTriggerFetchError: (error) {
print('❌ CardList: Trigger fetch error: $error');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('CardList Error: $error')),
);
},
onPlayerLoaded: () {
print('▶️ CardList: Player loaded and playing');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('CardList: Player started playing')),
);
},
onPlayerDismissed: (type) {
print('🔚 CardList: Player dismissed ($type)');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('CardList: Player dismissed ($type)')),
);
},
onContentLinkClick: (link) {
print('🔗 CardList: Content link clicked: $link');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('CardList: Link clicked - $link')),
);
},
onAnalyticsEvent: (eventName, payload) {
print('📊 CardList Analytics: $eventName - $payload');
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('CardList Analytics: $eventName')),
);
},
onSeeToCart: () async {
await JOINStories.dismissPlayer();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Cart opened (demo simulation)')),
);
}
},
onShoppingRedirect: (offerId) async {
await JOINStories.dismissPlayer();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Redirect to product: $offerId (demo)')),
);
}
},
),
const SizedBox(height: 16),
CardGridWidget(
alias: 'widget-fft',
onNativeViewCreated: (id) => _cardGridCtrl.attach(id),
configuration: const CardConfiguration(
showLabel: true,
labelColor: Color(0xFF27AE60),
labelFontSize: 18,
fontName: 'Parisienne-Regular',
showPlayButton: true,
cardElevation: 6,
cardRadius: 16,
showOverlay: true,
spacing: 16,
numberOfColumns: 2,
cardSize: 150,
borderWidth: 3,
borderColor: Color(0xFF2ECC71),
storyViewedBorderColor: Color(0xFFF39C12),
animationType: CardAnimationType.pulse,
reorderedReadStories: true,
maxStories: 5,
playerBackgroundColor: Color(0xFF27AE60),
playerVerticalAnchor: "top",
playerHorizontalMargins: 25.0,
playerCornerRadius: 15.0,
playerProgressBarDefaultColor: Color(0xFFBDC3C7),
playerProgressBarFillColor: Color(0xFFF39C12),
playerProgressBarThickness: 5.0,
playerProgressBarRadius: 10.0,
playerStandaloneAnimationOrigin: "topLeft",
),
),
const Text(
'Standalone Player Tests',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () => _startPlayer('test-sdk-shopping'),
child: const Text('Open Story (Legacy API)'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () =>
_startPlayer('test-sdk-shopping', standaloneOrigin: 'top'),
child: const Text('Standalone Player (top)'),
),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () => _startPlayer('test-sdk-shopping',
standaloneOrigin: 'bottom'),
child: const Text('Standalone Player (bottom)'),
),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () => _startPlayer('test-sdk-shopping',
standaloneOrigin: 'topLeft'),
child: const Text('Standalone Player (topLeft)'),
),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () => _startPlayer('test-sdk-shopping',
standaloneOrigin: 'bottomRight'),
child: const Text('Standalone Player (bottomRight)'),
),
const SizedBox(height: 16),
const Text(
'Custom Player Configuration Tests',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () =>
_startPlayerWithTheme('test-sdk-shopping', 'blue'),
child: const Text('Custom Player (Blue Theme)'),
),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () =>
_startPlayerWithTheme('test-sdk-shopping', 'red'),
child: const Text('Custom Player (Red Theme)'),
),
const SizedBox(height: 4),
ElevatedButton(
onPressed: () =>
_startPlayerWithTheme('test-sdk-shopping', 'green'),
child: const Text('Custom Player (Green Theme)'),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
try {
await JOINStories.dismissPlayer();
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error dismissing player: $e')),
);
}
}
},
child: const Text('Dismiss Player'),
),
const SizedBox(height: 16),
const Text(
'Analytics Tests',
style: TextStyle(fontSize: 20, fontWeight: FontWeight.bold),
),
const SizedBox(height: 16),
ElevatedButton(
onPressed: () async {
try {
await JOINStories.setTrackingUserId('user_123');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('Tracking User ID set to: user_123')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error setting tracking user ID: $e')),
);
}
},
child: const Text('Set Tracking User ID'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () async {
try {
await JOINStories.sendConversion('purchase', 'ecommerce');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('Conversion sent: purchase (ecommerce)')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Error sending conversion: $e')),
);
}
},
child: const Text('Send Conversion'),
),
const SizedBox(height: 8),
ElevatedButton(
onPressed: () async {
try {
await JOINStories.setSegmentationKey('premium_user');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('Segmentation key set to: premium_user')),
);
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Error setting segmentation key: $e')),
);
}
},
child: const Text('Set Segmentation Key'),
),
],
),
),
),
);
}
}