Compare commits

...

6 Commits

Author SHA1 Message Date
Guanciottaman
0edb33fd20
Merge ff252d6b14 into d3edf07ac9 2025-04-07 19:20:20 +06:00
Kingkor Roy Tirtho
d3edf07ac9 fix: default accent color in orange but it shows blue in settings 2025-04-07 16:16:43 +06:00
Kingkor Roy Tirtho
8fc319d980 fix(mobile): dialogs in bottom sheet are not opening 2025-04-07 14:53:05 +06:00
Guanciottaman
ff252d6b14
Merge branch 'dev' into patch-1 2024-06-03 16:26:24 +02:00
Guanciottaman
195cad8f39
Update app_it.arb fixing translations 2024-03-27 20:54:18 +01:00
Guanciottaman
19f525fa3c
Update app_it.arb
Made the translations more friendly
2024-03-12 21:44:24 +01:00
16 changed files with 3783 additions and 77 deletions

View File

@ -3,4 +3,17 @@
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name="${applicationName}"
android:allowBackup="false"
android:fullBackupContent="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name_en"
android:requestLegacyExternalStorage="true"
android:usesCleartextTraffic="true">
<!-- Disable Impeller -->
<meta-data
android:name="io.flutter.embedding.android.EnableImpeller"
android:value="false" />
</application>
</manifest>

File diff suppressed because one or more lines are too long

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart' show showModalBottomSheet;
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:shadcn_flutter/shadcn_flutter_extension.dart';
import 'package:spotube/collections/spotube_icons.dart';
@ -26,7 +25,7 @@ class AdaptiveMenuButton<T> extends MenuButton {
/// An adaptive widget that shows a [PopupMenuButton] when screen size is above
/// or equal to 640px
/// In smaller screen, a [IconButton] with a [showModalBottomSheet] is shown
/// In smaller screen, a [IconButton] with a [openDrawer] is shown
class AdaptivePopSheetList<T> extends StatelessWidget {
final List<AdaptiveMenuButton<T>> Function(BuildContext context) items;
final Widget? icon;
@ -102,15 +101,13 @@ class AdaptivePopSheetList<T> extends StatelessWidget {
return;
}
showModalBottomSheet(
await openDrawer(
context: context,
enableDrag: true,
draggable: true,
showDragHandle: true,
useRootNavigator: true,
shape: RoundedRectangleBorder(
borderRadius: context.theme.borderRadiusMd,
),
backgroundColor: context.theme.colorScheme.card,
position: OverlayPosition.bottom,
borderRadius: context.theme.borderRadiusMd,
transformBackdrop: false,
builder: (context) {
final children = childrenModified(context);
return ListView.builder(
@ -127,7 +124,7 @@ class AdaptivePopSheetList<T> extends StatelessWidget {
onPressed: () {
data.onPressed?.call(context);
if (data.autoClose) {
Navigator.of(context).pop();
closeDrawer(context);
}
},
leading: data.leading,

View File

@ -74,6 +74,26 @@ class TrackPresentationActionsSection extends HookConsumerWidget {
ref.watch(presentationStateProvider(options.collection).notifier);
final selectedTracks = state.selectedTracks;
Future<void> actionDownloadTracks({
required BuildContext context,
required List<Track> tracks,
required String action,
}) async {
final confirmed = audioSource == AudioSource.piped ||
(await showDialog<bool>(
context: context,
builder: (context) {
return const ConfirmDownloadDialog();
},
) ??
false);
if (confirmed != true) return;
downloader.batchAddToQueue(tracks);
notifier.deselectAllTracks();
if (!context.mounted) return;
showToastForAction(context, action, tracks.length);
}
return AdaptivePopSheetList(
tooltip: context.l10n.more_actions,
headings: [
@ -95,22 +115,12 @@ class TrackPresentationActionsSection extends HookConsumerWidget {
switch (action) {
case "download":
{
final confirmed = audioSource == AudioSource.piped ||
(await showDialog<bool?>(
context: context,
builder: (context) {
return const ConfirmDownloadDialog();
},
) ??
false);
if (confirmed != true) return;
downloader.batchAddToQueue(tracks);
notifier.deselectAllTracks();
if (!context.mounted) return;
showToastForAction(context, action, tracks.length);
break;
}
await actionDownloadTracks(
context: context,
tracks: tracks,
action: action,
);
break;
case "add-to-playlist":
{
if (context.mounted) {

View File

@ -57,7 +57,7 @@ class TrackPresentationTopSection extends HookConsumerWidget {
Tooltip(
tooltip: TooltipContainer(
child: Text(context.l10n.shuffle_playlist),
),
).call,
child: IconButton.secondary(
icon: isLoading
? const Center(
@ -73,7 +73,7 @@ class TrackPresentationTopSection extends HookConsumerWidget {
Tooltip(
tooltip: TooltipContainer(
child: Text(context.l10n.add_to_queue),
),
).call,
child: IconButton.secondary(
icon: const Icon(SpotubeIcons.queueAdd),
enabled: !isLoading && !isActive,
@ -126,7 +126,7 @@ class TrackPresentationTopSection extends HookConsumerWidget {
Tooltip(
tooltip: TooltipContainer(
child: Text(context.l10n.share),
),
).call,
child: IconButton.outline(
icon: const Icon(SpotubeIcons.share),
size: ButtonSize.small,

View File

@ -91,24 +91,14 @@ class TrackOptions extends HookConsumerWidget {
) {
/// showDialog doesn't work for some reason. So we have to
/// manually push a Dialog Route in the Navigator to get it working
Navigator.push(
context,
DialogRoute(
alignment: Alignment.bottomCenter,
transitionBuilder: (context, animation, secondaryAnimation, child) {
return FadeTransition(opacity: animation, child: child);
},
context: context,
barrierColor: Colors.black.withValues(alpha: 0.5),
builder: (context) {
return Center(
child: PlaylistAddTrackDialog(
tracks: [track],
openFromPlaylist: playlistId,
),
);
},
),
showDialog(
context: context,
builder: (context) {
return PlaylistAddTrackDialog(
tracks: [track],
openFromPlaylist: playlistId,
);
},
);
}
@ -338,6 +328,7 @@ class TrackOptions extends HookConsumerWidget {
}
},
icon: icon ?? const Icon(SpotubeIcons.moreHorizontal),
variance: ButtonVariance.outline,
headings: [
Basic(
leading: AspectRatio(

View File

@ -137,16 +137,16 @@
"pre_download_play_description": "Anzi che effettuare lo stream dell'audio, scarica invece i byte e li riproduce (raccomandato per gli utenti con banda più alta)",
"skip_non_music": "Salta i segmenti non di musica (SponsorBlock)",
"blacklist_description": "Tracce e artisti in blacklist",
"wait_for_download_to_finish": "Prego attendere che lo scaricamento corrente finisca",
"wait_for_download_to_finish": "Prego attendere che il download corrente finisca",
"desktop": "Desktop",
"close_behavior": "Comportamento Chiusura",
"close": "Chiudi",
"minimize_to_tray": "Minimizza in tray",
"show_tray_icon": "Mostra icona in tray di sistema",
"about": "A proposito di",
"about": "Informazioni su",
"u_love_spotube": "Sappiamo che ami Spotube",
"check_for_updates": "Controlla aggiornamenti",
"about_spotube": "A proposito di Spotube",
"about_spotube": "Informazioni su Spotube",
"blacklist": "Blacklist",
"please_sponsor": "Per favore sponsorizza/dona",
"spotube_description": "Spotube, un client spotify gratis per tutti, multipiattaforma e leggero",
@ -187,7 +187,7 @@
"generate_playlist": "Genera Playlist",
"track_exists": "La traccia {track} esiste già",
"replace_downloaded_tracks": "Sostituisci tutte le tracce scaricate",
"skip_download_tracks": "Salta lo scaricamento di tutte le tracce scaricate",
"skip_download_tracks": "Salta il download di tutte le tracce scaricate",
"do_you_want_to_replace": "Vuoi sovrascrivere la traccia esistente??",
"replace": "Sovrascrivi",
"skip": "Salta",
@ -256,7 +256,7 @@
"querying_info": "Richiesta informazioni...",
"piped_api_down": "Le Piped API non funzionano",
"piped_down_error_instructions": "L'istanza di Piped {pipedInstance} è correntemente offline\n\nCambia istanza o cambia 'Tipo API' alle API ufficiali YouTube\n\nAssicurati di riavviare l'app dopo il cambio",
"you_are_offline": "Sei correntemente offline",
"you_are_offline": "Al momento sei offline",
"connection_restored": "Connessione ad internet ripristinata",
"use_system_title_bar": "Usa la barra del titolo di sistema",
"crunching_results": "Elaborazione risultati...",
@ -267,15 +267,15 @@
"change_cover": "Cambia copertina",
"add_cover": "Aggiungi copertina",
"restore_defaults": "Ripristina default",
"download_music_codec": "Codec musicale scaricamento",
"streaming_music_codec": "Codec musicale streaming",
"login_with_lastfm": "Accesso a Last.fm",
"connect": "Connetti",
"disconnect_lastfm": "Disconnetti Last.fm",
"download_music_codec": "Codec download musica",
"streaming_music_codec": "Codec streaming musica",
"login_with_lastfm": "Accedi con Last.fm",
"connect": "Connettiti",
"disconnect_lastfm": "Disconnettiti da Last.fm",
"disconnect": "Disconnetti",
"username": "Nome utente",
"password": "Password",
"login": "Accesso",
"login": "Accedi",
"login_with_your_lastfm": "Accedi con il tuo account Last.fm",
"scrobble_to_lastfm": "Invia a Last.fm",
"audio_source": "Fonte audio",
@ -299,7 +299,7 @@
"song_link": "Link della Canzone",
"skip_this_nonsense": "Salta questa sciocchezza",
"freedom_of_music": "“Libertà della Musica”",
"freedom_of_music_palm": "“Libertà della Musica nel palmo della tua mano”",
"freedom_of_music_palm": "“Libertà della Musica nelle tue mani”",
"get_started": "Cominciamo",
"youtube_source_description": "Consigliato e funziona meglio.",
"piped_source_description": "Ti senti libero? Come YouTube ma molto più gratuito.",

View File

@ -62,7 +62,7 @@ class AppDatabase extends _$AppDatabase {
AppDatabase() : super(_openConnection());
@override
int get schemaVersion => 4;
int get schemaVersion => 5;
@override
MigrationStrategy get migration {
@ -87,6 +87,33 @@ class AppDatabase extends _$AppDatabase {
schema.preferencesTable.youtubeClientEngine,
);
},
from4To5: (m, schema) async {
final columnName = schema.preferencesTable.accentColorScheme
.escapedNameFor(SqlDialect.sqlite);
final columnNameOld =
'"${schema.preferencesTable.accentColorScheme.name}_old"';
final tableName = schema.preferencesTable.actualTableName;
await customStatement(
"ALTER TABLE $tableName "
"RENAME COLUMN $columnName to $columnNameOld",
);
await customStatement(
"ALTER TABLE $tableName "
"ADD COLUMN $columnName TEXT NOT NULL DEFAULT 'Orange:0xFFf97315'",
);
await customStatement(
"UPDATE $tableName "
"SET $columnName = $columnNameOld",
);
await customStatement(
"ALTER TABLE $tableName "
"DROP COLUMN $columnNameOld",
);
await customStatement(
"UPDATE $tableName "
"SET $columnName = 'Orange:0xFFf97315' WHERE $columnName = 'Blue:0xFF2196F3'",
);
},
),
);
}

View File

@ -666,7 +666,7 @@ class $PreferencesTableTable extends PreferencesTable
'accent_color_scheme', aliasedName, false,
type: DriftSqlType.string,
requiredDuringInsert: false,
defaultValue: const Constant("Blue:0xFF2196F3"))
defaultValue: const Constant("Orange:0xFFf97315"))
.withConverter<SpotubeColor>(
$PreferencesTableTable.$converteraccentColorScheme);
static const VerificationMeta _layoutModeMeta =

View File

@ -2,7 +2,7 @@
import 'package:drift/internal/versioned_schema.dart' as i0;
import 'package:drift/drift.dart' as i1;
import 'package:drift/drift.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:flutter/material.dart';
import 'package:spotify/spotify.dart';
import 'package:spotube/models/database/database.dart';
import 'package:spotube/services/sourced_track/enums.dart'; // ignore_for_file: type=lint,unused_import
@ -1188,10 +1188,232 @@ i1.GeneratedColumn<String> _column_54(String aliasedName) =>
i1.GeneratedColumn<String>('youtube_client_engine', aliasedName, false,
type: i1.DriftSqlType.string,
defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name));
final class Schema5 extends i0.VersionedSchema {
Schema5({required super.database}) : super(version: 5);
@override
late final List<i1.DatabaseSchemaEntity> entities = [
authenticationTable,
blacklistTable,
preferencesTable,
scrobblerTable,
skipSegmentTable,
sourceMatchTable,
audioPlayerStateTable,
playlistTable,
playlistMediaTable,
historyTable,
lyricsTable,
uniqueBlacklist,
uniqTrackMatch,
];
late final Shape0 authenticationTable = Shape0(
source: i0.VersionedTable(
entityName: 'authentication_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_1,
_column_2,
_column_3,
],
attachedDatabase: database,
),
alias: null);
late final Shape1 blacklistTable = Shape1(
source: i0.VersionedTable(
entityName: 'blacklist_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_4,
_column_5,
_column_6,
],
attachedDatabase: database,
),
alias: null);
late final Shape12 preferencesTable = Shape12(
source: i0.VersionedTable(
entityName: 'preferences_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_7,
_column_8,
_column_9,
_column_10,
_column_11,
_column_12,
_column_13,
_column_14,
_column_15,
_column_55,
_column_17,
_column_18,
_column_19,
_column_20,
_column_21,
_column_22,
_column_23,
_column_24,
_column_25,
_column_26,
_column_54,
_column_27,
_column_28,
_column_29,
_column_30,
_column_31,
_column_53,
],
attachedDatabase: database,
),
alias: null);
late final Shape3 scrobblerTable = Shape3(
source: i0.VersionedTable(
entityName: 'scrobbler_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_32,
_column_33,
_column_34,
],
attachedDatabase: database,
),
alias: null);
late final Shape4 skipSegmentTable = Shape4(
source: i0.VersionedTable(
entityName: 'skip_segment_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_35,
_column_36,
_column_37,
_column_32,
],
attachedDatabase: database,
),
alias: null);
late final Shape5 sourceMatchTable = Shape5(
source: i0.VersionedTable(
entityName: 'source_match_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_37,
_column_38,
_column_39,
_column_32,
],
attachedDatabase: database,
),
alias: null);
late final Shape6 audioPlayerStateTable = Shape6(
source: i0.VersionedTable(
entityName: 'audio_player_state_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_40,
_column_41,
_column_42,
_column_43,
],
attachedDatabase: database,
),
alias: null);
late final Shape7 playlistTable = Shape7(
source: i0.VersionedTable(
entityName: 'playlist_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_44,
_column_45,
],
attachedDatabase: database,
),
alias: null);
late final Shape8 playlistMediaTable = Shape8(
source: i0.VersionedTable(
entityName: 'playlist_media_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_46,
_column_47,
_column_48,
_column_49,
],
attachedDatabase: database,
),
alias: null);
late final Shape9 historyTable = Shape9(
source: i0.VersionedTable(
entityName: 'history_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_32,
_column_50,
_column_51,
_column_52,
],
attachedDatabase: database,
),
alias: null);
late final Shape10 lyricsTable = Shape10(
source: i0.VersionedTable(
entityName: 'lyrics_table',
withoutRowId: false,
isStrict: false,
tableConstraints: [],
columns: [
_column_0,
_column_37,
_column_52,
],
attachedDatabase: database,
),
alias: null);
final i1.Index uniqueBlacklist = i1.Index('unique_blacklist',
'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)');
final i1.Index uniqTrackMatch = i1.Index('uniq_track_match',
'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)');
}
i1.GeneratedColumn<String> _column_55(String aliasedName) =>
i1.GeneratedColumn<String>('accent_color_scheme', aliasedName, false,
type: i1.DriftSqlType.string,
defaultValue: const Constant("Orange:0xFFf97315"));
i0.MigrationStepWithVersion migrationSteps({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
}) {
return (currentVersion, database) async {
switch (currentVersion) {
@ -1210,6 +1432,11 @@ i0.MigrationStepWithVersion migrationSteps({
final migrator = i1.Migrator(database, schema);
await from3To4(migrator, schema);
return 4;
case 4:
final schema = Schema5(database: database);
final migrator = i1.Migrator(database, schema);
await from4To5(migrator, schema);
return 5;
default:
throw ArgumentError.value('Unknown migration from $currentVersion');
}
@ -1220,10 +1447,12 @@ i1.OnUpgrade stepByStep({
required Future<void> Function(i1.Migrator m, Schema2 schema) from1To2,
required Future<void> Function(i1.Migrator m, Schema3 schema) from2To3,
required Future<void> Function(i1.Migrator m, Schema4 schema) from3To4,
required Future<void> Function(i1.Migrator m, Schema5 schema) from4To5,
}) =>
i0.VersionedSchema.stepByStepHelper(
step: migrationSteps(
from1To2: from1To2,
from2To3: from2To3,
from3To4: from3To4,
from4To5: from4To5,
));

View File

@ -79,7 +79,7 @@ class PreferencesTable extends Table {
TextColumn get closeBehavior => textEnum<CloseBehavior>()
.withDefault(Constant(CloseBehavior.close.name))();
TextColumn get accentColorScheme => text()
.withDefault(const Constant("Blue:0xFF2196F3"))
.withDefault(const Constant("Orange:0xFFf97315"))
.map(const SpotubeColorConverter())();
TextColumn get layoutMode =>
textEnum<LayoutMode>().withDefault(Constant(LayoutMode.adaptive.name))();
@ -130,7 +130,7 @@ class PreferencesTable extends Table {
systemTitleBar: false,
skipNonMusic: false,
closeBehavior: CloseBehavior.close,
accentColorScheme: SpotubeColor(Colors.blue.value, name: "Blue"),
accentColorScheme: SpotubeColor(Colors.orange.value, name: "Orange"),
layoutMode: LayoutMode.adaptive,
locale: const Locale("system", "system"),
market: Market.US,

View File

@ -132,7 +132,7 @@ class PlayerView extends HookConsumerWidget {
Tooltip(
tooltip: TooltipContainer(
child: Text(context.l10n.details),
),
).call,
child: IconButton.ghost(
icon: const Icon(SpotubeIcons.info, size: 18),
onPressed: currentTrack == null

View File

@ -82,7 +82,7 @@ class PlayerActions extends HookConsumerWidget {
children: [
if (showQueue)
Tooltip(
tooltip: TooltipContainer(child: Text(context.l10n.queue)),
tooltip: TooltipContainer(child: Text(context.l10n.queue)).call,
child: IconButton.ghost(
icon: const Icon(SpotubeIcons.queue),
enabled: playlist.activeTrack != null,
@ -119,7 +119,8 @@ class PlayerActions extends HookConsumerWidget {
if (!isLocalTrack)
Tooltip(
tooltip: TooltipContainer(
child: Text(context.l10n.alternative_track_sources)),
child: Text(context.l10n.alternative_track_sources),
).call,
child: IconButton.ghost(
enabled: playlist.activeTrack != null,
icon: const Icon(SpotubeIcons.alternativeRoute),
@ -160,7 +161,8 @@ class PlayerActions extends HookConsumerWidget {
else
Tooltip(
tooltip:
TooltipContainer(child: Text(context.l10n.download_track)),
TooltipContainer(child: Text(context.l10n.download_track))
.call,
child: IconButton.ghost(
icon: Icon(
isDownloaded ? SpotubeIcons.done : SpotubeIcons.download,

View File

@ -90,9 +90,9 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
Future<void> reset() async {
final db = ref.read(databaseProvider);
final query = db.update(db.preferencesTable)..where((t) => t.id.equals(0));
final query = db.update(db.preferencesTable);
await query.replace(PreferencesTableCompanion.insert());
await query.replace(PreferencesTableCompanion.insert(id: const Value(0)));
}
static Future<String> getMusicCacheDir() async {

View File

@ -3,27 +3,30 @@
// ignore_for_file: type=lint
import 'package:drift/drift.dart';
import 'package:drift/internal/migrations.dart';
import 'schema_v4.dart' as v4;
import 'schema_v3.dart' as v3;
import 'schema_v2.dart' as v2;
import 'schema_v5.dart' as v5;
import 'schema_v1.dart' as v1;
import 'schema_v2.dart' as v2;
import 'schema_v4.dart' as v4;
class GeneratedHelper implements SchemaInstantiationHelper {
@override
GeneratedDatabase databaseForVersion(QueryExecutor db, int version) {
switch (version) {
case 4:
return v4.DatabaseAtV4(db);
case 3:
return v3.DatabaseAtV3(db);
case 2:
return v2.DatabaseAtV2(db);
case 5:
return v5.DatabaseAtV5(db);
case 1:
return v1.DatabaseAtV1(db);
case 2:
return v2.DatabaseAtV2(db);
case 4:
return v4.DatabaseAtV4(db);
default:
throw MissingSchemaException(version, versions);
}
}
static const versions = const [1, 2, 3, 4];
static const versions = const [1, 2, 3, 4, 5];
}

File diff suppressed because it is too large Load Diff