// Copyright 2020 Ben Hills and the project contributors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import 'package:pinepods_mobile/bloc/podcast/podcast_bloc.dart'; import 'package:pinepods_mobile/bloc/settings/settings_bloc.dart'; import 'package:pinepods_mobile/core/utils.dart'; import 'package:pinepods_mobile/entities/app_settings.dart'; import 'package:pinepods_mobile/l10n/L.dart'; import 'package:pinepods_mobile/ui/settings/episode_refresh.dart'; import 'package:pinepods_mobile/ui/settings/search_provider.dart'; import 'package:pinepods_mobile/ui/settings/settings_section_label.dart'; import 'package:pinepods_mobile/ui/settings/bottom_bar_order.dart'; import 'package:pinepods_mobile/ui/widgets/action_text.dart'; import 'package:pinepods_mobile/ui/settings/pinepods_login.dart'; import 'package:pinepods_mobile/ui/debug/debug_logs_page.dart'; import 'package:pinepods_mobile/ui/themes.dart'; import 'package:file_picker/file_picker.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_dialogs/flutter_dialogs.dart'; import 'package:provider/provider.dart'; /// This is the settings page and allows the user to select various /// options for the app. /// /// This is a self contained page and so, unlike the other forms, talks directly /// to a settings service rather than a BLoC. Whilst this deviates slightly from /// the overall architecture, adding a BLoC to simply be consistent with the rest /// of the application would add unnecessary complexity. /// /// This page is built with both Android & iOS in mind. However, the /// rest of the application is not prepared for iOS design; this /// is in preparation for the iOS version. class Settings extends StatefulWidget { const Settings({ super.key, }); @override State createState() => _SettingsState(); } class _SettingsState extends State { bool sdcard = false; Widget _buildList(BuildContext context) { var settingsBloc = Provider.of(context); var podcastBloc = Provider.of(context); return StreamBuilder( stream: settingsBloc.settings, initialData: settingsBloc.currentSettings, builder: (context, snapshot) { return ListView( children: [ SettingsDividerLabel(label: L.of(context)!.settings_personalisation_divider_label), const Divider(), MergeSemantics( child: Padding( padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( L.of(context)!.settings_theme_switch_label, style: Theme.of(context).textTheme.titleMedium, ), const SizedBox(height: 8), Text( ThemeRegistry.getTheme(snapshot.data!.theme).description, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: Theme.of(context).colorScheme.onSurface.withOpacity(0.7), ), ), const SizedBox(height: 12), Row( children: [ const Icon(Icons.palette, size: 20), const SizedBox(width: 12), Expanded( child: DropdownButton( value: snapshot.data!.theme, isExpanded: true, underline: Container(), items: ThemeRegistry.themeList.map((theme) { return DropdownMenuItem( value: theme.key, child: Row( children: [ Container( width: 16, height: 16, decoration: BoxDecoration( color: theme.isDark ? Colors.grey[800] : Colors.grey[200], border: Border.all( color: theme.themeData.colorScheme.primary, width: 2, ), borderRadius: BorderRadius.circular(8), ), ), const SizedBox(width: 8), Expanded( child: Text( theme.name, overflow: TextOverflow.ellipsis, ), ), ], ), ); }).toList(), onChanged: (String? newTheme) { if (newTheme != null) { settingsBloc.setTheme(newTheme); } }, ), ), ], ), ], ), ), ), sdcard ? MergeSemantics( child: ListTile( title: Text(L.of(context)!.settings_download_sd_card_label), trailing: Switch.adaptive( value: snapshot.data!.storeDownloadsSDCard, onChanged: (value) => sdcard ? setState(() { if (value) { _showStorageDialog(enableExternalStorage: true); } else { _showStorageDialog(enableExternalStorage: false); } settingsBloc.storeDownloadonSDCard(value); }) : null, ), ), ) : const SizedBox( height: 0, width: 0, ), SettingsDividerLabel(label: 'Navigation'), const Divider(), ListTile( title: const Text('Reorganize Bottom Bar'), subtitle: const Text('Customize the order of bottom navigation items'), leading: const Icon(Icons.reorder), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const BottomBarOrderWidget(), ), ); }, ), SettingsDividerLabel(label: L.of(context)!.settings_playback_divider_label), const Divider(), MergeSemantics( child: ListTile( title: Text(L.of(context)!.settings_auto_open_now_playing), trailing: Switch.adaptive( value: snapshot.data!.autoOpenNowPlaying, onChanged: (value) => setState(() => settingsBloc.setAutoOpenNowPlaying(value)), ), ), ), const SearchProviderWidget(), SettingsDividerLabel(label: 'Debug'), const Divider(), ListTile( title: const Text('App Logs'), subtitle: const Text('View debug logs and device information'), leading: const Icon(Icons.bug_report), onTap: () { Navigator.push( context, MaterialPageRoute( builder: (context) => const DebugLogsPage(), ), ); }, ), const PinepodsLoginWidget(), const _WebAppInfoWidget(), ], ); }); } Widget _buildAndroid(BuildContext context) { return AnnotatedRegion( value: Theme.of(context).appBarTheme.systemOverlayStyle!, child: Scaffold( appBar: AppBar( elevation: 0.0, title: Text( L.of(context)!.settings_label, ), ), body: _buildList(context), ), ); } Widget _buildIos(BuildContext context) { return CupertinoPageScaffold( navigationBar: CupertinoNavigationBar( padding: const EdgeInsetsDirectional.all(0.0), leading: CupertinoButton( child: const Icon(Icons.arrow_back_ios), onPressed: () { Navigator.pop(context); }), middle: Text( L.of(context)!.settings_label, style: TextStyle(color: Theme.of(context).colorScheme.primary), ), backgroundColor: Theme.of(context).colorScheme.surface, ), child: Material(child: _buildList(context)), ); } void _showStorageDialog({required bool enableExternalStorage}) { showPlatformDialog( context: context, useRootNavigator: false, builder: (_) => BasicDialogAlert( title: Text(L.of(context)!.settings_download_switch_label), content: Text( enableExternalStorage ? L.of(context)!.settings_download_switch_card : L.of(context)!.settings_download_switch_internal, ), actions: [ BasicDialogAction( title: Text( L.of(context)!.ok_button_label, ), onPressed: () { Navigator.pop(context); }, ), ], ), ); } @override Widget build(context) { switch (defaultTargetPlatform) { case TargetPlatform.android: return _buildAndroid(context); case TargetPlatform.iOS: return _buildIos(context); default: assert(false, 'Unexpected platform $defaultTargetPlatform'); return _buildAndroid(context); } } @override void initState() { super.initState(); hasExternalStorage().then((value) { setState(() { sdcard = value; }); }); } } class _WebAppInfoWidget extends StatelessWidget { const _WebAppInfoWidget(); @override Widget build(BuildContext context) { return Consumer( builder: (context, settingsBloc, child) { final settings = settingsBloc.currentSettings; final serverUrl = settings.pinepodsServer; // Only show if user is connected to a server if (serverUrl == null || serverUrl.isEmpty) { return const SizedBox.shrink(); } return Padding( padding: const EdgeInsets.all(16.0), child: Card( child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Icon( Icons.web, color: Theme.of(context).primaryColor, size: 20, ), const SizedBox(width: 8), Text( 'Web App Settings', style: TextStyle( fontWeight: FontWeight.bold, color: Theme.of(context).primaryColor, ), ), ], ), const SizedBox(height: 8), Text( 'Many more server side and user settings available from the PinePods web app. Please head to $serverUrl to adjust much more', style: Theme.of(context).textTheme.bodySmall, ), ], ), ), ), ); }, ); } }