Merge branch 'settings-and-dark-mode' into 'main'

Settings and dark mode

See merge request mysocialportal/friendica_portal!6
codemagic-setup
HankG 2023-01-20 01:49:11 +00:00
commit 6e9d34ff54
14 zmienionych plików z 346 dodań i 189 usunięć

11
lib/app_theme.dart 100644
Wyświetl plik

@ -0,0 +1,11 @@
import 'package:flutter/material.dart';
class AppTheme {
static ThemeData light = ThemeData(primarySwatch: Colors.indigo);
static ThemeData dark = ThemeData.from(
colorScheme: ColorScheme.fromSwatch(
primarySwatch: Colors.cyan,
brightness: Brightness.dark,
));
}

Wyświetl plik

@ -9,10 +9,8 @@ import '../services/notifications_manager.dart';
enum NavBarButtons {
timelines,
notifications,
messages,
gallery,
contacts,
profile,
menu,
}
class AppBottomNavBar extends StatelessWidget {
@ -41,25 +39,17 @@ class AppBottomNavBar extends StatelessWidget {
case NavBarButtons.notifications:
context.pushNamed(ScreenPaths.notifications);
break;
case NavBarButtons.messages:
// TODO: Handle this case.
break;
case NavBarButtons.contacts:
context.pushNamed(ScreenPaths.contacts);
break;
case NavBarButtons.profile:
context.pushNamed(ScreenPaths.profile);
break;
case NavBarButtons.gallery:
context.pushNamed(ScreenPaths.gallery);
case NavBarButtons.menu:
context.pushNamed(ScreenPaths.menu);
break;
}
},
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.black,
unselectedItemColor: Colors.black54,
currentIndex: _buttonToIndex(currentButton),
items: _menuItems(hasNotifications),
items: _menuItems(context, hasNotifications),
);
}
@ -69,14 +59,10 @@ class AppBottomNavBar extends StatelessWidget {
return 0;
case NavBarButtons.notifications:
return 1;
case NavBarButtons.gallery:
return 2;
case NavBarButtons.messages:
return 3;
case NavBarButtons.contacts:
return 4;
case NavBarButtons.profile:
return 5;
return 2;
case NavBarButtons.menu:
return 3;
}
}
@ -90,25 +76,18 @@ class AppBottomNavBar extends StatelessWidget {
}
if (index == 2) {
return NavBarButtons.gallery;
}
if (index == 3) {
return NavBarButtons.messages;
}
if (index == 4) {
return NavBarButtons.contacts;
}
if (index == 5) {
return NavBarButtons.profile;
if (index == 3) {
return NavBarButtons.menu;
}
throw ArgumentError('$index has no button type');
}
List<BottomNavigationBarItem> _menuItems(bool hasNotifications) {
List<BottomNavigationBarItem> _menuItems(
BuildContext context, bool hasNotifications) {
return [
const BottomNavigationBarItem(
label: 'Timelines',
@ -124,25 +103,15 @@ class AppBottomNavBar extends StatelessWidget {
? Icons.notifications_active
: Icons.notifications),
),
const BottomNavigationBarItem(
label: 'Gallery',
icon: Icon(Icons.photo_library_outlined),
activeIcon: Icon(Icons.photo_library),
),
const BottomNavigationBarItem(
label: 'Messages',
icon: Icon(Icons.messenger_outline),
activeIcon: Icon(Icons.messenger),
),
const BottomNavigationBarItem(
label: 'Contacts',
icon: Icon(Icons.people_outline),
activeIcon: Icon(Icons.people_sharp),
),
const BottomNavigationBarItem(
label: 'Profile',
icon: Icon(Icons.person_outline),
activeIcon: Icon(Icons.person),
label: 'Menu',
icon: Icon(Icons.menu),
activeIcon: Icon(Icons.menu_open),
),
];
}

Wyświetl plik

@ -0,0 +1,21 @@
import 'package:flutter/material.dart';
import '../routes.dart';
class StandardAppBar {
static AppBar build(BuildContext context, String title) {
return AppBar(
title: Text(title),
actions: [
IconButton(
onPressed: () {
Navigator.of(context).popUntil((route) {
return route.settings.name == ScreenPaths.timelines;
});
},
icon: const Icon(Icons.home),
),
],
);
}
}

Wyświetl plik

@ -0,0 +1,73 @@
import 'package:logging/logging.dart';
import 'package:result_monad/result_monad.dart';
import 'data/interfaces/connections_repo_intf.dart';
import 'data/interfaces/groups_repo.intf.dart';
import 'data/interfaces/hashtag_repo_intf.dart';
import 'data/memory/memory_groups_repo.dart';
import 'data/objectbox/objectbox_cache.dart';
import 'data/objectbox/objectbox_connections_repo.dart';
import 'data/objectbox/objectbox_hashtag_repo.dart';
import 'globals.dart';
import 'models/TimelineIdentifiers.dart';
import 'services/auth_service.dart';
import 'services/connections_manager.dart';
import 'services/entry_manager_service.dart';
import 'services/gallery_service.dart';
import 'services/hashtag_service.dart';
import 'services/media_upload_attachment_helper.dart';
import 'services/notifications_manager.dart';
import 'services/secrets_service.dart';
import 'services/setting_service.dart';
import 'services/timeline_manager.dart';
final _logger = Logger('DI_Init');
Future<void> dependencyInjectionInitialization() async {
final authService = AuthService();
final secretsService = SecretsService();
final entryManagerService = EntryManagerService();
final timelineManager = TimelineManager();
final galleryService = GalleryService();
getIt.registerSingletonAsync(() async {
final service = SettingsService();
await service.initialize();
return service;
});
final objectBoxCache = await ObjectBoxCache.create();
getIt.registerSingleton<ObjectBoxCache>(objectBoxCache);
getIt.registerSingleton<IConnectionsRepo>(ObjectBoxConnectionsRepo());
getIt.registerSingleton<IHashtagRepo>(ObjectBoxHashtagRepo());
getIt.registerSingleton<IGroupsRepo>(MemoryGroupsRepo());
getIt.registerLazySingleton<ConnectionsManager>(() => ConnectionsManager());
getIt.registerLazySingleton<HashtagService>(() => HashtagService());
getIt.registerSingleton(galleryService);
getIt.registerSingleton<EntryManagerService>(entryManagerService);
getIt.registerSingleton<SecretsService>(secretsService);
getIt.registerSingleton<AuthService>(authService);
getIt.registerSingleton<TimelineManager>(timelineManager);
getIt.registerLazySingleton<MediaUploadAttachmentHelper>(
() => MediaUploadAttachmentHelper());
getIt.registerLazySingleton<NotificationsManager>(
() => NotificationsManager());
galleryService.getGalleries();
await secretsService.initialize().andThenSuccessAsync((credentials) async {
if (credentials.isEmpty) {
return;
}
final wasLoggedIn = await authService.getStoredLoginState();
if (wasLoggedIn) {
final result = await authService.signIn(credentials);
if (result.isSuccess) {
timelineManager.updateTimeline(
TimelineIdentifiers.home(), TimelineRefreshType.loadOlder);
}
} else {
_logger.severe('Was not logged in');
}
});
}

Wyświetl plik

@ -3,32 +3,21 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:logging/logging.dart';
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
import 'package:provider/provider.dart';
import 'package:result_monad/result_monad.dart';
import 'data/interfaces/connections_repo_intf.dart';
import 'data/interfaces/groups_repo.intf.dart';
import 'data/interfaces/hashtag_repo_intf.dart';
import 'data/memory/memory_groups_repo.dart';
import 'data/objectbox/objectbox_cache.dart';
import 'data/objectbox/objectbox_connections_repo.dart';
import 'data/objectbox/objectbox_hashtag_repo.dart';
import 'app_theme.dart';
import 'di_initialization.dart';
import 'globals.dart';
import 'models/TimelineIdentifiers.dart';
import 'routes.dart';
import 'services/auth_service.dart';
import 'services/connections_manager.dart';
import 'services/entry_manager_service.dart';
import 'services/gallery_service.dart';
import 'services/hashtag_service.dart';
import 'services/media_upload_attachment_helper.dart';
import 'services/notifications_manager.dart';
import 'services/secrets_service.dart';
import 'services/setting_service.dart';
import 'services/timeline_manager.dart';
import 'utils/app_scrolling_behavior.dart';
final _logger = Logger('Main');
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await dotenv.load(fileName: '.env');
@ -41,52 +30,7 @@ void main() async {
print(msg);
});
final authService = AuthService();
final secretsService = SecretsService();
final entryManagerService = EntryManagerService();
final timelineManager = TimelineManager();
final galleryService = GalleryService();
getIt.registerSingletonAsync(() async {
final service = SettingsService();
await service.initialize();
return service;
});
final objectBoxCache = await ObjectBoxCache.create();
getIt.registerSingleton<ObjectBoxCache>(objectBoxCache);
getIt.registerSingleton<IConnectionsRepo>(ObjectBoxConnectionsRepo());
getIt.registerSingleton<IHashtagRepo>(ObjectBoxHashtagRepo());
getIt.registerSingleton<IGroupsRepo>(MemoryGroupsRepo());
getIt.registerLazySingleton<ConnectionsManager>(() => ConnectionsManager());
getIt.registerLazySingleton<HashtagService>(() => HashtagService());
getIt.registerSingleton(galleryService);
getIt.registerSingleton<EntryManagerService>(entryManagerService);
getIt.registerSingleton<SecretsService>(secretsService);
getIt.registerSingleton<AuthService>(authService);
getIt.registerSingleton<TimelineManager>(timelineManager);
getIt.registerLazySingleton<MediaUploadAttachmentHelper>(
() => MediaUploadAttachmentHelper());
getIt.registerLazySingleton<NotificationsManager>(
() => NotificationsManager());
galleryService.getGalleries();
await secretsService.initialize().andThenSuccessAsync((credentials) async {
if (credentials.isEmpty) {
return;
}
final wasLoggedIn = await authService.getStoredLoginState();
if (wasLoggedIn) {
final result = await authService.signIn(credentials);
if (result.isSuccess) {
timelineManager.updateTimeline(
TimelineIdentifiers.home(), TimelineRefreshType.loadOlder);
}
} else {
_logger.severe('Was not logged in');
}
});
dependencyInjectionInitialization();
runApp(const App());
}
@ -97,51 +41,56 @@ class App extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return Portal(
child: MultiProvider(
providers: [
ChangeNotifierProvider<SettingsService>(
create: (_) => getIt<SettingsService>(),
lazy: true,
return AnimatedBuilder(
builder: (context, child) {
return Portal(
child: MultiProvider(
providers: [
ChangeNotifierProvider<SettingsService>(
create: (_) => getIt<SettingsService>(),
lazy: true,
),
ChangeNotifierProvider<AuthService>(
create: (_) => getIt<AuthService>(),
lazy: true,
),
ChangeNotifierProvider<ConnectionsManager>(
create: (_) => getIt<ConnectionsManager>(),
lazy: true,
),
ChangeNotifierProvider<EntryManagerService>(
create: (_) => getIt<EntryManagerService>(),
lazy: true,
),
ChangeNotifierProvider<GalleryService>(
create: (_) => getIt<GalleryService>(),
lazy: true,
),
ChangeNotifierProvider<HashtagService>(
create: (_) => getIt<HashtagService>(),
lazy: true,
),
ChangeNotifierProvider<TimelineManager>(
create: (_) => getIt<TimelineManager>(),
),
ChangeNotifierProvider<NotificationsManager>(
create: (_) => getIt<NotificationsManager>(),
),
],
child: MaterialApp.router(
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: getIt<SettingsService>().themeMode,
debugShowCheckedModeBanner: false,
scrollBehavior: AppScrollingBehavior(),
routerDelegate: appRouter.routerDelegate,
routeInformationProvider: appRouter.routeInformationProvider,
routeInformationParser: appRouter.routeInformationParser,
),
),
ChangeNotifierProvider<AuthService>(
create: (_) => getIt<AuthService>(),
lazy: true,
),
ChangeNotifierProvider<ConnectionsManager>(
create: (_) => getIt<ConnectionsManager>(),
lazy: true,
),
ChangeNotifierProvider<EntryManagerService>(
create: (_) => getIt<EntryManagerService>(),
lazy: true,
),
ChangeNotifierProvider<GalleryService>(
create: (_) => getIt<GalleryService>(),
lazy: true,
),
ChangeNotifierProvider<HashtagService>(
create: (_) => getIt<HashtagService>(),
lazy: true,
),
ChangeNotifierProvider<TimelineManager>(
create: (_) => getIt<TimelineManager>(),
),
ChangeNotifierProvider<NotificationsManager>(
create: (_) => getIt<NotificationsManager>(),
),
],
child: MaterialApp.router(
theme: ThemeData(
primarySwatch: Colors.indigo,
),
debugShowCheckedModeBanner: false,
scrollBehavior: AppScrollingBehavior(),
routerDelegate: appRouter.routerDelegate,
routeInformationProvider: appRouter.routeInformationProvider,
routeInformationParser: appRouter.routeInformationParser,
),
),
);
},
animation: getIt<SettingsService>(),
);
}
}

Wyświetl plik

@ -7,9 +7,11 @@ import 'screens/follow_request_adjudication_screen.dart';
import 'screens/gallery_browsers_screen.dart';
import 'screens/gallery_screen.dart';
import 'screens/home.dart';
import 'screens/menus_screen.dart';
import 'screens/notifications_screen.dart';
import 'screens/post_screen.dart';
import 'screens/profile_screen.dart';
import 'screens/settings_screen.dart';
import 'screens/sign_in.dart';
import 'screens/splash.dart';
import 'screens/user_posts_screen.dart';
@ -20,13 +22,14 @@ class ScreenPaths {
static String connectHandle = '/connect';
static String contacts = '/contacts';
static String splash = '/splash';
static String menu = '/menu';
static String settings = '/settings';
static String timelines = '/';
static String gallery = '/gallery';
static String profile = '/profile';
static String notifications = '/notifications';
static String signin = '/signin';
static String signup = '/signup';
static String settings = '/settings';
static String userProfile = '/user_profile';
static String userPosts = '/user_posts';
}
@ -90,6 +93,20 @@ final appRouter = GoRouter(
child: ProfileScreen(),
),
),
GoRoute(
path: ScreenPaths.menu,
name: ScreenPaths.menu,
pageBuilder: (context, state) => NoTransitionPage(
child: MenusScreen(),
),
),
GoRoute(
path: ScreenPaths.settings,
name: ScreenPaths.settings,
pageBuilder: (context, state) => NoTransitionPage(
child: SettingsScreen(),
),
),
GoRoute(
path: ScreenPaths.gallery,
name: ScreenPaths.gallery,

Wyświetl plik

@ -3,8 +3,8 @@ import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../controls/padding.dart';
import '../controls/standard_appbar.dart';
import '../services/gallery_service.dart';
class GalleryBrowsersScreen extends StatelessWidget {
@ -15,13 +15,12 @@ class GalleryBrowsersScreen extends StatelessWidget {
_logger.finest('Building');
final service = context.watch<GalleryService>();
return Scaffold(
appBar: StandardAppBar.build(context, 'Galleries'),
body: RefreshIndicator(
onRefresh: () async {
await service.updateGalleries();
},
child: buildBody(context, service)),
bottomNavigationBar: const AppBottomNavBar(
currentButton: NavBarButtons.gallery,
onRefresh: () async {
await service.updateGalleries();
},
child: buildBody(context, service),
),
);
}

Wyświetl plik

@ -4,6 +4,7 @@ import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import '../controls/padding.dart';
import '../controls/standard_appbar.dart';
import '../serializers/friendica/image_entry_friendica_extensions.dart';
import '../services/gallery_service.dart';
import 'image_viewer_screen.dart';
@ -20,9 +21,7 @@ class GalleryScreen extends StatelessWidget {
_logger.finest('Building');
final service = context.watch<GalleryService>();
return Scaffold(
appBar: AppBar(
title: Text(galleryName),
),
appBar: StandardAppBar.build(context, galleryName),
body: RefreshIndicator(
onRefresh: () async {
await service.updateGalleryImageList(galleryName);

Wyświetl plik

@ -81,8 +81,8 @@ class _HomeScreenState extends State<HomeScreen> {
context.push('/post/new');
},
icon: Icon(
Icons.add,
color: Theme.of(context).primaryColor,
Icons.edit,
color: Theme.of(context).textTheme.bodyText1!.color,
),
),
],

Wyświetl plik

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:friendica_portal/globals.dart';
import 'package:friendica_portal/services/auth_service.dart';
import 'package:go_router/go_router.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../routes.dart';
class MenusScreen extends StatelessWidget {
static const menuButtonWidth = 350.0;
static const menuButtonHeight = 125.0;
@override
Widget build(BuildContext context) {
final menuItems = [
buildMenuButton('Gallery', () => context.pushNamed(ScreenPaths.gallery)),
buildMenuButton('Profile', () => context.pushNamed(ScreenPaths.profile)),
buildMenuButton(
'Settings', () => context.pushNamed(ScreenPaths.settings)),
buildMenuButton('Logout', () async {
final confirm = await showYesNoDialog(context, 'Log out account?');
if (confirm == true) {
await getIt<AuthService>().signOut();
}
}),
];
return Scaffold(
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: GridView(
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
mainAxisExtent: menuButtonHeight,
maxCrossAxisExtent: menuButtonWidth,
),
children: menuItems,
),
),
),
bottomNavigationBar: const AppBottomNavBar(
currentButton: NavBarButtons.menu,
),
);
}
Widget buildMenuButton(String title, Function() onPressed) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: ElevatedButton(
onPressed: onPressed,
child: Text(title),
),
);
}
}

Wyświetl plik

@ -1,10 +1,8 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../controls/padding.dart';
import '../controls/standard_appbar.dart';
import '../services/auth_service.dart';
import '../services/setting_service.dart';
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
@ -16,41 +14,18 @@ class ProfileScreen extends StatefulWidget {
class _ProfileScreenState extends State<ProfileScreen> {
@override
Widget build(BuildContext context) {
final settings = context.watch<SettingsService>();
final authService = context.watch<AuthService>();
return Scaffold(
appBar: AppBar(
title: Text('Profile'),
),
appBar: StandardAppBar.build(context, 'Profile'),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Checkbox(
value: settings.lowBandwidthMode,
onChanged: (value) =>
settings.lowBandwidthMode = value ?? false,
),
Text('Low Bandwidth Mode'),
],
),
Text(
'Profile: ${authService.currentClient.fold(onSuccess: (client) => client.credentials.handle, onError: (error) => 'Error Getting Profile')}'),
VerticalPadding(),
ElevatedButton(
onPressed: () async {
await authService.signOut();
},
child: Text('Sign Out')),
],
),
),
bottomNavigationBar: AppBottomNavBar(
currentButton: NavBarButtons.profile,
),
);
}
}

Wyświetl plik

@ -0,0 +1,55 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../controls/standard_appbar.dart';
import '../services/setting_service.dart';
import '../utils/theme_mode_extensions.dart';
class SettingsScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final settings = context.watch<SettingsService>();
return Scaffold(
appBar: StandardAppBar.build(context, 'Settings'),
body: Center(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ListView(
children: [
buildLowBandwidthWidget(settings),
buildThemeWidget(settings),
],
),
),
));
}
Widget buildLowBandwidthWidget(SettingsService settings) {
return ListTile(
title: const Text('Low bandwidth mode'),
trailing: Checkbox(
onChanged: (value) {
settings.lowBandwidthMode = value ?? false;
},
value: settings.lowBandwidthMode,
),
);
}
Widget buildThemeWidget(SettingsService settings) {
return ListTile(
title: const Text('Dark Mode Theme:'),
trailing: DropdownButton<ThemeMode>(
value: settings.themeMode,
items: ThemeMode.values
.map((m) => DropdownMenuItem(value: m, child: Text(m.toLabel())))
.toList(),
onChanged: (value) {
if (value != null) {
settings.themeMode = value;
}
},
),
);
}
}

Wyświetl plik

@ -1,6 +1,8 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../utils/theme_mode_extensions.dart';
class SettingsService extends ChangeNotifier {
late final SharedPreferences _prefs;
var _initialized = false;
@ -17,14 +19,26 @@ class SettingsService extends ChangeNotifier {
notifyListeners();
}
var _themeMode = ThemeMode.system;
ThemeMode get themeMode => _themeMode;
set themeMode(ThemeMode mode) {
_themeMode = mode;
_prefs.setString(_themeModeKey, _themeMode.name);
notifyListeners();
}
Future<void> initialize() async {
if (_initialized) {
return;
}
_prefs = await SharedPreferences.getInstance();
_lowBandwidthMode = _prefs.getBool(_lowBandwidthModeKey) ?? false;
_themeMode = ThemeModeExtensions.parse(_prefs.getString(_themeModeKey));
_initialized = true;
}
}
const _lowBandwidthModeKey = 'LowBandwidthMode';
const _themeModeKey = 'ThemeMode';

Wyświetl plik

@ -0,0 +1,20 @@
import 'package:flutter/material.dart';
extension ThemeModeExtensions on ThemeMode {
static ThemeMode parse(String? themeModeString) =>
ThemeMode.values.firstWhere(
(m) => m.name == themeModeString,
orElse: () => ThemeMode.system,
);
String toLabel() {
switch (this) {
case ThemeMode.system:
return 'System';
case ThemeMode.light:
return 'Light';
case ThemeMode.dark:
return 'Dark';
}
}
}