nkbmusic/lib/main.dart
metatroncubeswdev fc989b4c77 Store release setup: app icon, launch screen, iOS config, Android signing, Codemagic
- App icon from the NK emblem (iOS set, Android adaptive icon, Play 512 icon)
- Dark launch screen on both platforms; app locked to portrait; iPhone-only
- Info.plist: display name, photo/camera reasons, export compliance
- Android release signing from key.properties or Codemagic CM_KEYSTORE_*
- codemagic.yaml: iOS to TestFlight, Android to Play internal testing
- Account deletion web page; fix trailing dot in email links
- docs/app-store-release.md: step-by-step store guide

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 00:06:31 -04:00

123 lines
4.2 KiB
Dart

import 'package:audio_service/audio_service.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
import 'app/router.dart';
import 'app/theme.dart';
import 'audio/nk_audio_handler.dart';
import 'audio/player_providers.dart';
import 'audio/sound_settings.dart';
import 'core/env.dart';
import 'data/user_library.dart';
import 'features/support/support_screen.dart';
import 'offline/download_store.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
if (Env.hasSupabase) {
await Supabase.initialize(url: Env.supabaseUrl, publishableKey: Env.supabasePublishableKey);
}
final prefs = await SharedPreferences.getInstance();
final audioHandler = await AudioService.init(
builder: NkAudioHandler.new,
config: const AudioServiceConfig(
androidNotificationChannelId: 'com.metatroncubesolutions.nanga_kathai.playback',
androidNotificationChannelName: 'Nanga Kathai playback',
androidNotificationOngoing: true,
androidStopForegroundOnPause: true,
),
);
final downloads = await DownloadStore.open();
audioHandler.localSource = (item) => downloads.sourceFor(item.id, tag: item);
runApp(
ProviderScope(
overrides: [
sharedPrefsProvider.overrideWithValue(prefs),
audioHandlerProvider.overrideWithValue(audioHandler),
downloadStoreProvider.overrideWithValue(downloads),
],
child: const NangaKathaiApp(),
),
);
}
class NangaKathaiApp extends ConsumerWidget {
const NangaKathaiApp({super.key});
@override
Widget build(BuildContext context, WidgetRef ref) {
ref.watch(soundSettingsProvider); // apply the saved equalizer and boost at startup
ref.read(supportProvider); // finish any support payment that completed while the app was closed
final router = ref.watch(routerProvider);
// Guests hear a short preview of each song, then are invited to sign up.
ref.listen(positionProvider, (_, next) {
final position = next.value ?? Duration.zero;
if (!Env.hasSupabase || isSignedIn || position < guestPreview) return;
final handler = ref.read(audioHandlerProvider);
if (!handler.playbackState.value.playing) return;
handler
..pause()
..seek(Duration.zero);
final context = router.routerDelegate.navigatorKey.currentContext;
if (context != null) _showPreviewEnded(context);
});
return MaterialApp.router(
title: 'Nanga Kathai',
debugShowCheckedModeBanner: false,
theme: buildNkTheme(),
routerConfig: router,
);
}
}
const guestPreview = Duration(seconds: 30);
void _showPreviewEnded(BuildContext context) {
showModalBottomSheet<void>(
context: context,
backgroundColor: NkColors.surface,
showDragHandle: true,
builder: (sheet) => SafeArea(
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 24),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text('Enjoying the song?', style: Theme.of(sheet).textTheme.headlineSmall),
const SizedBox(height: 8),
const Text(
'Previews are 30 seconds. Create a free account to listen to every song in full.',
style: TextStyle(color: NkColors.muted, height: 1.5),
),
const SizedBox(height: 20),
FilledButton(
onPressed: () {
Navigator.pop(sheet);
GoRouter.of(context).push('/login?mode=signup');
},
child: const Text('Create free account'),
),
TextButton(
onPressed: () {
Navigator.pop(sheet);
GoRouter.of(context).push('/login');
},
child: const Text('I already have an account'),
),
],
),
),
),
);
}