Add initial basic focus mode implementation

merge-requests/67/merge
Hank Grabowski 2024-07-24 11:42:24 -04:00
rodzic 0e18e02b3a
commit f5241a72fa
5 zmienionych plików z 332 dodań i 253 usunięć

Wyświetl plik

@ -0,0 +1,55 @@
import 'dart:async';
import 'package:flutter/material.dart';
import '../utils/dateutils.dart';
class AppBottomFocusModeStatus extends StatefulWidget {
final DateTime? disableTime;
const AppBottomFocusModeStatus({super.key, required this.disableTime});
@override
State<AppBottomFocusModeStatus> createState() =>
_AppBottomFocusModeStatusState();
}
class _AppBottomFocusModeStatusState extends State<AppBottomFocusModeStatus> {
Timer? updateTimer;
Duration? timeUntil;
@override
void initState() {
super.initState();
_updateTimeUntil();
updateTimer = Timer.periodic(Duration(seconds: 1), (_) {
setState(() {
_updateTimeUntil();
});
});
}
@override
void dispose() {
print('Disposing');
updateTimer?.cancel();
super.dispose();
}
void _updateTimeUntil() {
timeUntil = widget.disableTime?.difference(DateTime.now());
}
@override
Widget build(BuildContext context) {
final title = timeUntil == null
? 'Focus Mode'
: 'Focus Mode for ${timeUntil!.simpleLabel}';
return Text(
title,
style: Theme.of(context).textTheme.headlineSmall!.copyWith(
fontWeight: FontWeight.bold,
),
);
}
}

Wyświetl plik

@ -5,6 +5,7 @@ import 'package:logging/logging.dart';
import '../globals.dart';
import '../routes.dart';
import '../services/auth_service.dart';
import 'focus_mode_menu_item.dart';
import 'login_aware_cached_network_image.dart';
class StandardAppDrawer extends StatelessWidget {
@ -62,6 +63,7 @@ class StandardAppDrawer extends StatelessWidget {
'Manage Profiles',
() => context.pushNamed(ScreenPaths.manageProfiles),
),
const FocusModeMenuItem(),
const Divider(),
buildMenuButton(
context,

Wyświetl plik

@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart' as fr;
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:media_kit/media_kit.dart';
import 'package:multi_trigger_autocomplete/multi_trigger_autocomplete.dart';
@ -8,6 +10,7 @@ import 'package:provider/provider.dart';
import 'app_theme.dart';
import 'di_initialization.dart';
import 'globals.dart';
import 'riverpod_controllers/focus_mode.dart';
import 'routes.dart';
import 'services/auth_service.dart';
import 'services/blocks_manager.dart';
@ -51,7 +54,7 @@ void main() async {
// enabled: !kReleaseMode && enablePreview,
// builder: (context) => const App(),
// ));
runApp(const App());
runApp(const fr.ProviderScope(child: App()));
}
Future<void> setupPackageInfoAndUserAgent() async {
@ -60,13 +63,45 @@ Future<void> setupPackageInfoAndUserAgent() async {
userAgent = 'Relatica/$appVersion';
}
class App extends StatelessWidget {
class App extends fr.ConsumerStatefulWidget {
const App({super.key});
// This widget is the root of your application.
@override
fr.ConsumerState<App> createState() => _AppState();
}
class _AppState extends fr.ConsumerState<App> {
@override
Widget build(BuildContext context) {
final settingsService = getIt<SettingsService>();
final authService = getIt<AccountsService>();
final appRouter = GoRouter(
initialLocation: ScreenPaths.timelines,
debugLogDiagnostics: true,
refreshListenable: authService,
redirect: (context, state) async {
final loggedIn = authService.loggedIn;
final focusMode = ref.read(focusModeProvider);
print('Focus mode? $focusMode');
if (!loggedIn && authService.initializing) {
return ScreenPaths.splash;
}
if (!loggedIn && !allowedLoggedOut.contains(state.uri.toString())) {
return ScreenPaths.signin;
}
if (loggedIn && allowedLoggedOut.contains(state.uri.toString())) {
return ScreenPaths.timelines;
}
return null;
},
routes: routes,
);
return AnimatedBuilder(
builder: (context, child) {
Logger.root.level = settingsService.logLevel;

Wyświetl plik

@ -1,6 +1,5 @@
import 'package:go_router/go_router.dart';
import 'globals.dart';
import 'models/interaction_type_enum.dart';
import 'screens/blocks_screen.dart';
import 'screens/circle_add_users_screen.dart';
@ -29,7 +28,6 @@ import 'screens/sign_in.dart';
import 'screens/splash.dart';
import 'screens/user_posts_screen.dart';
import 'screens/user_profile_screen.dart';
import 'services/auth_service.dart';
class ScreenPaths {
static String blocks = '/blocks';
@ -56,271 +54,249 @@ class ScreenPaths {
}
bool needAuthChangeInitialized = true;
final _authService = getIt<AccountsService>();
final allowedLoggedOut = [
ScreenPaths.splash,
ScreenPaths.signin,
ScreenPaths.signup
];
final appRouter = GoRouter(
initialLocation: ScreenPaths.timelines,
debugLogDiagnostics: true,
refreshListenable: _authService,
redirect: (context, state) async {
final loggedIn = _authService.loggedIn;
if (!loggedIn && _authService.initializing) {
return ScreenPaths.splash;
}
if (!loggedIn && !allowedLoggedOut.contains(state.uri.toString())) {
return ScreenPaths.signin;
}
if (loggedIn && allowedLoggedOut.contains(state.uri.toString())) {
return ScreenPaths.timelines;
}
return null;
},
final routes = [
GoRoute(
path: ScreenPaths.blocks,
name: ScreenPaths.blocks,
builder: (context, state) => const BlocksScreen(),
),
GoRoute(
path: ScreenPaths.filters,
name: ScreenPaths.filters,
builder: (context, state) => const FiltersScreen(),
routes: [
GoRoute(
path: ScreenPaths.blocks,
name: ScreenPaths.blocks,
builder: (context, state) => const BlocksScreen(),
path: 'new',
pageBuilder: (context, state) => const NoTransitionPage(
child: FilterEditorScreen(id: ''),
),
),
GoRoute(
path: ScreenPaths.filters,
name: ScreenPaths.filters,
builder: (context, state) => const FiltersScreen(),
routes: [
GoRoute(
path: 'new',
pageBuilder: (context, state) => const NoTransitionPage(
child: FilterEditorScreen(id: ''),
),
),
GoRoute(
path: 'edit/:id',
pageBuilder: (context, state) => NoTransitionPage(
child: FilterEditorScreen(id: state.pathParameters['id']!)),
)
],
),
GoRoute(
path: ScreenPaths.signin,
name: ScreenPaths.signin,
builder: (context, state) => const SignInScreen(),
),
GoRoute(
path: ScreenPaths.manageProfiles,
name: ScreenPaths.manageProfiles,
builder: (context, state) => const SignInScreen(),
),
GoRoute(
path: ScreenPaths.contacts,
name: ScreenPaths.contacts,
path: 'edit/:id',
pageBuilder: (context, state) => NoTransitionPage(
name: ScreenPaths.contacts,
child: const ContactsScreen(),
),
),
child: FilterEditorScreen(id: state.pathParameters['id']!)),
)
],
),
GoRoute(
path: ScreenPaths.signin,
name: ScreenPaths.signin,
builder: (context, state) => const SignInScreen(),
),
GoRoute(
path: ScreenPaths.manageProfiles,
name: ScreenPaths.manageProfiles,
builder: (context, state) => const SignInScreen(),
),
GoRoute(
path: ScreenPaths.contacts,
name: ScreenPaths.contacts,
pageBuilder: (context, state) => NoTransitionPage(
name: ScreenPaths.contacts,
child: const ContactsScreen(),
),
),
GoRoute(
path: '/connect/:id',
name: ScreenPaths.connectHandle,
builder: (context, state) =>
FollowRequestAdjudicationScreen(userId: state.pathParameters['id']!),
),
GoRoute(
path: ScreenPaths.timelines,
name: ScreenPaths.timelines,
pageBuilder: (context, state) => NoTransitionPage(
name: ScreenPaths.timelines,
child: const HomeScreen(),
),
),
GoRoute(
path: ScreenPaths.messages,
name: ScreenPaths.messages,
pageBuilder: (context, state) => const NoTransitionPage(
child: MessagesScreen(),
),
routes: [
GoRoute(
path: '/connect/:id',
name: ScreenPaths.connectHandle,
builder: (context, state) => FollowRequestAdjudicationScreen(
userId: state.pathParameters['id']!),
),
GoRoute(
path: ScreenPaths.timelines,
name: ScreenPaths.timelines,
path: 'new_thread',
pageBuilder: (context, state) => NoTransitionPage(
name: ScreenPaths.timelines,
child: const HomeScreen(),
child: MessagesNewThread(),
),
),
],
),
GoRoute(
name: ScreenPaths.thread,
path: ScreenPaths.thread,
builder: (context, state) =>
MessageThreadScreen(parentThreadId: state.uri.queryParameters['uri']!),
),
GoRoute(
name: ScreenPaths.circleManagement,
path: ScreenPaths.circleManagement,
builder: (context, state) => const CircleManagementScreen(),
routes: [
GoRoute(
path: 'show/:id',
builder: (context, state) => CircleEditorScreen(
circleId: state.pathParameters['id']!,
),
),
GoRoute(
path: ScreenPaths.messages,
name: ScreenPaths.messages,
pageBuilder: (context, state) => const NoTransitionPage(
child: MessagesScreen(),
),
routes: [
GoRoute(
path: 'new_thread',
pageBuilder: (context, state) => NoTransitionPage(
child: MessagesNewThread(),
),
),
],
path: 'new',
builder: (context, state) => const CircleCreateScreen(),
),
GoRoute(
name: ScreenPaths.thread,
path: ScreenPaths.thread,
builder: (context, state) => MessageThreadScreen(
parentThreadId: state.uri.queryParameters['uri']!),
),
GoRoute(
name: ScreenPaths.circleManagement,
path: ScreenPaths.circleManagement,
builder: (context, state) => const CircleManagementScreen(),
routes: [
GoRoute(
path: 'show/:id',
builder: (context, state) => CircleEditorScreen(
circleId: state.pathParameters['id']!,
),
),
GoRoute(
path: 'new',
builder: (context, state) => const CircleCreateScreen(),
),
GoRoute(
path: 'add_users/:id',
builder: (context, state) =>
CircleAddUsersScreen(circleId: state.pathParameters['id']!),
),
],
),
GoRoute(
path: ScreenPaths.settings,
name: ScreenPaths.settings,
pageBuilder: (context, state) => const NoTransitionPage(
child: SettingsScreen(),
),
),
GoRoute(
path: ScreenPaths.gallery,
name: ScreenPaths.gallery,
pageBuilder: (context, state) => const NoTransitionPage(
child: GalleryBrowsersScreen(),
),
routes: [
GoRoute(
path: 'show',
builder: (context, state) => GalleryScreen(
galleryName: state.extra!.toString(),
),
),
GoRoute(
path: 'edit/:name/image/:id',
builder: (context, state) => ImageEditorScreen(
galleryName: state.pathParameters['name']!,
imageId: state.pathParameters['id']!,
),
),
],
),
GoRoute(
path: ScreenPaths.notifications,
name: ScreenPaths.notifications,
pageBuilder: (context, state) => const NoTransitionPage(
child: NotificationsScreen(),
),
),
GoRoute(
path: ScreenPaths.splash,
name: ScreenPaths.splash,
builder: (context, state) => const SplashScreen(),
),
GoRoute(
path: '/post',
redirect: (context, state) {
if (state.uri.toString() == '/post') {
return '/post/new';
}
return null;
},
routes: [
GoRoute(
path: 'new',
builder: (context, state) => const EditorScreen(
forEditing: false,
),
),
GoRoute(
path: 'edit/:id',
builder: (context, state) => EditorScreen(
id: state.pathParameters['id'] ?? 'Not Found',
forEditing: true,
),
),
GoRoute(
path: 'view/:id/:goto_id',
builder: (context, state) => PostScreen(
id: state.pathParameters['id'] ?? 'Not Found',
goToId: state.pathParameters['goto_id'] ?? 'Not Found',
),
),
]),
GoRoute(
path: '/comment',
redirect: (context, state) {
if (state.uri.toString() == '/comment') {
return '/comment/new';
}
return null;
},
routes: [
GoRoute(
path: 'new',
builder: (context, state) => EditorScreen(
parentId: state.uri.queryParameters['parent_id'] ?? '',
forEditing: false,
),
),
GoRoute(
path: 'edit/:id',
builder: (context, state) => EditorScreen(
id: state.pathParameters['id'] ?? 'Not Found',
forEditing: true,
),
),
]),
GoRoute(
path: '/user_posts/:id',
name: ScreenPaths.userPosts,
path: 'add_users/:id',
builder: (context, state) =>
UserPostsScreen(userId: state.pathParameters['id']!),
CircleAddUsersScreen(circleId: state.pathParameters['id']!),
),
],
),
GoRoute(
path: ScreenPaths.settings,
name: ScreenPaths.settings,
pageBuilder: (context, state) => const NoTransitionPage(
child: SettingsScreen(),
),
),
GoRoute(
path: ScreenPaths.gallery,
name: ScreenPaths.gallery,
pageBuilder: (context, state) => const NoTransitionPage(
child: GalleryBrowsersScreen(),
),
routes: [
GoRoute(
path: '/likes/:id',
name: ScreenPaths.likes,
builder: (context, state) => InteractionsViewerScreen(
statusId: state.pathParameters['id']!,
type: InteractionType.like,
path: 'show',
builder: (context, state) => GalleryScreen(
galleryName: state.extra!.toString(),
),
),
GoRoute(
path: '/reshares/:id',
name: ScreenPaths.reshares,
builder: (context, state) => InteractionsViewerScreen(
statusId: state.pathParameters['id']!,
type: InteractionType.reshare,
path: 'edit/:name/image/:id',
builder: (context, state) => ImageEditorScreen(
galleryName: state.pathParameters['name']!,
imageId: state.pathParameters['id']!,
),
),
GoRoute(
path: '/user_profile/:id',
name: ScreenPaths.userProfile,
builder: (context, state) =>
UserProfileScreen(userId: state.pathParameters['id']!),
),
GoRoute(
path: ScreenPaths.search,
name: ScreenPaths.search,
pageBuilder: (context, state) => NoTransitionPage(
name: ScreenPaths.search,
child: const SearchScreen(),
],
),
GoRoute(
path: ScreenPaths.notifications,
name: ScreenPaths.notifications,
pageBuilder: (context, state) => const NoTransitionPage(
child: NotificationsScreen(),
),
),
GoRoute(
path: ScreenPaths.splash,
name: ScreenPaths.splash,
builder: (context, state) => const SplashScreen(),
),
GoRoute(
path: '/post',
redirect: (context, state) {
if (state.uri.toString() == '/post') {
return '/post/new';
}
return null;
},
routes: [
GoRoute(
path: 'new',
builder: (context, state) => const EditorScreen(
forEditing: false,
),
),
),
GoRoute(
path: ScreenPaths.logViewer,
name: ScreenPaths.logViewer,
pageBuilder: (context, state) => const NoTransitionPage(
child: LogViewerScreen(),
GoRoute(
path: 'edit/:id',
builder: (context, state) => EditorScreen(
id: state.pathParameters['id'] ?? 'Not Found',
forEditing: true,
),
),
),
]);
GoRoute(
path: 'view/:id/:goto_id',
builder: (context, state) => PostScreen(
id: state.pathParameters['id'] ?? 'Not Found',
goToId: state.pathParameters['goto_id'] ?? 'Not Found',
),
),
]),
GoRoute(
path: '/comment',
redirect: (context, state) {
if (state.uri.toString() == '/comment') {
return '/comment/new';
}
return null;
},
routes: [
GoRoute(
path: 'new',
builder: (context, state) => EditorScreen(
parentId: state.uri.queryParameters['parent_id'] ?? '',
forEditing: false,
),
),
GoRoute(
path: 'edit/:id',
builder: (context, state) => EditorScreen(
id: state.pathParameters['id'] ?? 'Not Found',
forEditing: true,
),
),
]),
GoRoute(
path: '/user_posts/:id',
name: ScreenPaths.userPosts,
builder: (context, state) =>
UserPostsScreen(userId: state.pathParameters['id']!),
),
GoRoute(
path: '/likes/:id',
name: ScreenPaths.likes,
builder: (context, state) => InteractionsViewerScreen(
statusId: state.pathParameters['id']!,
type: InteractionType.like,
),
),
GoRoute(
path: '/reshares/:id',
name: ScreenPaths.reshares,
builder: (context, state) => InteractionsViewerScreen(
statusId: state.pathParameters['id']!,
type: InteractionType.reshare,
),
),
GoRoute(
path: '/user_profile/:id',
name: ScreenPaths.userProfile,
builder: (context, state) =>
UserProfileScreen(userId: state.pathParameters['id']!),
),
GoRoute(
path: ScreenPaths.search,
name: ScreenPaths.search,
pageBuilder: (context, state) => NoTransitionPage(
name: ScreenPaths.search,
child: const SearchScreen(),
),
),
GoRoute(
path: ScreenPaths.logViewer,
name: ScreenPaths.logViewer,
pageBuilder: (context, state) => const NoTransitionPage(
child: LogViewerScreen(),
),
),
];

Wyświetl plik

@ -1,7 +1,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:logging/logging.dart';
import 'package:provider/provider.dart';
import 'package:relatica/controls/focus_mode_status_headline.dart';
import 'package:relatica/riverpod_controllers/focus_mode.dart';
import '../controls/app_bottom_nav_bar.dart';
import '../controls/linear_status_indicator.dart';
@ -16,14 +19,14 @@ import '../services/network_status_service.dart';
import '../services/timeline_manager.dart';
import '../utils/active_profile_selector.dart';
class HomeScreen extends StatefulWidget {
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
ConsumerState<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
class _HomeScreenState extends ConsumerState<HomeScreen> {
final _logger = Logger('$HomeScreen');
TimelineIdentifiers currentTimeline = TimelineIdentifiers.home();
@ -49,8 +52,12 @@ class _HomeScreenState extends State<HomeScreen> {
_logger.finest('Build');
final accountService = getIt<AccountsService>();
final nss = getIt<NetworkStatusService>();
final focusMode = ref.watch(focusModeProvider);
final timeline = TimelinePanel(timeline: currentTimeline);
final timeline = TimelinePanel(
timeline: focusMode.enabled
? TimelineIdentifiers.myPosts()
: currentTimeline);
return Scaffold(
appBar: AppBar(
@ -65,7 +72,9 @@ class _HomeScreenState extends State<HomeScreen> {
})
: null,
backgroundColor: Theme.of(context).canvasColor,
title: buildTimelineSelector(context),
title: focusMode.enabled
? AppBottomFocusModeStatus(disableTime: focusMode.disableTime)
: buildTimelineSelector(context),
),
body: Center(
child: Column(
@ -78,10 +87,12 @@ class _HomeScreenState extends State<HomeScreen> {
),
),
drawer: const StandardAppDrawer(),
bottomNavigationBar: AppBottomNavBar(
currentButton: NavBarButtons.timelines,
onHomeButtonReclick: () => timeline.scrollToTop(),
),
bottomNavigationBar: focusMode.enabled
? null
: AppBottomNavBar(
currentButton: NavBarButtons.timelines,
onHomeButtonReclick: () => timeline.scrollToTop(),
),
floatingActionButton: FloatingActionButton.small(
onPressed: () {
context.push('/post/new');