Browse Source

Merge pull request #265 from provokateurin/feature/files-navigation

Add proper files navigation
pull/266/head
Kate 2 years ago committed by GitHub
parent
commit
e146c81397
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      packages/neon/neon/lib/neon.dart
  2. 104
      packages/neon/neon/lib/src/app.dart
  3. 8
      packages/neon/neon/lib/src/pages/home.dart
  4. 10
      packages/neon/neon/lib/src/pages/login.dart
  5. 51
      packages/neon/neon/lib/src/router.dart
  6. 260
      packages/neon/neon_files/lib/widgets/browser_view.dart
  7. 8
      packages/neon/neon_news/lib/pages/article.dart
  8. 6
      packages/neon/neon_notes/lib/pages/note.dart

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

@ -66,6 +66,7 @@ part 'src/platform/abstract.dart';
part 'src/platform/android.dart';
part 'src/platform/linux.dart';
part 'src/platform/platform.dart';
part 'src/router.dart';
part 'src/utils/account_options.dart';
part 'src/utils/app_implementation.dart';
part 'src/utils/bloc.dart';

104
packages/neon/neon/lib/src/app.dart

@ -25,7 +25,15 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
late NeonPlatform _platform;
late GlobalOptions _globalOptions;
late AccountsBloc _accountsBloc;
final _globalPopups = const GlobalPopups();
late final _routerDelegate = AppRouter(
navigatorKey: _navigatorKey,
accountsBloc: _accountsBloc,
onThemeChanged: (final nextcloudTheme) {
setState(() {
_nextcloudTheme = nextcloudTheme;
});
},
);
NextcloudCoreServerCapabilities_Ocs_Data_Capabilities_Theming? _nextcloudTheme;
final _platformBrightness = BehaviorSubject<Brightness>.seeded(WidgetsBinding.instance.window.platformBrightness);
@ -55,50 +63,6 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
}
WidgetsBinding.instance.addPostFrameCallback((final _) async {
widget.accountsBloc.activeAccount.listen((final activeAccount) async {
FlutterNativeSplash.remove();
if (activeAccount == null) {
await _navigatorKey.currentState!.pushAndRemoveUntil(
MaterialPageRoute(
builder: (final context) => const LoginPage(),
),
(final _) => false,
);
} else {
const settings = RouteSettings(
name: 'home',
);
Widget builder(final context) => Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
_globalPopups,
HomePage(
account: activeAccount,
onThemeChanged: (final nextcloudTheme) {
setState(() {
_nextcloudTheme = nextcloudTheme;
});
},
),
],
),
);
await _navigatorKey.currentState!.pushAndRemoveUntil(
widget.globalOptions.navigationMode.value == NavigationMode.drawer
? MaterialPageRoute(
settings: settings,
builder: builder,
)
: _NoAnimationPageRoute(
settings: settings,
builder: builder,
),
(final _) => false,
);
}
});
final localizations = await appLocalizationsFromSystem();
if (!mounted) {
@ -310,36 +274,30 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
option: widget.globalOptions.themeOLEDAsDark,
builder: (final context, final themeOLEDAsDark) => OptionBuilder(
option: widget.globalOptions.themeKeepOriginalAccentColor,
builder: (final context, final themeKeepOriginalAccentColor) {
if (themeMode == null || !platformBrightnessSnapshot.hasData || themeOLEDAsDark == null) {
return Container();
}
return MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
navigatorKey: _navigatorKey,
theme: getThemeFromNextcloudTheme(
_nextcloudTheme,
themeMode,
platformBrightnessSnapshot.data!,
oledAsDark: themeOLEDAsDark,
keepOriginalAccentColor: _nextcloudTheme == null || (themeKeepOriginalAccentColor ?? false),
),
home: Container(),
);
},
builder: (final context, final themeKeepOriginalAccentColor) => StreamBuilder<Account?>(
stream: widget.accountsBloc.activeAccount,
builder: (final context, final activeAccountSnapshot) {
if (themeMode == null || !platformBrightnessSnapshot.hasData || themeOLEDAsDark == null) {
return Container();
}
FlutterNativeSplash.remove();
return MaterialApp.router(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
theme: getThemeFromNextcloudTheme(
_nextcloudTheme,
themeMode,
platformBrightnessSnapshot.data!,
oledAsDark: themeOLEDAsDark,
keepOriginalAccentColor: _nextcloudTheme == null || (themeKeepOriginalAccentColor ?? false),
),
routerDelegate: _routerDelegate,
);
},
),
),
),
),
);
}
class _NoAnimationPageRoute extends MaterialPageRoute {
_NoAnimationPageRoute({
required super.builder,
super.settings,
});
@override
Duration get transitionDuration => Duration.zero;
}

8
packages/neon/neon/lib/src/pages/home.dart

@ -202,15 +202,15 @@ class _HomePageState extends State<HomePage> {
) =>
OptionBuilder<NavigationMode>(
option: _globalOptions.navigationMode,
builder: (final context, final navigationMode) => WillPopScope(
onWillPop: () async {
builder: (final context, final navigationMode) => BackButtonListener(
onBackButtonPressed: () async {
if (_scaffoldKey.currentState!.isDrawerOpen) {
Navigator.pop(context);
return true;
return false;
}
_scaffoldKey.currentState!.openDrawer();
return false;
return true;
},
child: Builder(
builder: (final context) {

10
packages/neon/neon/lib/src/pages/login.dart

@ -90,18 +90,18 @@ class _LoginPageState extends State<LoginPage> {
@override
Widget build(final BuildContext context) => StreamBuilder<List<Account>>(
stream: _accountsBloc.accounts,
builder: (final context, final accountsSnapshot) => WillPopScope(
onWillPop: () async {
builder: (final context, final accountsSnapshot) => BackButtonListener(
onBackButtonPressed: () async {
if (accountsSnapshot.data?.isNotEmpty ?? false) {
return true;
return false;
}
if ((await _loginBloc.serverURL.first) == null) {
return true;
return false;
}
_loginBloc.setServerURL(null);
return false;
return true;
},
child: StreamBuilder<String?>(
stream: _loginBloc.serverURL,

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

@ -0,0 +1,51 @@
part of '../neon.dart';
// ignore: prefer_mixin
class AppRouter extends RouterDelegate<Account> with ChangeNotifier, PopNavigatorRouterDelegateMixin<Account> {
AppRouter({
required this.navigatorKey,
required this.accountsBloc,
required this.onThemeChanged,
});
final AccountsBloc accountsBloc;
final Function(NextcloudTheme? theme) onThemeChanged;
final _globalPopups = const GlobalPopups();
@override
final GlobalKey<NavigatorState> navigatorKey;
@override
Future setNewRoutePath(final Account? configuration) async {}
@override
Account? get currentConfiguration => accountsBloc.activeAccount.valueOrNull;
@override
Widget build(final BuildContext context) => Navigator(
key: navigatorKey,
onPopPage: (final route, final result) => route.didPop(result),
pages: [
if (currentConfiguration == null) ...[
const MaterialPage(
child: LoginPage(),
),
] else ...[
MaterialPage(
child: Scaffold(
resizeToAvoidBottomInset: false,
body: Stack(
children: [
_globalPopups,
HomePage(
account: currentConfiguration!,
onThemeChanged: onThemeChanged,
),
],
),
),
),
],
],
);
}

260
packages/neon/neon_files/lib/widgets/browser_view.dart

@ -46,142 +46,154 @@ class _FilesBrowserViewState extends State<FilesBrowserView> {
!uploadTasksSnapshot.hasData ||
!downloadTasksSnapshot.hasData
? Container()
: Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: widget.enableCreateActions
? FloatingActionButton(
onPressed: () async {
await showDialog(
context: context,
builder: (final context) => FilesChooseCreateDialog(
bloc: widget.filesBloc,
basePath: widget.bloc.path.value,
),
);
},
child: const Icon(Icons.add),
)
: null,
body: NeonListView<Widget>(
scrollKey: 'files-${pathSnapshot.data!.join('/')}',
withFloatingActionButton: true,
items: [
for (final uploadTask in files.data == null
? <UploadTask>[]
: uploadTasksSnapshot.data!.where(
(final task) =>
files.data!.where((final file) => _pathMatchesFile(task.path, file.name)).isEmpty,
)) ...[
StreamBuilder<int>(
stream: uploadTask.progress,
builder: (final context, final uploadTaskProgressSnapshot) =>
!uploadTaskProgressSnapshot.hasData
? Container()
: _buildFile(
context: context,
details: FileDetails(
path: uploadTask.path,
isDirectory: false,
size: uploadTask.size,
etag: null,
mimeType: null,
lastModified: uploadTask.lastModified,
hasPreview: null,
isFavorite: null,
: BackButtonListener(
onBackButtonPressed: () async {
final path = pathSnapshot.data!;
if (path.isNotEmpty) {
widget.bloc.setPath(path.sublist(0, path.length - 1));
return true;
}
return false;
},
child: Scaffold(
resizeToAvoidBottomInset: false,
floatingActionButton: widget.enableCreateActions
? FloatingActionButton(
onPressed: () async {
await showDialog(
context: context,
builder: (final context) => FilesChooseCreateDialog(
bloc: widget.filesBloc,
basePath: widget.bloc.path.value,
),
);
},
child: const Icon(Icons.add),
)
: null,
body: NeonListView<Widget>(
scrollKey: 'files-${pathSnapshot.data!.join('/')}',
withFloatingActionButton: true,
items: [
for (final uploadTask in files.data == null
? <UploadTask>[]
: uploadTasksSnapshot.data!.where(
(final task) => files.data!
.where((final file) => _pathMatchesFile(task.path, file.name))
.isEmpty,
)) ...[
StreamBuilder<int>(
stream: uploadTask.progress,
builder: (final context, final uploadTaskProgressSnapshot) =>
!uploadTaskProgressSnapshot.hasData
? Container()
: _buildFile(
context: context,
details: FileDetails(
path: uploadTask.path,
isDirectory: false,
size: uploadTask.size,
etag: null,
mimeType: null,
lastModified: uploadTask.lastModified,
hasPreview: null,
isFavorite: null,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: null,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: null,
),
),
],
if (files.data != null) ...[
for (final file in files.data!) ...[
if (!widget.onlyShowDirectories || file.isDirectory) ...[
Builder(
builder: (final context) {
final matchingUploadTasks = uploadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
final matchingDownloadTasks = downloadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
),
],
if (files.data != null) ...[
for (final file in files.data!) ...[
if (!widget.onlyShowDirectories || file.isDirectory) ...[
Builder(
builder: (final context) {
final matchingUploadTasks = uploadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
final matchingDownloadTasks = downloadTasksSnapshot.data!
.where((final task) => _pathMatchesFile(task.path, file.name));
return StreamBuilder<int?>(
stream: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.progress
: Stream.value(null),
builder: (final context, final uploadTaskProgressSnapshot) => StreamBuilder<int?>(
stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress
return StreamBuilder<int?>(
stream: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.progress
: Stream.value(null),
builder: (final context, final downloadTaskProgressSnapshot) => _buildFile(
context: context,
details: FileDetails(
path: [...widget.bloc.path.value, file.name],
isDirectory: matchingUploadTasks.isEmpty && file.isDirectory,
size: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.size
: file.size!,
etag: matchingUploadTasks.isNotEmpty ? null : file.etag,
mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType,
lastModified: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.lastModified
: file.lastModified!,
hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview,
isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite,
builder: (final context, final uploadTaskProgressSnapshot) =>
StreamBuilder<int?>(
stream: matchingDownloadTasks.isNotEmpty
? matchingDownloadTasks.first.progress
: Stream.value(null),
builder: (final context, final downloadTaskProgressSnapshot) => _buildFile(
context: context,
details: FileDetails(
path: [...widget.bloc.path.value, file.name],
isDirectory: matchingUploadTasks.isEmpty && file.isDirectory,
size: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.size
: file.size!,
etag: matchingUploadTasks.isNotEmpty ? null : file.etag,
mimeType: matchingUploadTasks.isNotEmpty ? null : file.mimeType,
lastModified: matchingUploadTasks.isNotEmpty
? matchingUploadTasks.first.lastModified
: file.lastModified!,
hasPreview: matchingUploadTasks.isNotEmpty ? null : file.hasPreview,
isFavorite: matchingUploadTasks.isNotEmpty ? null : file.favorite,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: downloadTaskProgressSnapshot.data,
),
uploadProgress: uploadTaskProgressSnapshot.data,
downloadProgress: downloadTaskProgressSnapshot.data,
),
),
);
},
),
);
},
),
],
],
],
],
],
isLoading: files.loading,
error: files.error,
onRefresh: widget.bloc.refresh,
builder: (final context, final widget) => widget,
topScrollingChildren: [
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 10,
),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
SizedBox(
height: 40,
child: InkWell(
onTap: () {
widget.bloc.setPath([]);
},
child: const Icon(Icons.house),
),
),
for (var i = 0; i < pathSnapshot.data!.length; i++) ...[
InkWell(
onTap: () {
widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1));
},
child: Text(pathSnapshot.data![i]),
isLoading: files.loading,
error: files.error,
onRefresh: widget.bloc.refresh,
builder: (final context, final widget) => widget,
topScrollingChildren: [
Align(
alignment: Alignment.topLeft,
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 10,
),
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: <Widget>[
SizedBox(
height: 40,
child: InkWell(
onTap: () {
widget.bloc.setPath([]);
},
child: const Icon(Icons.house),
),
),
],
]
.intersperse(
const Icon(
Icons.keyboard_arrow_right,
size: 40,
for (var i = 0; i < pathSnapshot.data!.length; i++) ...[
InkWell(
onTap: () {
widget.bloc.setPath(pathSnapshot.data!.sublist(0, i + 1));
},
child: Text(pathSnapshot.data![i]),
),
)
.toList(),
],
]
.intersperse(
const Icon(
Icons.keyboard_arrow_right,
size: 40,
),
)
.toList(),
),
),
),
),
],
],
),
),
),
),

8
packages/neon/neon_news/lib/pages/article.dart

@ -81,17 +81,17 @@ class _NewsArticlePageState extends State<NewsArticlePage> {
}
@override
Widget build(final BuildContext context) => WillPopScope(
onWillPop: () async {
Widget build(final BuildContext context) => BackButtonListener(
onBackButtonPressed: () async {
if (_webviewController != null && await _webviewController!.canGoBack()) {
await _webviewController!.goBack();
return false;
return true;
}
if (mounted && Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
await Wakelock.disable();
}
return true;
return false;
},
child: Scaffold(
resizeToAvoidBottomInset: false,

6
packages/neon/neon_notes/lib/pages/note.dart

@ -63,12 +63,12 @@ class _NotesNotePageState extends State<NotesNotePage> {
}
@override
Widget build(final BuildContext context) => WillPopScope(
onWillPop: () async {
Widget build(final BuildContext context) => BackButtonListener(
onBackButtonPressed: () async {
if (Provider.of<NeonPlatform>(context, listen: false).canUseWakelock) {
await Wakelock.disable();
}
return true;
return false;
},
child: Scaffold(
resizeToAvoidBottomInset: true,

Loading…
Cancel
Save