implemented native share intent support for android and ios with chat and folder pickers
This commit is contained in:
@@ -0,0 +1,187 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:persistent_bottom_nav_bar_v2/persistent_bottom_nav_bar_v2.dart';
|
||||
|
||||
import '../../../routing/app_routes.dart';
|
||||
import '../../../share_intent/pending_share.dart';
|
||||
import '../../../share_intent/share_intent_listener.dart';
|
||||
import '../../../state/app/infrastructure/loadable_state/loadable_state.dart';
|
||||
import '../../../state/app/infrastructure/loadable_state/view/loadable_state_consumer.dart';
|
||||
import '../../../state/app/infrastructure/utility_widgets/bloc_module.dart';
|
||||
import '../../../state/app/modules/files/bloc/files_bloc.dart';
|
||||
import '../../../state/app/modules/files/bloc/files_state.dart';
|
||||
import '../../../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||
import '../../../widget/placeholder_view.dart';
|
||||
import '../files/data/sort_options.dart';
|
||||
import '../files/files_upload_dialog.dart';
|
||||
import '../files/widgets/add_file_menu.dart';
|
||||
import '../files/widgets/files_sort_actions.dart';
|
||||
|
||||
class ShareFolderPicker extends StatelessWidget {
|
||||
final PendingShare share;
|
||||
|
||||
const ShareFolderPicker({super.key, required this.share});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) =>
|
||||
BlocModule<FilesBloc, LoadableState<FilesState>>(
|
||||
create: (_) => FilesBloc(),
|
||||
child: (context, _, _) => _ShareFolderPickerView(share: share),
|
||||
);
|
||||
}
|
||||
|
||||
class _ShareFolderPickerView extends StatefulWidget {
|
||||
final PendingShare share;
|
||||
const _ShareFolderPickerView({required this.share});
|
||||
|
||||
@override
|
||||
State<_ShareFolderPickerView> createState() => _ShareFolderPickerViewState();
|
||||
}
|
||||
|
||||
class _ShareFolderPickerViewState extends State<_ShareFolderPickerView> {
|
||||
late final SettingsCubit _settings;
|
||||
late SortOption _currentSort;
|
||||
late bool _ascending;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_settings = context.read<SettingsCubit>();
|
||||
_currentSort = _settings.val().fileSettings.sortBy;
|
||||
_ascending = _settings.val().fileSettings.ascending;
|
||||
}
|
||||
|
||||
void _enter(FilesBloc bloc, List<String> currentPath, String folderName) {
|
||||
bloc.setPath([...currentPath, folderName]);
|
||||
}
|
||||
|
||||
void _goUp(FilesBloc bloc, List<String> currentPath) {
|
||||
if (currentPath.isEmpty) return;
|
||||
bloc.setPath(currentPath.sublist(0, currentPath.length - 1));
|
||||
}
|
||||
|
||||
Future<void> _uploadHere(List<String> currentPath) async {
|
||||
await pushScreen(
|
||||
context,
|
||||
withNavBar: false,
|
||||
screen: FilesUploadDialog(
|
||||
filePaths: widget.share.filePaths,
|
||||
remotePath: currentPath.join('/'),
|
||||
onUploadFinished: (_) => _afterUploaded(currentPath),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void _afterUploaded(List<String> targetPath) {
|
||||
ShareIntentListener.instance.clear();
|
||||
if (!mounted) return;
|
||||
Navigator.of(context).popUntil((route) => route.isFirst);
|
||||
AppRoutes.openFolder(context, targetPath);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final bloc = context.read<FilesBloc>();
|
||||
return BlocBuilder<FilesBloc, LoadableState<FilesState>>(
|
||||
buildWhen: (a, b) => a.data?.currentPath != b.data?.currentPath,
|
||||
builder: (_, outerState) {
|
||||
final currentPath = outerState.data?.currentPath ?? const [];
|
||||
return PopScope(
|
||||
// Back navigates one level up while inside a sub-folder; only the
|
||||
// root level actually closes the picker. Matches the standard
|
||||
// files-app pattern and keeps the AppBar back-arrow consistent
|
||||
// with the chat picker.
|
||||
canPop: currentPath.isEmpty,
|
||||
onPopInvokedWithResult: (didPop, _) {
|
||||
if (didPop) return;
|
||||
if (currentPath.isNotEmpty) _goUp(bloc, currentPath);
|
||||
},
|
||||
child: _buildScaffold(context, bloc, currentPath),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScaffold(
|
||||
BuildContext context,
|
||||
FilesBloc bloc,
|
||||
List<String> currentPath,
|
||||
) => Scaffold(
|
||||
appBar: AppBar(
|
||||
title: Text(
|
||||
currentPath.isEmpty ? 'Ordner wählen' : '/${currentPath.join('/')}',
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.create_new_folder_outlined),
|
||||
tooltip: 'Ordner erstellen',
|
||||
onPressed: () => showCreateFolderDialog(context, bloc),
|
||||
),
|
||||
FilesSortActions(
|
||||
currentSort: _currentSort,
|
||||
ascending: _ascending,
|
||||
onDirectionChanged: (e) {
|
||||
setState(() {
|
||||
_ascending = e;
|
||||
_settings.val(write: true).fileSettings.ascending = e;
|
||||
});
|
||||
},
|
||||
onSortChanged: (e) {
|
||||
setState(() {
|
||||
_currentSort = e;
|
||||
_settings.val(write: true).fileSettings.sortBy = e;
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
floatingActionButton: FloatingActionButton.extended(
|
||||
heroTag: 'shareUploadHere',
|
||||
onPressed: () => _uploadHere(currentPath),
|
||||
icon: const Icon(Icons.upload),
|
||||
label: const Text('Hier hochladen'),
|
||||
),
|
||||
body: LoadableStateConsumer<FilesBloc, FilesState>(
|
||||
isReady: (state) => state.listing != null,
|
||||
child: (state, _) {
|
||||
final listing = state.listing!;
|
||||
final entries = listing.sortBy(
|
||||
sortOption: _currentSort,
|
||||
foldersToTop: _settings.val().fileSettings.sortFoldersToTop,
|
||||
reversed: _ascending,
|
||||
);
|
||||
|
||||
if (entries.isEmpty) {
|
||||
return PlaceholderView(
|
||||
icon: Icons.folder_off_rounded,
|
||||
text: state.currentPath.isEmpty
|
||||
? 'Leer. Du kannst hier direkt hochladen.'
|
||||
: 'Ordner ist leer. Du kannst hier hochladen.',
|
||||
);
|
||||
}
|
||||
|
||||
return ListView.builder(
|
||||
padding: EdgeInsets.zero,
|
||||
itemCount: entries.length,
|
||||
itemBuilder: (context, i) {
|
||||
final entry = entries[i];
|
||||
if (entry.isDirectory) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.folder_outlined),
|
||||
title: Text(entry.name),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () => _enter(bloc, state.currentPath, entry.name),
|
||||
);
|
||||
}
|
||||
return ListTile(
|
||||
enabled: false,
|
||||
leading: const Icon(Icons.description_outlined),
|
||||
title: Text(entry.name),
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user