mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-06-29 17:50:00 +02:00
chore: trim redundant comments
This commit is contained in:
@@ -119,11 +119,9 @@ func (ext *loadedExtension) lockReadyVM() (*goja.Runtime, error) {
|
||||
|
||||
type extensionManager struct {
|
||||
mu sync.RWMutex
|
||||
// mutationMu serializes heavy mutating operations (install / upgrade /
|
||||
// remove). These tear down and re-extract files and rebuild goja VMs;
|
||||
// running two at once (e.g. updating two extensions simultaneously) races
|
||||
// the non-thread-safe goja runtime and can hard-crash the process. Always
|
||||
// acquired before m.mu; internal "*Locked" helpers assume it is held.
|
||||
// mutationMu serializes install/upgrade/remove (heavy FS + goja VM
|
||||
// teardown/reload), which are not safe to run concurrently. Acquired before
|
||||
// m.mu; "*Locked" helpers assume it is held.
|
||||
mutationMu sync.Mutex
|
||||
extensions map[string]*loadedExtension
|
||||
extensionsDir string
|
||||
|
||||
@@ -21,11 +21,8 @@ import Gobackend // Import Go framework
|
||||
/// Currently accessed security-scoped URL for library folder
|
||||
private var activeSecurityScopedURL: URL?
|
||||
|
||||
/// Whether a download queue is currently active. When true, the app begins a
|
||||
/// fresh background task each time it enters the background so an in-flight
|
||||
/// download keeps running for the limited window iOS allows. iOS has no
|
||||
/// foreground-service equivalent, so this is best-effort. Only touched on the
|
||||
/// main thread.
|
||||
/// Whether a download queue is active; while true a background task is
|
||||
/// started on each background entry to extend execution time. Main-thread only.
|
||||
private var downloadsActive = false
|
||||
private var downloadBackgroundTask: UIBackgroundTaskIdentifier = .invalid
|
||||
|
||||
@@ -241,17 +238,12 @@ import Gobackend // Import Go framework
|
||||
}
|
||||
|
||||
private func handleMethodCall(call: FlutterMethodCall, result: @escaping FlutterResult) {
|
||||
// Background-task management must run on the main thread and does not
|
||||
// touch the Go backend, so handle it directly without dispatching.
|
||||
switch call.method {
|
||||
case "beginBackgroundDownloadTask":
|
||||
// Queue started: remember it so we extend background time whenever
|
||||
// the app is backgrounded while downloads run.
|
||||
downloadsActive = true
|
||||
result(nil)
|
||||
return
|
||||
case "endBackgroundDownloadTask":
|
||||
// Queue finished/paused: stop extending background time.
|
||||
downloadsActive = false
|
||||
endBackgroundDownloadTask()
|
||||
result(nil)
|
||||
@@ -283,14 +275,9 @@ import Gobackend // Import Go framework
|
||||
|
||||
override func applicationWillEnterForeground(_ application: UIApplication) {
|
||||
super.applicationWillEnterForeground(application)
|
||||
// No background-time countdown while in the foreground.
|
||||
endBackgroundDownloadTask()
|
||||
}
|
||||
|
||||
/// Begins a background task (if one is not already active) so an in-flight
|
||||
/// download keeps running for the limited window iOS allows after the app is
|
||||
/// backgrounded. The expiration handler ends the task to avoid the app being
|
||||
/// force-terminated by the watchdog. Must run on the main thread.
|
||||
private func beginBackgroundDownloadTask() {
|
||||
if downloadBackgroundTask != .invalid { return }
|
||||
downloadBackgroundTask = UIApplication.shared.beginBackgroundTask(
|
||||
|
||||
+2
-6
@@ -114,12 +114,8 @@ class SpotiFLACApp extends ConsumerWidget {
|
||||
scrollBehavior: scrollBehavior,
|
||||
themeAnimationDuration: const Duration(milliseconds: 300),
|
||||
themeAnimationCurve: Curves.easeInOut,
|
||||
// Treat the display as one continuous surface. Some large/foldable
|
||||
// devices report a full-height display feature (hinge/cutout) which
|
||||
// makes Flutter split modal routes into a sub-screen, leaving bottom
|
||||
// sheets and dialogs visibly off-center instead of centered on the
|
||||
// full screen. Clearing displayFeatures keeps them centered for every
|
||||
// modal/dialog generically, without per-sheet workarounds.
|
||||
// Treat the display as one continuous surface so bottom sheets and
|
||||
// dialogs stay centered on large/foldable devices.
|
||||
builder: (context, child) {
|
||||
final mediaQuery = MediaQuery.of(context);
|
||||
return MediaQuery(
|
||||
|
||||
+2
-4
@@ -20,10 +20,8 @@ import 'package:spotiflac_android/utils/logger.dart';
|
||||
final _log = AppLogger('Main');
|
||||
|
||||
void main() {
|
||||
// Catch uncaught errors so a failure in a provider/async path (e.g. a
|
||||
// misbehaving extension request) is logged instead of taking the whole app
|
||||
// down. Native (Go) fatal crashes can't be caught here, but Dart-side ones
|
||||
// can.
|
||||
// Catch uncaught Dart errors so a failing async path is logged, not fatal.
|
||||
// Native (Go) crashes still can't be caught here.
|
||||
runZonedGuarded(
|
||||
() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
@@ -6676,9 +6676,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
}
|
||||
}
|
||||
|
||||
// iOS has no foreground service; request a background execution window so
|
||||
// an in-flight download can keep running for a short time after the app is
|
||||
// backgrounded instead of being suspended immediately.
|
||||
// iOS: request a background execution window (no foreground service).
|
||||
if (Platform.isIOS && _totalQueuedAtStart > 0) {
|
||||
await PlatformBridge.beginBackgroundDownloadTask();
|
||||
}
|
||||
|
||||
@@ -206,10 +206,7 @@ class StoreState {
|
||||
}
|
||||
|
||||
class StoreNotifier extends Notifier<StoreState> {
|
||||
/// Serializes extension install/upgrade operations. Running two of these at
|
||||
/// once (e.g. updating two extensions simultaneously) races the native
|
||||
/// extension manager's goja VM teardown/reload and can hard-crash the app, so
|
||||
/// they must run strictly one at a time.
|
||||
/// Serializes install/upgrade so two never race the native VM teardown/reload.
|
||||
Future<void> _mutationChain = Future<void>.value();
|
||||
|
||||
Future<T> _runSerialized<T>(Future<T> Function() action) {
|
||||
|
||||
@@ -123,10 +123,8 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
List<UnifiedLibraryItem> _selectionOverlayItems = const [];
|
||||
double _selectionOverlayBottomPadding = 0;
|
||||
|
||||
/// When true, the floating selection overlays are kept hidden even though
|
||||
/// selection mode is still active. Used while a modal sheet/dialog launched
|
||||
/// from the selection toolbar is open, so the overlay does not reappear on
|
||||
/// top of (or behind) the modal's open/close animation.
|
||||
/// Keeps the selection overlays hidden while a modal launched from the
|
||||
/// selection toolbar is open, so they don't reappear over its animation.
|
||||
bool _suppressSelectionOverlay = false;
|
||||
|
||||
bool _isPlaylistSelectionMode = false;
|
||||
@@ -4849,9 +4847,8 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
|
||||
var didStartConversion = false;
|
||||
|
||||
// Resolve localized strings up front: the builder closure must not look up
|
||||
// Localizations via the outer (State) context, which may be deactivated by
|
||||
// the time the root-navigator sheet rebuilds.
|
||||
// Resolve localized strings up front; the builder must not look up
|
||||
// Localizations via the (possibly deactivated) State context.
|
||||
final sheetTitle = context.l10n.selectionBatchConvertConfirmTitle;
|
||||
final sheetConfirmLabel = context.l10n.selectionConvertCount(
|
||||
_selectedIds.length,
|
||||
@@ -4883,10 +4880,8 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
),
|
||||
);
|
||||
|
||||
// The showModalBottomSheet future completes when the sheet begins closing,
|
||||
// not when its exit animation finishes. Wait out the exit transition
|
||||
// (~200ms) before restoring the selection toolbar so it does not pop in
|
||||
// front of the still-animating sheet.
|
||||
// Wait out the sheet's exit animation before restoring the toolbar so it
|
||||
// doesn't pop in front of the still-closing sheet.
|
||||
await Future<void>.delayed(const Duration(milliseconds: 260));
|
||||
if (!mounted) {
|
||||
_suppressSelectionOverlay = false;
|
||||
@@ -5321,8 +5316,7 @@ class _QueueTabState extends ConsumerState<QueueTab> {
|
||||
return;
|
||||
}
|
||||
if (confirmed != true) {
|
||||
// Restore after the dialog's exit animation so the toolbar does not
|
||||
// appear in front of the closing dialog.
|
||||
// Restore after the dialog's exit animation.
|
||||
await Future<void>.delayed(const Duration(milliseconds: 220));
|
||||
_suppressSelectionOverlay = false;
|
||||
if (!mounted) return;
|
||||
|
||||
@@ -712,10 +712,6 @@ class _FilesSettingsPageState extends ConsumerState<FilesSettingsPage> {
|
||||
final save =
|
||||
onSave ?? ref.read(settingsProvider.notifier).setFilenameFormat;
|
||||
|
||||
// The controller is owned by a StatefulWidget so it is disposed in its
|
||||
// State.dispose() (after the subtree is removed), instead of in
|
||||
// whenComplete which fires while the closing/keyboard-hide animations can
|
||||
// still rebuild the TextField and touch a disposed controller.
|
||||
showModalBottomSheet<void>(
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
@@ -938,12 +934,8 @@ class _FolderOption extends StatelessWidget {
|
||||
}
|
||||
|
||||
|
||||
/// Bottom sheet body for editing a filename format. Owns its
|
||||
/// [TextEditingController] and disposes it in [dispose], which runs only after
|
||||
/// the sheet's subtree has been removed from the tree. This avoids the
|
||||
/// "TextEditingController used after being disposed" crash that happens when
|
||||
/// the controller is torn down in `whenComplete` while the closing and
|
||||
/// keyboard-hide animations are still rebuilding the field.
|
||||
/// Bottom sheet for editing a filename format. Owns its controller and disposes
|
||||
/// it in [dispose] to avoid use-after-dispose during the close animation.
|
||||
class _FilenameFormatEditorSheet extends StatefulWidget {
|
||||
final String initialText;
|
||||
final void Function(String) onSave;
|
||||
|
||||
@@ -1699,9 +1699,8 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Fill for the modern input fields. Sits one elevation step apart from the
|
||||
/// section card so each field reads as a distinct, recessed surface in both
|
||||
/// light and dark (including AMOLED) themes.
|
||||
/// Fill for input fields, one step apart from the card so each field reads as
|
||||
/// a distinct surface in light/dark/AMOLED.
|
||||
Color _fieldFill(ColorScheme cs) {
|
||||
final isDark = Theme.of(context).brightness == Brightness.dark;
|
||||
return isDark
|
||||
@@ -1745,8 +1744,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
filled: true,
|
||||
fillColor: _fieldFill(cs),
|
||||
isDense: true,
|
||||
// Borderless by default; definition comes from the fill contrast.
|
||||
// A soft primary ring appears only on focus for a clean look.
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: radius,
|
||||
borderSide: BorderSide.none,
|
||||
@@ -1770,8 +1767,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Shared shape for the edit sections, mirroring the bounded cards used on the
|
||||
/// track metadata screen (rounded with a subtle outline).
|
||||
RoundedRectangleBorder _sectionCardShape(ColorScheme cs) {
|
||||
return RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
@@ -1779,10 +1774,8 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
/// A titled section card matching the track metadata screen layout. When
|
||||
/// [onHeaderTap] is provided the header becomes a full-width tappable row so
|
||||
/// the ink ripple follows the card's rounded shape (clipped to the card),
|
||||
/// and a chevron is rendered automatically based on [expanded].
|
||||
/// Titled section card. When [onHeaderTap] is set the header is a full-width
|
||||
/// tappable row (ripple clipped to the card) with an auto chevron.
|
||||
Widget _sectionCard({
|
||||
required IconData icon,
|
||||
required String title,
|
||||
|
||||
@@ -1468,8 +1468,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
|
||||
);
|
||||
}
|
||||
|
||||
/// Shared shape for the main section cards: rounded with a subtle outline so
|
||||
/// each section (Metadata, File Info, Lyrics, Audio Analysis) is bounded.
|
||||
RoundedRectangleBorder _sectionCardShape(ColorScheme colorScheme) {
|
||||
return RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
|
||||
@@ -865,10 +865,7 @@ class PlatformBridge {
|
||||
return result as bool;
|
||||
}
|
||||
|
||||
/// iOS only: mark that a download queue is active so the app keeps in-flight
|
||||
/// downloads running for a short window whenever it is backgrounded. iOS
|
||||
/// grants a limited budget (roughly 30s on modern versions) per background
|
||||
/// entry; there is no foreground-service equivalent, so this is best-effort.
|
||||
/// iOS only: keep in-flight downloads running briefly after backgrounding.
|
||||
static Future<void> beginBackgroundDownloadTask() async {
|
||||
if (!Platform.isIOS) return;
|
||||
try {
|
||||
@@ -876,8 +873,7 @@ class PlatformBridge {
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
/// iOS only: mark that the download queue is no longer active (queue finished
|
||||
/// or paused), stopping background-time extension.
|
||||
/// iOS only: stop the background-time extension (queue finished or paused).
|
||||
static Future<void> endBackgroundDownloadTask() async {
|
||||
if (!Platform.isIOS) return;
|
||||
try {
|
||||
|
||||
@@ -474,12 +474,9 @@ class GridSkeleton extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Artist screen skeleton – shown *below* the SliverAppBar header while the
|
||||
/// discography loads. Renders a cover placeholder (only when the header image
|
||||
/// isn't available yet), the "Popular" section (rank + cover 48x48 + title +
|
||||
/// badge + trailing), and the horizontal album sections. The artist name and
|
||||
/// listeners are intentionally omitted here since the header already shows them
|
||||
/// overlaid on the cover.
|
||||
/// Artist screen skeleton shown below the SliverAppBar header while the
|
||||
/// discography loads: optional cover placeholder, "Popular" section, and the
|
||||
/// horizontal album sections.
|
||||
class ArtistScreenSkeleton extends StatelessWidget {
|
||||
final int popularCount;
|
||||
final int albumCount;
|
||||
|
||||
@@ -4,22 +4,12 @@ import 'package:spotiflac_android/utils/audio_conversion_utils.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
|
||||
/// Modern, card-based batch convert sheet shared by the queue and album
|
||||
/// screens. It mirrors the single-track convert sheet styling so format and
|
||||
/// bitrate selection look consistent across the app.
|
||||
/// screens, matching the single-track convert sheet styling.
|
||||
class BatchConvertSheet extends StatefulWidget {
|
||||
/// Available target formats.
|
||||
final List<String> formats;
|
||||
|
||||
/// Sheet title.
|
||||
final String title;
|
||||
|
||||
/// Optional subtitle shown under the title (e.g. number of tracks).
|
||||
final String? subtitle;
|
||||
|
||||
/// Label for the primary action button.
|
||||
final String confirmLabel;
|
||||
|
||||
/// Called with the selected format and bitrate when the user confirms.
|
||||
final void Function(String format, String bitrate) onConvert;
|
||||
|
||||
const BatchConvertSheet({
|
||||
|
||||
Reference in New Issue
Block a user