diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 1693956..0dc0d58 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -1,34 +1,36 @@
+
-
-
+
-
+
+
+ the Android process has started. This theme is visible to the user
+ while the Flutter UI initializes. After that, this theme continues
+ to determine the Window background behind the Flutter UI. -->
+ android:name="io.flutter.embedding.android.NormalTheme"
+ android:resource="@style/NormalTheme" />
-
-
+
+
+
+
+
+
+
+
diff --git a/android/build.gradle b/android/build.gradle
index 3d5d3ff..2e68eb0 100644
--- a/android/build.gradle
+++ b/android/build.gradle
@@ -7,7 +7,7 @@ buildscript {
}
dependencies {
- classpath 'com.android.tools.build:gradle:8.6.1'
+ classpath 'com.android.tools.build:gradle:8.9.1'
classpath 'com.google.gms:google-services:4.4.4'
classpath 'com.huawei.agconnect:agcp:1.9.1.302'
}
diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties
index 514d9bb..1126aa0 100644
--- a/android/gradle/wrapper/gradle-wrapper.properties
+++ b/android/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-8.10-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
diff --git a/android/settings.gradle b/android/settings.gradle
index d1065d1..f8aa66f 100644
--- a/android/settings.gradle
+++ b/android/settings.gradle
@@ -18,7 +18,7 @@ pluginManagement {
plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
- id "com.android.application" version "8.6.1" apply false
+ id "com.android.application" version "8.9.1" apply false
id "org.jetbrains.kotlin.android" version "2.3.10" apply false
}
diff --git a/lib/insider/MessageCenter.dart b/lib/insider/MessageCenter.dart
index 727c6ca..ad76cc8 100644
--- a/lib/insider/MessageCenter.dart
+++ b/lib/insider/MessageCenter.dart
@@ -1,8 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_demo/components/CustomButton.dart';
+import 'package:flutter_demo/insider/MessageCenterInboxPage.dart';
import 'package:flutter_insider/flutter_insider.dart';
-import 'package:flutter_insider/src/identifiers.dart';
class MessageCenter extends StatelessWidget {
MessageCenter();
@@ -16,42 +16,48 @@ class MessageCenter extends StatelessWidget {
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
- child: CustomButton(buttonText: 'Get Message Center Data', onPressed: () async {
- // --- MESSAGE CENTER --- //
- DateTime startDate = DateTime.now().subtract(const Duration(days: 90));
- DateTime endDate = DateTime.now().add(const Duration(days: 90));
-
- print('[INSIDER][getMessageCenterData]: Method is triggered , waiting response.');
-
- List? messageCenterData =
- await FlutterInsider.Instance.getMessageCenterData(startDate, endDate, 100);
-
- print('[INSIDER][getMessageCenterData]: $messageCenterData');
- }),
+ child: CustomButton(
+ buttonText: 'Open App Cards',
+ onPressed: () {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (context) => const AppCardsPage(),
+ ),
+ );
+ },
+ ),
),
],
),
+ const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(
width: MediaQuery.of(context).size.width * 0.9,
- child: CustomButton(buttonText: 'Get Message Center Data With Identifiers', onPressed: () async {
- // --- MESSAGE CENTER WITH IDENTIFIERS --- //
- DateTime startDate = DateTime.now().subtract(const Duration(days: 90));
- DateTime endDate = DateTime.now().add(const Duration(days: 90));
-
- FlutterInsiderIdentifiers identifiers = FlutterInsiderIdentifiers()
- .addEmail("test@example.com")
- .addPhoneNumber("+1234567890")
- .addUserID("user123");
-
- print('[INSIDER][getMessageCenterDataWithIdentifiers]: Method is triggered , waiting response.');
+ child: CustomButton(buttonText: 'Get Campaigns Data (Console)', onPressed: () async {
+ print('[INSIDER][getCampaigns]: Method is triggered, waiting response...');
- List? messageCenterData =
- await FlutterInsider.Instance.getMessageCenterDataWithIdentifiers(startDate, endDate, identifiers, 100);
+ try {
+ final response = await FlutterInsider.Instance.appCards.getCampaigns();
- print('[INSIDER][getMessageCenterDataWithIdentifiers]: $messageCenterData');
+ if (response != null) {
+ print('[INSIDER][getCampaigns]: Received ${response.appCards.length} app cards');
+ for (var card in response.appCards) {
+ print(' - Card ID: ${card.id}');
+ print(' Title: ${card.content?.title ?? "N/A"}');
+ print(' Description: ${card.content?.description ?? "N/A"}');
+ print(' Read: ${card.isRead}');
+ print(' Images: ${card.images?.length ?? 0}');
+ print(' Buttons: ${card.buttons?.length ?? 0}');
+ }
+ } else {
+ print('[INSIDER][getCampaigns]: No campaigns data received');
+ }
+ } catch (e) {
+ print('[INSIDER][getCampaigns]: Error: $e');
+ }
}),
),
],
@@ -59,4 +65,4 @@ class MessageCenter extends StatelessWidget {
],
);
}
-}
\ No newline at end of file
+}
diff --git a/lib/insider/MessageCenterInboxPage.dart b/lib/insider/MessageCenterInboxPage.dart
new file mode 100644
index 0000000..1369a77
--- /dev/null
+++ b/lib/insider/MessageCenterInboxPage.dart
@@ -0,0 +1,878 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_insider/flutter_insider.dart';
+
+class AppCardsPage extends StatefulWidget {
+ const AppCardsPage({Key? key}) : super(key: key);
+
+ @override
+ State createState() => _AppCardsPageState();
+}
+
+class _AppCardsPageState extends State {
+ List appCards = [];
+ bool isLoading = true;
+ bool hasError = false;
+ String errorMessage = '';
+
+ @override
+ void initState() {
+ super.initState();
+ loadAppCards();
+ }
+
+ Future loadAppCards() async {
+ setState(() {
+ isLoading = true;
+ hasError = false;
+ });
+
+ try {
+ print('[INSIDER][AppCards]: Loading campaigns...');
+
+ final response = await FlutterInsider.Instance.appCards.getCampaigns();
+ if (!mounted) return;
+
+ if (response != null && response.appCards.isNotEmpty) {
+ print('[INSIDER][AppCards]: Received ${response.appCards.length} cards');
+ setState(() {
+ appCards = response.appCards;
+ isLoading = false;
+ });
+ } else {
+ print('[INSIDER][AppCards]: No cards found');
+ setState(() {
+ appCards = [];
+ isLoading = false;
+ });
+ }
+ } catch (e) {
+ print('[INSIDER][AppCards]: Error loading cards: $e');
+ if (!mounted) return;
+ setState(() {
+ hasError = true;
+ errorMessage = 'Error loading cards: $e';
+ isLoading = false;
+ });
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ body: CustomScrollView(
+ slivers: [
+ SliverAppBar(
+ expandedHeight: 120.0,
+ floating: false,
+ pinned: true,
+ backgroundColor: Colors.black,
+ flexibleSpace: FlexibleSpaceBar(
+ title: const Text(
+ 'App Cards',
+ style: TextStyle(
+ fontWeight: FontWeight.bold,
+ color: Colors.white,
+ ),
+ ),
+ background: Container(
+ decoration: BoxDecoration(
+ gradient: LinearGradient(
+ begin: Alignment.topLeft,
+ end: Alignment.bottomRight,
+ colors: [
+ Colors.deepPurple.shade700,
+ Colors.deepPurple.shade900,
+ ],
+ ),
+ ),
+ child: const Align(
+ alignment: Alignment.bottomLeft,
+ child: Padding(
+ padding: EdgeInsets.only(left: 16, bottom: 60),
+ child: Text(
+ 'Your cards and notifications',
+ style: TextStyle(
+ color: Colors.white70,
+ fontSize: 14,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ SliverToBoxAdapter(
+ child: _buildContent(),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildContent() {
+ if (isLoading) {
+ return _buildLoadingState();
+ }
+
+ if (hasError) {
+ return _buildErrorState();
+ }
+
+ if (appCards.isEmpty) {
+ return _buildEmptyState();
+ }
+
+ return _buildCardsList();
+ }
+
+ Widget _buildLoadingState() {
+ return Container(
+ padding: const EdgeInsets.all(48),
+ child: const Center(
+ child: CircularProgressIndicator(
+ valueColor: AlwaysStoppedAnimation(Colors.deepPurple),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildErrorState() {
+ return Container(
+ padding: const EdgeInsets.all(48),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ const Text('Warning', style: TextStyle(fontSize: 64)),
+ const SizedBox(height: 16),
+ Text(
+ errorMessage,
+ style: const TextStyle(
+ fontSize: 16,
+ color: Colors.red,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 24),
+ ElevatedButton(
+ onPressed: loadAppCards,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.deepPurple,
+ foregroundColor: Colors.white,
+ ),
+ child: const Text('Retry'),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildEmptyState() {
+ return Container(
+ padding: const EdgeInsets.all(48),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: const [
+ Text('No Cards', style: TextStyle(fontSize: 64)),
+ SizedBox(height: 16),
+ Text(
+ 'No cards yet',
+ style: TextStyle(
+ fontSize: 20,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ ),
+ ),
+ SizedBox(height: 8),
+ Text(
+ 'When you receive cards, they\'ll appear here',
+ style: TextStyle(
+ fontSize: 14,
+ color: Colors.black54,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ],
+ ),
+ );
+ }
+
+ Future _deleteAllCards() async {
+ final allIds = appCards.map((c) => c.id).toList();
+
+ if (allIds.isEmpty) return;
+
+ try {
+ print('[INSIDER][AppCards]: Deleting all ${allIds.length} cards');
+ await FlutterInsider.Instance.appCards.delete(allIds);
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('All ${allIds.length} cards deleted'),
+ backgroundColor: Colors.red,
+ duration: const Duration(seconds: 2),
+ ),
+ );
+ await Future.delayed(const Duration(milliseconds: 500));
+ loadAppCards();
+ } catch (e) {
+ print('[INSIDER][AppCards]: Error deleting all cards: $e');
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error deleting cards: $e'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ }
+ }
+
+ Widget _buildCardsList() {
+ return Column(
+ children: [
+ Padding(
+ padding: const EdgeInsets.fromLTRB(16, 16, 16, 4),
+ child: SizedBox(
+ width: double.infinity,
+ child: OutlinedButton.icon(
+ onPressed: _deleteAllCards,
+ icon: const Icon(Icons.delete_sweep, color: Colors.red),
+ label: const Text('Remove All'),
+ style: OutlinedButton.styleFrom(
+ foregroundColor: Colors.red,
+ side: const BorderSide(color: Colors.red),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ ),
+ ),
+ ),
+ ),
+ ListView.builder(
+ shrinkWrap: true,
+ physics: const NeverScrollableScrollPhysics(),
+ padding: const EdgeInsets.symmetric(vertical: 8),
+ itemCount: appCards.length,
+ itemBuilder: (context, index) {
+ return _AppCardItem(
+ appCard: appCards[index],
+ onRefresh: loadAppCards,
+ );
+ },
+ ),
+ ],
+ );
+ }
+}
+
+class _AppCardItem extends StatefulWidget {
+ final InsiderAppCard appCard;
+ final VoidCallback onRefresh;
+
+ const _AppCardItem({
+ Key? key,
+ required this.appCard,
+ required this.onRefresh,
+ }) : super(key: key);
+
+ @override
+ State<_AppCardItem> createState() => _AppCardItemState();
+}
+
+class _AppCardItemState extends State<_AppCardItem> with SingleTickerProviderStateMixin {
+ late AnimationController _animationController;
+ late Animation _fadeAnimation;
+ int _currentImageIndex = 0;
+
+ @override
+ void initState() {
+ super.initState();
+ _animationController = AnimationController(
+ duration: const Duration(milliseconds: 300),
+ vsync: this,
+ );
+ _fadeAnimation = CurvedAnimation(
+ parent: _animationController,
+ curve: Curves.easeIn,
+ );
+ _animationController.forward();
+
+ // Track card view
+ widget.appCard.view();
+ }
+
+ @override
+ void dispose() {
+ _animationController.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final appCard = widget.appCard;
+ final cardId = appCard.id;
+ final isRead = appCard.isRead;
+ final title = appCard.content?.title ?? '';
+ final description = appCard.content?.description ?? '';
+ final images = appCard.images ?? [];
+
+ return FadeTransition(
+ opacity: _fadeAnimation,
+ child: Card(
+ margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
+ elevation: 2,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: InkWell(
+ onTap: () => _handleCardClick(),
+ borderRadius: BorderRadius.circular(12),
+ child: Padding(
+ padding: const EdgeInsets.all(20),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ // Header: Title and Status Badge
+ Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Expanded(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ if (title.isNotEmpty)
+ Text(
+ title,
+ style: const TextStyle(
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ color: Colors.black87,
+ ),
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ if (title.isNotEmpty && description.isNotEmpty)
+ const SizedBox(height: 4),
+ if (description.isNotEmpty)
+ Text(
+ description,
+ style: const TextStyle(
+ fontSize: 14,
+ color: Colors.black54,
+ ),
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ],
+ ),
+ ),
+ const SizedBox(width: 12),
+ _buildStatusBadge(isRead),
+ ],
+ ),
+
+ // Images Carousel
+ if (images.isNotEmpty) ...[
+ const SizedBox(height: 16),
+ _buildImageCarousel(images),
+ const SizedBox(height: 8),
+ _buildImageIndicators(images.length),
+ ],
+
+ // Card ID
+ const SizedBox(height: 16),
+ Text(
+ 'ID: ${cardId.length > 16 ? cardId.substring(0, 16) + '...' : cardId}',
+ style: const TextStyle(
+ fontSize: 11,
+ color: Colors.black38,
+ ),
+ ),
+
+ // Divider
+ const SizedBox(height: 16),
+ const Divider(height: 1),
+ const SizedBox(height: 16),
+
+ // Action Buttons
+ Row(
+ children: [
+ Expanded(
+ child: OutlinedButton(
+ onPressed: () => isRead ? _markAsUnread() : _markAsRead(),
+ style: OutlinedButton.styleFrom(
+ foregroundColor: Colors.deepPurple,
+ side: BorderSide(
+ color: isRead ? Colors.grey.shade500 : Colors.deepPurple,
+ ),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ ),
+ child: Text(isRead ? 'Mark Unread' : 'Mark Read'),
+ ),
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: OutlinedButton(
+ onPressed: () => _deleteCard(),
+ style: OutlinedButton.styleFrom(
+ foregroundColor: Colors.red,
+ side: const BorderSide(color: Colors.red),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ ),
+ child: const Text('Delete'),
+ ),
+ ),
+ const SizedBox(width: 8),
+ Expanded(
+ child: ElevatedButton(
+ onPressed: () => _showCardDetails(),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.deepPurple,
+ foregroundColor: Colors.white,
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ ),
+ child: const Text('View Details'),
+ ),
+ ),
+ ],
+ ),
+
+ // Dynamic Buttons
+ ..._buildDynamicButtons(),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+
+ Widget _buildStatusBadge(bool isRead) {
+ return Container(
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
+ decoration: BoxDecoration(
+ color: isRead ? Colors.grey.shade300 : Colors.deepPurple,
+ borderRadius: BorderRadius.circular(12),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Container(
+ width: 6,
+ height: 6,
+ decoration: BoxDecoration(
+ color: isRead ? Colors.grey.shade600 : Colors.white,
+ shape: BoxShape.circle,
+ ),
+ ),
+ const SizedBox(width: 6),
+ Text(
+ isRead ? 'Read' : 'Unread',
+ style: TextStyle(
+ fontSize: 10,
+ fontWeight: FontWeight.bold,
+ color: isRead ? Colors.grey.shade700 : Colors.white,
+ letterSpacing: 0.5,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildImageCarousel(List images) {
+ if (images.isEmpty) return const SizedBox.shrink();
+
+ return SizedBox(
+ height: 200,
+ child: PageView.builder(
+ itemCount: images.length,
+ onPageChanged: (index) {
+ setState(() {
+ _currentImageIndex = index;
+ });
+ },
+ itemBuilder: (context, index) {
+ final imageUrl = images[index].url;
+ return Container(
+ margin: const EdgeInsets.symmetric(horizontal: 4),
+ decoration: BoxDecoration(
+ borderRadius: BorderRadius.circular(8),
+ color: Colors.grey.shade200,
+ ),
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(8),
+ child: Image.network(
+ imageUrl,
+ fit: BoxFit.cover,
+ loadingBuilder: (context, child, loadingProgress) {
+ if (loadingProgress == null) return child;
+ return Center(
+ child: CircularProgressIndicator(
+ value: loadingProgress.expectedTotalBytes != null
+ ? loadingProgress.cumulativeBytesLoaded /
+ loadingProgress.expectedTotalBytes!
+ : null,
+ ),
+ );
+ },
+ errorBuilder: (context, error, stackTrace) {
+ return const Center(
+ child: Icon(Icons.broken_image, size: 48, color: Colors.grey),
+ );
+ },
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ Widget _buildImageIndicators(int count) {
+ if (count <= 1) return const SizedBox.shrink();
+
+ return Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: List.generate(count, (index) {
+ return Container(
+ width: 8,
+ height: 8,
+ margin: const EdgeInsets.symmetric(horizontal: 4),
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: _currentImageIndex == index
+ ? Colors.deepPurple
+ : Colors.deepPurple.withOpacity(0.3),
+ ),
+ );
+ }),
+ );
+ }
+
+ List _buildDynamicButtons() {
+ final buttons = widget.appCard.buttons;
+
+ if (buttons == null || buttons.isEmpty) {
+ return [];
+ }
+
+ return [
+ const SizedBox(height: 12),
+ Text(
+ 'Card Actions:',
+ style: TextStyle(
+ fontSize: 13,
+ fontWeight: FontWeight.bold,
+ color: Colors.grey.shade700,
+ ),
+ ),
+ ...buttons.map((button) {
+ return Padding(
+ padding: const EdgeInsets.only(top: 8),
+ child: SizedBox(
+ width: double.infinity,
+ child: ElevatedButton(
+ onPressed: () => _handleButtonClick(button),
+ style: ElevatedButton.styleFrom(
+ backgroundColor: Colors.deepPurple,
+ foregroundColor: Colors.white,
+ padding: const EdgeInsets.symmetric(vertical: 12),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(8),
+ ),
+ ),
+ child: Text(button.text),
+ ),
+ ),
+ );
+ }).toList(),
+ ];
+ }
+
+ void _handleCardClick() {
+ final appCard = widget.appCard;
+ print('[INSIDER][AppCardItem]: Card clicked');
+ print(' Card ID: ${appCard.id}');
+
+ try {
+ appCard.click();
+ print('[INSIDER][AppCardItem]: Card click tracked in SDK');
+
+ final action = appCard.action;
+ if (action is InsiderAppCardDeeplinkAction && action.url.isEmpty) {
+ final jsonData = action.json;
+ final keyValueData = action.keysAndValues;
+ if (jsonData != null && jsonData.isNotEmpty) {
+ print('[INSIDER][AppCardItem]: Deep link JSON data: $jsonData');
+ } else if (keyValueData != null && keyValueData.isNotEmpty) {
+ print('[INSIDER][AppCardItem]: Deep link key-value data: $keyValueData');
+ }
+ }
+ } catch (e) {
+ print('[INSIDER][AppCardItem]: Error handling card click: $e');
+ }
+ }
+
+ Future _handleButtonClick(InsiderAppCardButton button) async {
+ print('[INSIDER][AppCardItem]: Button clicked');
+ print(' Button ID: ${button.id}');
+ print(' Button Text: ${button.text}');
+ print(' Card ID: ${widget.appCard.id}');
+
+ try {
+ button.click();
+ print('[INSIDER][AppCardItem]: Button click tracked in SDK');
+
+ final action = button.action;
+
+ if (action != null && action is InsiderAppCardDeeplinkAction) {
+ final deeplinkAction = action;
+ final url = deeplinkAction.url;
+
+ if (!url.isNotEmpty) {
+ final jsonData = deeplinkAction.json;
+ final keyValueData = deeplinkAction.keysAndValues;
+ if (jsonData != null && jsonData.isNotEmpty) {
+ print('[INSIDER][AppCardItem]: Deep link JSON data: $jsonData');
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Deep link JSON: $jsonData'),
+ duration: const Duration(seconds: 2),
+ ),
+ );
+ } else if (keyValueData != null && keyValueData.isNotEmpty) {
+ print('[INSIDER][AppCardItem]: Deep link key-value data: $keyValueData');
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Deep link key-value: $keyValueData'),
+ duration: const Duration(seconds: 2),
+ ),
+ );
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Deep link action triggered'),
+ duration: Duration(seconds: 2),
+ ),
+ );
+ }
+ }
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Button "${button.text}" clicked'),
+ duration: const Duration(seconds: 2),
+ ),
+ );
+ }
+ } catch (e) {
+ print('[INSIDER][AppCardItem]: Error handling button click: $e');
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error: $e'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ }
+ }
+
+ Future _markAsRead() async {
+ try {
+ print('[INSIDER][AppCardItem]: Marking card as read: ${widget.appCard.id}');
+ await widget.appCard.markAsRead();
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Card marked as read'),
+ backgroundColor: Colors.green,
+ duration: Duration(seconds: 2),
+ ),
+ );
+
+ await Future.delayed(const Duration(milliseconds: 500));
+ widget.onRefresh();
+ } catch (e) {
+ print('[INSIDER][AppCardItem]: Error marking as read: $e');
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error marking card as read: $e'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ }
+ }
+
+ Future _markAsUnread() async {
+ try {
+ print('[INSIDER][AppCardItem]: Marking card as unread: ${widget.appCard.id}');
+ await widget.appCard.markAsUnread();
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Card marked as unread'),
+ backgroundColor: Colors.orange,
+ duration: Duration(seconds: 2),
+ ),
+ );
+
+ await Future.delayed(const Duration(milliseconds: 500));
+ widget.onRefresh();
+ } catch (e) {
+ print('[INSIDER][AppCardItem]: Error marking as unread: $e');
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error marking as unread: $e'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ }
+ }
+
+ Future _deleteCard() async {
+ try {
+ print('[INSIDER][AppCardItem]: Deleting card: ${widget.appCard.id}');
+ await widget.appCard.delete();
+
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('Card deleted'),
+ backgroundColor: Colors.red,
+ duration: Duration(seconds: 2),
+ ),
+ );
+
+ await Future.delayed(const Duration(milliseconds: 500));
+ widget.onRefresh();
+ } catch (e) {
+ print('[INSIDER][AppCardItem]: Error deleting card: $e');
+ ScaffoldMessenger.of(context).showSnackBar(
+ SnackBar(
+ content: Text('Error deleting card: $e'),
+ backgroundColor: Colors.red,
+ ),
+ );
+ }
+ }
+
+ void _showCardDetails() {
+ final appCard = widget.appCard;
+ final images = appCard.images ?? [];
+ final buttons = appCard.buttons ?? [];
+
+ showDialog(
+ context: context,
+ builder: (context) => AlertDialog(
+ title: const Text('Card Details'),
+ content: SingleChildScrollView(
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ _buildDetailRow('ID', appCard.id),
+ _buildDetailRow('Type', appCard.type),
+ _buildDetailRow('Status', appCard.isRead ? 'Read' : 'Unread'),
+ if (appCard.content != null) ...[
+ const SizedBox(height: 16),
+ const Text(
+ 'Content:',
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(height: 8),
+ if (appCard.content!.title.isNotEmpty)
+ _buildDetailRow('Title', appCard.content!.title),
+ if (appCard.content!.description.isNotEmpty)
+ _buildDetailRow('Description', appCard.content!.description),
+ ],
+ if (images.isNotEmpty) ...[
+ const SizedBox(height: 16),
+ const Text(
+ 'Images:',
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(height: 8),
+ ...images.asMap().entries.map((entry) {
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 4),
+ child: Text('${entry.key + 1}. ${entry.value.url}',
+ style: const TextStyle(fontSize: 12)),
+ );
+ }).toList(),
+ ],
+ if (buttons.isNotEmpty) ...[
+ const SizedBox(height: 16),
+ const Text(
+ 'Buttons:',
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(height: 8),
+ ...buttons.asMap().entries.map((entry) {
+ final btn = entry.value;
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text('${entry.key + 1}. ${btn.text}',
+ style: const TextStyle(fontSize: 12)),
+ Text(' ID: ${btn.id}',
+ style: const TextStyle(fontSize: 11, color: Colors.grey)),
+ if (btn.action != null)
+ Text(' Action: ${btn.action!.actionType}',
+ style: const TextStyle(fontSize: 11, color: Colors.grey)),
+ ],
+ ),
+ );
+ }).toList(),
+ ],
+ if (appCard.action != null) ...[
+ const SizedBox(height: 16),
+ const Text(
+ 'Action:',
+ style: TextStyle(fontWeight: FontWeight.bold),
+ ),
+ const SizedBox(height: 8),
+ Text('Type: ${appCard.action!.actionType}',
+ style: const TextStyle(fontSize: 12)),
+ ],
+ ],
+ ),
+ ),
+ actions: [
+ TextButton(
+ onPressed: () => Navigator.of(context).pop(),
+ child: const Text('OK'),
+ ),
+ ],
+ ),
+ );
+ }
+
+ Widget _buildDetailRow(String label, String value) {
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 8),
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ Text(
+ '$label: ',
+ style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
+ ),
+ Expanded(
+ child: Text(
+ value,
+ style: const TextStyle(fontSize: 13),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/lib/main.dart b/lib/main.dart
index 38fc9ed..36f13ca 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -128,7 +128,7 @@ class _HomePageState extends State {
PageVisit(),
CustomTitle(title: 'GDPR'),
GDPR(),
- CustomTitle(title: 'Message Center'),
+ CustomTitle(title: 'App Cards'),
MessageCenter(),
CustomTitle(title: 'Content Optimizer'),
ContentOptimizer(),
diff --git a/pubspec.yaml b/pubspec.yaml
index 700b882..2d6ebd2 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -29,7 +29,7 @@ environment:
dependencies:
flutter:
sdk: flutter
- flutter_insider: 4.0.7
+ flutter_insider: 5.0.1
# The following adds the Cupertino Icons font to your application.