Compare commits

...

11 Commits

Author SHA1 Message Date
jld3103 6e984c9911
refactor(app): Adjust 1 year ago
jld3103 4494e0a4dc
refactor(neon_files): Adjust 1 year ago
jld3103 1b4da66784
feat(tool): Generate web sqflite files 1 year ago
jld3103 5ba8fe384d
feat(app): Add Dockerfile for web 1 year ago
jld3103 aded2e89ca
feat(neon): Use window location for login on web 1 year ago
jld3103 4272042c6f
feat(neon): Add neon web platform 1 year ago
jld3103 004e58b168
feat(app): Add web flutter platform 1 year ago
jld3103 9e2d14f0e7
fix(neon): Fix svg loading for web 1 year ago
jld3103 7391081019
fix(neon_files): Fix uploading files for web 1 year ago
jld3103 65cc267bf5
fix(neon): Disable CookieJar on web 1 year ago
jld3103 65ff49345f
fix(neon_files): Use universal_io to compile for web 1 year ago
  1. 1
      .cspell/misc.txt
  2. 10
      .dockerignore
  3. 2
      cspell.json
  4. 2
      packages/app/.gitattributes
  5. 20
      packages/app/Dockerfile
  6. 4
      packages/app/nginx.conf
  7. 184
      packages/app/pubspec.lock
  8. BIN
      packages/app/web/favicon.png
  9. BIN
      packages/app/web/icons/Icon-192.png
  10. BIN
      packages/app/web/icons/Icon-512.png
  11. 59
      packages/app/web/index.html
  12. 23
      packages/app/web/manifest.json
  13. 11559
      packages/app/web/sqflite_sw.js
  14. BIN
      packages/app/web/sqlite3.wasm
  15. 6
      packages/neon/neon/lib/neon.dart
  16. 7
      packages/neon/neon/lib/src/app.dart
  17. 3
      packages/neon/neon/lib/src/models/account.dart
  18. 5
      packages/neon/neon/lib/src/platform/android.dart
  19. 6
      packages/neon/neon/lib/src/platform/linux.dart
  20. 13
      packages/neon/neon/lib/src/platform/platform.dart
  21. 48
      packages/neon/neon/lib/src/platform/web.dart
  22. 8
      packages/neon/neon/lib/src/router.dart
  23. 3
      packages/neon/neon/lib/src/utils/global_options.dart
  24. 4
      packages/neon/neon/lib/src/utils/push_utils.dart
  25. 4
      packages/neon/neon/lib/src/utils/request_manager.dart
  26. 2
      packages/neon/neon/lib/src/utils/save_file.dart
  27. 2
      packages/neon/neon/lib/src/widgets/error.dart
  28. 3
      packages/neon/neon/pubspec.yaml
  29. 24
      packages/neon/neon_files/lib/blocs/files.dart
  30. 25
      packages/neon/neon_files/lib/dialogs/choose_create.dart
  31. 4
      packages/neon/neon_files/lib/models/file_details.dart
  32. 3
      packages/neon/neon_files/lib/neon_files.dart
  33. 65
      packages/neon/neon_files/lib/utils/task.dart
  34. 1
      packages/neon/neon_files/pubspec.yaml
  35. 12
      tool/generate-assets.sh

1
.cspell/misc.txt

@ -9,3 +9,4 @@ postmarket
provokateurin
subroutes
uncategorized
leoafarias

10
.dockerignore

@ -0,0 +1,10 @@
**/.dart_tool
**/build
**/coverage
.git/
external/
packages/app/.flatpak-builder
packages/app/android
packages/app/build-dir
packages/app/linux
packages/app/screenshots

2
cspell.json

@ -11,6 +11,8 @@
"**/assets",
"**/l10n/!(en.arb)",
"external",
"packages/app/web/sqlite3.wasm",
"packages/app/web/sqflite_sw.js",
"packages/file_icons/lib/src/data.dart",
"packages/neon_lints/lib",
"packages/nextcloud/test/files"

2
packages/app/.gitattributes vendored

@ -0,0 +1,2 @@
web/sqflite_sw.js -diff
web/sqlite3.wasm -diff

20
packages/app/Dockerfile

@ -0,0 +1,20 @@
FROM leoafarias/fvm:latest as build
# This is needed, because for some reason it doesn't work with the default user because of permissions
USER root
WORKDIR /app
COPY .fvm/fvm_config.json .fvm/
RUN fvm install
RUN fvm flutter config --enable-web
RUN fvm flutter precache
# TODO: Efficiently cache dependencies
COPY . .
RUN cd packages/app && fvm flutter build web --no-web-resources-cdn
FROM nginx:alpine
COPY packages/app/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build /app/packages/app/build/web/ /usr/share/nginx/html

4
packages/app/nginx.conf

@ -0,0 +1,4 @@
server {
listen 8080;
root /usr/share/nginx/html;
}

184
packages/app/pubspec.lock

@ -1,6 +1,22 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a
url: "https://pub.dev"
source: hosted
version: "61.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562
url: "https://pub.dev"
source: hosted
version: "5.13.0"
archive:
dependency: transitive
description:
@ -113,6 +129,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.0"
charcode:
dependency: transitive
description:
name: charcode
sha256: fb98c0f6d12c920a02ee2d998da788bca066ca5f148492b7085ee23372b12306
url: "https://pub.dev"
source: hosted
version: "1.3.1"
checked_yaml:
dependency: transitive
description:
@ -153,6 +177,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.0.8"
coverage:
dependency: transitive
description:
name: coverage
sha256: "2fb815080e44a09b85e0f2ca8a820b15053982b2e714b59267719e8a9ff17097"
url: "https://pub.dev"
source: hosted
version: "1.6.3"
cross_file:
dependency: transitive
description:
@ -201,6 +233,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.2.3"
dev_test:
dependency: transitive
description:
name: dev_test
sha256: "8a4aaca13079c6ca8f96124eb9c4fb45b71629ea9f2af052e42634aacae573ae"
url: "https://pub.dev"
source: hosted
version: "0.16.3"
dynamite_runtime:
dependency: "direct overridden"
description:
@ -416,6 +456,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.4.1"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: "408e3ca148b31c20282ad6f37ebfa6f4bdc8fede5b74bc2f08d9d92b55db3612"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
fuchsia_remote_debug_protocol:
dependency: transitive
description: flutter
@ -453,6 +501,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser:
dependency: transitive
description:
@ -706,6 +762,14 @@ packages:
relative: true
source: path
version: "1.0.0"
node_preamble:
dependency: transitive
description:
name: node_preamble
sha256: "6e7eac89047ab8a8d26cf16127b5ed26de65209847630400f9aefd7cd5c730db"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
open_file:
dependency: transitive
description:
@ -874,6 +938,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.7.3"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
process:
dependency: transitive
description:
@ -882,6 +954,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.2.4"
process_run:
dependency: transitive
description:
name: process_run
sha256: "1cb96f835ec02ba1a35af4e6f99f937618023b6d2ffba88f0c0f1041af2e9f12"
url: "https://pub.dev"
source: hosted
version: "0.13.2"
provider:
dependency: transitive
description:
@ -1050,6 +1130,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.2"
shelf:
dependency: transitive
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_packages_handler:
dependency: transitive
description:
name: shelf_packages_handler
sha256: "89f967eca29607c933ba9571d838be31d67f53f6e4ee15147d5dc2934fee1b1e"
url: "https://pub.dev"
source: hosted
version: "3.0.2"
shelf_static:
dependency: transitive
description:
name: shelf_static
sha256: a41d3f53c4adf0f57480578c1d61d90342cd617de7fc8077b1304643c2d85c1e
url: "https://pub.dev"
source: hosted
version: "1.1.2"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
shortid:
dependency: transitive
description:
@ -1070,6 +1182,22 @@ packages:
relative: true
source: path
version: "1.0.0"
source_map_stack_trace:
dependency: transitive
description:
name: source_map_stack_trace
sha256: "84cf769ad83aa6bb61e0aa5a18e53aea683395f196a6f39c4c881fb90ed4f7ae"
url: "https://pub.dev"
source: hosted
version: "2.1.1"
source_maps:
dependency: transitive
description:
name: source_maps
sha256: "708b3f6b97248e5781f493b765c3337db11c5d2c81c3094f10904bfa8004c703"
url: "https://pub.dev"
source: hosted
version: "0.10.12"
source_span:
dependency: transitive
description:
@ -1110,6 +1238,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.3.0+2"
sqflite_common_ffi_web:
dependency: transitive
description:
name: sqflite_common_ffi_web
sha256: db9a7ef6adcfb6c9b4115f628c1d3efe3774b385309a80e75c1bafb97da2c9d1
url: "https://pub.dev"
source: hosted
version: "0.4.0"
sqlite3:
dependency: transitive
description:
@ -1174,6 +1310,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.2.1"
test:
dependency: transitive
description:
name: test
sha256: "13b41f318e2a5751c3169137103b60c584297353d4b1761b66029bae6411fe46"
url: "https://pub.dev"
source: hosted
version: "1.24.3"
test_api:
dependency: transitive
description:
@ -1182,6 +1326,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.6.0"
test_core:
dependency: transitive
description:
name: test_core
sha256: "99806e9e6d95c7b059b7a0fc08f07fc53fabe54a829497f0d9676299f1e8637e"
url: "https://pub.dev"
source: hosted
version: "0.5.3"
timezone:
dependency: transitive
description:
@ -1230,6 +1382,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.1"
universal_html:
dependency: transitive
description:
name: universal_html
sha256: "56536254004e24d9d8cfdb7dbbf09b74cf8df96729f38a2f5c238163e3d58971"
url: "https://pub.dev"
source: hosted
version: "2.2.4"
universal_io:
dependency: transitive
description:
@ -1374,6 +1534,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.1.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web:
dependency: transitive
description:
@ -1382,6 +1550,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.1.4-beta"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b
url: "https://pub.dev"
source: hosted
version: "2.4.0"
webdriver:
dependency: transitive
description:
@ -1390,6 +1566,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "3.0.2"
webkit_inspection_protocol:
dependency: transitive
description:
name: webkit_inspection_protocol
sha256: "87d3f2333bb240704cd3f1c6b5b7acd8a10e7f0bc28c28dcf14e782014f4a572"
url: "https://pub.dev"
source: hosted
version: "1.2.1"
webview_flutter:
dependency: transitive
description:

BIN
packages/app/web/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

BIN
packages/app/web/icons/Icon-192.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

BIN
packages/app/web/icons/Icon-512.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

59
packages/app/web/index.html

@ -0,0 +1,59 @@
<!DOCTYPE html>
<html>
<head>
<!--
If you are serving your web app in a path other than the root, change the
href value below to reflect the base path you are serving from.
The path provided below has to start and end with a slash "/" in order for
it to work correctly.
For more details:
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base
This is a placeholder for base href that will be replaced by the value of
the `--base-href` argument provided to `flutter build`.
-->
<base href="$FLUTTER_BASE_HREF">
<meta charset="UTF-8">
<meta content="IE=Edge" http-equiv="X-UA-Compatible">
<meta name="description" content="A beautiful convergent cross-platform client for Nextcloud written in Flutter.">
<!-- iOS meta tags & icons -->
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Neon">
<link rel="apple-touch-icon" href="icons/Icon-192.png">
<!-- Favicon -->
<link rel="icon" type="image/png" href="favicon.png"/>
<title>Neon</title>
<link rel="manifest" href="manifest.json">
<script>
// The value below is injected by flutter build, do not touch.
var serviceWorkerVersion = null;
</script>
<!-- This script adds the flutter initialization JS code -->
<script src="flutter.js" defer></script>
</head>
<body>
<script>
window.addEventListener('load', function(ev) {
// Download main.dart.js
_flutter.loader.loadEntrypoint({
serviceWorker: {
serviceWorkerVersion: serviceWorkerVersion,
},
onEntrypointLoaded: function(engineInitializer) {
engineInitializer.initializeEngine().then(function(appRunner) {
appRunner.runApp();
});
}
});
});
</script>
</body>
</html>

23
packages/app/web/manifest.json

@ -0,0 +1,23 @@
{
"name": "Neon",
"short_name": "Neon",
"start_url": ".",
"display": "standalone",
"background_color": "#f37736",
"theme_color": "#f37736",
"description": "A beautiful convergent cross-platform client for Nextcloud written in Flutter.",
"orientation": "portrait-primary",
"prefer_related_applications": false,
"icons": [
{
"src": "icons/Icon-192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "icons/Icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
]
}

11559
packages/app/web/sqflite_sw.js

File diff suppressed because one or more lines are too long

BIN
packages/app/web/sqlite3.wasm

Binary file not shown.

6
packages/neon/neon/lib/neon.dart

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:neon/src/app.dart';
@ -29,7 +30,10 @@ Future<void> runNeon({
@visibleForTesting final bool nextPushDisabled = false,
}) async {
final binding = bindingOverride ?? WidgetsFlutterBinding.ensureInitialized();
FlutterNativeSplash.preserve(widgetsBinding: binding);
if (!kIsWeb) {
FlutterNativeSplash.preserve(widgetsBinding: binding);
}
await NeonPlatform.setup();
await RequestManager.instance.initCache();

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

@ -1,8 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_native_splash/flutter_native_splash.dart';
import 'package:meta/meta.dart';
@ -27,6 +27,7 @@ import 'package:nextcloud/core.dart' as core;
import 'package:nextcloud/nextcloud.dart';
import 'package:quick_actions/quick_actions.dart';
import 'package:tray_manager/tray_manager.dart' as tray;
import 'package:universal_io/io.dart';
import 'package:window_manager/window_manager.dart';
@internal
@ -281,7 +282,9 @@ class _NeonAppState extends State<NeonApp> with WidgetsBindingObserver, tray.Tra
builder: (final context, final options, final _) => StreamBuilder<Account?>(
stream: _accountsBloc.activeAccount,
builder: (final context, final activeAccountSnapshot) {
FlutterNativeSplash.remove();
if (!kIsWeb) {
FlutterNativeSplash.remove();
}
return ResultBuilder<core.OcsGetCapabilitiesResponseApplicationJson_Ocs_Data?>.behaviorSubject(
subject: activeAccountSnapshot.hasData
? _accountsBloc.getCapabilitiesBlocFor(activeAccountSnapshot.data!).capabilities

3
packages/neon/neon/lib/src/models/account.dart

@ -2,6 +2,7 @@ import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:crypto/crypto.dart';
import 'package:flutter/foundation.dart';
import 'package:json_annotation/json_annotation.dart';
import 'package:meta/meta.dart';
import 'package:nextcloud/nextcloud.dart';
@ -36,7 +37,7 @@ class Account implements Credentials {
password: password,
appPassword: password,
userAgentOverride: userAgent,
cookieJar: CookieJar(),
cookieJar: !kIsWeb ? CookieJar() : null,
);
factory Account.fromJson(final Map<String, dynamic> json) => _$AccountFromJson(json);

5
packages/neon/neon/lib/src/platform/android.dart

@ -1,3 +1,5 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/utils/exceptions.dart';
@ -43,6 +45,9 @@ class AndroidNeonPlatform implements NeonPlatform {
return p.join((await getExternalStorageDirectory())!.path);
}
@override
String? getWindowLocation() => null;
@override
void init() {}
}

6
packages/neon/neon/lib/src/platform/linux.dart

@ -1,9 +1,8 @@
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:path/path.dart' as p;
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:universal_io/io.dart';
@immutable
@internal
@ -37,6 +36,9 @@ class LinuxNeonPlatform implements NeonPlatform {
@override
String get userAccessibleAppDataPath => p.join(Platform.environment['HOME']!, 'Neon');
@override
String? getWindowLocation() => null;
@override
void init() {
sqfliteFfiInit();

13
packages/neon/neon/lib/src/platform/platform.dart

@ -1,9 +1,10 @@
import 'dart:async';
import 'dart:io';
import 'package:meta/meta.dart';
import 'package:flutter/foundation.dart';
import 'package:neon/src/platform/android.dart';
import 'package:neon/src/platform/linux.dart';
import 'package:neon/src/platform/web.dart';
import 'package:universal_io/io.dart';
/// Implements platform specific functionality and exposes the availability of certain features.
@immutable
@ -21,7 +22,9 @@ abstract interface class NeonPlatform {
return;
}
if (Platform.isAndroid) {
if (kIsWeb) {
_platform = const WebNeonPlatform();
} else if (Platform.isAndroid) {
_platform = const AndroidNeonPlatform();
} else if (Platform.isLinux) {
_platform = const LinuxNeonPlatform();
@ -64,7 +67,9 @@ abstract interface class NeonPlatform {
/// This is needed to compensate lacking support of `https://pub.dev/packages/file_picker`.
abstract final bool shouldUseFileDialog;
FutureOr<String> get userAccessibleAppDataPath;
FutureOr<String?> get userAccessibleAppDataPath;
String? getWindowLocation();
FutureOr<void> init();
}

48
packages/neon/neon/lib/src/platform/web.dart

@ -0,0 +1,48 @@
import 'dart:async';
import 'package:meta/meta.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
import 'package:universal_html/html.dart' as html;
@immutable
@internal
class WebNeonPlatform implements NeonPlatform {
const WebNeonPlatform();
@override
bool get canUseCamera => false;
@override
bool get canUsePushNotifications => false;
@override
bool get canUseQuickActions => false;
@override
bool get canUseSystemTray => false;
@override
bool get canUseWebView => false;
@override
bool get canUseWindowManager => false;
@override
bool get canUseSharing => true;
@override
bool get shouldUseFileDialog => throw UnimplementedError();
@override
FutureOr<String>? get userAccessibleAppDataPath => null;
@override
String getWindowLocation() => html.window.location.href;
@override
FutureOr<void> init() {
databaseFactory = databaseFactoryFfiWeb;
}
}

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

@ -19,6 +19,7 @@ import 'package:neon/src/pages/login_qr_code.dart';
import 'package:neon/src/pages/nextcloud_app_settings.dart';
import 'package:neon/src/pages/route_not_found.dart';
import 'package:neon/src/pages/settings.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:neon/src/utils/provider.dart';
import 'package:neon/src/utils/stream_listenable.dart';
@ -54,6 +55,13 @@ GoRouter buildAppRouter({
// redirect to login screen when no account is logged in
if (!accountsBloc.hasAccounts && !state.uri.toString().startsWith(const LoginRoute().location)) {
final windowLocation = NeonProvider.of<NeonPlatform>(context).getWindowLocation();
if (windowLocation != null) {
var uri = Uri.parse(windowLocation).removeFragment();
uri = uri.replace(path: uri.path.replaceFirst(RegExp(r'/$', multiLine: true), ''));
return LoginCheckServerStatusRoute(serverUrl: uri).location;
}
return const LoginRoute().location;
}

3
packages/neon/neon/lib/src/utils/global_options.dart

@ -1,4 +1,4 @@
import 'dart:io';
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:meta/meta.dart';
@ -10,6 +10,7 @@ import 'package:neon/src/settings/models/options_collection.dart';
import 'package:neon/src/settings/models/storage.dart';
import 'package:package_info_plus/package_info_plus.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:universal_io/io.dart';
const unifiedPushNextPushID = 'org.unifiedpush.distributor.nextpush';

4
packages/neon/neon/lib/src/utils/push_utils.dart

@ -7,7 +7,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_svg/flutter_svg.dart' show SvgFileLoader, vg;
import 'package:flutter_svg/flutter_svg.dart' show SvgStringLoader, vg;
import 'package:image/image.dart' as img;
import 'package:meta/meta.dart';
import 'package:neon/src/blocs/accounts.dart';
@ -106,7 +106,7 @@ class PushUtils {
final cacheManager = DefaultCacheManager();
final file = await cacheManager.getSingleFile(notification.icon!);
final pictureInfo = await vg.loadPicture(SvgFileLoader(file), null);
final pictureInfo = await vg.loadPicture(SvgStringLoader(file.readAsStringSync()), null);
const largeIconSize = 256;
final scale = largeIconSize / pictureInfo.size.longestSide;

4
packages/neon/neon/lib/src/utils/request_manager.dart

@ -231,9 +231,9 @@ class Cache {
return;
}
final cacheDir = await getApplicationCacheDirectory();
final cacheDir = kIsWeb ? '' : (await getApplicationCacheDirectory()).path;
_database = await openDatabase(
p.join(cacheDir.path, 'cache.db'),
p.join(cacheDir, 'cache.db'),
version: 1,
onCreate: (final db, final version) async {
await db.execute('CREATE TABLE cache (id INTEGER PRIMARY KEY, key TEXT, value TEXT, UNIQUE(key))');

2
packages/neon/neon/lib/src/utils/save_file.dart

@ -1,9 +1,9 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:file_picker/file_picker.dart';
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
import 'package:neon/src/platform/platform.dart';
import 'package:universal_io/io.dart';
Future<String?> saveFileWithPickDialog(final String fileName, final Uint8List data) async {
if (NeonPlatform.instance.shouldUseFileDialog) {

2
packages/neon/neon/lib/src/widgets/error.dart

@ -1,5 +1,4 @@
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
@ -10,6 +9,7 @@ import 'package:neon/src/router.dart';
import 'package:neon/src/utils/exceptions.dart';
import 'package:neon/src/utils/provider.dart';
import 'package:nextcloud/nextcloud.dart';
import 'package:universal_io/io.dart';
/// An indicator that an [error] has occurred.
///

3
packages/neon/neon/pubspec.yaml

@ -48,9 +48,12 @@ dependencies:
path: packages/sort_box
sqflite: ^2.0.0
sqflite_common_ffi: ^2.2.8-2
sqflite_common_ffi_web: ^0.4.0
tray_manager: ^0.2.0
unifiedpush: ^5.0.0
unifiedpush_android: ^2.0.0
universal_html: ^2.0.0
universal_io: ^2.0.0
url_launcher: ^6.1.0
vector_graphics: ^1.0.0
window_manager: ^0.3.0

24
packages/neon/neon_files/lib/blocs/files.dart

@ -3,6 +3,8 @@ part of '../neon_files.dart';
abstract interface class FilesBlocEvents {
void uploadFile(final List<String> path, final String localPath);
void uploadBytes(final List<String> path, final Uint8List bytes);
void syncFile(final List<String> path);
void openFile(final List<String> path, final String etag, final String? mimeType);
@ -140,7 +142,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
() async {
final file = File(
p.join(
await NeonPlatform.instance.userAccessibleAppDataPath,
await NeonPlatform.instance.userAccessibleAppDataPath ?? '',
account.humanReadableID,
'files',
path.join(Platform.pathSeparator),
@ -159,7 +161,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
void uploadFile(final List<String> path, final String localPath) {
wrapAction(
() async {
final task = FilesUploadTask(
final task = FilesUploadFileTask(
path: path,
file: File(localPath),
);
@ -171,6 +173,22 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
);
}
@override
void uploadBytes(final List<String> path, final Uint8List bytes) {
wrapAction(
() async {
final task = FilesUploadBytesTask(
path: path,
bytes: bytes,
);
tasks.add(tasks.value..add(task));
await _uploadQueue.add(() => task.execute(account.client));
tasks.add(tasks.value..removeWhere((final t) => t == task));
},
disableTimeout: true,
);
}
Future<File> _cacheFile(final List<String> path, final String etag) async {
final cacheDir = await getApplicationCacheDirectory();
final file = File(p.join(cacheDir.path, 'files', etag.replaceAll('"', ''), path.last));
@ -190,7 +208,7 @@ class FilesBloc extends InteractiveBloc implements FilesBlocEvents, FilesBlocSta
final List<String> path,
final File file,
) async {
final task = FilesDownloadTask(
final task = FilesDownloadFileTask(
path: path,
file: file,
);

25
packages/neon/neon_files/lib/dialogs/choose_create.dart

@ -22,14 +22,24 @@ class _FilesChooseCreateDialogState extends State<FilesChooseCreateDialog> {
);
if (result != null) {
for (final file in result.files) {
await upload(File(file.path!));
await upload(
file.name,
path: kIsWeb ? null : file.path,
bytes: file.bytes,
);
}
}
}
Future<void> upload(final File file) async {
Future<void> upload(
final String name, {
final String? path,
final Uint8List? bytes,
}) async {
assert((path == null) != (bytes == null), 'Provide either path or bytes');
final sizeWarning = widget.bloc.options.uploadSizeWarning.value;
if (sizeWarning != null) {
if (path != null && sizeWarning != null) {
final file = File(path);
final stat = file.statSync();
if (stat.size > sizeWarning) {
if (!(await showConfirmationDialog(
@ -43,7 +53,12 @@ class _FilesChooseCreateDialogState extends State<FilesChooseCreateDialog> {
}
}
}
widget.bloc.uploadFile([...widget.basePath, p.basename(file.path)], file.path);
if (path != null) {
widget.bloc.uploadFile([...widget.basePath, name], path);
} else {
widget.bloc.uploadBytes([...widget.basePath, name], bytes!);
}
}
@override
@ -90,7 +105,7 @@ class _FilesChooseCreateDialogState extends State<FilesChooseCreateDialog> {
final picker = ImagePicker();
final result = await picker.pickImage(source: ImageSource.camera);
if (result != null) {
await upload(File(result.path));
await upload(result.name, path: result.path);
}
},
),

4
packages/neon/neon_files/lib/models/file_details.dart

@ -29,8 +29,8 @@ class FileDetails {
FileDetails.fromUploadTask({
required FilesUploadTask this.task,
}) : path = task.path,
size = task.stat.size,
lastModified = task.stat.modified,
size = task.size,
lastModified = task.modified,
isDirectory = false,
etag = null,
mimeType = null,

3
packages/neon/neon_files/lib/neon_files.dart

@ -1,12 +1,12 @@
library neon_files;
import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:file_icons/file_icons.dart';
import 'package:file_picker/file_picker.dart';
import 'package:filesize/filesize.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_material_design_icons/flutter_material_design_icons.dart';
import 'package:go_router/go_router.dart';
@ -30,6 +30,7 @@ import 'package:path_provider/path_provider.dart';
import 'package:queue/queue.dart';
import 'package:rxdart/rxdart.dart';
import 'package:share_plus/share_plus.dart';
import 'package:universal_io/io.dart';
part 'blocs/browser.dart';
part 'blocs/files.dart';

65
packages/neon/neon_files/lib/utils/task.dart

@ -3,13 +3,10 @@ part of '../neon_files.dart';
sealed class FilesTask {
FilesTask({
required this.path,
required this.file,
});
final List<String> path;
final File file;
@protected
final streamController = StreamController<double>();
@ -17,12 +14,18 @@ sealed class FilesTask {
late final progress = streamController.stream.asBroadcastStream();
}
class FilesDownloadTask extends FilesTask {
FilesDownloadTask({
abstract class FilesDownloadTask extends FilesTask {
FilesDownloadTask({required super.path});
}
class FilesDownloadFileTask extends FilesDownloadTask {
FilesDownloadFileTask({
required super.path,
required super.file,
required this.file,
});
final File file;
Future<void> execute(final NextcloudClient client) async {
await client.webdav.getFile(
Uri(pathSegments: path),
@ -33,15 +36,31 @@ class FilesDownloadTask extends FilesTask {
}
}
class FilesUploadTask extends FilesTask {
FilesUploadTask({
abstract class FilesUploadTask extends FilesTask {
FilesUploadTask({required super.path});
int get size;
DateTime? get modified;
}
class FilesUploadFileTask extends FilesUploadTask {
FilesUploadFileTask({
required super.path,
required super.file,
required this.file,
});
final File file;
FileStat? _stat;
FileStat get stat => _stat ??= file.statSync();
@override
int get size => stat.size;
@override
DateTime? get modified => stat.modified;
Future<void> execute(final NextcloudClient client) async {
await client.webdav.putFile(
file,
@ -53,3 +72,31 @@ class FilesUploadTask extends FilesTask {
await streamController.close();
}
}
class FilesUploadBytesTask extends FilesUploadTask {
FilesUploadBytesTask({
required super.path,
required this.bytes,
this.modified,
});
final Uint8List bytes;
@override
int get size => bytes.lengthInBytes;
@override
DateTime? modified;
Future<void> execute(final NextcloudClient client) async {
await client.webdav.putStream(
Stream.value(bytes),
Uri(pathSegments: path),
lastModified: modified,
contentLength: bytes.lengthInBytes,
onProgress: (final progress) {
streamController.add(progress);
},
);
}
}

1
packages/neon/neon_files/pubspec.yaml

@ -36,6 +36,7 @@ dependencies:
queue: ^3.0.0
rxdart: ^0.27.0
share_plus: ^7.0.0
universal_io: ^2.0.0
dev_dependencies:
build_runner: ^2.4.6

12
tool/generate-assets.sh

@ -41,6 +41,13 @@ function export_mipmap_icon_all() {
wait
}
function export_favicon() {
source="$1"
size="$2"
destination="$3"
inkscape "$source" -o "$destination" -w "$size" -h "$size"
}
function precompile_assets() {
fvm dart run vector_graphics_compiler --input-dir assets/
find assets/ -name "*.svg" -exec rm {} \;
@ -91,7 +98,12 @@ copy_app_svg notifications external/nextcloud-notifications
done
wait
export_favicon "assets/logo.svg" 192 "web/icons/Icon-192.png"
export_favicon "assets/logo.svg" 512 "web/icons/Icon-512.png"
export_favicon "assets/logo.svg" 16 "web/favicon.png"
fvm dart run flutter_native_splash:create
fvm dart run sqflite_common_ffi_web:setup --force
precompile_assets
)

Loading…
Cancel
Save