claude refactorings, flutter best practices, platform dependent changes, general cleanup
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:animated_digit/animated_digit.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AnimatedTime extends StatefulWidget {
|
||||
@@ -42,14 +41,18 @@ class _AnimatedTimeState extends State<AnimatedTime> {
|
||||
],
|
||||
);
|
||||
|
||||
AnimatedDigitWidget buildWidget(int value) => AnimatedDigitWidget(
|
||||
value: value,
|
||||
duration: const Duration(milliseconds: 100),
|
||||
textStyle: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
);
|
||||
Widget buildWidget(int value) => AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 100),
|
||||
transitionBuilder: (child, animation) => FadeTransition(opacity: animation, child: child),
|
||||
child: Text(
|
||||
'$value',
|
||||
key: ValueKey<int>(value),
|
||||
style: TextStyle(
|
||||
fontSize: 15,
|
||||
color: Theme.of(context).colorScheme.onSurface,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
|
||||
import '../../api/mhsl/breaker/getBreakers/getBreakersResponse.dart';
|
||||
import '../../api/mhsl/breaker/get_breakers/get_breakers_response.dart';
|
||||
import '../../state/app/modules/breaker/bloc/breaker_bloc.dart';
|
||||
import '../../widget/placeholder_view.dart';
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import 'package:jiffy/jiffy.dart';
|
||||
import 'package:localstore/localstore.dart';
|
||||
|
||||
import '../../../widget/placeholder_view.dart';
|
||||
import '../../api/requestCache.dart';
|
||||
import '../../api/request_cache.dart';
|
||||
import 'json_viewer.dart';
|
||||
|
||||
class CacheView extends StatefulWidget {
|
||||
@@ -21,9 +21,9 @@ class CacheView extends StatefulWidget {
|
||||
}
|
||||
|
||||
Future<int> totalSize() async {
|
||||
var data = await Localstore.instance.collection(RequestCache.collection).get();
|
||||
if(data!.length <= 1) return jsonEncode(data.values.first).length * 8;
|
||||
return data.values.reduce((a, b) => jsonEncode(a).length + jsonEncode(b).length) * 8;
|
||||
final data = await Localstore.instance.collection(RequestCache.collection).get();
|
||||
if (data == null || data.isEmpty) return 0;
|
||||
return data.values.fold<int>(0, (sum, value) => sum + jsonEncode(value).length) * 8;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,15 +49,16 @@ class _CacheViewState extends State<CacheView> {
|
||||
return ListView.builder(
|
||||
itemCount: snapshot.data!.length,
|
||||
itemBuilder: (context, index) {
|
||||
Map<String, dynamic> element = snapshot.data![snapshot.data!.keys.elementAt(index)];
|
||||
var filename = snapshot.data!.keys.elementAt(index).split('/').last;
|
||||
final key = snapshot.data!.keys.elementAt(index);
|
||||
final element = snapshot.data![key] as Map<String, dynamic>;
|
||||
final filename = key.split('/').last;
|
||||
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.text_snippet_outlined),
|
||||
title: Text(filename),
|
||||
subtitle: Text("${filesize(jsonEncode(element).length * 8)}, ${Jiffy.parseFromMillisecondsSinceEpoch(element['lastupdate']).fromNow()}"),
|
||||
subtitle: Text('${filesize(jsonEncode(element).length * 8)}, ${Jiffy.parseFromMillisecondsSinceEpoch(element['lastupdate'] as int).fromNow()}'),
|
||||
trailing: const Icon(Icons.arrow_right),
|
||||
onTap: () => JsonViewer.asDialog(context, jsonDecode(element['json'])),
|
||||
onTap: () => JsonViewer.asDialog(context, jsonDecode(element['json'] as String) as Map<String, dynamic>),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:pretty_json/pretty_json.dart';
|
||||
|
||||
class JsonViewer extends StatelessWidget {
|
||||
final String title;
|
||||
@@ -19,7 +20,9 @@ class JsonViewer extends StatelessWidget {
|
||||
),
|
||||
);
|
||||
|
||||
static String format(Map<String, dynamic> jsonInput) => prettyJson(jsonInput, indent: 2);
|
||||
static final _encoder = const JsonEncoder.withIndent(' ');
|
||||
|
||||
static String format(Map<String, dynamic> jsonInput) => _encoder.convert(jsonInput);
|
||||
|
||||
static void asDialog(BuildContext context, Map<String, dynamic> dataMap) {
|
||||
showDialog(context: context, builder: (context) => AlertDialog(
|
||||
|
||||
@@ -1,29 +1,19 @@
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:image_picker/image_picker.dart';
|
||||
|
||||
class FilePick {
|
||||
static final _picker = ImagePicker();
|
||||
|
||||
static Future<XFile?> galleryPick() async {
|
||||
final pickedImage = await _picker.pickImage(source: ImageSource.gallery);
|
||||
if (pickedImage != null) {
|
||||
return pickedImage;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static Future<List<XFile>?> multipleGalleryPick() async {
|
||||
final pickedImages = await _picker.pickMultiImage();
|
||||
if(pickedImages.isNotEmpty) {
|
||||
return pickedImages;
|
||||
}
|
||||
return null;
|
||||
return pickedImages.isNotEmpty ? pickedImages : null;
|
||||
}
|
||||
|
||||
static Future<XFile?> cameraPick() => _picker.pickImage(source: ImageSource.camera);
|
||||
|
||||
static Future<List<String>?> documentPick() async {
|
||||
var result = await FilePicker.platform.pickFiles(allowMultiple: true);
|
||||
var paths = result?.files.nonNulls.map((e) => e.path).toList();
|
||||
final result = await FilePicker.pickFiles(allowMultiple: true);
|
||||
final paths = result?.files.nonNulls.map((e) => e.path).toList();
|
||||
return paths?.nonNulls.toList();
|
||||
}
|
||||
}
|
||||
|
||||
+73
-16
@@ -1,16 +1,17 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:photo_view/photo_view.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart';
|
||||
|
||||
import '../routing/app_routes.dart';
|
||||
import '../state/app/modules/settings/bloc/settings_cubit.dart';
|
||||
import '../utils/file_saver.dart';
|
||||
import 'info_dialog.dart';
|
||||
import 'placeholder_view.dart';
|
||||
import 'share_position_origin.dart';
|
||||
@@ -30,6 +31,55 @@ enum FileViewingActions {
|
||||
save
|
||||
}
|
||||
|
||||
/// Workaround for a Syncfusion PDF viewer race: SfPdfViewer's internal
|
||||
/// LayoutBuilder calls `localToGlobal` during build, which asserts when an
|
||||
/// ancestor RenderTransform (from the page-push animation) is still mid-layout.
|
||||
/// We wait for the route's enter animation to complete before mounting it.
|
||||
class _DeferredPdfViewer extends StatefulWidget {
|
||||
const _DeferredPdfViewer({required this.path});
|
||||
final String path;
|
||||
|
||||
@override
|
||||
State<_DeferredPdfViewer> createState() => _DeferredPdfViewerState();
|
||||
}
|
||||
|
||||
class _DeferredPdfViewerState extends State<_DeferredPdfViewer> {
|
||||
bool _ready = false;
|
||||
Animation<double>? _routeAnimation;
|
||||
|
||||
@override
|
||||
void didChangeDependencies() {
|
||||
super.didChangeDependencies();
|
||||
if (_ready || _routeAnimation != null) return;
|
||||
final animation = ModalRoute.of(context)?.animation;
|
||||
if (animation == null || animation.isCompleted) {
|
||||
_ready = true;
|
||||
return;
|
||||
}
|
||||
_routeAnimation = animation..addStatusListener(_onAnimationStatus);
|
||||
}
|
||||
|
||||
void _onAnimationStatus(AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed && mounted) {
|
||||
setState(() => _ready = true);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_routeAnimation?.removeStatusListener(_onAnimationStatus);
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!_ready) {
|
||||
return const Center(child: CircularProgressIndicator());
|
||||
}
|
||||
return SfPdfViewer.file(File(widget.path));
|
||||
}
|
||||
}
|
||||
|
||||
class _FileViewerState extends State<FileViewer> {
|
||||
PhotoViewController photoViewController = PhotoViewController();
|
||||
|
||||
@@ -44,7 +94,7 @@ class _FileViewerState extends State<FileViewer> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
AppBar appbar({List actions = const []}) => AppBar(
|
||||
AppBar appbar({List<Widget> actions = const []}) => AppBar(
|
||||
title: Text(widget.path.split('/').last),
|
||||
actions: [
|
||||
...actions,
|
||||
@@ -55,17 +105,26 @@ class _FileViewerState extends State<FileViewer> {
|
||||
AppRoutes.openFileViewer(context, widget.path, openExternal: true);
|
||||
break;
|
||||
case FileViewingActions.share:
|
||||
SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile(widget.path)],
|
||||
sharePositionOrigin: SharePositionOrigin.get(context)
|
||||
)
|
||||
);
|
||||
unawaited(SharePlus.instance.share(
|
||||
ShareParams(
|
||||
files: [XFile(widget.path)],
|
||||
sharePositionOrigin: SharePositionOrigin.get(context),
|
||||
),
|
||||
));
|
||||
break;
|
||||
case FileViewingActions.save:
|
||||
await FileSaver.writeBytes(await File(widget.path).readAsBytes(), widget.path.split('/').last);
|
||||
if(!context.mounted) return;
|
||||
InfoDialog.show(context, 'Die Datei wurde im Downloads Ordner gespeichert.');
|
||||
try {
|
||||
final bytes = await File(widget.path).readAsBytes();
|
||||
final saved = await FilePicker.saveFile(
|
||||
fileName: widget.path.split('/').last,
|
||||
bytes: bytes,
|
||||
);
|
||||
if (!context.mounted) return;
|
||||
if (saved != null) InfoDialog.show(context, 'Datei gespeichert.');
|
||||
} on Object catch (e) {
|
||||
if (!context.mounted) return;
|
||||
InfoDialog.show(context, 'Speichern fehlgeschlagen: $e');
|
||||
}
|
||||
break;
|
||||
}
|
||||
},
|
||||
@@ -86,7 +145,7 @@ class _FileViewerState extends State<FileViewer> {
|
||||
dense: true,
|
||||
),
|
||||
),
|
||||
if(Platform.isAndroid) const PopupMenuItem(
|
||||
const PopupMenuItem(
|
||||
value: FileViewingActions.save,
|
||||
child: ListTile(
|
||||
leading: Icon(Icons.save_alt_outlined),
|
||||
@@ -129,9 +188,7 @@ class _FileViewerState extends State<FileViewer> {
|
||||
case 'pdf':
|
||||
return Scaffold(
|
||||
appBar: appbar(),
|
||||
body: SfPdfViewer.file(
|
||||
File(widget.path),
|
||||
),
|
||||
body: _DeferredPdfViewer(path: widget.path),
|
||||
);
|
||||
|
||||
default:
|
||||
|
||||
Reference in New Issue
Block a user