diff --git a/.gitignore b/.gitignore index 222a16b..1eb9807 100644 --- a/.gitignore +++ b/.gitignore @@ -5,23 +5,27 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ migrate_working_dir/ -.dart_tool/ -macos/ -test/ -web/ -linux/ -windows/ -.rmv/ +*.metadeta +*main_sub_test* +*main_test* # IntelliJ related *.iml *.ipr *.iws -.idea/ +.idea/ +*.gitignore +*ios +*android +*.vscode +*pubspec.lock +*test/ # The .vscode folder contains launch configuration and tasks you configure in # VS Code which you may wish to be included in version control, so this line @@ -29,11 +33,8 @@ windows/ #.vscode/ # Flutter/Dart/Pub related -# **/doc/api/ -# **/ios/Flutter/.last_build_id - -*/doc/api/ -ios/ +**/doc/api/ +**/ios/Flutter/.last_build_id .dart_tool/ .flutter-plugins .flutter-plugins-dependencies @@ -48,7 +49,6 @@ app.*.symbols app.*.map.json # Android Studio will place build artifacts here -# android/app/debug -# android/app/profile -# android/app/release -android/ \ No newline at end of file +/android/app/debug +/android/app/profile +/android/app/release diff --git a/.metadata b/.metadata index 6eb54a1..b02a7e4 100644 --- a/.metadata +++ b/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "761747bfc538b5af34aa0d3fac380f1bc331ec49" + revision: "17025dd88227cd9532c33fa78f5250d548d87e9a" channel: "stable" project_type: app @@ -13,26 +13,26 @@ project_type: app migration: platforms: - platform: root - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: android - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: ios - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: linux - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: macos - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: web - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a - platform: windows - create_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 - base_revision: 761747bfc538b5af34aa0d3fac380f1bc331ec49 + create_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a + base_revision: 17025dd88227cd9532c33fa78f5250d548d87e9a # User provided section diff --git a/assets/images/nextcloud_logo.png b/assets/images/nextcloud_logo.png new file mode 100644 index 0000000..e1c0845 Binary files /dev/null and b/assets/images/nextcloud_logo.png differ diff --git a/devtools_options.yaml b/devtools_options.yaml new file mode 100644 index 0000000..fa0b357 --- /dev/null +++ b/devtools_options.yaml @@ -0,0 +1,3 @@ +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/lib/api/login_api.dart b/lib/api/login_api.dart deleted file mode 100644 index 1be1cda..0000000 --- a/lib/api/login_api.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:nextcloud/nextcloud.dart'; -import 'package:nextcloud/provisioning_api.dart'; - -Future login(String uri, String username, String password) async { - try { - - final client = NextcloudClient( - Uri.parse(uri), - loginName: username, - password: password, - ); - - - final response = - await client.provisioningApi.users.getUser(userId: username); - - if (response.statusCode == 200) { - print(response.body); - return "successful|$response.body.ocs.data."; - } else { - return "failed|$response.body.ocs.data"; - } - } catch (e) { - print(""); - print("$e"); - print(""); - return "$e"; - } -} diff --git a/lib/app/extension.dart b/lib/app/extension.dart new file mode 100644 index 0000000..4055bae --- /dev/null +++ b/lib/app/extension.dart @@ -0,0 +1,19 @@ +import 'dart:io'; + +import 'package:http/io_client.dart'; +import 'package:nextcloud/nextcloud.dart'; + + +extension NextcloudClientExtension on NextcloudClient { + // + static final userAgent = 'NextCloud App' + '(${Platform.operatingSystem}) '; + // 'Dart/${Platform.version.split(' ').first}'; + + // + static IOClient newHttpClient() => IOClient( + HttpClient()..userAgent = userAgent, + ); + + +} diff --git a/lib/app/login_flow.dart b/lib/app/login_flow.dart new file mode 100644 index 0000000..9b5eaa3 --- /dev/null +++ b/lib/app/login_flow.dart @@ -0,0 +1,104 @@ +import 'dart:async'; +import 'dart:io'; + +import 'package:flutter_web_auth_2/flutter_web_auth_2.dart'; +import 'package:http/http.dart' as http; +import 'package:logging/logging.dart'; +import 'package:nextcloud/core.dart'; +import 'package:nextcloud/nextcloud.dart'; +import 'package:nextcloudapp/app/extension.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class SaberLoginFlow { + SaberLoginFlow.start({ + required this.serverUrl, + }) { + // NcHttpOverrides.tempAcceptBadCertificateFrom(serverUrl); + unawaited(_run()); + } + + final Uri serverUrl; + + final log = Logger('SaberLoginFlow'); + Timer? _pollTimer; + + final completer = Completer(); + late final future = completer.future; + + LoginFlowV2? init; + + Future openLoginUrl() async { + final init = this.init; + if (init == null) return; + + if (Platform.isMacOS || Platform.isIOS || Platform.isAndroid) { + // Use FlutterWebAuth2 which returns to Saber when authenticated + log.info('Opening login link in-app: ${init.login}'); + await FlutterWebAuth2.authenticate( + url: init.login, + callbackUrlScheme: 'nc', + ); + } else { + // Use url_launcher to open the link in the default browser + log.info('Opening login link in browser: ${init.login}'); + final loginLink = Uri.parse(init.login); + await launchUrl(loginLink); + } + } + + Future _run() async { + _catchHttpError(() async { + final client = NextcloudClient(serverUrl, + httpClient: NextcloudClientExtension.newHttpClient()); + final flowClient = client.core.clientFlowLoginV2; + init = await flowClient.init().then((response) => response.body); + log.info('init: $init'); + + openLoginUrl(); + + _pollTimer?.cancel(); + _pollTimer = Timer.periodic( + const Duration(seconds: 1), + (_) => _catch404Error(() async { + // Throws 404 if not logged in yet + final poll = await flowClient.poll( + $body: ClientFlowLoginV2PollRequestApplicationJson( + (b) => b..token = init!.poll.token, + ), + ); + + _pollTimer?.cancel(); + if (!completer.isCompleted) completer.complete(poll.body); + }), + ); + }); + } + + Future _catchHttpError(Future Function() fn) async { + try { + return await fn(); + } on http.ClientException catch (error, stackTrace) { + log.severe('Error while polling the login flow.', error, stackTrace); + if (!completer.isCompleted) completer.completeError(error, stackTrace); + return null; + } + } + + Future _catch404Error(Future Function() fn) async { + try { + return await fn(); + } on DynamiteStatusCodeException catch (error) { + if (error.statusCode != 404) rethrow; + + log.fine('Login flow not found or completed yet, will repoll'); + return null; + } + } + + void dispose() { + _pollTimer?.cancel(); + if (!completer.isCompleted) { + completer.completeError(TimeoutException('Login flow was disposed')); + } + } +} diff --git a/lib/app/nextcloud_app.dart b/lib/app/nextcloud_app.dart new file mode 100644 index 0000000..e582c22 --- /dev/null +++ b/lib/app/nextcloud_app.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:nextcloudapp/components/nextcloud_routes.dart'; +import 'package:nextcloudapp/service/navigation_service.dart'; + +class NextcloudApp extends StatelessWidget { + NextcloudApp({ + super.key, + }); + + final NextcloudAppRoutes _nextcloudAppRoutes = NextcloudAppRoutes.instance; + final NavigationService _navigationService = NavigationService.instance; + + // This widget is the root of your application. + @override + Widget build(BuildContext context) { + return MaterialApp( + debugShowCheckedModeBanner: false, + title: 'Nextcloud App', + navigatorKey: _navigationService.navigatorKey, + theme: ThemeData( + primaryColor: Color.fromARGB(255, 152, 120, 57), + scaffoldBackgroundColor: Colors.white, + elevatedButtonTheme: ElevatedButtonThemeData( + style: ElevatedButton.styleFrom( + elevation: 0, + foregroundColor: Colors.white, + backgroundColor: Color.fromARGB(255, 152, 120, 57), + shape: const StadiumBorder(), + maximumSize: const Size(double.infinity, 56), + minimumSize: const Size(double.infinity, 56), + ), + ), + // inputDecorationTheme: const InputDecorationTheme( + // filled: true, + // fillColor: primaryLightColor, + // iconColor: primaryColor, + // prefixIconColor: primaryColor, + // contentPadding: EdgeInsets.symmetric( + // horizontal: defaultPadding, vertical: defaultPadding), + // border: OutlineInputBorder( + // borderRadius: BorderRadius.all(Radius.circular(30)), + // borderSide: BorderSide.none, + // ), + // ), + fontFamily: 'Poppins', + ), + routes: _nextcloudAppRoutes.routes, + initialRoute: "login", + ); + } +} diff --git a/lib/components/default_values.dart b/lib/components/default_values.dart deleted file mode 100644 index 98888c7..0000000 --- a/lib/components/default_values.dart +++ /dev/null @@ -1,54 +0,0 @@ -// import 'package:nextcloud/nextcloud.dart'; -import 'package:shared_preferences/shared_preferences.dart'; -import 'package:flutter/material.dart'; -// import 'package:http/http.dart' as http; - - - -const primaryColor = Color.fromARGB(255, 26, 26, 74); -const primaryLightColor = Color.fromARGB(150, 238, 224, 173); -const double defaultPadding = 15.0; -const timeout = 3; -// var client = http.Client(); -// var client = NextcloudClient() - - -setSharedPref(String key, var value) async { - SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); - - switch (value.runtimeType.toString()) { - case 'String': - sharedPreferences.setString(key, value); - break; - - case 'double': - sharedPreferences.setDouble(key, value); - break; - - case 'int': - sharedPreferences.setInt(key, value); - break; - - case 'bool': - sharedPreferences.setBool(key, value); - break; - } -} - -getSharedPref(var key, var type) async { - SharedPreferences sharedPreferences = await SharedPreferences.getInstance(); - - switch (type) { - case 'String': - return sharedPreferences.getString(key); - - case 'double': - return sharedPreferences.getDouble(key); - - case 'int': - return sharedPreferences.getInt(key); - - case 'bool': - return sharedPreferences.getBool(key); - } -} \ No newline at end of file diff --git a/lib/components/file_size.dart b/lib/components/file_size.dart new file mode 100644 index 0000000..79236d6 --- /dev/null +++ b/lib/components/file_size.dart @@ -0,0 +1,15 @@ +import 'dart:math'; + +class FileSize { + static FileSize instance = FileSize(); + + String getFileSizeString({required int bytes, int decimals = 0}) { + const suffixes = ["Bytes", "KB", "MB", "GB", "TB"]; + if (bytes == 0) return '0 ${suffixes[0]}'; + + var i = (log(bytes) / log(1024)).floor(); + return "${(bytes / pow(1024, i)).toStringAsFixed( + i == 0 ? 0 : decimals, + )} ${suffixes[i]}"; + } +} diff --git a/lib/components/mime_type.dart b/lib/components/mime_type.dart new file mode 100644 index 0000000..a0961e1 --- /dev/null +++ b/lib/components/mime_type.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class MimetypeIcon { + static MimetypeIcon instance = MimetypeIcon(); + + IconData getIconForMimeType(String mimeType) { + if (mimeType.startsWith('image/')) { + return Icons.image; + } else if (mimeType.startsWith('video/')) { + return Icons.video_file; + } else if (mimeType.startsWith('audio/')) { + return Icons.audiotrack; + } else if (mimeType == 'application/pdf') { + return Icons.picture_as_pdf; + } else if (mimeType.startsWith('text/')) { + return Icons.text_snippet; + } else if (mimeType.contains("application/octet-stream")) { + return Icons.disc_full_rounded; + } else if (mimeType.contains("application/zip")) { + return Icons.folder_zip; + } else { + return Icons.folder; // Default icon + } + } +} diff --git a/lib/components/nextcloud_routes.dart b/lib/components/nextcloud_routes.dart new file mode 100644 index 0000000..eb1f9fa --- /dev/null +++ b/lib/components/nextcloud_routes.dart @@ -0,0 +1,13 @@ +import 'package:flutter/material.dart'; +import 'package:nextcloudapp/screens/login_screen.dart'; +import 'package:nextcloudapp/screens/home_screen.dart'; + +class NextcloudAppRoutes { + // + static NextcloudAppRoutes instance = NextcloudAppRoutes(); + + Map get routes => { + "login": (BuildContext _context) => LoginScreen(), + "home": (BuildContext _context) => HomeSceen(), + }; +} diff --git a/lib/main.dart b/lib/main.dart index a862a85..14982e4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,48 +1,11 @@ import 'package:flutter/material.dart'; -import 'package:nextcloudapp/components/default_values.dart'; -import 'package:nextcloudapp/screens/login_screen.dart'; +import 'package:nextcloudapp/app/nextcloud_app.dart'; -void main() => runApp( - const MyApp(), - ); +void main() { + WidgetsFlutterBinding.ensureInitialized(); -class MyApp extends StatelessWidget { - const MyApp({super.key}); + runApp( + NextcloudApp(), + ); - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - debugShowCheckedModeBanner: false, - title: 'Nextcloud', - theme: ThemeData( - primaryColor: primaryColor, - scaffoldBackgroundColor: Colors.white, - elevatedButtonTheme: ElevatedButtonThemeData( - style: ElevatedButton.styleFrom( - elevation: 0, - foregroundColor: Colors.white, - backgroundColor: primaryColor, - shape: const StadiumBorder(), - maximumSize: const Size(double.infinity, 56), - minimumSize: const Size(double.infinity, 56), - ), - ), - inputDecorationTheme: const InputDecorationTheme( - filled: true, - fillColor: primaryLightColor, - iconColor: primaryColor, - prefixIconColor: primaryColor, - contentPadding: EdgeInsets.symmetric( - horizontal: defaultPadding, vertical: defaultPadding), - border: OutlineInputBorder( - borderRadius: BorderRadius.all(Radius.circular(30)), - borderSide: BorderSide.none, - ), - ), - fontFamily: 'Poppins', - ), - home: const LoginScreen(), - ); - } } diff --git a/lib/models/details.dart b/lib/models/details.dart new file mode 100644 index 0000000..5d5188c --- /dev/null +++ b/lib/models/details.dart @@ -0,0 +1,19 @@ +import 'package:nextcloud/nextcloud.dart'; + +class ServerDetails { + String name; + String description; + + ServerDetails({ + required this.name, + required this.description, + }); + + factory ServerDetails.fromTheme(Map _themeValues) { + // + DynamiteResponse icon = _themeValues['icon']; + DynamiteResponse theme = _themeValues['systemtheme']; + + return ServerDetails(name: '', description: ''); + } +} diff --git a/lib/models/files.dart b/lib/models/files.dart new file mode 100644 index 0000000..aadea10 --- /dev/null +++ b/lib/models/files.dart @@ -0,0 +1,53 @@ +import 'package:nextcloud/webdav.dart'; + +class Files { + String? path; + String? name; + String? resourceType; + String? contentType; + DateTime? creationDate; + DateTime? modifiedDate; + int? size; + + Files({ + required this.path, + required this.name, + required this.resourceType, + required this.contentType, + required this.creationDate, + required this.modifiedDate, + required this.size, + }); + + factory Files.fromWebDavResponse(WebDavResponse _response) { +// + var _name; + var _resourceType; + var _size; + + var _tempPath = _response.href!.split("/"); + var _length = _tempPath.length; + var _collection = _response.propstats[0].prop.davResourcetype!.collection; + if (_collection == null) { + _resourceType = 'file'; + _name = _tempPath[_length - 1]; + _size = _response.propstats[0].prop.davGetcontentlength; + } else { + _resourceType = 'folder'; + _name = _tempPath[_length - 2].contains("dav") + ? ".." + : _tempPath[_length - 2]; + _size = _response.propstats[0].prop.davQuotaUsedBytes; + } + + return Files( + path: _response.href ?? '', + name: Uri.decodeFull(_name), + contentType: _response.propstats[0].prop.davGetcontenttype ?? '', + creationDate: DateTime.now(), + modifiedDate: _response.propstats[0].prop.davGetlastmodified, + size: _size, + resourceType: _resourceType, + ); + } +} diff --git a/lib/provider/nextcloud_provider.dart b/lib/provider/nextcloud_provider.dart new file mode 100644 index 0000000..95a3559 --- /dev/null +++ b/lib/provider/nextcloud_provider.dart @@ -0,0 +1,182 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:http/http.dart'; +import 'package:nextcloud/core.dart'; +import 'package:nextcloud/nextcloud.dart'; +import 'package:nextcloud/provisioning_api.dart'; +import 'package:nextcloud/theming.dart'; +import 'package:nextcloud/webdav.dart'; +import 'package:nextcloudapp/app/extension.dart'; +import 'package:nextcloudapp/models/details.dart'; +import 'package:nextcloudapp/models/files.dart'; + +import 'package:nextcloudapp/service/navigation_service.dart'; +import 'package:nextcloudapp/service/snackbar_service.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +enum AuthStatus { + notAuthenticated, + authenticated, + authenticating, + userNotFound, + error, +} + +class NextcloudProvider extends ChangeNotifier { + // + // + + static NextcloudProvider instance = NextcloudProvider(); + final NavigationService _navigationService = NavigationService.instance; + + late NextcloudClient client; + final SharedPreferencesAsync _sharedPreferences = SharedPreferencesAsync(); + // SharedPreferences? _sharedPreferences; + + AuthStatus? status; + UserDetails? userDetails; + + NextcloudProvider() { + // _init(); + _checkLogin(); + } + + // void _init() async { + // _sharedPreferences = SharedPreferencesAsync(); + // } + + void _checkLogin() async { + await Future.delayed(Duration(milliseconds: 300)); + try { + String? _appPassword = await _sharedPreferences.getString('apppassword'); + print(_appPassword); + String? _uri = await _sharedPreferences.getString('uri'); + if (_appPassword != null && _uri != null) { + client = NextcloudClient( + Uri.parse(_uri), + appPassword: _appPassword, + httpClient: NextcloudClientExtension.newHttpClient(), + ); + + var _user = await client.provisioningApi.users.getCurrentUser(); + userDetails = _user.body.ocs.data; + + status = AuthStatus.authenticated; + _navigationService.navigateToReplacement("home"); + } else { + status = AuthStatus.notAuthenticated; + } + } on Exception catch (e) { + status = AuthStatus.notAuthenticated; + SnackBarService.instance.showSnackBarError("$e"); + } + } + + Future envData() async { + Map _themeValues = {}; + + _themeValues['icon'] = await client.theming.icon.getFavicon(); + _themeValues['systemtheme'] = await client.theming.theming.getManifest(); + + return ServerDetails.fromTheme(_themeValues); + } + + Future login(String _uri, String _username, String _password, + bool _savePassword) async { + notifyListeners(); + status = AuthStatus.error; + try { + status = AuthStatus.authenticating; + client = NextcloudClient( + Uri.parse(_uri), + loginName: _username, + password: _password, + httpClient: NextcloudClientExtension.newHttpClient(), + ); + var _user = await client.provisioningApi.users.getCurrentUser(); + userDetails = _user.body.ocs.data; + client.core.appPassword.getAppPassword().then((_response) async { + await _sharedPreferences.setString( + 'apppassword', _response.body.ocs.data.apppassword); + await _sharedPreferences.setString('uri', _uri); + await _sharedPreferences.setString('username', _username); + if (_savePassword) { + await _sharedPreferences.setString('password', _password); + } + }); + + status = AuthStatus.authenticated; + SnackBarService.instance.showSnackBarSuccess('successfully logged in'); + _navigationService.navigateToReplacement("home"); + } on ClientException catch (e) { + SnackBarService.instance.showSnackBarError("$e"); + status = AuthStatus.error; + } on Exception catch (e) { + status = AuthStatus.error; + SnackBarService.instance.showSnackBarError("$e"); + } + notifyListeners(); + } + + void logout() async { + try { + var _resp = await client.core.appPassword.deleteAppPassword(); + if (_resp.statusCode == 200) { + _sharedPreferences.remove('apppassword'); + SnackBarService.instance.showSnackBarSuccess("succssfully loggedout"); + _navigationService.navigateToReplacement("login"); + } else { + SnackBarService.instance + .showSnackBarError(_resp.body.ocs.meta.message!); + } + } on Exception catch (e) { + SnackBarService.instance.showSnackBarError("$e"); + } + } + + Future> listDirectory(String _cd) async { + List _fileList = []; + + try { + WebDavClient _client = client.webdav; + var _returnVal = await _client.propfind( + PathUri.parse(Uri.decodeFull(_cd)), + prop: WebDavPropWithoutValues(), + depth: WebDavDepth.one, + ); + + _fileList = _returnVal.responses.map((_value) { + return Files.fromWebDavResponse(_value); + }).toList(); + + _fileList[0].name = ".."; + _fileList[0].size = 0; + _fileList[0].resourceType = 'folder'; + List filteredList = List.from(_fileList)..removeAt(0); + filteredList.sort((a, b) => b.resourceType!.compareTo(a.resourceType!)); + filteredList.insert(0, _fileList[0]); + return filteredList; + } on Exception catch (e) { + print(e.toString()); + return Future.delayed(Duration.zero); + } + } + + Future getIt() async { + Directory? storageDirectory = await getApplicationDocumentsDirectory(); + String _sdPath = storageDirectory.path; + var d = Directory(_sdPath); + if (!d.existsSync()) { + d.createSync(recursive: true); + File _fileTemp = File("$_sdPath/.nomedia"); + _fileTemp.writeAsString(''); + } + + // File temp = File('$_sdPath/temp.png'); + + // await _client.getFile( + // PathUri.parse("/files/${userDetails!.id}/Nextcloud.png"), temp); + } +} diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart new file mode 100644 index 0000000..104d84c --- /dev/null +++ b/lib/screens/home_screen.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_speed_dial/flutter_speed_dial.dart'; +import 'package:intl/intl.dart'; +import 'package:nextcloud/webdav.dart'; +import 'package:nextcloudapp/components/file_size.dart'; +import 'package:nextcloudapp/models/files.dart'; +import 'package:nextcloudapp/provider/nextcloud_provider.dart'; +import 'package:nextcloudapp/service/snackbar_service.dart'; +import 'package:nextcloudapp/components/mime_type.dart'; +import 'package:file_icon/file_icon.dart'; + +class HomeSceen extends StatefulWidget { + const HomeSceen({super.key}); + + @override + State createState() => _HomeSceenState(); +} + +class _HomeSceenState extends State { +// + var _deviceHeight; + var _deviceWidth; + var _percentage; + var _usedMemory; + var _totalMemory; + var _cwd; + + List _temp = []; + List? _data = []; + + final MimetypeIcon _mimetypeIcon = MimetypeIcon.instance; + final FileSize _fileSize = FileSize.instance; + final NextcloudProvider _provider = NextcloudProvider.instance; + + final ScrollController _scrollController = ScrollController(); + + _HomeSceenState() { + _cwd = ""; + } + + @override + Widget build(BuildContext context) { + _deviceHeight = MediaQuery.of(context).size.height; + _deviceWidth = MediaQuery.of(context).size.width; + SnackBarService.instance.buildContext = context; + return Scaffold( + appBar: AppBar( + title: Row( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Container( + height: _deviceHeight * 0.10, + width: _deviceWidth * 0.20, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(10), + image: DecorationImage( + image: AssetImage('assets/images/nextcloud_logo.png'), + ), + ), + ), + Text("NextCloud"), + ], + ), + ), + drawer: _appDrawerUI(), + body: _appBodyUI(), + floatingActionButton: _floatingButton()); + } + + Widget _floatingButton() { + return SpeedDial( + closeManually: true, + animatedIcon: AnimatedIcons.menu_arrow, + children: [ + SpeedDialChild( + label: "File Upload", + child: Icon(Icons.upload), + ), + SpeedDialChild( + label: "Folder Upload", + child: Icon(Icons.upload), + ), + ], + ); + } + + Widget _appDrawerUI() { + _totalMemory = _provider.userDetails!.quota.total; + _usedMemory = _provider.userDetails!.quota.used; + _percentage = (_usedMemory / _totalMemory); + return NavigationDrawer( + backgroundColor: Color.fromARGB(255, 72, 160, 232), + children: [ + Stack( + clipBehavior: Clip.none, + children: [ + SizedBox( + height: _deviceHeight * 0.05, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisSize: MainAxisSize.max, + children: [ + Text(_provider.userDetails!.displayName), + IconButton( + onPressed: () { + _provider.logout(); + }, + icon: Icon(Icons.logout_sharp), + ), + ], + ), + ), + SizedBox( + height: _deviceHeight * 0.20, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.max, + children: [ + Text( + 'Memory Usage', + style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold), + ), + SizedBox(height: 10), + LinearProgressIndicator( + value: _percentage, + backgroundColor: Colors.grey[300], + valueColor: AlwaysStoppedAnimation( + _percentage > 0.80 ? Colors.red : Colors.green, + ), + ), + SizedBox(height: 10), + Text( + '${(_percentage * 100).toStringAsFixed(2)}% used', + style: TextStyle(fontSize: 14), + ), + Text( + '${_fileSize.getFileSizeString( + bytes: _usedMemory, + decimals: 1, + )} of ${_fileSize.getFileSizeString( + bytes: _totalMemory, + decimals: 1, + )}', + style: TextStyle(fontSize: 14), + ), + ], + ), + ) + ], + ), + ], + ); + } + + Widget _appBodyUI() { + return SizedBox( + height: _deviceHeight * 0.80, + child: FutureBuilder( + future: _provider.listDirectory(_cwd), + builder: (context, _snapshot) { + _data = _snapshot.data; + if (_data != null) { + // WidgetsBinding.instance.addPostFrameCallback( + // (_) { + // if (_scrollController.hasClients) { + // _scrollController + // .jumpTo(_scrollController.position.minScrollExtent); + // } + // }, + // ); + + return Scrollbar( + controller: _scrollController, + child: ListView.builder( + controller: _scrollController, + itemCount: _data!.length, + itemBuilder: (BuildContext _context, int _index) { + return ListTile( + onLongPress: () {}, + onTap: () { + setState(() { + if (_data![_index].resourceType! == "folder") { + if (_index == 0) { + _temp.removeAt(_temp.length - 2); + _cwd = _temp + .join("/") + .replaceAll(webdavBase.toString(), ''); + } else { + _cwd = _data![_index] + .path! + .replaceAll(webdavBase.toString(), ''); + _temp = _data![_index].path!.split("/"); + } + } + }); + }, + leading: _data![_index].resourceType! == 'folder' + ? Icon(_mimetypeIcon + .getIconForMimeType(_data![_index].contentType!)) + : FileIcon( + _data![_index].name!, + size: 32.0, + ), + title: Text( + _data![_index].name!, + overflow: TextOverflow.ellipsis, + style: TextStyle(fontSize: 15), + ), + subtitle: Text( + _index == 0 + ? '' + : DateFormat('dd MMM, yyyy hh:mm a') + .format(_data![_index].modifiedDate!), + style: TextStyle( + fontSize: 12, + ), + ), + trailing: Text( + _index != 0 + ? _fileSize.getFileSizeString( + bytes: _data![_index].size!, + decimals: 2, + ) + : '', + ), + ); + }, + ), + ); + } else if (_snapshot.hasError) { + return Align( + child: Text("No Data"), + ); + } else if (_snapshot.connectionState == ConnectionState.waiting) { + return Align( + child: CircularProgressIndicator(), + ); + } else { + return Align( + child: Text("Undecided"), + ); + ; + } + }, + ), + ); + } +} diff --git a/lib/screens/login_screen.dart b/lib/screens/login_screen.dart index 43b73c9..86ce8a7 100644 --- a/lib/screens/login_screen.dart +++ b/lib/screens/login_screen.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:nextcloudapp/api/login_api.dart'; -import 'package:nextcloudapp/components/default_values.dart'; -import 'package:nextcloudapp/screens/welcome_screen.dart'; +import 'package:nextcloudapp/provider/nextcloud_provider.dart'; import 'package:nextcloudapp/service/snackbar_service.dart'; +import 'package:provider/provider.dart'; class LoginScreen extends StatefulWidget { const LoginScreen({super.key}); @@ -14,44 +13,55 @@ class LoginScreen extends StatefulWidget { class _LoginScreenState extends State { var _deviceHeight; var _deviceWidth; - var urlController; - var usernameController; - var passwordController; - - final GlobalKey _formKey = GlobalKey(); - - var checked = false; + var _url; + var _username; + var _password; + var enableInput = true; + var savePassword = false; var obscureText = true; + NextcloudProvider _provider = NextcloudProvider.instance; + final GlobalKey _formKey = GlobalKey(); + @override Widget build(BuildContext context) { - SnackBarService.instance.buildContext = context; _deviceHeight = MediaQuery.of(context).size.height; _deviceWidth = MediaQuery.of(context).size.width; return Scaffold( body: Align( - child: Container( - height: _deviceHeight * 0.60, - padding: EdgeInsets.symmetric(horizontal: _deviceWidth * 0.05), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - _inputField(), - _passwordSaveField(), - _signInField(), - ], - ), + child: ChangeNotifierProvider.value( + value: _provider, + child: _loginPage(), ), ), ); } - Widget _inputField() { + Widget _loginPage() { + return Builder(builder: (BuildContext _context) { + SnackBarService.instance.buildContext = _context; + _provider = Provider.of(_context); + return Container( + height: _deviceHeight * 0.80, + padding: EdgeInsets.symmetric(horizontal: _deviceWidth * 0.05), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + _inputForm(), + _passwordSaveField(), + _signInButton(_context), + ], + ), + ); + }); + } + + Widget _inputForm() { return SizedBox( - height: _deviceHeight * 0.25, + height: _deviceHeight * 0.27, child: Form( key: _formKey, onChanged: () { @@ -63,64 +73,84 @@ class _LoginScreenState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ TextFormField( + initialValue: "https://storage.theonasanyas.com", + enabled: enableInput, keyboardType: TextInputType.url, textInputAction: TextInputAction.next, - cursorColor: Colors.white, + cursorColor: Colors.black, style: TextStyle( color: Colors.black, ), onSaved: (_input) { setState(() { - urlController = _input!; + _url = _input!; }); }, validator: (_input) { - return null; + return _input!.isNotEmpty ? null : "nnnns"; }, - decoration: const InputDecoration( + decoration: InputDecoration( hintText: "hosting URL", focusedBorder: UnderlineInputBorder( - borderSide: BorderSide(color: Colors.white) - ), + borderSide: BorderSide(color: Colors.white)), prefixIcon: Padding( - padding: EdgeInsets.all(defaultPadding), + padding: EdgeInsets.all(15.0), child: Icon(Icons.computer_outlined), ), ), ), TextFormField( + initialValue: "fluttertest", + enabled: enableInput, keyboardType: TextInputType.emailAddress, textInputAction: TextInputAction.next, + cursorColor: Colors.black, style: TextStyle( color: Colors.black, ), onSaved: (_input) { + if (_input == null) {} setState(() { - usernameController = _input!; + _username = _input!; }); }, - decoration: const InputDecoration( + validator: (_input) { + return _input!.isNotEmpty ? null : "nnnns"; + }, + decoration: InputDecoration( hintText: "username", + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white)), prefixIcon: Padding( - padding: EdgeInsets.all(defaultPadding), + padding: EdgeInsets.all(15.0), child: Icon(Icons.person), ), ), ), TextFormField( + initialValue: "9tWx8XAv545+ijnBxOrh", + enabled: enableInput, keyboardType: TextInputType.visiblePassword, textInputAction: TextInputAction.done, + cursorColor: Colors.black, + style: TextStyle( + color: Colors.black, + ), obscureText: obscureText, onSaved: (_input) { setState(() { - passwordController = _input!; + _password = _input!; }); }, - cursorColor: primaryColor, + validator: (_input) { + return _input!.isNotEmpty ? null : "nnnns"; + }, decoration: InputDecoration( - hintText: "Password", + hintText: "password", + focusedBorder: UnderlineInputBorder( + borderSide: BorderSide(color: Colors.white)), prefixIcon: const Padding( - padding: EdgeInsets.all(defaultPadding), + padding: EdgeInsets.all(15.0), child: Icon(Icons.lock), ), suffixIcon: IconButton( @@ -146,51 +176,45 @@ class _LoginScreenState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Checkbox( - value: checked, + value: savePassword, onChanged: (value) { setState(() { - checked = value!; + savePassword = value!; }); }), - const Text('Save Password'), + const Text('save password'), ], ); } - Widget _signInField() { - return ElevatedButton( - onPressed: () { - login(urlController.value.text, usernameController.value.text, - passwordController.value.text) - .then(( - status, - ) { - if (status.startsWith('successful')) { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) { - return const WelcomeScreen(); - }, + Widget _signInButton(BuildContext _context) { + return _provider.status == AuthStatus.authenticating + ? Align( + alignment: Alignment.center, + child: CircularProgressIndicator(), + ) + : SizedBox( + height: _deviceHeight * 0.06, + width: _deviceWidth * 0.30, + child: MaterialButton( + onPressed: () async { + enableInput = !enableInput; + if (_formKey.currentState!.validate()) { + //Login User + await _provider.login( + _url, _username, _password, savePassword); + } + enableInput = !enableInput; + }, + color: Colors.grey, + child: Text( + 'login', + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w700, + ), ), - ); - - SnackBarService.instance.showSnackBarSuccess(status); - } else { - SnackBarService.instance.showSnackBarError(status); - } - }); - }, - child: Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - "sign in".toUpperCase(), - ), - const SizedBox(width: 10), - const Icon(Icons.login) - ], - ), - ); + ), + ); } } diff --git a/lib/screens/welcome_screen.dart b/lib/screens/welcome_screen.dart deleted file mode 100644 index c072817..0000000 --- a/lib/screens/welcome_screen.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'package:flutter/material.dart'; - -class WelcomeScreen extends StatefulWidget { - const WelcomeScreen({super.key}); - - @override - State createState() => _WelcomeScreenState(); -} - -class _WelcomeScreenState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(BuildContext context) { - return const Scaffold( - body: Column( - children: [ - - - ], - ), - ); - } -} diff --git a/lib/service/navigation_service.dart b/lib/service/navigation_service.dart index b0bab56..c468639 100644 --- a/lib/service/navigation_service.dart +++ b/lib/service/navigation_service.dart @@ -10,7 +10,7 @@ class NavigationService { navigatorKey = GlobalKey(); } - Future? navigateToReplacement(String _routeName) { + Future navigateToReplacement(String _routeName) { return navigatorKey!.currentState!.pushReplacementNamed(_routeName); } diff --git a/pubspec.lock b/pubspec.lock index 8ffa3ec..e057562 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -34,7 +34,7 @@ packages: source: hosted version: "2.1.1" built_collection: - dependency: transitive + dependency: "direct main" description: name: built_collection sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100" @@ -81,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" crypto: dependency: transitive description: @@ -105,6 +113,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + desktop_webview_window: + dependency: transitive + description: + name: desktop_webview_window + sha256: "57cf20d81689d5cbb1adfd0017e96b669398a669d927906073b0e42fc64111c0" + url: "https://pub.dev" + source: hosted + version: "0.2.3" dynamite_runtime: dependency: transitive description: @@ -113,6 +129,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.0+1" + easy_image_viewer: + dependency: "direct main" + description: + name: easy_image_viewer + sha256: fb6cb123c3605552cc91150dcdb50ca977001dcddfb71d20caa0c5edc9a80947 + url: "https://pub.dev" + source: hosted + version: "1.5.1" fake_async: dependency: transitive description: @@ -137,6 +161,54 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.1" + file_icon: + dependency: "direct main" + description: + name: file_icon + sha256: c46b6c24d9595d18995758b90722865baeda407f56308eadd757e1ab913f50a1 + url: "https://pub.dev" + source: hosted + version: "1.0.0" + file_picker: + dependency: "direct main" + description: + name: file_picker + sha256: "77f8e81d22d2a07d0dee2c62e1dda71dc1da73bf43bb2d45af09727406167964" + url: "https://pub.dev" + source: hosted + version: "10.1.9" + file_selector_linux: + dependency: transitive + description: + name: file_selector_linux + sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + url: "https://pub.dev" + source: hosted + version: "0.9.3+2" + file_selector_macos: + dependency: transitive + description: + name: file_selector_macos + sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + url: "https://pub.dev" + source: hosted + version: "0.9.4+2" + file_selector_platform_interface: + dependency: transitive + description: + name: file_selector_platform_interface + sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + url: "https://pub.dev" + source: hosted + version: "2.6.2" + file_selector_windows: + dependency: transitive + description: + name: file_selector_windows + sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + url: "https://pub.dev" + source: hosted + version: "0.9.3+4" fixnum: dependency: transitive description: @@ -150,6 +222,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_context_menu: + dependency: "direct main" + description: + name: flutter_context_menu + sha256: "5e9c62a5937aba135e3787711b04c73b78729a9751c6c71c2bece4ed9a480d76" + url: "https://pub.dev" + source: hosted + version: "0.2.4" flutter_lints: dependency: "direct dev" description: @@ -158,6 +238,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_plugin_android_lifecycle: + dependency: transitive + description: + name: flutter_plugin_android_lifecycle + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + url: "https://pub.dev" + source: hosted + version: "2.0.28" + flutter_speed_dial: + dependency: "direct main" + description: + name: flutter_speed_dial + sha256: "698a037274a66dbae8697c265440e6acb6ab6cae9ac5f95c749e7944d8f28d41" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + flutter_spinkit: + dependency: "direct main" + description: + name: flutter_spinkit + sha256: d2696eed13732831414595b98863260e33e8882fc069ee80ec35d4ac9ddb0472 + url: "https://pub.dev" + source: hosted + version: "5.2.1" flutter_svg: dependency: "direct main" description: @@ -171,6 +275,22 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_web_auth_2: + dependency: "direct main" + description: + name: flutter_web_auth_2 + sha256: "3c14babeaa066c371f3a743f204dd0d348b7d42ffa6fae7a9847a521aff33696" + url: "https://pub.dev" + source: hosted + version: "4.1.0" + flutter_web_auth_2_platform_interface: + dependency: transitive + description: + name: flutter_web_auth_2_platform_interface + sha256: c63a472c8070998e4e422f6b34a17070e60782ac442107c70000dd1bed645f4d + url: "https://pub.dev" + source: hosted + version: "4.1.0" flutter_web_plugins: dependency: transitive description: flutter @@ -192,6 +312,70 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.2" + image_picker: + dependency: "direct main" + description: + name: image_picker + sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + url: "https://pub.dev" + source: hosted + version: "1.1.2" + image_picker_android: + dependency: transitive + description: + name: image_picker_android + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" + url: "https://pub.dev" + source: hosted + version: "0.8.12+23" + image_picker_for_web: + dependency: transitive + description: + name: image_picker_for_web + sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + url: "https://pub.dev" + source: hosted + version: "3.0.6" + image_picker_ios: + dependency: transitive + description: + name: image_picker_ios + sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + url: "https://pub.dev" + source: hosted + version: "0.8.12+2" + image_picker_linux: + dependency: transitive + description: + name: image_picker_linux + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_macos: + dependency: transitive + description: + name: image_picker_macos + sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + url: "https://pub.dev" + source: hosted + version: "0.2.1+2" + image_picker_platform_interface: + dependency: transitive + description: + name: image_picker_platform_interface + sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + url: "https://pub.dev" + source: hosted + version: "2.10.1" + image_picker_windows: + dependency: transitive + description: + name: image_picker_windows + sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + url: "https://pub.dev" + source: hosted + version: "0.2.1+1" intl: dependency: "direct main" description: @@ -241,7 +425,7 @@ packages: source: hosted version: "5.1.1" logging: - dependency: transitive + dependency: "direct main" description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 @@ -272,6 +456,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.15.0" + mime: + dependency: "direct main" + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" nextcloud: dependency: "direct main" description: @@ -296,6 +496,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + path_provider: + dependency: "direct main" + description: + name: path_provider + sha256: "50c5dd5b6e1aaf6fb3a78b33f6aa3afca52bf903a8a5298f53101fdaee55bbcd" + url: "https://pub.dev" + source: hosted + version: "2.1.5" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 + url: "https://pub.dev" + source: hosted + version: "2.2.17" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "4843174df4d288f5e29185bd6e72a6fbdf5a4a4602717eed565497429f179942" + url: "https://pub.dev" + source: hosted + version: "2.4.1" path_provider_linux: dependency: transitive description: @@ -320,6 +544,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.0" + permission_handler: + dependency: "direct main" + description: + name: permission_handler + sha256: "59adad729136f01ea9e35a48f5d1395e25cba6cea552249ddbe9cf950f5d7849" + url: "https://pub.dev" + source: hosted + version: "11.4.0" + permission_handler_android: + dependency: transitive + description: + name: permission_handler_android + sha256: d3971dcdd76182a0c198c096b5db2f0884b0d4196723d21a866fc4cdea057ebc + url: "https://pub.dev" + source: hosted + version: "12.1.0" + permission_handler_apple: + dependency: transitive + description: + name: permission_handler_apple + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 + url: "https://pub.dev" + source: hosted + version: "9.4.7" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24" + url: "https://pub.dev" + source: hosted + version: "0.1.3+5" + permission_handler_platform_interface: + dependency: transitive + description: + name: permission_handler_platform_interface + sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878 + url: "https://pub.dev" + source: hosted + version: "4.3.0" + permission_handler_windows: + dependency: transitive + description: + name: permission_handler_windows + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" + url: "https://pub.dev" + source: hosted + version: "0.2.1" petitparser: dependency: transitive description: @@ -352,6 +624,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.9.1" + provider: + dependency: "direct main" + description: + name: provider + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" + url: "https://pub.dev" + source: hosted + version: "6.1.5" quiver: dependency: transitive description: @@ -364,10 +644,10 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_android: dependency: transitive description: @@ -469,6 +749,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.3" + timeago: + dependency: "direct main" + description: + name: timeago + sha256: b05159406a97e1cbb2b9ee4faa9fb096fe0e2dfcd8b08fcd2a00553450d3422e + url: "https://pub.dev" + source: hosted + version: "3.7.1" timezone: dependency: transitive description: @@ -485,6 +773,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.4.0" + units_converter: + dependency: "direct main" + description: + name: units_converter + sha256: dfb40a9bd0df48701466ae17fd9a763eed3abe9b98b97be1d04a2fecb99673a6 + url: "https://pub.dev" + source: hosted + version: "3.0.3" universal_io: dependency: transitive description: @@ -501,6 +797,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603" + url: "https://pub.dev" + source: hosted + version: "6.3.1" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + url: "https://pub.dev" + source: hosted + version: "6.3.16" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + url: "https://pub.dev" + source: hosted + version: "6.3.3" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + url: "https://pub.dev" + source: hosted + version: "3.2.1" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + url: "https://pub.dev" + source: hosted + version: "3.2.2" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029" + url: "https://pub.dev" + source: hosted + version: "2.3.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + url: "https://pub.dev" + source: hosted + version: "3.1.4" vector_graphics: dependency: transitive description: @@ -557,6 +917,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.1" + win32: + dependency: transitive + description: + name: win32 + sha256: daf97c9d80197ed7b619040e86c8ab9a9dad285e7671ee7390f9180cc828a51e + url: "https://pub.dev" + source: hosted + version: "5.10.1" + window_to_front: + dependency: transitive + description: + name: window_to_front + sha256: "7aef379752b7190c10479e12b5fd7c0b9d92adc96817d9e96c59937929512aee" + url: "https://pub.dev" + source: hosted + version: "0.0.3" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e4754ec..68e5c7c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,14 +12,29 @@ dependencies: flutter: sdk: flutter - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 flutter_svg: ^2.0.17 http: ^1.3.0 - intl: ^0.20.2 - shared_preferences: ^2.5.2 + shared_preferences: ^2.5.3 nextcloud: ^8.1.0 + provider: ^6.1.2 + image_picker: ^1.1.2 + timeago: ^3.7.0 + easy_image_viewer: ^1.5.1 + permission_handler: ^11.4.0 + path_provider: ^2.1.5 + mime: ^2.0.0 + built_collection: ^5.1.1 + flutter_web_auth_2: ^4.1.0 + logging: ^1.3.0 + url_launcher: ^6.3.1 + flutter_spinkit: ^5.2.1 + units_converter: ^3.0.3 + file_icon: ^1.0.0 + intl: ^0.20.2 + flutter_speed_dial: ^7.0.0 + file_picker: ^10.1.9 + flutter_context_menu: ^0.2.4 dev_dependencies: flutter_test: