insightreader_sdk 1.0.0 copy "insightreader_sdk: ^1.0.0" to clipboard
insightreader_sdk: ^1.0.0 copied to clipboard

Personalisation for Flutter news apps — reading tracking, time-of-day category insights, streaks, curated notifications, personalised feeds and Gemini AI summaries.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:insightreader_sdk/insightreader_sdk.dart';

import 'sample_data.dart';
import 'screens/ai_screen.dart';
import 'screens/article_screen.dart';
import 'screens/feed_screen.dart';
import 'screens/insights_screen.dart';

// Demo harness for the Insightreader SDK. Tracking, categories, streaks,
// notifications and feeds all work without a Gemini key; only the AI tab needs
// one. See `kGeminiApiKey` below for how to supply it.

/// The Gemini API key handed to the SDK at initialization.
///
/// Supply it one of two ways — you do not need both:
///
/// 1. **Build-time define (recommended).** Leave the fallback below empty and
///    pass the key on the command line, so it never enters source control:
///
///    ```bash
///    flutter run --dart-define-from-file=env.json
///    ```
///
/// 2. **Paste it here.** Replace the empty fallback string with your key. This
///    is the quickest way to try the AI tab, but the key then lives in source —
///    fine for a throwaway local test, never for anything you commit or ship.
const String kGeminiApiKey = String.fromEnvironment(
  'GEMINI_API_KEY',
  defaultValue: '', // ← or paste your key here for a quick local test
);

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();

  try {
    await InsightreaderSdk.instance.initialize(
      configuration: InsightreaderConfiguration(
        // The key reaches the SDK here, at initialization — the SDK never
        // reads it from the environment itself, so a host app is free to
        // fetch it from its own backend or remote config instead.
        gemini: const GeminiConfiguration(apiKey: kGeminiApiKey),
        // The demo runs without Firebase, so the shared AI cache is off. A
        // production host that wants it calls Firebase.initializeApp() first
        // and drops this line.
        aiCache: AiCacheConfiguration.disabled,
        onStreakMilestone: (days) =>
            AppMessenger.show('🔥 $days-day reading streak!'),
      ),
    );
  } on InsightreaderException catch (error) {
    // A host that is not on the client allowlist still starts; the SDK's
    // widgets simply render as empty boxes.
    debugPrint('Insightreader unavailable: ${error.message}');
  }

  // Forward the SDK's telemetry wherever the host already sends analytics.
  InsightreaderSdk.instance.analyticsEventHandler = (name, parameters) =>
      debugPrint('[analytics] $name $parameters');

  runApp(const ExampleApp());
}

/// Lets any screen surface a snack bar without threading a context through.
abstract final class AppMessenger {
  static final GlobalKey<ScaffoldMessengerState> key =
      GlobalKey<ScaffoldMessengerState>();

  static void show(String message) => key.currentState
    ?..hideCurrentSnackBar()
    ..showSnackBar(SnackBar(content: Text(message)));
}

class ExampleApp extends StatelessWidget {
  const ExampleApp({super.key});

  @override
  Widget build(BuildContext context) => MaterialApp(
    title: 'Insightreader Demo',
    scaffoldMessengerKey: AppMessenger.key,
    theme: ThemeData(
      colorSchemeSeed: const Color(0xFF3E66DF),
      useMaterial3: true,
    ),
    darkTheme: ThemeData(
      colorSchemeSeed: const Color(0xFF3E66DF),
      brightness: Brightness.dark,
      useMaterial3: true,
    ),
    home: const HomeShell(),
  );
}

class HomeShell extends StatefulWidget {
  const HomeShell({super.key});

  @override
  State<HomeShell> createState() => _HomeShellState();
}

class _HomeShellState extends State<HomeShell> {
  int _tab = 0;

  @override
  void initState() {
    super.initState();
    _wireNotifications();
  }

  Future<void> _wireNotifications() async {
    if (!InsightreaderSdk.instance.isInitialized) return;

    // The SDK never navigates on its own — the host routes every tap.
    InsightreaderSdk.instance.onNotificationTap.listen((tap) {
      AppMessenger.show('Notification tapped: ${tap.type.wireName}');
      if (tap.type == InsightreaderNotificationType.briefing) {
        setState(() => _tab = 0);
      } else {
        setState(() => _tab = 2);
      }
    });

    final launch = InsightreaderSdk.instance.launchNotification;
    if (launch != null) {
      AppMessenger.show('Launched from ${launch.type.wireName}');
    }
  }

  @override
  Widget build(BuildContext context) => Scaffold(
    body: IndexedStack(
      index: _tab,
      children: const [
        ArticleListScreen(),
        FeedScreen(),
        InsightsScreen(),
        AiScreen(),
      ],
    ),
    bottomNavigationBar: NavigationBar(
      selectedIndex: _tab,
      onDestinationSelected: (index) => setState(() => _tab = index),
      destinations: const [
        NavigationDestination(
          icon: Icon(Icons.article_outlined),
          selectedIcon: Icon(Icons.article),
          label: 'Read',
        ),
        NavigationDestination(
          icon: Icon(Icons.dynamic_feed_outlined),
          selectedIcon: Icon(Icons.dynamic_feed),
          label: 'Feed',
        ),
        NavigationDestination(
          icon: Icon(Icons.insights_outlined),
          selectedIcon: Icon(Icons.insights),
          label: 'Insights',
        ),
        NavigationDestination(
          icon: Icon(Icons.auto_awesome_outlined),
          selectedIcon: Icon(Icons.auto_awesome),
          label: 'AI',
        ),
      ],
    ),
  );
}

/// Tab 1 — a list of sample stories. Opening one records a read, which is what
/// drives every other tab.
class ArticleListScreen extends StatelessWidget {
  const ArticleListScreen({super.key});

  @override
  Widget build(BuildContext context) => Scaffold(
    appBar: AppBar(
      title: const Text('Read something'),
      actions: [
        IconButton(
          tooltip: 'Reading preferences',
          icon: const Icon(Icons.settings_outlined),
          onPressed: () => Navigator.of(context).push(
            MaterialPageRoute<void>(
              builder: (_) => const InsightreaderSettingsScreen(),
            ),
          ),
        ),
      ],
    ),
    body: ListView.separated(
      itemCount: sampleArticles.length,
      separatorBuilder: (_, _) => const Divider(height: 1),
      itemBuilder: (context, index) {
        final article = sampleArticles[index];
        return ListTile(
          title: Text(article.title),
          subtitle: Text(article.category),
          trailing: const Icon(Icons.chevron_right),
          onTap: () => Navigator.of(context).push(
            MaterialPageRoute<void>(
              builder: (_) => ArticleScreen(article: article),
            ),
          ),
        );
      },
    ),
  );
}
0
likes
140
points
108
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Personalisation for Flutter news apps — reading tracking, time-of-day category insights, streaks, curated notifications, personalised feeds and Gemini AI summaries.

Homepage

Topics

#news #personalization #recommendations #notifications #ai

License

unknown (license)

Dependencies

cached_network_image, cloud_firestore, firebase_core, flutter, flutter_local_notifications, flutter_timezone, http, in_app_review, meta, path, permission_handler, shared_preferences, sqflite, timezone, url_launcher

More

Packages that depend on insightreader_sdk