Browse Source

Merge pull request #466 from Leptopoda/feature/go_router

Feature/go router
pull/473/head
Nikolas Rimikis 1 year ago committed by GitHub
parent
commit
6660547976
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 54
      packages/neon/neon/lib/src/blocs/apps.dart
  2. 29
      packages/neon/neon/lib/src/models/app_implementation.dart
  3. 4
      packages/neon/neon/lib/src/router.dart
  4. 16
      packages/neon/neon/lib/src/utils/app_route.dart
  5. 36
      packages/neon/neon/lib/src/utils/stream_listenable.dart
  6. 1
      packages/neon/neon/lib/utils.dart
  7. 0
      packages/neon/neon_files/build.yaml
  8. 9
      packages/neon/neon_files/lib/neon_files.dart
  9. 18
      packages/neon/neon_files/lib/routes.dart
  10. 33
      packages/neon/neon_files/lib/routes.g.dart
  11. 3
      packages/neon/neon_files/pubspec.yaml
  12. 0
      packages/neon/neon_news/build.yaml
  13. 9
      packages/neon/neon_news/lib/neon_news.dart
  14. 18
      packages/neon/neon_news/lib/routes.dart
  15. 33
      packages/neon/neon_news/lib/routes.g.dart
  16. 3
      packages/neon/neon_news/pubspec.yaml
  17. 0
      packages/neon/neon_notes/build.yaml
  18. 9
      packages/neon/neon_notes/lib/neon_notes.dart
  19. 18
      packages/neon/neon_notes/lib/routes.dart
  20. 33
      packages/neon/neon_notes/lib/routes.g.dart
  21. 3
      packages/neon/neon_notes/pubspec.yaml
  22. 0
      packages/neon/neon_notifications/build.yaml
  23. 9
      packages/neon/neon_notifications/lib/neon_notifications.dart
  24. 18
      packages/neon/neon_notifications/lib/routes.dart
  25. 33
      packages/neon/neon_notifications/lib/routes.g.dart
  26. 3
      packages/neon/neon_notifications/pubspec.yaml

54
packages/neon/neon/lib/src/blocs/apps.dart

@ -50,27 +50,22 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
});
appImplementations.listen((final result) {
if (result.hasData) {
final options = _accountsBloc.getOptionsFor(_account);
unawaited(
options.initialApp.stream.first.then((var initialApp) async {
if (initialApp == null) {
if (result.requireData.tryFind('files') != null) {
initialApp = 'files';
} else if (result.requireData.isNotEmpty) {
// This should never happen, because the files app is always installed and can not be removed, but just in
// case this changes at a later point.
initialApp = result.requireData.first.id;
}
}
if (!activeApp.hasValue && initialApp != null) {
await setActiveApp(initialApp);
}
}),
);
unawaited(_checkCompatibility());
if (!result.hasData) {
return;
}
final options = _accountsBloc.getOptionsFor(_account);
unawaited(
options.initialApp.stream.first.then((var initialApp) async {
initialApp ??= _getInitialAppFallback();
if (!activeApp.hasValue && initialApp != null) {
await setActiveApp(initialApp);
}
}),
);
unawaited(_checkCompatibility());
});
_capabilitiesBloc.capabilities.listen((final result) {
@ -86,6 +81,25 @@ class AppsBloc extends InteractiveBloc implements AppsBlocEvents, AppsBlocStates
unawaited(refresh());
}
/// Determines the appid of initial app.
///
/// It requires [appImplementations] to have both a value and data.
///
/// The files app is always installed and can not be removed so it will be used, but in the
/// case this changes at a later point the first supported app will be returned.
///
/// Returns null when no app is supported by the server.
String? _getInitialAppFallback() {
final supportedApps = appImplementations.value.requireData;
if (supportedApps.tryFind('files') != null) {
return 'files';
} else if (supportedApps.isNotEmpty) {
return supportedApps.first.id;
}
return null;
}
Future<void> _checkCompatibility() async {
final apps = appImplementations.valueOrNull;
final capabilities = _capabilitiesBloc.capabilities.valueOrNull;

29
packages/neon/neon/lib/src/models/app_implementation.dart

@ -1,6 +1,7 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:go_router/go_router.dart';
import 'package:neon/l10n/localizations.dart';
import 'package:neon/src/bloc/bloc.dart';
import 'package:neon/src/blocs/accounts.dart';
@ -68,6 +69,34 @@ abstract class AppImplementation<T extends Bloc, R extends NextcloudAppOptions>
);
}
/// Main branch displayed in the home page.
///
/// There's usually no need to override this.
StatefulShellBranch get mainBranch => StatefulShellBranch(
routes: [
route,
],
);
/// Route for the app.
///
/// All pages of the app must be specified as subroutes.
/// If this is not [GoRoute] an inital route name must be specified by overriding [initialRouteName].
RouteBase get route;
/// Name of the initial route for this app.
///
/// Subclasses that don't provide a [GoRoute] for [route] must override this.
String get initialRouteName {
final route = this.route;
if (route is GoRoute && route.name != null) {
return route.name!;
}
throw FlutterError('No name for the initial route provided.');
}
Widget buildIcon({
final Size size = const Size.square(32),
final Color? color,

4
packages/neon/neon/lib/src/router.dart

@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:meta/meta.dart';
import 'package:neon/src/blocs/accounts.dart';
import 'package:neon/src/models/account.dart';
import 'package:neon/src/models/app_implementation.dart';
@ -17,13 +18,14 @@ import 'package:provider/provider.dart';
part 'router.g.dart';
@internal
class AppRouter extends GoRouter {
AppRouter({
required final GlobalKey<NavigatorState> navigatorKey,
required final AccountsBloc accountsBloc,
}) : super(
debugLogDiagnostics: kDebugMode,
refreshListenable: StreamListenable.behaviorSubject(accountsBloc.activeAccount),
refreshListenable: StreamListenable(accountsBloc.activeAccount),
navigatorKey: navigatorKey,
initialLocation: const HomeRoute().location,
redirect: (final context, final state) {

16
packages/neon/neon/lib/src/utils/app_route.dart

@ -0,0 +1,16 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
/// [RouteData] for the initial page of an app.
///
/// Subclasses must override one of [build] or [redirect].
abstract class NeonAppRoute extends GoRouteData {
const NeonAppRoute();
@override
Page<void> buildPage(final BuildContext context, final GoRouterState state) => NoTransitionPage(
child: build(context, state),
);
}
const appsRoutePrefix = '/apps/';

36
packages/neon/neon/lib/src/utils/stream_listenable.dart

@ -9,32 +9,36 @@ import 'package:rxdart/rxdart.dart';
/// Objects need to be manually disposed.
class StreamListenable extends ChangeNotifier {
/// Listenable Stream
///
/// Implementation for all types of [Stream]s.
/// For an implementation tailored towards [BehaviorSubject] have a look at [StreamListenable.behaviorSubject].
StreamListenable(final Stream<dynamic> stream) {
notifyListeners();
_subscription = stream.asBroadcastStream().listen((final value) {
if (stream is! BehaviorSubject) {
notifyListeners();
});
}
addSubscription(stream);
}
/// Listenable BehaviorSubject
/// Listenable for multiple Streams.
///
/// Implementation for a [BehaviorSubject]. It ensures to not unececcary notify listeners.
/// For an implementation tailored towards otnher kinds of [Stream] have a look at [StreamListenable].
StreamListenable.behaviorSubject(final BehaviorSubject<dynamic> subject) {
_subscription = subject.listen((final value) {
notifyListeners();
});
/// Notifies it's listeners on every event emitted by any of the streams.
StreamListenable.multiListenable(final Iterable<Stream<dynamic>> streams) {
streams.forEach(addSubscription);
}
void addSubscription(final Stream<dynamic> stream) {
_subscriptions.add(
stream.asBroadcastStream().listen((final _) {
notifyListeners();
}),
);
}
late final StreamSubscription<dynamic> _subscription;
final List<StreamSubscription<dynamic>> _subscriptions = [];
@override
void dispose() {
unawaited(_subscription.cancel());
for (final subscription in _subscriptions) {
unawaited(subscription.cancel());
}
super.dispose();
}

1
packages/neon/neon/lib/utils.dart

@ -1,3 +1,4 @@
export 'package:neon/src/utils/app_route.dart';
export 'package:neon/src/utils/confirmation_dialog.dart';
export 'package:neon/src/utils/exceptions.dart';
export 'package:neon/src/utils/hex_color.dart';

0
packages/neon/neon_files/build.yaml

9
packages/neon/neon_files/lib/neon_files.dart

@ -9,6 +9,7 @@ import 'package:file_icons/file_icons.dart';
import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:image_picker/image_picker.dart';
import 'package:intersperse/intersperse.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
@ -21,6 +22,7 @@ import 'package:neon/theme.dart';
import 'package:neon/utils.dart';
import 'package:neon/widgets.dart';
import 'package:neon_files/l10n/localizations.dart';
import 'package:neon_files/routes.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:open_file/open_file.dart';
import 'package:path/path.dart' as p;
@ -47,7 +49,9 @@ class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
FilesApp(super.sharedPreferences, super.requestManager, super.platform);
@override
String id = 'files';
String id = appId;
static const String appId = 'files';
@override
LocalizationsDelegate localizationsDelegate = AppLocalizations.delegate;
@ -68,4 +72,7 @@ class FilesApp extends AppImplementation<FilesBloc, FilesAppSpecificOptions> {
@override
Widget get page => const FilesMainPage();
@override
RouteBase get route => $filesAppRoute;
}

18
packages/neon/neon_files/lib/routes.dart

@ -0,0 +1,18 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:neon/utils.dart';
import 'package:neon_files/neon_files.dart';
part 'routes.g.dart';
@TypedGoRoute<FilesAppRoute>(
path: '$appsRoutePrefix${FilesApp.appId}',
name: FilesApp.appId,
)
@immutable
class FilesAppRoute extends NeonAppRoute {
const FilesAppRoute();
@override
Widget build(final BuildContext context, final GoRouterState state) => const FilesMainPage();
}

33
packages/neon/neon_files/lib/routes.g.dart

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'routes.dart';
// **************************************************************************
// GoRouterGenerator
// **************************************************************************
List<RouteBase> get $appRoutes => [
$filesAppRoute,
];
RouteBase get $filesAppRoute => GoRouteData.$route(
path: '/apps/files',
name: 'files',
factory: $FilesAppRouteExtension._fromState,
);
extension $FilesAppRouteExtension on FilesAppRoute {
static FilesAppRoute _fromState(GoRouterState state) => const FilesAppRoute();
String get location => GoRouteData.$location(
'/apps/files',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}

3
packages/neon/neon_files/pubspec.yaml

@ -16,6 +16,7 @@ dependencies:
filesize: ^2.0.1
flutter:
sdk: flutter
go_router: ^8.0.3
image_picker: ^0.8.7+5
intersperse: ^2.0.0
material_design_icons_flutter: ^7.0.7296
@ -35,6 +36,8 @@ dependencies:
share_plus: ^7.0.0
dev_dependencies:
build_runner: ^2.4.4
go_router_builder: ^2.0.2
nit_picking:
git:
url: https://github.com/stack11/dart_nit_picking

0
packages/neon/neon_news/build.yaml

9
packages/neon/neon_news/lib/neon_news.dart

@ -5,6 +5,7 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:go_router/go_router.dart';
import 'package:html/dom.dart' as html_dom;
import 'package:html/parser.dart' as html_parser;
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
@ -16,6 +17,7 @@ import 'package:neon/sort_box.dart';
import 'package:neon/utils.dart';
import 'package:neon/widgets.dart';
import 'package:neon_news/l10n/localizations.dart';
import 'package:neon_news/routes.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
@ -53,7 +55,9 @@ class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
NewsApp(super.sharedPreferences, super.requestManager, super.platform);
@override
String id = 'news';
String id = appId;
static const String appId = 'news';
@override
LocalizationsDelegate localizationsDelegate = AppLocalizations.delegate;
@ -74,6 +78,9 @@ class NewsApp extends AppImplementation<NewsBloc, NewsAppSpecificOptions> {
@override
Widget get page => const NewsMainPage();
@override
RouteBase get route => $newsAppRoute;
@override
BehaviorSubject<int> getUnreadCounter(final NewsBloc bloc) => bloc.unreadCounter;
}

18
packages/neon/neon_news/lib/routes.dart

@ -0,0 +1,18 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:neon/utils.dart';
import 'package:neon_news/neon_news.dart';
part 'routes.g.dart';
@TypedGoRoute<NewsAppRoute>(
path: '$appsRoutePrefix${NewsApp.appId}',
name: NewsApp.appId,
)
@immutable
class NewsAppRoute extends NeonAppRoute {
const NewsAppRoute();
@override
Widget build(final BuildContext context, final GoRouterState state) => const NewsMainPage();
}

33
packages/neon/neon_news/lib/routes.g.dart

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'routes.dart';
// **************************************************************************
// GoRouterGenerator
// **************************************************************************
List<RouteBase> get $appRoutes => [
$newsAppRoute,
];
RouteBase get $newsAppRoute => GoRouteData.$route(
path: '/apps/news',
name: 'news',
factory: $NewsAppRouteExtension._fromState,
);
extension $NewsAppRouteExtension on NewsAppRoute {
static NewsAppRoute _fromState(GoRouterState state) => const NewsAppRoute();
String get location => GoRouteData.$location(
'/apps/news',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}

3
packages/neon/neon_news/pubspec.yaml

@ -10,6 +10,7 @@ dependencies:
flutter:
sdk: flutter
flutter_html: ^3.0.0-alpha.6
go_router: ^8.0.3
html: ^0.15.3
material_design_icons_flutter: ^7.0.7296
neon:
@ -28,6 +29,8 @@ dependencies:
webview_flutter: ^4.2.0
dev_dependencies:
build_runner: ^2.4.4
go_router_builder: ^2.0.2
nit_picking:
git:
url: https://github.com/stack11/dart_nit_picking

0
packages/neon/neon_notes/build.yaml

9
packages/neon/neon_notes/lib/neon_notes.dart

@ -7,6 +7,7 @@ import 'package:built_collection/built_collection.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:go_router/go_router.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/blocs.dart';
import 'package:neon/models.dart';
@ -16,6 +17,7 @@ import 'package:neon/sort_box.dart';
import 'package:neon/utils.dart';
import 'package:neon/widgets.dart';
import 'package:neon_notes/l10n/localizations.dart';
import 'package:neon_notes/routes.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart';
import 'package:queue/queue.dart';
@ -44,7 +46,9 @@ class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
NotesApp(super.sharedPreferences, super.requestManager, super.platform);
@override
String id = 'notes';
String id = appId;
static const String appId = 'notes';
@override
List<Locale> supportedLocales = AppLocalizations.supportedLocales;
@ -64,4 +68,7 @@ class NotesApp extends AppImplementation<NotesBloc, NotesAppSpecificOptions> {
@override
Widget get page => const NotesMainPage();
@override
RouteBase get route => $notesAppRoute;
}

18
packages/neon/neon_notes/lib/routes.dart

@ -0,0 +1,18 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:neon/utils.dart';
import 'package:neon_notes/neon_notes.dart';
part 'routes.g.dart';
@TypedGoRoute<NotesAppRoute>(
path: '$appsRoutePrefix${NotesApp.appId}',
name: NotesApp.appId,
)
@immutable
class NotesAppRoute extends NeonAppRoute {
const NotesAppRoute();
@override
Widget build(final BuildContext context, final GoRouterState state) => const NotesMainPage();
}

33
packages/neon/neon_notes/lib/routes.g.dart

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'routes.dart';
// **************************************************************************
// GoRouterGenerator
// **************************************************************************
List<RouteBase> get $appRoutes => [
$notesAppRoute,
];
RouteBase get $notesAppRoute => GoRouteData.$route(
path: '/apps/notes',
name: 'notes',
factory: $NotesAppRouteExtension._fromState,
);
extension $NotesAppRouteExtension on NotesAppRoute {
static NotesAppRoute _fromState(GoRouterState state) => const NotesAppRoute();
String get location => GoRouteData.$location(
'/apps/notes',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}

3
packages/neon/neon_notes/pubspec.yaml

@ -12,6 +12,7 @@ dependencies:
flutter:
sdk: flutter
flutter_markdown: ^0.6.14
go_router: ^8.0.3
material_design_icons_flutter: ^7.0.7296
neon:
git:
@ -28,6 +29,8 @@ dependencies:
wakelock: ^0.6.2
dev_dependencies:
build_runner: ^2.4.4
go_router_builder: ^2.0.2
nit_picking:
git:
url: https://github.com/stack11/dart_nit_picking

0
packages/neon/neon_notifications/build.yaml

9
packages/neon/neon_notifications/lib/neon_notifications.dart

@ -3,6 +3,7 @@ library notifications;
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:neon/blocs.dart';
import 'package:neon/models.dart';
@ -10,6 +11,7 @@ import 'package:neon/settings.dart';
import 'package:neon/utils.dart';
import 'package:neon/widgets.dart';
import 'package:neon_notifications/l10n/localizations.dart';
import 'package:neon_notifications/routes.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
@ -23,7 +25,9 @@ class NotificationsApp extends AppImplementation<NotificationsBloc, Notification
NotificationsApp(super.sharedPreferences, super.requestManager, super.platform);
@override
String id = 'notifications';
String id = appId;
static const String appId = 'notifications';
@override
LocalizationsDelegate localizationsDelegate = AppLocalizations.delegate;
@ -44,6 +48,9 @@ class NotificationsApp extends AppImplementation<NotificationsBloc, Notification
@override
Widget get page => const NotificationsMainPage();
@override
RouteBase get route => $notificationsAppRoute;
@override
BehaviorSubject<int> getUnreadCounter(final NotificationsBloc bloc) => bloc.unreadCounter;
}

18
packages/neon/neon_notifications/lib/routes.dart

@ -0,0 +1,18 @@
import 'package:flutter/widgets.dart';
import 'package:go_router/go_router.dart';
import 'package:neon/utils.dart';
import 'package:neon_notifications/neon_notifications.dart';
part 'routes.g.dart';
@TypedGoRoute<NotificationsAppRoute>(
path: '$appsRoutePrefix${NotificationsApp.appId}',
name: NotificationsApp.appId,
)
@immutable
class NotificationsAppRoute extends NeonAppRoute {
const NotificationsAppRoute();
@override
Widget build(final BuildContext context, final GoRouterState state) => const NotificationsMainPage();
}

33
packages/neon/neon_notifications/lib/routes.g.dart

@ -0,0 +1,33 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'routes.dart';
// **************************************************************************
// GoRouterGenerator
// **************************************************************************
List<RouteBase> get $appRoutes => [
$notificationsAppRoute,
];
RouteBase get $notificationsAppRoute => GoRouteData.$route(
path: '/apps/notifications',
name: 'notifications',
factory: $NotificationsAppRouteExtension._fromState,
);
extension $NotificationsAppRouteExtension on NotificationsAppRoute {
static NotificationsAppRoute _fromState(GoRouterState state) => const NotificationsAppRoute();
String get location => GoRouteData.$location(
'/apps/notifications',
);
void go(BuildContext context) => context.go(location);
Future<T?> push<T>(BuildContext context) => context.push<T>(location);
void pushReplacement(BuildContext context) => context.pushReplacement(location);
void replace(BuildContext context) => context.replace(location);
}

3
packages/neon/neon_notifications/pubspec.yaml

@ -9,6 +9,7 @@ environment:
dependencies:
flutter:
sdk: flutter
go_router: ^8.0.3
material_design_icons_flutter: ^7.0.7296
neon:
git:
@ -22,6 +23,8 @@ dependencies:
rxdart: ^0.27.7
dev_dependencies:
build_runner: ^2.4.4
go_router_builder: ^2.0.2
nit_picking:
git:
url: https://github.com/stack11/dart_nit_picking

Loading…
Cancel
Save