novvy_ads 1.0.0-beta.33
novvy_ads: ^1.0.0-beta.33 copied to clipboard
Flutter plugin for Novvy Ads SDK.
example/lib/main.dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:novvy_ads/novvy_ads.dart';
void main() {
runApp(const NovvyAdsExampleApp());
}
class NovvyAdsExampleApp extends StatelessWidget {
const NovvyAdsExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Novvy Ads Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: const Color(0xFF6C5CE7),
brightness: Brightness.light,
useMaterial3: true,
),
darkTheme: ThemeData(
colorSchemeSeed: const Color(0xFF6C5CE7),
brightness: Brightness.dark,
useMaterial3: true,
),
home: const HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> implements NovvyAdsCallback {
bool _initialized = false;
bool _initializing = false;
final _appIdController = TextEditingController(text: 'your_app_id');
final _endpointController = TextEditingController(text: 'your_end_point',);
final _apiKeyController = TextEditingController(text: 'your_api_key');
final _interstitialAdUnitController = TextEditingController(text: 'your_ad_unit_id');
final _rewardedAdUnitController = TextEditingController(text: 'your_ad_unit_id');
final _bannerAdUnitController = TextEditingController(text: 'your_ad_unit_id');
bool _showBanner = false;
_DemoPlayerAdapter _demoPlayer = _DemoPlayerAdapter();
final _logs = <_LogEntry>[];
void _addLog(String message, {_LogLevel level = _LogLevel.info}) {
setState(() {
_logs.insert(0, _LogEntry(message: message, level: level));
if (_logs.length > 200) _logs.removeLast();
});
}
Future<void> _initialize() async {
if (_initializing) return;
setState(() => _initializing = true);
_addLog('Initializing Novvy SDK...');
try {
await NovvyAds.initialize(
config: NovvyInitConfig(
appId: _appIdController.text.trim(),
endpoint: _endpointController.text.trim(),
apiKey: _apiKeyController.text.trim(),
),
callback: this,
);
_addLog('SDK initialized successfully', level: _LogLevel.success);
setState(() => _initialized = true);
} catch (e) {
_addLog('SDK initialization failed: $e', level: _LogLevel.error);
} finally {
setState(() => _initializing = false);
}
}
void _showInterstitial() {
final adUnitId = _interstitialAdUnitController.text.trim();
_addLog('Requesting interstitial: $adUnitId');
NovvyAds.showInterstitial(adUnitId);
}
void _showRewarded() {
final adUnitId = _rewardedAdUnitController.text.trim();
_addLog('Requesting rewarded: $adUnitId');
NovvyAds.showRewarded(adUnitId);
}
void _toggleBanner() {
setState(() {
_showBanner = !_showBanner;
if (_showBanner) {
// Reset player so position starts from 0 for the new banner session.
_demoPlayer = _DemoPlayerAdapter();
}
});
_addLog(
_showBanner ? 'Banner added to widget tree' : 'Banner removed from widget tree',
level: _LogLevel.info,
);
}
void _updateContext() {
NovvyAds.updateContext(
NovvyContextConfig(
seriesName: 'your_series_name',
episodeNumber: 1, // your_episode_number
contentUrl: 'your_content_url',
),
);
_addLog('Context updated', level: _LogLevel.success);
}
// ── NovvyAdsCallback ──
@override
void onInterstitialDismissed(String adUnitId) {
_addLog('Interstitial dismissed: $adUnitId', level: _LogLevel.success);
}
@override
void onInterstitialFailed(String adUnitId, String error) {
_addLog('Interstitial failed [$adUnitId]: $error', level: _LogLevel.error);
}
@override
void onRewardedDismissed(String adUnitId, NovvyReward? reward) {
if (reward != null) {
_addLog(
'Rewarded dismissed [$adUnitId]: earned ${reward.amount} ${reward.type}',
level: _LogLevel.success,
);
} else {
_addLog(
'Rewarded dismissed [$adUnitId]: no reward (user skipped)',
level: _LogLevel.warning,
);
}
}
@override
void onRewardedFailed(String adUnitId, String error) {
_addLog('Rewarded failed [$adUnitId]: $error', level: _LogLevel.error);
}
@override
void onBannerImpression(String adUnitId) {
_addLog('Banner impression (callback): $adUnitId', level: _LogLevel.success);
}
@override
void onBannerFailed(String adUnitId, String error) {
_addLog('Banner failed (callback) [$adUnitId]: $error', level: _LogLevel.error);
}
@override
void onBannerClosed(String adUnitId) {
_addLog('Banner closed (callback): $adUnitId');
}
@override
void onFeedAdReady(String adUnitId, NovvyFeedAdProvider provider) {
_addLog('Feed ad ready: $adUnitId', level: _LogLevel.success);
}
@override
void onFeedAdFailed(String adUnitId, String error) {
_addLog('Feed ad failed [$adUnitId]: $error', level: _LogLevel.error);
}
@override
void onFeedAdPlaybackTick(String adUnitId, int remainingSeconds) {
_addLog('Feed ad tick [$adUnitId]: ${remainingSeconds}s remaining');
}
@override
void onFeedAdImpression(String adUnitId) {
_addLog('Feed ad impression: $adUnitId', level: _LogLevel.success);
}
@override
void onFeedAdClicked(String adUnitId) {
_addLog('Feed ad clicked: $adUnitId');
}
@override
void dispose() {
_appIdController.dispose();
_endpointController.dispose();
_apiKeyController.dispose();
_interstitialAdUnitController.dispose();
_rewardedAdUnitController.dispose();
_bannerAdUnitController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final colorScheme = theme.colorScheme;
return Scaffold(
appBar: AppBar(
title: const Text('Novvy Ads Demo'),
centerTitle: true,
),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
// Platform badge
Center(
child: Chip(
avatar: Icon(
Platform.isAndroid ? Icons.android : Icons.apple,
size: 18,
),
label: Text(Platform.isAndroid ? 'Android' : 'iOS'),
),
),
const SizedBox(height: 16),
// Init config
_SectionCard(
title: 'SDK Configuration',
icon: Icons.settings,
children: [
_ConfigField(
label: 'App ID',
controller: _appIdController,
enabled: !_initialized,
),
_ConfigField(
label: 'Endpoint',
controller: _endpointController,
enabled: !_initialized,
),
_ConfigField(
label: 'API Key',
controller: _apiKeyController,
enabled: !_initialized,
obscure: true,
),
const SizedBox(height: 12),
FilledButton.icon(
onPressed: _initialized || _initializing ? null : _initialize,
icon: _initializing
? SizedBox.square(
dimension: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
color: colorScheme.onPrimary,
),
)
: Icon(_initialized ? Icons.check : Icons.rocket_launch),
label: Text(
_initialized
? 'Initialized'
: _initializing
? 'Initializing...'
: 'Initialize SDK',
),
),
],
),
const SizedBox(height: 16),
// Ad controls
_SectionCard(
title: 'Ad Controls',
icon: Icons.play_circle_outline,
children: [
_ConfigField(
label: 'Interstitial Ad Unit ID',
controller: _interstitialAdUnitController,
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: FilledButton.tonalIcon(
onPressed: _initialized ? _showInterstitial : null,
icon: const Icon(Icons.fullscreen),
label: const Text('Show Interstitial'),
),
),
],
),
const SizedBox(height: 16),
_ConfigField(
label: 'Rewarded Ad Unit ID',
controller: _rewardedAdUnitController,
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: FilledButton.tonalIcon(
onPressed: _initialized ? _showRewarded : null,
icon: const Icon(Icons.card_giftcard),
label: const Text('Show Rewarded'),
),
),
],
),
const SizedBox(height: 16),
OutlinedButton.icon(
onPressed: _initialized ? _updateContext : null,
icon: const Icon(Icons.tune),
label: const Text('Update Context'),
),
],
),
const SizedBox(height: 16),
// Banner ad controls
_SectionCard(
title: 'Banner Ad',
icon: Icons.view_agenda_outlined,
children: [
_ConfigField(
label: 'Banner Ad Unit ID',
controller: _bannerAdUnitController,
),
const SizedBox(height: 8),
FilledButton.tonalIcon(
onPressed: _initialized ? _toggleBanner : null,
icon: Icon(_showBanner ? Icons.visibility_off : Icons.visibility),
label: Text(_showBanner ? 'Remove Banner' : 'Show Banner'),
),
const SizedBox(height: 12),
// Banner container — simulates a video player area
Container(
width: 260,
height: 600,
decoration: BoxDecoration(
border: Border.all(
color: colorScheme.outlineVariant,
),
borderRadius: BorderRadius.circular(8),
),
clipBehavior: Clip.antiAlias,
child: Stack(
children: [
// Test button — positioned to overlap with banner area
Positioned(
bottom: 110,
right: 0,
child: TextButton(
onPressed: () => _addLog(
'Test button tapped!',
level: _LogLevel.warning,
),
child: const Text('Test'),
),
),
// Banner — one line to create a fully managed banner
if (_showBanner)
NovvyAds.banner(
adUnitId: _bannerAdUnitController.text.trim(),
player: _demoPlayer,
),
],
),
),
],
),
const SizedBox(height: 16),
// Event log
_SectionCard(
title: 'Event Log',
icon: Icons.receipt_long,
trailing: _logs.isNotEmpty
? TextButton(
onPressed: () => setState(_logs.clear),
child: const Text('Clear'),
)
: null,
children: [
if (_logs.isEmpty)
Padding(
padding: const EdgeInsets.symmetric(vertical: 24),
child: Center(
child: Text(
'No events yet.\nInitialize the SDK and trigger an ad.',
textAlign: TextAlign.center,
style: theme.textTheme.bodyMedium?.copyWith(
color: colorScheme.onSurfaceVariant,
),
),
),
)
else
...List.generate(
_logs.length.clamp(0, 50),
(i) => _LogTile(entry: _logs[i]),
),
],
),
const SizedBox(height: 32),
],
),
);
}
}
// ── Helper widgets ──
class _SectionCard extends StatelessWidget {
final String title;
final IconData icon;
final Widget? trailing;
final List<Widget> children;
const _SectionCard({
required this.title,
required this.icon,
this.trailing,
required this.children,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return Card(
elevation: 0,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
side: BorderSide(color: theme.colorScheme.outlineVariant),
),
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Row(
children: [
Icon(icon, size: 20, color: theme.colorScheme.primary),
const SizedBox(width: 8),
Expanded(
child: Text(title, style: theme.textTheme.titleMedium),
),
?trailing,
],
),
const SizedBox(height: 12),
...children,
],
),
),
);
}
}
class _ConfigField extends StatelessWidget {
final String label;
final TextEditingController controller;
final bool enabled;
final bool obscure;
const _ConfigField({
required this.label,
required this.controller,
this.enabled = true,
this.obscure = false,
});
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(bottom: 8),
child: TextField(
controller: controller,
enabled: enabled,
obscureText: obscure,
style: const TextStyle(fontSize: 13),
decoration: InputDecoration(
labelText: label,
isDense: true,
border: const OutlineInputBorder(),
),
),
);
}
}
class _LogTile extends StatelessWidget {
final _LogEntry entry;
const _LogTile({required this.entry});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final (icon, color) = switch (entry.level) {
_LogLevel.info => (Icons.info_outline, theme.colorScheme.onSurface),
_LogLevel.success => (Icons.check_circle_outline, Colors.green),
_LogLevel.warning => (Icons.warning_amber, Colors.orange),
_LogLevel.error => (Icons.error_outline, Colors.redAccent),
};
return Padding(
padding: const EdgeInsets.symmetric(vertical: 3),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(icon, size: 16, color: color),
const SizedBox(width: 8),
Expanded(
child: Text(
entry.message,
style: theme.textTheme.bodySmall?.copyWith(color: color),
),
),
],
),
);
}
}
// ── Models ──
enum _LogLevel { info, success, warning, error }
class _LogEntry {
final String message;
final _LogLevel level;
final DateTime timestamp;
_LogEntry({
required this.message,
this.level = _LogLevel.info,
}) : timestamp = DateTime.now();
}
/// A simple player adapter for demo purposes that simulates playback.
class _DemoPlayerAdapter extends NovvyPlayerAdapter {
final _start = DateTime.now();
@override
int get currentPositionMs =>
DateTime.now().difference(_start).inMilliseconds;
@override
bool get isPlaying => true;
}