Compare commits
2 Commits
66f283d6e8
...
84cfe643f5
| Author | SHA1 | Date | |
|---|---|---|---|
|
84cfe643f5
|
|||
|
05ac04e9a2
|
@@ -1,10 +1,11 @@
|
|||||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:island/models/folder.dart';
|
||||||
|
|
||||||
part 'file_list_item.freezed.dart';
|
part 'file_list_item.freezed.dart';
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
sealed class FileListItem with _$FileListItem {
|
sealed class FileListItem with _$FileListItem {
|
||||||
const factory FileListItem.file(SnCloudFileIndex fileIndex) = FileItem;
|
const factory FileListItem.file(SnCloudFileIndex fileIndex) = FileItem;
|
||||||
const factory FileListItem.folder(String name) = FolderItem;
|
const factory FileListItem.folder(SnCloudFolder folder) = FolderItem;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -119,11 +119,11 @@ return folder(_that);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( SnCloudFileIndex fileIndex)? file,TResult Function( String name)? folder,required TResult orElse(),}) {final _that = this;
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>({TResult Function( SnCloudFileIndex fileIndex)? file,TResult Function( SnCloudFolder folder)? folder,required TResult orElse(),}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case FileItem() when file != null:
|
case FileItem() when file != null:
|
||||||
return file(_that.fileIndex);case FolderItem() when folder != null:
|
return file(_that.fileIndex);case FolderItem() when folder != null:
|
||||||
return folder(_that.name);case _:
|
return folder(_that.folder);case _:
|
||||||
return orElse();
|
return orElse();
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -141,11 +141,11 @@ return folder(_that.name);case _:
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( SnCloudFileIndex fileIndex) file,required TResult Function( String name) folder,}) {final _that = this;
|
@optionalTypeArgs TResult when<TResult extends Object?>({required TResult Function( SnCloudFileIndex fileIndex) file,required TResult Function( SnCloudFolder folder) folder,}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case FileItem():
|
case FileItem():
|
||||||
return file(_that.fileIndex);case FolderItem():
|
return file(_that.fileIndex);case FolderItem():
|
||||||
return folder(_that.name);}
|
return folder(_that.folder);}
|
||||||
}
|
}
|
||||||
/// A variant of `when` that fallback to returning `null`
|
/// A variant of `when` that fallback to returning `null`
|
||||||
///
|
///
|
||||||
@@ -159,11 +159,11 @@ return folder(_that.name);}
|
|||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
|
||||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( SnCloudFileIndex fileIndex)? file,TResult? Function( String name)? folder,}) {final _that = this;
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>({TResult? Function( SnCloudFileIndex fileIndex)? file,TResult? Function( SnCloudFolder folder)? folder,}) {final _that = this;
|
||||||
switch (_that) {
|
switch (_that) {
|
||||||
case FileItem() when file != null:
|
case FileItem() when file != null:
|
||||||
return file(_that.fileIndex);case FolderItem() when folder != null:
|
return file(_that.fileIndex);case FolderItem() when folder != null:
|
||||||
return folder(_that.name);case _:
|
return folder(_that.folder);case _:
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -250,10 +250,10 @@ $SnCloudFileIndexCopyWith<$Res> get fileIndex {
|
|||||||
|
|
||||||
|
|
||||||
class FolderItem implements FileListItem {
|
class FolderItem implements FileListItem {
|
||||||
const FolderItem(this.name);
|
const FolderItem(this.folder);
|
||||||
|
|
||||||
|
|
||||||
final String name;
|
final SnCloudFolder folder;
|
||||||
|
|
||||||
/// Create a copy of FileListItem
|
/// Create a copy of FileListItem
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@@ -265,16 +265,16 @@ $FolderItemCopyWith<FolderItem> get copyWith => _$FolderItemCopyWithImpl<FolderI
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is FolderItem&&(identical(other.name, name) || other.name == name));
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is FolderItem&&(identical(other.folder, folder) || other.folder == folder));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode => Object.hash(runtimeType,name);
|
int get hashCode => Object.hash(runtimeType,folder);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'FileListItem.folder(name: $name)';
|
return 'FileListItem.folder(folder: $folder)';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -285,11 +285,11 @@ abstract mixin class $FolderItemCopyWith<$Res> implements $FileListItemCopyWith<
|
|||||||
factory $FolderItemCopyWith(FolderItem value, $Res Function(FolderItem) _then) = _$FolderItemCopyWithImpl;
|
factory $FolderItemCopyWith(FolderItem value, $Res Function(FolderItem) _then) = _$FolderItemCopyWithImpl;
|
||||||
@useResult
|
@useResult
|
||||||
$Res call({
|
$Res call({
|
||||||
String name
|
SnCloudFolder folder
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
$SnCloudFolderCopyWith<$Res> get folder;
|
||||||
|
|
||||||
}
|
}
|
||||||
/// @nodoc
|
/// @nodoc
|
||||||
@@ -302,14 +302,23 @@ class _$FolderItemCopyWithImpl<$Res>
|
|||||||
|
|
||||||
/// Create a copy of FileListItem
|
/// Create a copy of FileListItem
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
@pragma('vm:prefer-inline') $Res call({Object? name = null,}) {
|
@pragma('vm:prefer-inline') $Res call({Object? folder = null,}) {
|
||||||
return _then(FolderItem(
|
return _then(FolderItem(
|
||||||
null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
null == folder ? _self.folder : folder // ignore: cast_nullable_to_non_nullable
|
||||||
as String,
|
as SnCloudFolder,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a copy of FileListItem
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFolderCopyWith<$Res> get folder {
|
||||||
|
|
||||||
|
return $SnCloudFolderCopyWith<$Res>(_self.folder, (value) {
|
||||||
|
return _then(_self.copyWith(folder: value));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// dart format on
|
// dart format on
|
||||||
|
|||||||
19
lib/models/folder.dart
Normal file
19
lib/models/folder.dart
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||||
|
|
||||||
|
part 'folder.freezed.dart';
|
||||||
|
part 'folder.g.dart';
|
||||||
|
|
||||||
|
@freezed
|
||||||
|
sealed class SnCloudFolder with _$SnCloudFolder {
|
||||||
|
const factory SnCloudFolder({
|
||||||
|
required String id,
|
||||||
|
required String name,
|
||||||
|
required String? parentFolderId,
|
||||||
|
required String accountId,
|
||||||
|
required DateTime createdAt,
|
||||||
|
required DateTime updatedAt,
|
||||||
|
}) = _SnCloudFolder;
|
||||||
|
|
||||||
|
factory SnCloudFolder.fromJson(Map<String, dynamic> json) =>
|
||||||
|
_$SnCloudFolderFromJson(json);
|
||||||
|
}
|
||||||
286
lib/models/folder.freezed.dart
Normal file
286
lib/models/folder.freezed.dart
Normal file
@@ -0,0 +1,286 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
// coverage:ignore-file
|
||||||
|
// ignore_for_file: type=lint
|
||||||
|
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||||
|
|
||||||
|
part of 'folder.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// FreezedGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
// dart format off
|
||||||
|
T _$identity<T>(T value) => value;
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
mixin _$SnCloudFolder {
|
||||||
|
|
||||||
|
String get id; String get name; String? get parentFolderId; String get accountId; DateTime get createdAt; DateTime get updatedAt;
|
||||||
|
/// Create a copy of SnCloudFolder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
$SnCloudFolderCopyWith<SnCloudFolder> get copyWith => _$SnCloudFolderCopyWithImpl<SnCloudFolder>(this as SnCloudFolder, _$identity);
|
||||||
|
|
||||||
|
/// Serializes this SnCloudFolder to a JSON map.
|
||||||
|
Map<String, dynamic> toJson();
|
||||||
|
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnCloudFolder&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.parentFolderId, parentFolderId) || other.parentFolderId == parentFolderId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,name,parentFolderId,accountId,createdAt,updatedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnCloudFolder(id: $id, name: $name, parentFolderId: $parentFolderId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class $SnCloudFolderCopyWith<$Res> {
|
||||||
|
factory $SnCloudFolderCopyWith(SnCloudFolder value, $Res Function(SnCloudFolder) _then) = _$SnCloudFolderCopyWithImpl;
|
||||||
|
@useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class _$SnCloudFolderCopyWithImpl<$Res>
|
||||||
|
implements $SnCloudFolderCopyWith<$Res> {
|
||||||
|
_$SnCloudFolderCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final SnCloudFolder _self;
|
||||||
|
final $Res Function(SnCloudFolder) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnCloudFolder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@pragma('vm:prefer-inline') @override $Res call({Object? id = null,Object? name = null,Object? parentFolderId = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,}) {
|
||||||
|
return _then(_self.copyWith(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,parentFolderId: freezed == parentFolderId ? _self.parentFolderId : parentFolderId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/// Adds pattern-matching-related methods to [SnCloudFolder].
|
||||||
|
extension SnCloudFolderPatterns on SnCloudFolder {
|
||||||
|
/// A variant of `map` that fallback to returning `orElse`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnCloudFolder value)? $default,{required TResult orElse(),}){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCloudFolder() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// Callbacks receives the raw object, upcasted.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case final Subclass2 value:
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnCloudFolder value) $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCloudFolder():
|
||||||
|
return $default(_that);}
|
||||||
|
}
|
||||||
|
/// A variant of `map` that fallback to returning `null`.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case final Subclass value:
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnCloudFolder value)? $default,){
|
||||||
|
final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCloudFolder() when $default != null:
|
||||||
|
return $default(_that);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to an `orElse` callback.
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return orElse();
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt)? $default,{required TResult orElse(),}) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCloudFolder() when $default != null:
|
||||||
|
return $default(_that.id,_that.name,_that.parentFolderId,_that.accountId,_that.createdAt,_that.updatedAt);case _:
|
||||||
|
return orElse();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A `switch`-like method, using callbacks.
|
||||||
|
///
|
||||||
|
/// As opposed to `map`, this offers destructuring.
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case Subclass2(:final field2):
|
||||||
|
/// return ...;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt) $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCloudFolder():
|
||||||
|
return $default(_that.id,_that.name,_that.parentFolderId,_that.accountId,_that.createdAt,_that.updatedAt);}
|
||||||
|
}
|
||||||
|
/// A variant of `when` that fallback to returning `null`
|
||||||
|
///
|
||||||
|
/// It is equivalent to doing:
|
||||||
|
/// ```dart
|
||||||
|
/// switch (sealedClass) {
|
||||||
|
/// case Subclass(:final field):
|
||||||
|
/// return ...;
|
||||||
|
/// case _:
|
||||||
|
/// return null;
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
|
||||||
|
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt)? $default,) {final _that = this;
|
||||||
|
switch (_that) {
|
||||||
|
case _SnCloudFolder() when $default != null:
|
||||||
|
return $default(_that.id,_that.name,_that.parentFolderId,_that.accountId,_that.createdAt,_that.updatedAt);case _:
|
||||||
|
return null;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
@JsonSerializable()
|
||||||
|
|
||||||
|
class _SnCloudFolder implements SnCloudFolder {
|
||||||
|
const _SnCloudFolder({required this.id, required this.name, required this.parentFolderId, required this.accountId, required this.createdAt, required this.updatedAt});
|
||||||
|
factory _SnCloudFolder.fromJson(Map<String, dynamic> json) => _$SnCloudFolderFromJson(json);
|
||||||
|
|
||||||
|
@override final String id;
|
||||||
|
@override final String name;
|
||||||
|
@override final String? parentFolderId;
|
||||||
|
@override final String accountId;
|
||||||
|
@override final DateTime createdAt;
|
||||||
|
@override final DateTime updatedAt;
|
||||||
|
|
||||||
|
/// Create a copy of SnCloudFolder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@pragma('vm:prefer-inline')
|
||||||
|
_$SnCloudFolderCopyWith<_SnCloudFolder> get copyWith => __$SnCloudFolderCopyWithImpl<_SnCloudFolder>(this, _$identity);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return _$SnCloudFolderToJson(this, );
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnCloudFolder&&(identical(other.id, id) || other.id == id)&&(identical(other.name, name) || other.name == name)&&(identical(other.parentFolderId, parentFolderId) || other.parentFolderId == parentFolderId)&&(identical(other.accountId, accountId) || other.accountId == accountId)&&(identical(other.createdAt, createdAt) || other.createdAt == createdAt)&&(identical(other.updatedAt, updatedAt) || other.updatedAt == updatedAt));
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
@override
|
||||||
|
int get hashCode => Object.hash(runtimeType,id,name,parentFolderId,accountId,createdAt,updatedAt);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'SnCloudFolder(id: $id, name: $name, parentFolderId: $parentFolderId, accountId: $accountId, createdAt: $createdAt, updatedAt: $updatedAt)';
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// @nodoc
|
||||||
|
abstract mixin class _$SnCloudFolderCopyWith<$Res> implements $SnCloudFolderCopyWith<$Res> {
|
||||||
|
factory _$SnCloudFolderCopyWith(_SnCloudFolder value, $Res Function(_SnCloudFolder) _then) = __$SnCloudFolderCopyWithImpl;
|
||||||
|
@override @useResult
|
||||||
|
$Res call({
|
||||||
|
String id, String name, String? parentFolderId, String accountId, DateTime createdAt, DateTime updatedAt
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
/// @nodoc
|
||||||
|
class __$SnCloudFolderCopyWithImpl<$Res>
|
||||||
|
implements _$SnCloudFolderCopyWith<$Res> {
|
||||||
|
__$SnCloudFolderCopyWithImpl(this._self, this._then);
|
||||||
|
|
||||||
|
final _SnCloudFolder _self;
|
||||||
|
final $Res Function(_SnCloudFolder) _then;
|
||||||
|
|
||||||
|
/// Create a copy of SnCloudFolder
|
||||||
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
@override @pragma('vm:prefer-inline') $Res call({Object? id = null,Object? name = null,Object? parentFolderId = freezed,Object? accountId = null,Object? createdAt = null,Object? updatedAt = null,}) {
|
||||||
|
return _then(_SnCloudFolder(
|
||||||
|
id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,parentFolderId: freezed == parentFolderId ? _self.parentFolderId : parentFolderId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String?,accountId: null == accountId ? _self.accountId : accountId // ignore: cast_nullable_to_non_nullable
|
||||||
|
as String,createdAt: null == createdAt ? _self.createdAt : createdAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,updatedAt: null == updatedAt ? _self.updatedAt : updatedAt // ignore: cast_nullable_to_non_nullable
|
||||||
|
as DateTime,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dart format on
|
||||||
27
lib/models/folder.g.dart
Normal file
27
lib/models/folder.g.dart
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||||
|
|
||||||
|
part of 'folder.dart';
|
||||||
|
|
||||||
|
// **************************************************************************
|
||||||
|
// JsonSerializableGenerator
|
||||||
|
// **************************************************************************
|
||||||
|
|
||||||
|
_SnCloudFolder _$SnCloudFolderFromJson(Map<String, dynamic> json) =>
|
||||||
|
_SnCloudFolder(
|
||||||
|
id: json['id'] as String,
|
||||||
|
name: json['name'] as String,
|
||||||
|
parentFolderId: json['parent_folder_id'] as String?,
|
||||||
|
accountId: json['account_id'] as String,
|
||||||
|
createdAt: DateTime.parse(json['created_at'] as String),
|
||||||
|
updatedAt: DateTime.parse(json['updated_at'] as String),
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> _$SnCloudFolderToJson(_SnCloudFolder instance) =>
|
||||||
|
<String, dynamic>{
|
||||||
|
'id': instance.id,
|
||||||
|
'name': instance.name,
|
||||||
|
'parent_folder_id': instance.parentFolderId,
|
||||||
|
'account_id': instance.accountId,
|
||||||
|
'created_at': instance.createdAt.toIso8601String(),
|
||||||
|
'updated_at': instance.updatedAt.toIso8601String(),
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/file.dart';
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/models/file_list_item.dart';
|
import 'package:island/models/file_list_item.dart';
|
||||||
|
import 'package:island/models/folder.dart';
|
||||||
import 'package:island/pods/network.dart';
|
import 'package:island/pods/network.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
@@ -31,8 +32,10 @@ class CloudFileListNotifier extends _$CloudFileListNotifier
|
|||||||
queryParameters: {'path': _currentPath},
|
queryParameters: {'path': _currentPath},
|
||||||
);
|
);
|
||||||
|
|
||||||
final List<String> folders =
|
final List<SnCloudFolder> folders =
|
||||||
(response.data['folders'] as List).cast<String>();
|
(response.data['folders'] as List)
|
||||||
|
.map((e) => SnCloudFolder.fromJson(e as Map<String, dynamic>))
|
||||||
|
.toList();
|
||||||
final List<SnCloudFileIndex> files =
|
final List<SnCloudFileIndex> files =
|
||||||
(response.data['files'] as List)
|
(response.data['files'] as List)
|
||||||
.map((e) => SnCloudFileIndex.fromJson(e as Map<String, dynamic>))
|
.map((e) => SnCloudFileIndex.fromJson(e as Map<String, dynamic>))
|
||||||
|
|||||||
@@ -12,7 +12,9 @@ import 'package:island/screens/developers/hub.dart';
|
|||||||
import 'package:island/screens/developers/edit_project.dart';
|
import 'package:island/screens/developers/edit_project.dart';
|
||||||
import 'package:island/screens/developers/new_project.dart';
|
import 'package:island/screens/developers/new_project.dart';
|
||||||
import 'package:island/screens/discovery/articles.dart';
|
import 'package:island/screens/discovery/articles.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
import 'package:island/screens/files/file_list.dart';
|
import 'package:island/screens/files/file_list.dart';
|
||||||
|
import 'package:island/screens/files/file_detail.dart';
|
||||||
import 'package:island/screens/posts/post_categories_list.dart';
|
import 'package:island/screens/posts/post_categories_list.dart';
|
||||||
import 'package:island/screens/posts/post_category_detail.dart';
|
import 'package:island/screens/posts/post_category_detail.dart';
|
||||||
import 'package:island/screens/posts/post_search.dart';
|
import 'package:island/screens/posts/post_search.dart';
|
||||||
@@ -445,6 +447,23 @@ final routerProvider = Provider<GoRouter>((ref) {
|
|||||||
name: 'files',
|
name: 'files',
|
||||||
path: '/files',
|
path: '/files',
|
||||||
builder: (context, state) => const FileListScreen(),
|
builder: (context, state) => const FileListScreen(),
|
||||||
|
routes: [
|
||||||
|
GoRoute(
|
||||||
|
name: 'fileDetail',
|
||||||
|
path: ':id',
|
||||||
|
builder: (context, state) {
|
||||||
|
// For now, we'll need to pass the file object through extra
|
||||||
|
// This will be updated when we modify the file list navigation
|
||||||
|
final file = state.extra as SnCloudFile?;
|
||||||
|
if (file != null) {
|
||||||
|
return FileDetailScreen(item: file);
|
||||||
|
}
|
||||||
|
// Fallback - this shouldn't happen in normal flow
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
|
|
||||||
// Creator hub tab
|
// Creator hub tab
|
||||||
|
|||||||
538
lib/screens/files/file_detail.dart
Normal file
538
lib/screens/files/file_detail.dart
Normal file
@@ -0,0 +1,538 @@
|
|||||||
|
import 'dart:io';
|
||||||
|
import 'dart:math' as math;
|
||||||
|
|
||||||
|
import 'package:dismissible_page/dismissible_page.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
import 'package:file_saver/file_saver.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
|
import 'package:gal/gal.dart';
|
||||||
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
|
import 'package:island/models/file.dart';
|
||||||
|
import 'package:island/pods/config.dart';
|
||||||
|
import 'package:island/pods/network.dart';
|
||||||
|
import 'package:island/services/responsive.dart';
|
||||||
|
import 'package:island/utils/format.dart';
|
||||||
|
import 'package:island/widgets/alert.dart';
|
||||||
|
import 'package:island/widgets/app_scaffold.dart';
|
||||||
|
import 'package:island/widgets/content/audio.dart';
|
||||||
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
|
import 'package:island/widgets/content/file_info_sheet.dart';
|
||||||
|
import 'package:island/widgets/content/video.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:path/path.dart' show extension;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:photo_view/photo_view.dart';
|
||||||
|
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||||
|
|
||||||
|
class FileDetailScreen extends HookConsumerWidget {
|
||||||
|
final SnCloudFile item;
|
||||||
|
|
||||||
|
const FileDetailScreen({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final serverUrl = ref.watch(serverUrlProvider);
|
||||||
|
final isWide = isWideScreen(context);
|
||||||
|
|
||||||
|
// Animation controller for the drawer
|
||||||
|
final animationController = useAnimationController(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
);
|
||||||
|
final animation = useMemoized(
|
||||||
|
() => Tween<double>(begin: 0, end: 1).animate(
|
||||||
|
CurvedAnimation(parent: animationController, curve: Curves.easeInOut),
|
||||||
|
),
|
||||||
|
[animationController],
|
||||||
|
);
|
||||||
|
|
||||||
|
final showDrawer = useState(false);
|
||||||
|
|
||||||
|
void showInfoSheet() {
|
||||||
|
if (isWide) {
|
||||||
|
// Show as animated right panel on wide screens
|
||||||
|
showDrawer.value = !showDrawer.value;
|
||||||
|
if (showDrawer.value) {
|
||||||
|
animationController.forward();
|
||||||
|
} else {
|
||||||
|
animationController.reverse();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Show as bottom sheet on narrow screens
|
||||||
|
showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => FileInfoSheet(item: item),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listen to drawer state changes
|
||||||
|
useEffect(() {
|
||||||
|
void listener() {
|
||||||
|
if (!animationController.isAnimating) {
|
||||||
|
if (animationController.value == 0) {
|
||||||
|
showDrawer.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
animationController.addListener(listener);
|
||||||
|
return () => animationController.removeListener(listener);
|
||||||
|
}, [animationController]);
|
||||||
|
|
||||||
|
return AppScaffold(
|
||||||
|
isNoBackground: true,
|
||||||
|
appBar: AppBar(
|
||||||
|
elevation: 0,
|
||||||
|
leading: IconButton(
|
||||||
|
icon: Icon(Icons.close),
|
||||||
|
onPressed: () => Navigator.of(context).pop(),
|
||||||
|
),
|
||||||
|
title: Text(item.name.isEmpty ? 'File Details' : item.name),
|
||||||
|
actions: _buildAppBarActions(context, ref, showInfoSheet),
|
||||||
|
),
|
||||||
|
body: AnimatedBuilder(
|
||||||
|
animation: animation,
|
||||||
|
builder: (context, child) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
// Main content area
|
||||||
|
Expanded(child: _buildContent(context, ref, serverUrl)),
|
||||||
|
// Animated drawer panel
|
||||||
|
if (isWide)
|
||||||
|
SizedBox(
|
||||||
|
height: double.infinity,
|
||||||
|
width: animation.value * 400, // Max width of 400px
|
||||||
|
child: Container(
|
||||||
|
child:
|
||||||
|
animation.value > 0.1
|
||||||
|
? FileInfoSheet(item: item, onClose: showInfoSheet)
|
||||||
|
: const SizedBox.shrink(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Widget> _buildAppBarActions(
|
||||||
|
BuildContext context,
|
||||||
|
WidgetRef ref,
|
||||||
|
VoidCallback showInfoSheet,
|
||||||
|
) {
|
||||||
|
final actions = <Widget>[];
|
||||||
|
|
||||||
|
// Add content-specific actions
|
||||||
|
switch (item.mimeType?.split('/').firstOrNull) {
|
||||||
|
case 'image':
|
||||||
|
if (!kIsWeb) {
|
||||||
|
actions.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.save_alt),
|
||||||
|
onPressed: () async => _saveToGallery(ref),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// HD/SD toggle will be handled in the image content overlay
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (!kIsWeb) {
|
||||||
|
actions.add(
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.save_alt),
|
||||||
|
onPressed: () async => _downloadFile(ref),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Always add info button
|
||||||
|
actions.add(
|
||||||
|
IconButton(icon: Icon(Icons.info_outline), onPressed: showInfoSheet),
|
||||||
|
);
|
||||||
|
|
||||||
|
actions.add(const Gap(8));
|
||||||
|
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _saveToGallery(WidgetRef ref) async {
|
||||||
|
try {
|
||||||
|
showSnackBar('Saving image...');
|
||||||
|
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
var extName = extension(item.name).trim();
|
||||||
|
if (extName.isEmpty) {
|
||||||
|
extName = item.mimeType?.split('/').lastOrNull ?? 'jpeg';
|
||||||
|
}
|
||||||
|
final filePath = '${tempDir.path}/${item.id}.$extName';
|
||||||
|
|
||||||
|
await client.download(
|
||||||
|
'/drive/files/${item.id}',
|
||||||
|
filePath,
|
||||||
|
queryParameters: {'original': true},
|
||||||
|
);
|
||||||
|
if (!kIsWeb && (Platform.isAndroid || Platform.isIOS)) {
|
||||||
|
await Gal.putImage(filePath, album: 'Solar Network');
|
||||||
|
showSnackBar('Image saved to gallery');
|
||||||
|
} else {
|
||||||
|
await FileSaver.instance.saveFile(
|
||||||
|
name: item.name.isEmpty ? '${item.id}.$extName' : item.name,
|
||||||
|
file: File(filePath),
|
||||||
|
);
|
||||||
|
showSnackBar('Image saved to $filePath');
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _downloadFile(WidgetRef ref) async {
|
||||||
|
try {
|
||||||
|
showSnackBar('Downloading file...');
|
||||||
|
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
var extName = extension(item.name).trim();
|
||||||
|
if (extName.isEmpty) {
|
||||||
|
extName = item.mimeType?.split('/').lastOrNull ?? 'bin';
|
||||||
|
}
|
||||||
|
final filePath = '${tempDir.path}/${item.id}.$extName';
|
||||||
|
|
||||||
|
await client.download(
|
||||||
|
'/drive/files/${item.id}',
|
||||||
|
filePath,
|
||||||
|
queryParameters: {'original': true},
|
||||||
|
);
|
||||||
|
|
||||||
|
await FileSaver.instance.saveFile(
|
||||||
|
name: item.name.isEmpty ? '${item.id}.$extName' : item.name,
|
||||||
|
file: File(filePath),
|
||||||
|
);
|
||||||
|
showSnackBar('File saved to downloads');
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildContent(BuildContext context, WidgetRef ref, String serverUrl) {
|
||||||
|
final uri = '$serverUrl/drive/files/${item.id}';
|
||||||
|
|
||||||
|
return switch (item.mimeType?.split('/').firstOrNull) {
|
||||||
|
'image' => _buildImageContent(context, ref, uri),
|
||||||
|
'video' => _buildVideoContent(context, ref, uri),
|
||||||
|
'audio' => _buildAudioContent(context, ref, uri),
|
||||||
|
_ when item.mimeType == 'application/pdf' => _PdfContent(uri: uri),
|
||||||
|
_ when item.mimeType?.startsWith('text/') == true => _TextContent(
|
||||||
|
uri: uri,
|
||||||
|
),
|
||||||
|
_ => _buildGenericContent(context, ref),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildImageContent(BuildContext context, WidgetRef ref, String uri) {
|
||||||
|
final photoViewController = useMemoized(() => PhotoViewController(), []);
|
||||||
|
final rotation = useState(0);
|
||||||
|
final showOriginal = useState(false);
|
||||||
|
|
||||||
|
final shadow = [
|
||||||
|
Shadow(color: Colors.black54, blurRadius: 5.0, offset: Offset(1.0, 1.0)),
|
||||||
|
];
|
||||||
|
|
||||||
|
return DismissiblePage(
|
||||||
|
isFullScreen: true,
|
||||||
|
backgroundColor: Colors.transparent,
|
||||||
|
direction: DismissiblePageDismissDirection.down,
|
||||||
|
onDismissed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Positioned.fill(
|
||||||
|
child: PhotoView(
|
||||||
|
backgroundDecoration: BoxDecoration(
|
||||||
|
color: Colors.black.withOpacity(0.9),
|
||||||
|
),
|
||||||
|
controller: photoViewController,
|
||||||
|
imageProvider: CloudImageWidget.provider(
|
||||||
|
fileId: item.id,
|
||||||
|
serverUrl: ref.watch(serverUrlProvider),
|
||||||
|
original: showOriginal.value,
|
||||||
|
),
|
||||||
|
customSize: MediaQuery.of(context).size,
|
||||||
|
basePosition: Alignment.center,
|
||||||
|
filterQuality: FilterQuality.high,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// Controls overlay
|
||||||
|
Positioned(
|
||||||
|
bottom: MediaQuery.of(context).padding.bottom + 16,
|
||||||
|
left: 16,
|
||||||
|
right: 16,
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.remove,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: shadow,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
photoViewController.scale =
|
||||||
|
(photoViewController.scale ?? 1) - 0.05;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(Icons.add, color: Colors.white, shadows: shadow),
|
||||||
|
onPressed: () {
|
||||||
|
photoViewController.scale =
|
||||||
|
(photoViewController.scale ?? 1) + 0.05;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.rotate_left,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: shadow,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
rotation.value = (rotation.value - 1) % 4;
|
||||||
|
photoViewController.rotation =
|
||||||
|
rotation.value * -math.pi / 2;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
icon: Icon(
|
||||||
|
Icons.rotate_right,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: shadow,
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
rotation.value = (rotation.value + 1) % 4;
|
||||||
|
photoViewController.rotation =
|
||||||
|
rotation.value * -math.pi / 2;
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const Spacer(),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () {
|
||||||
|
showOriginal.value = !showOriginal.value;
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
showOriginal.value ? Symbols.hd : Symbols.sd,
|
||||||
|
color: Colors.white,
|
||||||
|
shadows: shadow,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildVideoContent(BuildContext context, WidgetRef ref, String uri) {
|
||||||
|
var ratio =
|
||||||
|
item.fileMeta?['ratio'] is num
|
||||||
|
? item.fileMeta!['ratio'].toDouble()
|
||||||
|
: 1.0;
|
||||||
|
if (ratio == 0) ratio = 1.0;
|
||||||
|
|
||||||
|
return DismissiblePage(
|
||||||
|
isFullScreen: true,
|
||||||
|
backgroundColor: Colors.black,
|
||||||
|
direction: DismissiblePageDismissDirection.down,
|
||||||
|
onDismissed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: ratio,
|
||||||
|
child: UniversalVideo(uri: uri, autoplay: true),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildAudioContent(BuildContext context, WidgetRef ref, String uri) {
|
||||||
|
return DismissiblePage(
|
||||||
|
isFullScreen: true,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
direction: DismissiblePageDismissDirection.down,
|
||||||
|
onDismissed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: ConstrainedBox(
|
||||||
|
constraints: BoxConstraints(
|
||||||
|
maxWidth: math.min(360, MediaQuery.of(context).size.width * 0.8),
|
||||||
|
),
|
||||||
|
child: UniversalAudio(uri: uri, filename: item.name),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildGenericContent(BuildContext context, WidgetRef ref) {
|
||||||
|
Future<void> downloadFile() async {
|
||||||
|
try {
|
||||||
|
showSnackBar('Downloading file...');
|
||||||
|
|
||||||
|
final client = ref.read(apiClientProvider);
|
||||||
|
final tempDir = await getTemporaryDirectory();
|
||||||
|
var extName = extension(item.name).trim();
|
||||||
|
if (extName.isEmpty) {
|
||||||
|
extName = item.mimeType?.split('/').lastOrNull ?? 'bin';
|
||||||
|
}
|
||||||
|
final filePath = '${tempDir.path}/${item.id}.$extName';
|
||||||
|
|
||||||
|
await client.download(
|
||||||
|
'/drive/files/${item.id}',
|
||||||
|
filePath,
|
||||||
|
queryParameters: {'original': true},
|
||||||
|
);
|
||||||
|
|
||||||
|
await FileSaver.instance.saveFile(
|
||||||
|
name: item.name.isEmpty ? '${item.id}.$extName' : item.name,
|
||||||
|
file: File(filePath),
|
||||||
|
);
|
||||||
|
showSnackBar('File saved to downloads');
|
||||||
|
} catch (e) {
|
||||||
|
showErrorAlert(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DismissiblePage(
|
||||||
|
isFullScreen: true,
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||||
|
direction: DismissiblePageDismissDirection.down,
|
||||||
|
onDismissed: () {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Center(
|
||||||
|
child: Container(
|
||||||
|
margin: const EdgeInsets.all(32),
|
||||||
|
padding: const EdgeInsets.all(32),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(
|
||||||
|
color: Theme.of(context).colorScheme.outline,
|
||||||
|
width: 1,
|
||||||
|
),
|
||||||
|
borderRadius: BorderRadius.circular(16),
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Symbols.insert_drive_file,
|
||||||
|
size: 64,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
Text(
|
||||||
|
item.name,
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 20,
|
||||||
|
fontWeight: FontWeight.bold,
|
||||||
|
color: Theme.of(context).colorScheme.onSurface,
|
||||||
|
),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
const Gap(8),
|
||||||
|
Text(
|
||||||
|
formatFileSize(item.size),
|
||||||
|
style: TextStyle(
|
||||||
|
fontSize: 16,
|
||||||
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const Gap(24),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
FilledButton.icon(
|
||||||
|
onPressed: downloadFile,
|
||||||
|
icon: const Icon(Symbols.download),
|
||||||
|
label: Text('download').tr(),
|
||||||
|
),
|
||||||
|
const Gap(16),
|
||||||
|
OutlinedButton.icon(
|
||||||
|
onPressed: () {
|
||||||
|
showModalBottomSheet(
|
||||||
|
useRootNavigator: true,
|
||||||
|
context: context,
|
||||||
|
isScrollControlled: true,
|
||||||
|
builder: (context) => FileInfoSheet(item: item),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
icon: const Icon(Symbols.info),
|
||||||
|
label: Text('info').tr(),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PdfContent extends HookConsumerWidget {
|
||||||
|
final String uri;
|
||||||
|
|
||||||
|
const _PdfContent({required this.uri});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final pdfViewer = useMemoized(() => SfPdfViewer.network(uri), [uri]);
|
||||||
|
return pdfViewer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _TextContent extends HookConsumerWidget {
|
||||||
|
final String uri;
|
||||||
|
|
||||||
|
const _TextContent({required this.uri});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
final textFuture = useMemoized(
|
||||||
|
() => ref
|
||||||
|
.read(apiClientProvider)
|
||||||
|
.get(uri)
|
||||||
|
.then((response) => response.data as String),
|
||||||
|
[uri],
|
||||||
|
);
|
||||||
|
|
||||||
|
return FutureBuilder<String>(
|
||||||
|
future: textFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.waiting) {
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
} else if (snapshot.hasError) {
|
||||||
|
return Center(child: Text('Error loading text: ${snapshot.error}'));
|
||||||
|
} else if (snapshot.hasData) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
padding: EdgeInsets.all(20),
|
||||||
|
child: SelectableText(
|
||||||
|
snapshot.data!,
|
||||||
|
style: const TextStyle(fontFamily: 'monospace', fontSize: 14),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const Center(child: Text('No content'));
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,8 +13,8 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||||||
|
|
||||||
class FileInfoSheet extends StatelessWidget {
|
class FileInfoSheet extends StatelessWidget {
|
||||||
final SnCloudFile item;
|
final SnCloudFile item;
|
||||||
|
final VoidCallback? onClose;
|
||||||
const FileInfoSheet({super.key, required this.item});
|
const FileInfoSheet({super.key, required this.item, this.onClose});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -22,6 +22,7 @@ class FileInfoSheet extends StatelessWidget {
|
|||||||
final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {};
|
final exifData = item.fileMeta?['exif'] as Map<String, dynamic>? ?? {};
|
||||||
|
|
||||||
return SheetScaffold(
|
return SheetScaffold(
|
||||||
|
onClose: onClose,
|
||||||
titleText: 'fileInfoTitle'.tr(),
|
titleText: 'fileInfoTitle'.tr(),
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ class SheetScaffold extends StatelessWidget {
|
|||||||
final Widget child;
|
final Widget child;
|
||||||
final double heightFactor;
|
final double heightFactor;
|
||||||
final double? height;
|
final double? height;
|
||||||
|
final VoidCallback? onClose;
|
||||||
const SheetScaffold({
|
const SheetScaffold({
|
||||||
super.key,
|
super.key,
|
||||||
this.title,
|
this.title,
|
||||||
@@ -16,6 +17,7 @@ class SheetScaffold extends StatelessWidget {
|
|||||||
this.actions = const [],
|
this.actions = const [],
|
||||||
this.heightFactor = 0.8,
|
this.heightFactor = 0.8,
|
||||||
this.height,
|
this.height,
|
||||||
|
this.onClose,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@@ -50,7 +52,11 @@ class SheetScaffold extends StatelessWidget {
|
|||||||
...actions,
|
...actions,
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Symbols.close),
|
icon: const Icon(Symbols.close),
|
||||||
onPressed: () => Navigator.pop(context),
|
onPressed:
|
||||||
|
() =>
|
||||||
|
onClose != null
|
||||||
|
? onClose?.call()
|
||||||
|
: Navigator.pop(context),
|
||||||
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
style: IconButton.styleFrom(minimumSize: const Size(36, 36)),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||||
import 'package:gap/gap.dart';
|
import 'package:gap/gap.dart';
|
||||||
|
import 'package:go_router/go_router.dart';
|
||||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||||
import 'package:island/models/file_list_item.dart';
|
import 'package:island/models/file_list_item.dart';
|
||||||
import 'package:island/pods/file_list.dart';
|
import 'package:island/pods/file_list.dart';
|
||||||
@@ -9,7 +10,6 @@ import 'package:island/pods/network.dart';
|
|||||||
import 'package:island/utils/format.dart';
|
import 'package:island/utils/format.dart';
|
||||||
import 'package:island/widgets/alert.dart';
|
import 'package:island/widgets/alert.dart';
|
||||||
import 'package:island/widgets/content/cloud_files.dart';
|
import 'package:island/widgets/content/cloud_files.dart';
|
||||||
import 'package:island/widgets/content/file_info_sheet.dart';
|
|
||||||
import 'package:material_symbols_icons/symbols.dart';
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
import 'package:riverpod_paging_utils/riverpod_paging_utils.dart';
|
||||||
import 'package:styled_widget/styled_widget.dart';
|
import 'package:styled_widget/styled_widget.dart';
|
||||||
@@ -105,12 +105,9 @@ class FileListView extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
subtitle: Text(formatFileSize(file.size)),
|
subtitle: Text(formatFileSize(file.size)),
|
||||||
onTap: () {
|
onTap: () {
|
||||||
showModalBottomSheet(
|
context.push(
|
||||||
useRootNavigator: true,
|
'/files/${fileItem.fileIndex.id}',
|
||||||
context: context,
|
extra: file,
|
||||||
isScrollControlled: true,
|
|
||||||
builder:
|
|
||||||
(context) => FileInfoSheet(item: file),
|
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
trailing: IconButton(
|
trailing: IconButton(
|
||||||
@@ -163,7 +160,7 @@ class FileListView extends HookConsumerWidget {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(
|
title: Text(
|
||||||
folderItem.name,
|
folderItem.folder.name,
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
),
|
),
|
||||||
@@ -172,8 +169,8 @@ class FileListView extends HookConsumerWidget {
|
|||||||
// Navigate to folder
|
// Navigate to folder
|
||||||
final newPath =
|
final newPath =
|
||||||
currentPath.value == '/'
|
currentPath.value == '/'
|
||||||
? '/${folderItem.name}'
|
? '/${folderItem.folder.name}'
|
||||||
: '${currentPath.value}/${folderItem.name}';
|
: '${currentPath.value}/${folderItem.folder.name}';
|
||||||
currentPath.value = newPath;
|
currentPath.value = newPath;
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user