Tab-Struktur nach Referenz-UI: Online-Tab + Unterreiter Songs/Kuenstler/Alben
Untere Navigation folgt jetzt den Referenzbildern: Meine Musik / Online / Suchen / Favoriten. Der bisherige "Download"-Tab heisst "Online" und bekommt als Naechstes den YouTube-Downloader. In "Meine Musik" gibt es Unterreiter (Songs/Kuenstler/Alben) im Pillen-Stil der Referenz. Sie wechseln nur den Inhalt statt einen Bildschirm zu oeffnen; die Schnellzugriff-Kacheln "Kuenstler"/"Alben" entfallen dadurch, ebenso die damit tot gewordenen ArtistsScreen/AlbumsScreen-Huellen. 158 Tests gruen, flutter analyze ohne Befund. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018NLW1q1hzEC9goDQ1dJ98d
This commit is contained in:
co-authored by
Claude Opus 5
parent
4ebd0a8dd4
commit
a625d70da8
@@ -5,6 +5,25 @@ Format angelehnt an [Keep a Changelog](https://keepachangelog.com/de/1.0.0/).
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 🧭 Neue Tab-Struktur nach deinen Referenzbildern (2026-08-20)
|
||||
- 🔀 **Untere Leiste wie auf deinem Bild:** Die Reihenfolge ist jetzt
|
||||
**Meine Musik · Online · Suchen · Favoriten**. Der frühere Reiter
|
||||
"Download" heißt jetzt **Online** (Musiknoten-Symbol) — dort kommt als
|
||||
Nächstes der YouTube-Downloader dazu.
|
||||
- 🗂️ **Unterreiter in "Meine Musik":** Über der Liste sitzen jetzt
|
||||
**Songs · Künstler · Alben** als Pillen — genau wie in der Referenz. Der
|
||||
aktive Reiter ist hell, die anderen sind nur Text.
|
||||
Wichtig: Ein Unterreiter **wechselt nur den Inhalt** und öffnet keinen
|
||||
neuen Bildschirm — Suchfeld und Schnellzugriffe bleiben stehen.
|
||||
- 🧹 **Aufgeräumt:** Die Schnellzugriff-Kacheln "Künstler" und "Alben" sind
|
||||
weg, weil sie jetzt Unterreiter sind. Übrig bleiben — wie in der Referenz —
|
||||
**Favoriten · Wiedergabelisten · Zuletzt**.
|
||||
- 🔧 **Details:** `MyMusicScreen` ist jetzt zustandsbehaftet (`_subTab`), neues
|
||||
`_SubTabs`-Widget; die dadurch toten `ArtistsScreen`/`AlbumsScreen`-Hüllen
|
||||
wurden entfernt (`ArtistListScreen`/`AlbumListScreen` bleiben und werden
|
||||
direkt eingebettet).
|
||||
- ✅ **Getestet:** 158 Tests grün (4 neue), `flutter analyze` ohne Befund.
|
||||
|
||||
### 🎼 Songtext, Warteschlange, Sperrbildschirm-Cover und gleiche Lautstärke (2026-08-20)
|
||||
- 📝 **Automatischer Songtext:** Beim Scan wird der Songtext aus dem Tag der
|
||||
Datei mitgelesen und in der Wiedergabe sofort angezeigt — **ohne Server und
|
||||
|
||||
@@ -5,7 +5,7 @@ import 'package:provider/provider.dart';
|
||||
import '../player/audio_handler.dart';
|
||||
import '../services/navidrome_service.dart';
|
||||
|
||||
/// Download-Tab: durchsucht den verbundenen Navidrome-Server. Abgespielte
|
||||
/// Online-Tab: durchsucht den verbundenen Navidrome-Server. Abgespielte
|
||||
/// Titel werden vom [MeloAudioHandler] automatisch lokal zwischengespeichert
|
||||
/// und stehen danach offline zur Verfügung.
|
||||
class DownloadsScreen extends StatelessWidget {
|
||||
@@ -21,7 +21,7 @@ class DownloadsScreen extends StatelessWidget {
|
||||
padding: EdgeInsets.fromLTRB(20, 16, 20, 4),
|
||||
child: Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text('Download',
|
||||
child: Text('Online',
|
||||
style: TextStyle(fontSize: 24, fontWeight: FontWeight.w700)),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -67,16 +67,3 @@ class _AlbumSongsScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Alben-Übersicht als eigener Bildschirm (Schnellzugriff "Alben").
|
||||
class AlbumsScreen extends StatelessWidget {
|
||||
const AlbumsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Alben')),
|
||||
body: const AlbumListScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -59,16 +59,3 @@ class _ArtistSongsScreen extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Künstler-Übersicht als eigener Bildschirm (Schnellzugriff "Künstler").
|
||||
class ArtistsScreen extends StatelessWidget {
|
||||
const ArtistsScreen({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Künstler')),
|
||||
body: const ArtistListScreen(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,9 +15,9 @@ import 'permissions.dart';
|
||||
import 'song_list.dart';
|
||||
|
||||
/// Startbildschirm: Kopfzeile mit Einstellungen/Suche/Musikerkennung,
|
||||
/// Schnellzugriffe, darunter Shuffle + sortierbare Liederliste.
|
||||
/// Bewusst ohne AppBar — die Kopfzeile ist Teil des Inhalts.
|
||||
class MyMusicScreen extends StatelessWidget {
|
||||
/// Schnellzugriffe, Unterreiter (Songs/Künstler/Alben) und darunter der
|
||||
/// jeweilige Inhalt. Bewusst ohne AppBar — die Kopfzeile ist Teil des Inhalts.
|
||||
class MyMusicScreen extends StatefulWidget {
|
||||
const MyMusicScreen({super.key, this.onSearchTap, this.onFavoritesTap});
|
||||
|
||||
/// Wechselt zum Suche-Tab; die Suche ist ein eigener Tab, kein eigener Screen.
|
||||
@@ -26,6 +26,14 @@ class MyMusicScreen extends StatelessWidget {
|
||||
/// Wechselt zum Favoriten-Tab.
|
||||
final VoidCallback? onFavoritesTap;
|
||||
|
||||
@override
|
||||
State<MyMusicScreen> createState() => _MyMusicScreenState();
|
||||
}
|
||||
|
||||
class _MyMusicScreenState extends State<MyMusicScreen> {
|
||||
/// 0 = Songs, 1 = Künstler, 2 = Alben.
|
||||
int _subTab = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final db = context.read<MeloDb>();
|
||||
@@ -34,7 +42,7 @@ class MyMusicScreen extends StatelessWidget {
|
||||
bottom: false,
|
||||
child: Column(
|
||||
children: [
|
||||
_Header(onSearchTap: onSearchTap),
|
||||
_Header(onSearchTap: widget.onSearchTap),
|
||||
if (lib.permissionDenied)
|
||||
MaterialBanner(
|
||||
backgroundColor: Colors.red.shade900,
|
||||
@@ -73,25 +81,34 @@ class MyMusicScreen extends StatelessWidget {
|
||||
],
|
||||
),
|
||||
),
|
||||
_QuickAccessRow(onFavoritesTap: onFavoritesTap),
|
||||
_QuickAccessRow(onFavoritesTap: widget.onFavoritesTap),
|
||||
_SubTabs(
|
||||
index: _subTab,
|
||||
onChanged: (i) => setState(() => _subTab = i),
|
||||
),
|
||||
Expanded(
|
||||
child: StreamBuilder<List<Song>>(
|
||||
stream: db.watchSongs(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text('Fehler: ${snapshot.error}',
|
||||
style: const TextStyle(color: Colors.white54)),
|
||||
);
|
||||
}
|
||||
final songs = snapshot.data ?? const <Song>[];
|
||||
return SortableSongList(
|
||||
songs: songs,
|
||||
storeKey: SortStore.meineMusik,
|
||||
empty: lib.scanning ? const SizedBox.shrink() : const _Empty(),
|
||||
);
|
||||
},
|
||||
),
|
||||
child: switch (_subTab) {
|
||||
1 => const ArtistListScreen(),
|
||||
2 => const AlbumListScreen(),
|
||||
_ => StreamBuilder<List<Song>>(
|
||||
stream: db.watchSongs(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasError) {
|
||||
return Center(
|
||||
child: Text('Fehler: ${snapshot.error}',
|
||||
style: const TextStyle(color: Colors.white54)),
|
||||
);
|
||||
}
|
||||
final songs = snapshot.data ?? const <Song>[];
|
||||
return SortableSongList(
|
||||
songs: songs,
|
||||
storeKey: SortStore.meineMusik,
|
||||
empty:
|
||||
lib.scanning ? const SizedBox.shrink() : const _Empty(),
|
||||
);
|
||||
},
|
||||
),
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -154,6 +171,52 @@ class _Header extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Unterreiter im Stil der Referenz-UI: der aktive Reiter ist eine helle
|
||||
/// Pille, die übrigen sind nur Text.
|
||||
class _SubTabs extends StatelessWidget {
|
||||
const _SubTabs({required this.index, required this.onChanged});
|
||||
|
||||
final int index;
|
||||
final ValueChanged<int> onChanged;
|
||||
|
||||
static const _labels = ['Songs', 'Künstler', 'Alben'];
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.fromLTRB(12, 12, 12, 4),
|
||||
child: Row(
|
||||
children: [
|
||||
for (var i = 0; i < _labels.length; i++)
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(right: 8),
|
||||
child: GestureDetector(
|
||||
onTap: () => onChanged(i),
|
||||
child: Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 18, vertical: 9),
|
||||
decoration: BoxDecoration(
|
||||
color: i == index ? Colors.white : Colors.transparent,
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: Text(
|
||||
_labels[i],
|
||||
style: TextStyle(
|
||||
color: i == index ? Colors.black : Colors.white70,
|
||||
fontSize: 15,
|
||||
fontWeight:
|
||||
i == index ? FontWeight.w600 : FontWeight.w400,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Waagerecht scrollende Schnellzugriffe (Favoriten, Wiedergabelisten, …).
|
||||
class _QuickAccessRow extends StatelessWidget {
|
||||
const _QuickAccessRow({this.onFavoritesTap});
|
||||
@@ -210,20 +273,6 @@ class _QuickAccessRow extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
_QuickCard(
|
||||
icon: Icons.person,
|
||||
label: 'Künstler',
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const ArtistsScreen()),
|
||||
),
|
||||
),
|
||||
_QuickCard(
|
||||
icon: Icons.album,
|
||||
label: 'Alben',
|
||||
onTap: () => Navigator.of(context).push(
|
||||
MaterialPageRoute(builder: (_) => const AlbumsScreen()),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
+6
-6
@@ -107,12 +107,12 @@ class _HomeShellState extends State<HomeShell> {
|
||||
Widget build(BuildContext context) {
|
||||
final tabs = <Widget>[
|
||||
MyMusicScreen(
|
||||
onSearchTap: () => _goTo(1),
|
||||
onFavoritesTap: () => _goTo(2),
|
||||
onSearchTap: () => _goTo(2),
|
||||
onFavoritesTap: () => _goTo(3),
|
||||
),
|
||||
const DownloadsScreen(),
|
||||
const SearchScreen(),
|
||||
const FavoritesScreen(),
|
||||
const DownloadsScreen(),
|
||||
];
|
||||
return Scaffold(
|
||||
body: Column(
|
||||
@@ -127,11 +127,11 @@ class _HomeShellState extends State<HomeShell> {
|
||||
items: const [
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.headphones), label: 'Meine Musik'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Suche'),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.music_note_outlined), label: 'Online'),
|
||||
BottomNavigationBarItem(icon: Icon(Icons.search), label: 'Suchen'),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.favorite_border), label: 'Favoriten'),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(Icons.download_outlined), label: 'Download'),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
+8
-8
@@ -620,10 +620,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
sha256: "31bd099b47c10cd1aeb55146a2d46ce0277630ecef3f7dae54ad7873f36696cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
version: "0.12.20"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -636,10 +636,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
|
||||
sha256: "307249ce4ff29d58a18e97f6345f539382eb9c9c29ecda628900f31de0443dd9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.18.0"
|
||||
version: "1.19.0"
|
||||
mime:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1113,10 +1113,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
|
||||
sha256: "2a122cbe059f8b610d3a5415f42e255b6c17b1f21eee1d960f31080237fb4f11"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.11"
|
||||
version: "0.7.12"
|
||||
typed_data:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1137,10 +1137,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
sha256: f36f9f3be64c6198714492bb455c11056e33e2f85d9a0b676a48301e44fdcf47
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
version: "2.4.2"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:melo/library/category_service.dart';
|
||||
import 'package:melo/library/database.dart';
|
||||
import 'package:melo/library/library_service.dart';
|
||||
import 'package:melo/library/playlist_service.dart';
|
||||
import 'package:melo/main.dart';
|
||||
import 'package:melo/player/audio_handler.dart';
|
||||
import 'package:melo/services/offline_mode.dart';
|
||||
import 'package:melo/settings/app_settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
void main() {
|
||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||
|
||||
testWidgets('Untere Navigation folgt der Referenz-UI: '
|
||||
'Meine Musik, Online, Suchen, Favoriten', (tester) async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
final lib = LibraryService(db);
|
||||
final playlists = PlaylistService(db);
|
||||
final handler = MeloAudioHandler(db: db);
|
||||
|
||||
await tester.pumpWidget(MultiProvider(
|
||||
providers: [
|
||||
Provider<MeloDb>.value(value: db),
|
||||
ChangeNotifierProvider<LibraryService>.value(value: lib),
|
||||
ChangeNotifierProvider<PlaylistService>.value(value: playlists),
|
||||
Provider<MeloAudioHandler>.value(value: handler),
|
||||
ChangeNotifierProvider<CategoryService>(create: (_) => CategoryService(db)),
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
ChangeNotifierProvider<OfflineMode>(create: (_) => OfflineMode()),
|
||||
],
|
||||
child: const MaterialApp(home: HomeShell()),
|
||||
));
|
||||
await tester.pump();
|
||||
|
||||
final bar = tester.widget<BottomNavigationBar>(
|
||||
find.byType(BottomNavigationBar));
|
||||
expect(bar.items.map((i) => i.label).toList(),
|
||||
['Meine Musik', 'Online', 'Suchen', 'Favoriten']);
|
||||
|
||||
handler.dispose();
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
import 'package:drift/drift.dart';
|
||||
import 'package:drift/native.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:melo/library/category_service.dart';
|
||||
import 'package:melo/library/database.dart';
|
||||
import 'package:melo/library/library_service.dart';
|
||||
import 'package:melo/library/my_music_screen.dart';
|
||||
import 'package:melo/library/playlist_service.dart';
|
||||
import 'package:melo/player/audio_handler.dart';
|
||||
import 'package:melo/settings/app_settings.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
Widget _wrap(MeloDb db, LibraryService lib, PlaylistService playlists,
|
||||
MeloAudioHandler handler, Widget child) {
|
||||
return MultiProvider(
|
||||
providers: [
|
||||
Provider<MeloDb>.value(value: db),
|
||||
ChangeNotifierProvider<LibraryService>.value(value: lib),
|
||||
ChangeNotifierProvider<PlaylistService>.value(value: playlists),
|
||||
Provider<MeloAudioHandler>.value(value: handler),
|
||||
ChangeNotifierProvider<CategoryService>(create: (_) => CategoryService(db)),
|
||||
ChangeNotifierProvider<AppSettings>(create: (_) => AppSettings()),
|
||||
],
|
||||
child: MaterialApp(home: Scaffold(body: child)),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _insert(
|
||||
MeloDb db, {
|
||||
required String id,
|
||||
required String title,
|
||||
String? artist,
|
||||
String? album,
|
||||
}) {
|
||||
return db.into(db.songs).insert(SongsCompanion.insert(
|
||||
id: id,
|
||||
path: '/$id.mp3',
|
||||
title: title,
|
||||
artist: Value(artist),
|
||||
album: Value(album),
|
||||
dateAddedMs: 0,
|
||||
updatedAtMs: 0,
|
||||
));
|
||||
}
|
||||
|
||||
void main() {
|
||||
setUp(() => SharedPreferences.setMockInitialValues({}));
|
||||
|
||||
testWidgets('Meine Musik hat die Unterreiter Songs, Künstler und Alben',
|
||||
(tester) async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
final lib = LibraryService(db);
|
||||
final playlists = PlaylistService(db);
|
||||
final handler = MeloAudioHandler(db: db);
|
||||
await _insert(db, id: '1', title: 'Song A', artist: 'Neoni', album: 'Roses');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Songs'), findsOneWidget);
|
||||
expect(find.text('Künstler'), findsOneWidget);
|
||||
expect(find.text('Alben'), findsOneWidget);
|
||||
|
||||
handler.dispose();
|
||||
await db.close();
|
||||
});
|
||||
|
||||
testWidgets('Unterreiter Künstler zeigt die Künstlerliste statt der Songs',
|
||||
(tester) async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
final lib = LibraryService(db);
|
||||
final playlists = PlaylistService(db);
|
||||
final handler = MeloAudioHandler(db: db);
|
||||
await _insert(db, id: '1', title: 'Song A', artist: 'Neoni', album: 'Roses');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Künstler'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Neoni'), findsOneWidget);
|
||||
// Unterreiter wechseln den Inhalt, sie öffnen keinen neuen Bildschirm —
|
||||
// Suchfeld und Reiter bleiben sichtbar.
|
||||
expect(find.text('Titel, Künstler und Alben suchen'), findsOneWidget);
|
||||
expect(find.text('Songs'), findsOneWidget);
|
||||
expect(find.text('Shuffle-Wiedergabe'), findsNothing);
|
||||
|
||||
handler.dispose();
|
||||
await db.close();
|
||||
});
|
||||
|
||||
testWidgets('Unterreiter Alben zeigt die Albenliste', (tester) async {
|
||||
final db = MeloDb(NativeDatabase.memory());
|
||||
final lib = LibraryService(db);
|
||||
final playlists = PlaylistService(db);
|
||||
final handler = MeloAudioHandler(db: db);
|
||||
await _insert(db, id: '1', title: 'Song A', artist: 'Neoni', album: 'Roses');
|
||||
|
||||
await tester.pumpWidget(
|
||||
_wrap(db, lib, playlists, handler, const MyMusicScreen()));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
await tester.tap(find.text('Alben'));
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
expect(find.text('Roses'), findsOneWidget);
|
||||
expect(find.text('Titel, Künstler und Alben suchen'), findsOneWidget);
|
||||
expect(find.text('Songs'), findsOneWidget);
|
||||
|
||||
handler.dispose();
|
||||
await db.close();
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user