savers_app_sdk 1.0.3 copy "savers_app_sdk: ^1.0.3" to clipboard
savers_app_sdk: ^1.0.3 copied to clipboard

Flutter SDK for the Savers hosted app: WebView bridge, native maps/dialer/browser, device/session helpers, and encrypted URL generation.

example/lib/main.dart

import 'dart:convert';
import 'dart:io';

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

void main() {
  runApp(const SaversDemoApp());
}

/// Same generateUrl payload shape as clo-app CashbackScreen (EMAIL auth).
const UrlInput demoSaversUrlInput = UrlInput(
  profile: Profile(
    userId: '18898',
    firstname: 'Ramesh',
    lastname: 'Kumar',
    email: 'ramesh+2@saversapp.com',
    phone: '+14153036866',
    city: 'Mountain View',
    zipcode: '94043',
    dob: '',
    pv: '1',
    ev: '1',
  ),
  authType: AuthType.email,
);

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Savers SDK Demo',
      navigatorKey: navigationKey,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: const Color(0xFF2563EB)),
        scaffoldBackgroundColor: const Color(0xFFF8FAFC),
      ),
      home: const HomeScreen(),
    );
  }
}

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

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  String deviceId = '-';
  String locationResult = '-';
  String apiKey = '-';
  String encryptionKey = '-';
  String pRefCode = '-';
  String generatedUrl = '-';

  @override
  void initState() {
    super.initState();
    if (Platform.environment.containsKey('FLUTTER_TEST')) {
      return;
    }
    _boot();
  }

  Future<void> _boot() async {
    try {
      await _initializeSdk();
    } catch (_) {}
    await _fetchInfo();
  }

  Future<void> _initializeSdk() async {
    try {
      await SaversAppSDK.initialized(
        // Same sandbox credentials clo-app uses for testm.saversapp.com
        apiKey: '3xqpYp6CPn899166YXbEB1YLJIhfdj08BbCdfQdg',
        encryptionKey: 'MDEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNkZWY=',
        pRefCode: 'Vd7IR',
        authMode: 'EMAIL',
        environment: SaversSdkHostedEnvironment.sandbox,
      );
    } catch (e) {
      LogsManager.log(LogType.general, 'SDK init failed: $e');
    }
    try {
      await setSessionId('123456789');
    } catch (e) {
      LogsManager.log(LogType.general, 'Session init failed: $e');
    }
    try {
      await setLocationCoordinates(37.7749, -122.4194);
    } catch (e) {
      LogsManager.log(LogType.general, 'Location init failed: $e');
    }
  }

  Future<void> _fetchInfo() async {
    try {
      final id = await getDeviceId();
      setState(() => deviceId = id);
    } catch (e) {
      setState(() => deviceId = e.toString());
    }
    try {
      final loc = await getLocationCoordinates();
      setState(() {
        locationResult = loc != null
            ? '${loc['lat']}, ${loc['lng']}'
            : 'Location unavailable';
      });
      try {
        final url = await generateUrl(demoSaversUrlInput);
        setState(() => generatedUrl = url);
      } catch (e) {
        final message = e.toString();
        setState(() => generatedUrl = message);
        LogsManager.log(LogType.general, 'generateUrl failed: $message');
      }
    } catch (e) {
      setState(() => locationResult = e.toString());
    }
    try {
      final k = await getApiKey();
      setState(() => apiKey = k);
    } catch (e) {
      setState(() => apiKey = e.toString());
    }
    try {
      final ek = await getEncryptionKey();
      setState(() => encryptionKey = ek);
    } catch (e) {
      setState(() => encryptionKey = e.toString());
    }
    try {
      final pr = await getPRefCode();
      setState(() => pRefCode = pr);
    } catch (e) {
      setState(() => pRefCode = e.toString());
    }
  }

  Future<void> _handleAction(
    String action,
    Map<String, dynamic> payload,
  ) async {
    handleWebMessage(
      jsonEncode({'action': action, 'payload': payload}),
      postBack: (_) {},
    );
  }

  bool get _hasGeneratedUrl {
    final uri = Uri.tryParse(generatedUrl);
    return uri != null &&
        (uri.scheme == 'http' || uri.scheme == 'https') &&
        uri.host.isNotEmpty;
  }

  void _openHostedApp(String url) {
    Navigator.of(context).push(
      MaterialPageRoute(builder: (_) => HostedAppScreen(saversAppUrl: url)),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: Column(
          children: [
            const _Header(),
            Expanded(
              child: SingleChildScrollView(
                padding: const EdgeInsets.symmetric(
                  horizontal: 16,
                  vertical: 12,
                ),
                child: Column(
                  children: [
                    _Card(
                      title: 'Navigation Test',
                      child: _PrimaryButton(
                        label: 'Open Close Screen',
                        color: const Color(0xFF3B82F6),
                        onPressed: () {
                          Navigator.of(context).push(
                            MaterialPageRoute(
                              builder: (_) => const CloseTestScreen(),
                            ),
                          );
                        },
                      ),
                    ),
                    _InfoCard(title: 'Device ID', value: deviceId),
                    _InfoCard(title: 'Location', value: locationResult),
                    _InfoCard(title: 'API Key', value: apiKey, mono: true),
                    _InfoCard(
                      title: 'Encryption Key',
                      value: encryptionKey,
                      mono: true,
                    ),
                    _InfoCard(
                      title: 'Program Ref Code',
                      value: pRefCode,
                      mono: true,
                    ),
                    _Card(
                      title: 'Generated URL',
                      child: Column(
                        crossAxisAlignment: CrossAxisAlignment.start,
                        children: [
                          _ValueBox(text: generatedUrl, mono: true),
                          if (_hasGeneratedUrl)
                            Padding(
                              padding: const EdgeInsets.only(top: 12),
                              child: Row(
                                children: [
                                  Expanded(
                                    child: _PrimaryButton(
                                      label: 'Open in Browser',
                                      color: const Color(0xFF3B82F6),
                                      onPressed: () =>
                                          openBrowser(generatedUrl),
                                    ),
                                  ),
                                  const SizedBox(width: 8),
                                  Expanded(
                                    child: _PrimaryButton(
                                      label: 'Open in WebView',
                                      color: const Color(0xFF14B8A6),
                                      onPressed: () =>
                                          _openHostedApp(generatedUrl),
                                    ),
                                  ),
                                ],
                              ),
                            ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ),
            _ActionBar(onAction: _handleAction),
          ],
        ),
      ),
    );
  }
}

class HostedAppScreen extends StatelessWidget {
  const HostedAppScreen({super.key, required this.saversAppUrl});

  final String saversAppUrl;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      body: HostedAppComponent(
        saversAppUrl: saversAppUrl,
        travelHeaderTopInset: TravelPortalHeaderInsets.safeArea,
        onSaversSdkMessage: (raw, postBack) {
          try {
            final msg = jsonDecode(raw);
            if (msg is Map && msg['action'] == 'END_SESSION') {
              Navigator.of(context).maybePop();
              return;
            }
          } catch (_) {}
          handleWebMessage(raw, postBack: postBack);
        },
      ),
    );
  }
}

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

  Future<void> _handleAction(String action) async {
    handleWebMessage(jsonEncode({'action': action}));
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: SafeArea(
        child: SingleChildScrollView(
          padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
          child: Column(
            children: [
              const _Card(
                title: 'SDK Close Current Screen',
                child: _ValueBox(
                  text:
                      'Press the button to close this screen via the SDK navigationKey.',
                ),
              ),
              _MiniActionBar(onClose: () => _handleAction('END_SESSION')),
              Padding(
                padding: const EdgeInsets.only(top: 12),
                child: _PrimaryButton(
                  label: 'Push Another Screen',
                  color: const Color(0xFF14B8A6),
                  onPressed: () {
                    Navigator.of(context).push(
                      MaterialPageRoute(
                        builder: (_) => const CloseTestScreen(),
                      ),
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _Header extends StatelessWidget {
  const _Header();

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
      color: const Color(0xFF2563EB),
      child: const Row(
        children: [
          Text(
            'Savers SDK Demo',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.w700,
              color: Colors.white,
            ),
          ),
          SizedBox(width: 12),
          Text(
            'Native features showcase',
            style: TextStyle(fontSize: 12, color: Color(0xFFE5E7EB)),
          ),
        ],
      ),
    );
  }
}

class _Card extends StatelessWidget {
  final String title;
  final Widget child;

  const _Card({required this.title, required this.child});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: const EdgeInsets.symmetric(vertical: 8),
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: const [
          BoxShadow(
            color: Color(0x1A000000),
            blurRadius: 8,
            offset: Offset(0, 2),
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            title,
            style: const TextStyle(fontSize: 12, color: Color(0xFF6B7280)),
          ),
          const SizedBox(height: 8),
          child,
        ],
      ),
    );
  }
}

class _InfoCard extends StatelessWidget {
  final String title;
  final String value;
  final bool mono;

  const _InfoCard({
    required this.title,
    required this.value,
    this.mono = false,
  });

  @override
  Widget build(BuildContext context) {
    return _Card(
      title: title,
      child: _ValueBox(text: value, mono: mono),
    );
  }
}

class _ValueBox extends StatelessWidget {
  final String text;
  final bool mono;

  const _ValueBox({required this.text, this.mono = false});

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      decoration: BoxDecoration(
        color: const Color(0xFFF3F4F6),
        borderRadius: BorderRadius.circular(8),
      ),
      child: Text(
        text,
        style: TextStyle(
          fontSize: mono ? 14 : 16,
          color: const Color(0xFF111827),
        ),
      ),
    );
  }
}

class _PrimaryButton extends StatelessWidget {
  final String label;
  final Color color;
  final VoidCallback onPressed;

  const _PrimaryButton({
    required this.label,
    required this.color,
    required this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 40,
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
        ),
        onPressed: onPressed,
        child: Text(
          label,
          style: const TextStyle(
            color: Colors.white,
            fontSize: 14,
            fontWeight: FontWeight.w600,
          ),
        ),
      ),
    );
  }
}

class _ActionBar extends StatelessWidget {
  final Future<void> Function(String action, Map<String, dynamic> payload)
  onAction;

  const _ActionBar({required this.onAction});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 64,
      margin: const EdgeInsets.fromLTRB(16, 0, 16, 12),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFFE5E7EB)),
      ),
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 8),
        child: SizedBox(
          height: _actionButtonHeight,
          child: ListView(
            scrollDirection: Axis.horizontal,
            padding: const EdgeInsets.symmetric(horizontal: 16),
            children: [
              _ActionButton(
                label: 'Open Map',
                onPressed: () => onAction('OPEN_MAP', {
                  'lat': 37.7749,
                  'lng': -122.4194,
                  'label': 'SaverAppsPoint',
                }),
              ),
              _ActionButton(
                label: 'Open Dialpad',
                onPressed: () =>
                    onAction('SHOW_DIAL_PAD', {'number': '+1234567890'}),
              ),
              _ActionButton(
                label: 'Open Browser',
                onPressed: () => onAction('MERCHANT_PORTAL_REDIRECT', {
                  'url': 'https://www.google.com',
                }),
              ),
              _ActionButton(
                label: 'Set Session ID',
                onPressed: () =>
                    onAction('SESSION_ID', {'sessionId': '123456789'}),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

class _MiniActionBar extends StatelessWidget {
  final VoidCallback onClose;

  const _MiniActionBar({required this.onClose});

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 64,
      margin: const EdgeInsets.only(top: 12),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        border: Border.all(color: const Color(0xFFE5E7EB)),
      ),
      child: Center(
        child: _ActionButton(label: 'Close Screen', onPressed: onClose),
      ),
    );
  }
}

const double _actionButtonHeight = 44;

class _ActionButton extends StatelessWidget {
  final String label;
  final VoidCallback? onPressed;

  const _ActionButton({required this.label, required this.onPressed});

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.only(right: 8),
      child: OutlinedButton(
        style: OutlinedButton.styleFrom(
          backgroundColor: const Color(0xFFF5F5F5),
          foregroundColor: const Color(0xFF2563EB),
          disabledForegroundColor: const Color(0xFF9CA3AF),
          disabledBackgroundColor: const Color(0xFFF5F5F5),
          side: const BorderSide(color: Color(0xFFCCCCCC)),
          padding: const EdgeInsets.symmetric(horizontal: 16),
          minimumSize: const Size(0, _actionButtonHeight),
          maximumSize: const Size(double.infinity, _actionButtonHeight),
          fixedSize: const Size.fromHeight(_actionButtonHeight),
          tapTargetSize: MaterialTapTargetSize.shrinkWrap,
          visualDensity: VisualDensity.compact,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
        ),
        onPressed: onPressed,
        child: Text(label, style: const TextStyle(fontSize: 16)),
      ),
    );
  }
}
0
likes
0
points
454
downloads

Publisher

verified publishersaversapp.com

Weekly Downloads

Flutter SDK for the Savers hosted app: WebView bridge, native maps/dialer/browser, device/session helpers, and encrypted URL generation.

Repository (GitHub)
View/report issues

License

unknown (license)

Dependencies

connectivity_plus, cryptography, device_info_plus, dio, flutter, flutter_secure_storage, geolocator, hive, http, shared_preferences, url_launcher, uuid, webview_flutter

More

Packages that depend on savers_app_sdk