memory leak tracking & fixes
This commit is contained in:
parent
4c07a9da43
commit
bca78a0669
9 changed files with 238 additions and 144 deletions
|
@ -6,21 +6,21 @@ import 'package:provider/provider.dart';
|
|||
class PopupMenuExpansionPanel<T> extends PopupMenuEntry<T> {
|
||||
final bool enabled;
|
||||
final String value;
|
||||
final ValueNotifier<String?> expandedNotifier;
|
||||
final ValueNotifier<String?>? expandedNotifier;
|
||||
final IconData icon;
|
||||
final String title;
|
||||
final List<PopupMenuEntry<T>> items;
|
||||
|
||||
PopupMenuExpansionPanel({
|
||||
const PopupMenuExpansionPanel({
|
||||
super.key,
|
||||
this.enabled = true,
|
||||
this.height = kMinInteractiveDimension,
|
||||
required this.value,
|
||||
ValueNotifier<String?>? expandedNotifier,
|
||||
this.expandedNotifier,
|
||||
required this.icon,
|
||||
required this.title,
|
||||
required this.items,
|
||||
}) : expandedNotifier = expandedNotifier ?? ValueNotifier(null);
|
||||
});
|
||||
|
||||
@override
|
||||
final double height;
|
||||
|
@ -36,6 +36,16 @@ class _PopupMenuExpansionPanelState<T> extends State<PopupMenuExpansionPanel<T>>
|
|||
// ref `_kMenuHorizontalPadding` used in `PopupMenuItem`
|
||||
static const double _horizontalPadding = 16;
|
||||
|
||||
final ValueNotifier<String?> _internalExpandedNotifier = ValueNotifier(null);
|
||||
|
||||
ValueNotifier<String?> get expandedNotifier => widget.expandedNotifier ?? _internalExpandedNotifier;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_internalExpandedNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
@ -46,11 +56,11 @@ class _PopupMenuExpansionPanelState<T> extends State<PopupMenuExpansionPanel<T>>
|
|||
final animationDuration = context.select<DurationsData, Duration>((v) => v.expansionTileAnimation);
|
||||
|
||||
Widget child = ValueListenableBuilder<String?>(
|
||||
valueListenable: widget.expandedNotifier,
|
||||
valueListenable: expandedNotifier,
|
||||
builder: (context, expandedValue, child) {
|
||||
return ExpansionPanelList(
|
||||
expansionCallback: (index, isExpanded) {
|
||||
widget.expandedNotifier.value = isExpanded ? widget.value : null;
|
||||
expandedNotifier.value = isExpanded ? widget.value : null;
|
||||
},
|
||||
animationDuration: animationDuration,
|
||||
expandedHeaderPadding: EdgeInsets.zero,
|
||||
|
|
|
@ -6,45 +6,32 @@ import 'package:aves/model/filters/path.dart';
|
|||
import 'package:aves/model/filters/tag.dart';
|
||||
import 'package:aves/model/settings/settings.dart';
|
||||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/analysis_service.dart';
|
||||
import 'package:aves/theme/durations.dart';
|
||||
import 'package:aves/utils/android_file_utils.dart';
|
||||
import 'package:aves/widgets/common/basic/font_size_icon_theme.dart';
|
||||
import 'package:aves/widgets/common/basic/popup/menu_row.dart';
|
||||
import 'package:aves/widgets/common/basic/scaffold.dart';
|
||||
import 'package:aves/widgets/common/behaviour/pop/scope.dart';
|
||||
import 'package:aves/widgets/common/behaviour/pop/tv_navigation.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/debug/android_apps.dart';
|
||||
import 'package:aves/widgets/debug/android_codecs.dart';
|
||||
import 'package:aves/widgets/debug/android_dirs.dart';
|
||||
import 'package:aves/widgets/debug/app_debug_action.dart';
|
||||
import 'package:aves/widgets/debug/cache.dart';
|
||||
import 'package:aves/widgets/debug/database.dart';
|
||||
import 'package:aves/widgets/debug/general.dart';
|
||||
import 'package:aves/widgets/debug/media_store_scan_dialog.dart';
|
||||
import 'package:aves/widgets/debug/overlay.dart';
|
||||
import 'package:aves/widgets/debug/report.dart';
|
||||
import 'package:aves/widgets/debug/settings.dart';
|
||||
import 'package:aves/widgets/debug/storage.dart';
|
||||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:leak_tracker/leak_tracker.dart';
|
||||
|
||||
class AppDebugPage extends StatefulWidget {
|
||||
class AppDebugPage extends StatelessWidget {
|
||||
static const routeName = '/debug';
|
||||
|
||||
const AppDebugPage({super.key});
|
||||
|
||||
@override
|
||||
State<StatefulWidget> createState() => _AppDebugPageState();
|
||||
}
|
||||
|
||||
class _AppDebugPageState extends State<AppDebugPage> {
|
||||
static OverlayEntry? _taskQueueOverlayEntry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Directionality(
|
||||
|
@ -68,7 +55,7 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
onSelected: (action) async {
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
await Future.delayed(ADurations.popupMenuAnimation * timeDilation);
|
||||
unawaited(_onActionSelected(action));
|
||||
unawaited(_onActionSelected(context, action));
|
||||
},
|
||||
),
|
||||
),
|
||||
|
@ -79,16 +66,16 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
child: SafeArea(
|
||||
child: ListView(
|
||||
padding: const EdgeInsets.all(8),
|
||||
children: [
|
||||
_buildGeneralTabView(),
|
||||
const DebugAndroidAppSection(),
|
||||
const DebugAndroidCodecSection(),
|
||||
const DebugAndroidDirSection(),
|
||||
const DebugCacheSection(),
|
||||
const DebugAppDatabaseSection(),
|
||||
const DebugErrorReportingSection(),
|
||||
const DebugSettingsSection(),
|
||||
const DebugStorageSection(),
|
||||
children: const [
|
||||
DebugGeneralSection(),
|
||||
DebugAndroidAppSection(),
|
||||
DebugAndroidCodecSection(),
|
||||
DebugAndroidDirSection(),
|
||||
DebugCacheSection(),
|
||||
DebugAppDatabaseSection(),
|
||||
DebugErrorReportingSection(),
|
||||
DebugSettingsSection(),
|
||||
DebugStorageSection(),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
@ -97,100 +84,7 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
|||
);
|
||||
}
|
||||
|
||||
Widget _buildGeneralTabView() {
|
||||
final source = context.read<CollectionSource>();
|
||||
final visibleEntries = source.visibleEntries;
|
||||
final catalogued = visibleEntries.where((entry) => entry.isCatalogued);
|
||||
final withGps = catalogued.where((entry) => entry.hasGps);
|
||||
final withAddress = withGps.where((entry) => entry.hasAddress);
|
||||
final withFineAddress = withGps.where((entry) => entry.hasFineAddress);
|
||||
return AvesExpansionTile(
|
||||
title: 'General',
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text('Time dilation'),
|
||||
),
|
||||
Slider(
|
||||
value: timeDilation,
|
||||
onChanged: (v) => setState(() => timeDilation = v),
|
||||
min: 1.0,
|
||||
max: 10.0,
|
||||
divisions: 9,
|
||||
label: '$timeDilation',
|
||||
),
|
||||
SwitchListTile(
|
||||
value: _taskQueueOverlayEntry != null,
|
||||
onChanged: (v) {
|
||||
_taskQueueOverlayEntry?.remove();
|
||||
if (v) {
|
||||
_taskQueueOverlayEntry = OverlayEntry(
|
||||
builder: (context) => const DebugTaskQueueOverlay(),
|
||||
);
|
||||
Overlay.of(context).insert(_taskQueueOverlayEntry!);
|
||||
} else {
|
||||
_taskQueueOverlayEntry = null;
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
title: const Text('Show tasks overlay'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => LeakTracking.collectLeaks().then((leaks) {
|
||||
leaks.byType.forEach((type, reports) {
|
||||
debugPrint('* leak type=$type');
|
||||
groupBy(reports, (report) => report.type).forEach((reportType, typedReports) {
|
||||
debugPrint(' * report type=$reportType');
|
||||
groupBy(typedReports, (report) => report.trackedClass).forEach((trackedClass, classedReports) {
|
||||
debugPrint(' trackedClass=$trackedClass reports=${classedReports.length}');
|
||||
// classedReports.forEach((report) {
|
||||
// debugPrint(' phase=${report.phase} retainingPath=${report.retainingPath} detailedPath=${report.detailedPath} context=${report.context}');
|
||||
// });
|
||||
});
|
||||
});
|
||||
});
|
||||
}),
|
||||
child: const Text('Collect leaks'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => source.init(loadTopEntriesFirst: false),
|
||||
child: const Text('Source refresh (top off)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => source.init(loadTopEntriesFirst: true),
|
||||
child: const Text('Source refresh (top on)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => source.init(directory: '${androidFileUtils.dcimPath}/Camera'),
|
||||
child: const Text('Source refresh (camera)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => source.init(directory: androidFileUtils.picturesPath),
|
||||
child: const Text('Source refresh (pictures)'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => AnalysisService.startService(force: false),
|
||||
child: const Text('Start analysis service'),
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||
child: InfoRowGroup(
|
||||
info: {
|
||||
'All entries': '${source.allEntries.length}',
|
||||
'Visible entries': '${visibleEntries.length}',
|
||||
'Catalogued': '${catalogued.length}',
|
||||
'With GPS': '${withGps.length}',
|
||||
'With address': '${withAddress.length}',
|
||||
'With fine address': '${withFineAddress.length}',
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _onActionSelected(AppDebugAction action) async {
|
||||
Future<void> _onActionSelected(BuildContext context, AppDebugAction action) async {
|
||||
switch (action) {
|
||||
case AppDebugAction.prepScreenshotThumbnails:
|
||||
// get source beforehand, as widget may be unmounted during action handling
|
||||
|
|
104
lib/widgets/debug/general.dart
Normal file
104
lib/widgets/debug/general.dart
Normal file
|
@ -0,0 +1,104 @@
|
|||
import 'package:aves/model/source/collection_source.dart';
|
||||
import 'package:aves/services/analysis_service.dart';
|
||||
import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
||||
import 'package:aves/widgets/debug/overlay.dart';
|
||||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:leak_tracker/leak_tracker.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DebugGeneralSection extends StatefulWidget {
|
||||
const DebugGeneralSection({super.key});
|
||||
|
||||
@override
|
||||
State<DebugGeneralSection> createState() => _DebugGeneralSectionState();
|
||||
}
|
||||
|
||||
class _DebugGeneralSectionState extends State<DebugGeneralSection> with AutomaticKeepAliveClientMixin {
|
||||
static OverlayEntry? _taskQueueOverlayEntry;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
final source = context.read<CollectionSource>();
|
||||
final visibleEntries = source.visibleEntries;
|
||||
final catalogued = visibleEntries.where((entry) => entry.isCatalogued);
|
||||
final withGps = catalogued.where((entry) => entry.hasGps);
|
||||
final withAddress = withGps.where((entry) => entry.hasAddress);
|
||||
final withFineAddress = withGps.where((entry) => entry.hasFineAddress);
|
||||
return AvesExpansionTile(
|
||||
title: 'General',
|
||||
children: [
|
||||
const Padding(
|
||||
padding: EdgeInsets.all(8),
|
||||
child: Text('Time dilation'),
|
||||
),
|
||||
Slider(
|
||||
value: timeDilation,
|
||||
onChanged: (v) => setState(() => timeDilation = v),
|
||||
min: 1.0,
|
||||
max: 10.0,
|
||||
divisions: 9,
|
||||
label: '$timeDilation',
|
||||
),
|
||||
SwitchListTile(
|
||||
value: _taskQueueOverlayEntry != null,
|
||||
onChanged: (v) {
|
||||
_taskQueueOverlayEntry?.remove();
|
||||
if (v) {
|
||||
_taskQueueOverlayEntry = OverlayEntry(
|
||||
builder: (context) => const DebugTaskQueueOverlay(),
|
||||
);
|
||||
Overlay.of(context).insert(_taskQueueOverlayEntry!);
|
||||
} else {
|
||||
_taskQueueOverlayEntry = null;
|
||||
}
|
||||
setState(() {});
|
||||
},
|
||||
title: const Text('Show tasks overlay'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => LeakTracking.collectLeaks().then((leaks) {
|
||||
leaks.byType.forEach((type, reports) {
|
||||
debugPrint('* leak type=$type');
|
||||
groupBy(reports, (report) => report.type).forEach((reportType, typedReports) {
|
||||
debugPrint(' * report type=$reportType');
|
||||
groupBy(typedReports, (report) => report.trackedClass).forEach((trackedClass, classedReports) {
|
||||
debugPrint(' trackedClass=$trackedClass reports=${classedReports.length}');
|
||||
// classedReports.forEach((report) {
|
||||
// debugPrint(' phase=${report.phase} retainingPath=${report.retainingPath} detailedPath=${report.detailedPath} context=${report.context}');
|
||||
// });
|
||||
});
|
||||
});
|
||||
});
|
||||
}),
|
||||
child: const Text('Collect leaks'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () => AnalysisService.startService(force: false),
|
||||
child: const Text('Start analysis service'),
|
||||
),
|
||||
const Divider(),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8),
|
||||
child: InfoRowGroup(
|
||||
info: {
|
||||
'All entries': '${source.allEntries.length}',
|
||||
'Visible entries': '${visibleEntries.length}',
|
||||
'Catalogued': '${catalogued.length}',
|
||||
'With GPS': '${withGps.length}',
|
||||
'With address': '${withAddress.length}',
|
||||
'With fine address': '${withFineAddress.length}',
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
|
@ -4,11 +4,18 @@ import 'package:aves/widgets/common/identity/aves_expansion_tile.dart';
|
|||
import 'package:aves/widgets/viewer/info/common.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DebugErrorReportingSection extends StatelessWidget {
|
||||
class DebugErrorReportingSection extends StatefulWidget {
|
||||
const DebugErrorReportingSection({super.key});
|
||||
|
||||
@override
|
||||
State<DebugErrorReportingSection> createState() => _DebugErrorReportingSectionState();
|
||||
}
|
||||
|
||||
class _DebugErrorReportingSectionState extends State<DebugErrorReportingSection> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
return AvesExpansionTile(
|
||||
title: 'Reporting',
|
||||
children: [
|
||||
|
@ -56,4 +63,7 @@ class DebugErrorReportingSection extends StatelessWidget {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
|
|
@ -9,11 +9,18 @@ import 'package:aves/widgets/viewer/info/common.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
class DebugSettingsSection extends StatelessWidget {
|
||||
class DebugSettingsSection extends StatefulWidget {
|
||||
const DebugSettingsSection({super.key});
|
||||
|
||||
@override
|
||||
State<DebugSettingsSection> createState() => _DebugSettingsSectionState();
|
||||
}
|
||||
|
||||
class _DebugSettingsSectionState extends State<DebugSettingsSection> with AutomaticKeepAliveClientMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
|
||||
return Consumer<Settings>(
|
||||
builder: (context, settings, child) {
|
||||
String toMultiline(Iterable? l) => l != null && l.isNotEmpty ? '\n${l.join('\n')}' : '$l';
|
||||
|
@ -76,4 +83,7 @@ class DebugSettingsSection extends StatelessWidget {
|
|||
},
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
bool get wantKeepAlive => true;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,30 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:aves/model/entry/entry.dart';
|
||||
import 'package:aves/widgets/viewer/multipage/controller.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class MultiPageConductor {
|
||||
final List<MultiPageController> _controllers = [];
|
||||
|
||||
static const maxControllerCount = 3;
|
||||
|
||||
MultiPageConductor() {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'aves',
|
||||
className: '$MultiPageConductor',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await Future.forEach<MultiPageController>(_controllers, (controller) => controller.dispose());
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
await _disposeAll();
|
||||
_controllers.clear();
|
||||
}
|
||||
|
||||
|
@ -29,4 +45,8 @@ class MultiPageConductor {
|
|||
MultiPageController? getController(AvesEntry entry) {
|
||||
return _controllers.firstWhereOrNull((c) => c.entry.uri == entry.uri && c.entry.pageId == entry.pageId);
|
||||
}
|
||||
|
||||
Future<void> _applyToAll(FutureOr Function(MultiPageController controller) action) => Future.forEach<MultiPageController>(_controllers, action);
|
||||
|
||||
Future<void> _disposeAll() => _applyToAll((controller) => controller.dispose());
|
||||
}
|
||||
|
|
|
@ -143,7 +143,7 @@ class _TvButtonRowContent extends StatelessWidget {
|
|||
final enabled = actionDelegate.canApply(action);
|
||||
return CaptionedButton(
|
||||
scale: scale,
|
||||
iconButtonBuilder: (context, focusNode) => ViewerButtonRowContent._buildButtonIcon(
|
||||
iconButtonBuilder: (context, focusNode) => _ViewerButtonRowContentState._buildButtonIcon(
|
||||
context: context,
|
||||
action: action,
|
||||
mainEntry: mainEntry,
|
||||
|
@ -202,18 +202,15 @@ class _TvButtonRowContent extends StatelessWidget {
|
|||
}
|
||||
}
|
||||
|
||||
class ViewerButtonRowContent extends StatelessWidget {
|
||||
class ViewerButtonRowContent extends StatefulWidget {
|
||||
final EntryActionDelegate actionDelegate;
|
||||
final List<EntryAction> quickActions, topLevelActions, exportActions, videoActions;
|
||||
final Animation<double> scale;
|
||||
final AvesEntry mainEntry, pageEntry;
|
||||
final ValueNotifier<String?> _popupExpandedNotifier = ValueNotifier(null);
|
||||
|
||||
AvesEntry get favouriteTargetEntry => mainEntry.isBurst ? pageEntry : mainEntry;
|
||||
|
||||
static const double padding = 8;
|
||||
|
||||
ViewerButtonRowContent({
|
||||
const ViewerButtonRowContent({
|
||||
super.key,
|
||||
required this.actionDelegate,
|
||||
required this.quickActions,
|
||||
|
@ -225,8 +222,32 @@ class ViewerButtonRowContent extends StatelessWidget {
|
|||
required this.pageEntry,
|
||||
});
|
||||
|
||||
@override
|
||||
State<ViewerButtonRowContent> createState() => _ViewerButtonRowContentState();
|
||||
}
|
||||
|
||||
class _ViewerButtonRowContentState extends State<ViewerButtonRowContent> {
|
||||
final ValueNotifier<String?> _popupExpandedNotifier = ValueNotifier(null);
|
||||
|
||||
AvesEntry get mainEntry => widget.mainEntry;
|
||||
|
||||
AvesEntry get pageEntry => widget.pageEntry;
|
||||
|
||||
AvesEntry get favouriteTargetEntry => mainEntry.isBurst ? pageEntry : mainEntry;
|
||||
|
||||
static const double padding = ViewerButtonRowContent.padding;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_popupExpandedNotifier.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final topLevelActions = widget.topLevelActions;
|
||||
final exportActions = widget.exportActions;
|
||||
final videoActions = widget.videoActions;
|
||||
final hasOverflowMenu = pageEntry.canRotate || pageEntry.canFlip || topLevelActions.isNotEmpty || exportActions.isNotEmpty || videoActions.isNotEmpty;
|
||||
return Selector<VideoConductor, AvesVideoController?>(
|
||||
selector: (context, vc) => vc.getController(pageEntry),
|
||||
|
@ -236,12 +257,12 @@ class ViewerButtonRowContent extends StatelessWidget {
|
|||
child: Row(
|
||||
children: [
|
||||
const Spacer(),
|
||||
...quickActions.map((action) => _buildOverlayButton(context, action, videoController)),
|
||||
...widget.quickActions.map((action) => _buildOverlayButton(context, action, videoController)),
|
||||
if (hasOverflowMenu)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: padding / 2),
|
||||
child: OverlayButton(
|
||||
scale: scale,
|
||||
scale: widget.scale,
|
||||
child: FontSizeIconTheme(
|
||||
child: AvesPopupMenuButton<EntryAction>(
|
||||
key: const Key('entry-menu-button'),
|
||||
|
@ -282,7 +303,7 @@ class ViewerButtonRowContent extends StatelessWidget {
|
|||
onSelected: (action) {
|
||||
_popupExpandedNotifier.value = null;
|
||||
// wait for the popup menu to hide before proceeding with the action
|
||||
Future.delayed(ADurations.popupMenuAnimation * timeDilation, () => actionDelegate.onActionSelected(context, action));
|
||||
Future.delayed(ADurations.popupMenuAnimation * timeDilation, () => widget.actionDelegate.onActionSelected(context, action));
|
||||
},
|
||||
onCanceled: () {
|
||||
_popupExpandedNotifier.value = null;
|
||||
|
@ -309,14 +330,14 @@ class ViewerButtonRowContent extends StatelessWidget {
|
|||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: padding / 2),
|
||||
child: OverlayButton(
|
||||
scale: scale,
|
||||
scale: widget.scale,
|
||||
child: _buildButtonIcon(
|
||||
context: context,
|
||||
action: action,
|
||||
mainEntry: mainEntry,
|
||||
pageEntry: pageEntry,
|
||||
videoController: videoController,
|
||||
actionDelegate: actionDelegate,
|
||||
actionDelegate: widget.actionDelegate,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
@ -381,7 +402,7 @@ class ViewerButtonRowContent extends StatelessWidget {
|
|||
clipBehavior: Clip.antiAlias,
|
||||
child: PopupMenuItem(
|
||||
value: action,
|
||||
enabled: actionDelegate.canApply(action),
|
||||
enabled: widget.actionDelegate.canApply(action),
|
||||
child: Tooltip(
|
||||
message: action.getText(context),
|
||||
child: Center(child: action.getIcon()),
|
||||
|
|
|
@ -9,6 +9,7 @@ import 'package:aves/widgets/viewer/video/db_playback_state_handler.dart';
|
|||
import 'package:aves_model/aves_model.dart';
|
||||
import 'package:aves_video/aves_video.dart';
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
class VideoConductor {
|
||||
final CollectionLens? _collection;
|
||||
|
@ -18,10 +19,21 @@ class VideoConductor {
|
|||
|
||||
static const _defaultMaxControllerCount = 3;
|
||||
|
||||
VideoConductor({CollectionLens? collection}) : _collection = collection;
|
||||
VideoConductor({CollectionLens? collection}) : _collection = collection {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'aves',
|
||||
className: '$VideoConductor',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await disposeAll();
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
await _disposeAll();
|
||||
_subscriptions
|
||||
..forEach((sub) => sub.cancel())
|
||||
..clear();
|
||||
|
@ -82,7 +94,7 @@ class VideoConductor {
|
|||
|
||||
Future<void> _applyToAll(FutureOr Function(AvesVideoController controller) action) => Future.forEach<AvesVideoController>(_controllers, action);
|
||||
|
||||
Future<void> disposeAll() => _applyToAll((controller) => controller.dispose());
|
||||
Future<void> _disposeAll() => _applyToAll((controller) => controller.dispose());
|
||||
|
||||
Future<void> pauseAll() => _applyToAll((controller) => controller.pause());
|
||||
|
||||
|
|
|
@ -12,7 +12,20 @@ class ViewStateConductor {
|
|||
|
||||
static const maxControllerCount = 3;
|
||||
|
||||
ViewStateConductor() {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectCreated(
|
||||
library: 'aves',
|
||||
className: '$ViewStateConductor',
|
||||
object: this,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
if (kFlutterMemoryAllocationsEnabled) {
|
||||
MemoryAllocations.instance.dispatchObjectDisposed(object: this);
|
||||
}
|
||||
_controllers.forEach((v) => v.dispose());
|
||||
_controllers.clear();
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue