Skip to content

Commit

Permalink
Delete audio (#229)
Browse files Browse the repository at this point in the history
* Refactor timeline painter

* Remove id from sliver

* Selectable sound clip

* Sound sliver menu

* Prevent file rewrite on duplicate

* Delete sound clips

* Stop playing deleted clip

* Bump version

* Update release notes

* Extract makeFreeDuplicatePath

* Final

* Unused argument

* Missing return
  • Loading branch information
ruskakimov authored Aug 25, 2021
1 parent 947d23c commit 2863963
Show file tree
Hide file tree
Showing 17 changed files with 254 additions and 138 deletions.
2 changes: 1 addition & 1 deletion lib/common/data/io/disk_image.dart
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ class DiskImage with EquatableMixin {
}

Future<DiskImage> duplicate() async {
final duplicatePath = makeDuplicatePath(file.path);
final duplicatePath = makeFreeDuplicatePath(file.path);
final duplicateFile = await file.copy(duplicatePath);

return DiskImage(
Expand Down
15 changes: 15 additions & 0 deletions lib/common/data/io/make_duplicate_path.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:path/path.dart' as p;

/// Returns an unoccupied duplicate path.
String makeFreeDuplicatePath(String originalPath) {
var duplicatePath = makeDuplicatePath(originalPath);

while (File(duplicatePath).existsSync()) {
duplicatePath = makeDuplicatePath(duplicatePath);
}

return duplicatePath;
}

/// Returns a new path for a duplicate file.
/// `example/path/image.png` -> `example/path/image_1.png`
/// `example/path/image_1.png` -> `example/path/image_2.png`
@visibleForTesting
String makeDuplicatePath(String path) {
final dir = p.dirname(path);
final name = p.basenameWithoutExtension(path);
Expand Down
22 changes: 15 additions & 7 deletions lib/common/data/project/sound_clip.dart
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
import 'dart:io';

import 'package:mooltik/common/data/extensions/duration_methods.dart';
import 'package:mooltik/common/data/sequence/time_span.dart';
import 'package:path/path.dart' as p;

class SoundClip {
class SoundClip extends TimeSpan {
SoundClip({
required this.file,
required Duration startTime,
required Duration duration,
}) : _startTime = startTime,
_duration = duration;
super(duration);

String get path => file.path;
final File file;

Duration get startTime => _startTime;
Duration _startTime;

Duration get endTime => _startTime + _duration;

Duration get duration => _duration;
Duration _duration;
Duration get endTime => _startTime + duration;

factory SoundClip.fromJson(Map<String, dynamic> json, String soundDirPath) =>
SoundClip(
Expand All @@ -32,8 +30,18 @@ class SoundClip {
Map<String, dynamic> toJson() => {
_fileNameKey: p.basename(file.path),
_startTimeKey: _startTime.toString(),
_durationKey: _duration.toString(),
_durationKey: duration.toString(),
};

@override
SoundClip copyWith({Duration? duration}) => SoundClip(
file: file,
startTime: startTime,
duration: duration ?? this.duration,
);

@override
void dispose() {}
}

const String _fileNameKey = 'file_name';
Expand Down
39 changes: 28 additions & 11 deletions lib/common/ui/popup_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import 'package:flutter_portal/flutter_portal.dart';

const _popupAnimationDuration = Duration(milliseconds: 150);

class PopupEntry extends StatelessWidget {
class PopupEntry extends StatefulWidget {
const PopupEntry({
Key? key,
required this.visible,
Expand All @@ -23,37 +23,54 @@ class PopupEntry extends StatelessWidget {
final VoidCallback? onTapOutside;
final VoidCallback? onDragOutside;

@override
_PopupEntryState createState() => _PopupEntryState();
}

class _PopupEntryState extends State<PopupEntry> {
Widget? _popup;

@override
void didUpdateWidget(covariant PopupEntry oldWidget) {
super.didUpdateWidget(oldWidget);

// Don't update popup configuration when popup is fading out.
if (widget.visible) {
_popup = widget.popup;
}
}

@override
Widget build(BuildContext context) {
return PortalEntry(
visible: visible,
visible: widget.visible,
portal: Listener(
behavior: HitTestBehavior.translucent,
onPointerUp: (_) {
onTapOutside?.call();
widget.onTapOutside?.call();
},
onPointerMove: (_) {
onDragOutside?.call();
widget.onDragOutside?.call();
},
),
child: PortalEntry(
visible: visible,
visible: widget.visible,
closeDuration: _popupAnimationDuration,
portal: TweenAnimationBuilder<double>(
duration: _popupAnimationDuration,
tween: Tween(begin: 0, end: visible ? 1 : 0),
tween: Tween(begin: 0, end: widget.visible ? 1 : 0),
builder: (context, progress, child) => Opacity(
opacity: progress,
child: child,
),
child: popup,
child: _popup,
),
portalAnchor: popupAnchor,
portalAnchor: widget.popupAnchor,
child: IgnorePointer(
ignoring: visible,
child: child,
ignoring: widget.visible,
child: widget.child,
),
childAnchor: childAnchor,
childAnchor: widget.childAnchor,
),
);
}
Expand Down
8 changes: 6 additions & 2 deletions lib/editing/data/player_model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ class PlayerModel extends ChangeNotifier {
super.dispose();
}

final AudioPlayer _player;
AudioPlayer _player;

/// List of sound clips to play.
final List<SoundClip>? soundClips;
Expand All @@ -48,7 +48,11 @@ class PlayerModel extends ChangeNotifier {

/// Prepare player for playing from current playhead position.
Future<void> prepare() async {
if (soundClips!.isEmpty) return;
if (soundClips!.isEmpty) {
_player.dispose();
_player = AudioPlayer();
return;
}

await _player.setFilePath(
soundClips!.first.path,
Expand Down
Loading

0 comments on commit 2863963

Please sign in to comment.