From 73b43200acea617657f441b3551d62acd633d41b Mon Sep 17 00:00:00 2001 From: Yonggan Date: Fri, 23 Sep 2022 18:44:22 +0200 Subject: [PATCH 001/172] Add music visualizer on currently playing song --- .../AlbumScreen/album_screen_content.dart | 65 ++++++++++++------- .../AlbumScreen/song_list_tile.dart | 58 +++++++++++------ lib/screens/album_screen.dart | 2 + pubspec.yaml | 1 + 4 files changed, 84 insertions(+), 42 deletions(-) diff --git a/lib/components/AlbumScreen/album_screen_content.dart b/lib/components/AlbumScreen/album_screen_content.dart index ecc44161b..0b35afb63 100644 --- a/lib/components/AlbumScreen/album_screen_content.dart +++ b/lib/components/AlbumScreen/album_screen_content.dart @@ -10,6 +10,7 @@ import 'album_screen_content_flexible_space_bar.dart'; import 'download_button.dart'; import 'song_list_tile.dart'; import 'playlist_name_edit_button.dart'; +import '../../services/media_state_stream.dart'; class AlbumScreenContent extends StatelessWidget { const AlbumScreenContent({ @@ -112,27 +113,47 @@ class _SongsSliverList extends StatelessWidget { // Adding this offset ensures playback starts for nth song on correct disc. int indexOffset = childrenForQueue.indexOf(childrenForList[0]); - return SliverList( - delegate: SliverChildBuilderDelegate((BuildContext context, int index) { - final BaseItemDto item = childrenForList[index]; - return SongListTile( - item: item, - children: childrenForQueue, - index: index + indexOffset, - parentId: parent.id, - // show artists except for this one scenario - showArtists: !( - // we're on album screen - parent.type == "MusicAlbum" - // "hide song artists if they're the same as album artists" == true - && - FinampSettingsHelper - .finampSettings.hideSongArtistsIfSameAsAlbumArtists - // song artists == album artists - && - setEquals(parent.albumArtists?.map((e) => e.name).toSet(), - item.artists?.toSet()))); - }, childCount: childrenForList.length), - ); + return StreamBuilder( + stream: mediaStateStream, + builder: (context, snapshot) { + BaseItemDto? currentlyPlayingItem; + if (snapshot.hasData) { + // If we have a media item and the player hasn't finished, show + // the now playing bar. + if (snapshot.data!.mediaItem != null) { + final item = BaseItemDto.fromJson( + snapshot.data!.mediaItem!.extras!["itemJson"]); + currentlyPlayingItem = item; + } + } + return SliverList( + delegate: + SliverChildBuilderDelegate((BuildContext context, int index) { + final BaseItemDto item = childrenForList[index]; + return SongListTile( + item: item, + children: childrenForQueue, + index: index + indexOffset, + parentId: parent.id, + // show artists except for this one scenario + showArtists: !( + // we're on album screen + parent.type == "MusicAlbum" + // "hide song artists if they're the same as album artists" == true + && + FinampSettingsHelper + .finampSettings.hideSongArtistsIfSameAsAlbumArtists + // song artists == album artists + && + setEquals( + parent.albumArtists?.map((e) => e.name).toSet(), + item.artists?.toSet())), + currentlyPlaying: currentlyPlayingItem != null + ? currentlyPlayingItem.id == item.id + : false, + ); + }, childCount: childrenForList.length), + ); + }); } } diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index ade95f2ac..a6d3459bf 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -2,6 +2,7 @@ import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; +import 'package:mini_music_visualizer/mini_music_visualizer.dart'; import '../../models/jellyfin_models.dart'; import '../../services/audio_service_helper.dart'; @@ -29,23 +30,24 @@ enum SongListTileMenuItems { } class SongListTile extends StatefulWidget { - const SongListTile({ - Key? key, - required this.item, - - /// Children that are related to this list tile, such as the other songs in - /// the album. This is used to give the audio service all the songs for the - /// item. If null, only this song will be given to the audio service. - this.children, - - /// Index of the song in whatever parent this widget is in. Used to start - /// the audio service at a certain index, such as when selecting the middle - /// song in an album. - this.index, - this.parentId, - this.isSong = false, - this.showArtists = true, - }) : super(key: key); + const SongListTile( + {Key? key, + required this.item, + + /// Children that are related to this list tile, such as the other songs in + /// the album. This is used to give the audio service all the songs for the + /// item. If null, only this song will be given to the audio service. + this.children, + + /// Index of the song in whatever parent this widget is in. Used to start + /// the audio service at a certain index, such as when selecting the middle + /// song in an album. + this.index, + this.parentId, + this.isSong = false, + this.showArtists = true, + this.currentlyPlaying = false}) + : super(key: key); final BaseItemDto item; final List? children; @@ -53,6 +55,7 @@ class SongListTile extends StatefulWidget { final bool isSong; final String? parentId; final bool showArtists; + final bool currentlyPlaying; @override State createState() => _SongListTileState(); @@ -140,9 +143,24 @@ class _SongListTileState extends State { ), overflow: TextOverflow.ellipsis, ), - trailing: FavoriteButton( - item: mutableItem, - onlyIfFav: true, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Visibility( + visible: widget.currentlyPlaying, + child: const Padding( + padding: EdgeInsets.fromLTRB(0, 0, 8, 0), + child: MiniMusicVisualizer( + color: Colors.blue, + width: 4, + height: 15, + ), + )), + FavoriteButton( + item: mutableItem, + onlyIfFav: true, + ), + ], ), onTap: () { _audioServiceHelper.replaceQueueWithItem( diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index 6b03f3cc0..063bb8493 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -10,6 +10,7 @@ import '../services/finamp_settings_helper.dart'; import '../services/downloads_helper.dart'; import '../components/now_playing_bar.dart'; import '../components/AlbumScreen/album_screen_content.dart'; +import '../services/music_player_background_task.dart'; class AlbumScreen extends StatefulWidget { const AlbumScreen({ @@ -29,6 +30,7 @@ class AlbumScreen extends StatefulWidget { class _AlbumScreenState extends State { Future?>? albumScreenContentFuture; JellyfinApiHelper jellyfinApiHelper = GetIt.instance(); + final audioHandler = GetIt.instance(); @override Widget build(BuildContext context) { diff --git a/pubspec.yaml b/pubspec.yaml index 8c28bab12..5b4fa5633 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -64,6 +64,7 @@ dependencies: intl: ^0.17.0 auto_size_text: ^3.0.0 flutter_riverpod: ^1.0.4 + mini_music_visualizer: ^1.0.2 dev_dependencies: flutter_test: From b1209325b70f48d926464c0f938fcc8144fe6b64 Mon Sep 17 00:00:00 2001 From: Yonggan Date: Fri, 23 Sep 2022 19:51:57 +0200 Subject: [PATCH 002/172] Fix in Music Screen Tab --- .../MusicScreen/music_screen_tab_view.dart | 614 ++++++++++-------- 1 file changed, 329 insertions(+), 285 deletions(-) diff --git a/lib/components/MusicScreen/music_screen_tab_view.dart b/lib/components/MusicScreen/music_screen_tab_view.dart index cb7e429c2..b0c21d439 100644 --- a/lib/components/MusicScreen/music_screen_tab_view.dart +++ b/lib/components/MusicScreen/music_screen_tab_view.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; - +import '../../services/media_state_stream.dart'; import '../../models/jellyfin_models.dart'; import '../../models/finamp_models.dart'; import '../../services/finamp_user_helper.dart'; @@ -134,305 +134,349 @@ class _MusicScreenTabViewState extends State Widget build(BuildContext context) { super.build(context); - return ValueListenableBuilder>( - valueListenable: FinampSettingsHelper.finampSettingsListener, - builder: (context, box, _) { - final isOffline = box.get("FinampSettings")?.isOffline ?? false; - - if (isOffline) { - // We do the same checks we do when online to ensure that the list is - // not resorted when it doesn't have to be. - if (widget.searchTerm != _lastSearch || - offlineSortedItems == null || - widget.isFavourite != _oldIsFavourite || - widget.sortBy != _oldSortBy || - widget.sortOrder != _oldSortOrder || - widget.view != _oldView) { - _lastSearch = widget.searchTerm; - _oldIsFavourite = widget.isFavourite; - _oldSortBy = widget.sortBy; - _oldSortOrder = widget.sortOrder; - _oldView = widget.view; + return StreamBuilder( + stream: mediaStateStream, + builder: (context, snapshot) { + BaseItemDto? currentlyPlayingItem; + if (snapshot.hasData) { + // If we have a media item and the player hasn't finished, show + // the now playing bar. + if (snapshot.data!.mediaItem != null) { + final item = BaseItemDto.fromJson( + snapshot.data!.mediaItem!.extras!["itemJson"]); + currentlyPlayingItem = item; + } + } + return ValueListenableBuilder>( + valueListenable: FinampSettingsHelper.finampSettingsListener, + builder: (context, box, _) { + final isOffline = box.get("FinampSettings")?.isOffline ?? false; - DownloadsHelper downloadsHelper = GetIt.instance(); + if (isOffline) { + // We do the same checks we do when online to ensure that the list is + // not resorted when it doesn't have to be. + if (widget.searchTerm != _lastSearch || + offlineSortedItems == null || + widget.isFavourite != _oldIsFavourite || + widget.sortBy != _oldSortBy || + widget.sortOrder != _oldSortOrder || + widget.view != _oldView) { + _lastSearch = widget.searchTerm; + _oldIsFavourite = widget.isFavourite; + _oldSortBy = widget.sortBy; + _oldSortOrder = widget.sortOrder; + _oldView = widget.view; - if (widget.tabContentType == TabContentType.artists) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.cloud_off, - size: 64, - color: Colors.white.withOpacity(0.5), - ), - const Padding(padding: EdgeInsets.all(8.0)), - const Text("Offline artists view hasn't been implemented") - ], - ), - ); - } + DownloadsHelper downloadsHelper = + GetIt.instance(); - if (widget.searchTerm == null) { - if (widget.tabContentType == TabContentType.songs) { - // If we're on the songs tab, just get all of the downloaded items - offlineSortedItems = downloadsHelper.downloadedItems - .where((element) => - element.viewId == - _finampUserHelper.currentUser!.currentViewId) - .map((e) => e.song) - .toList(); - } else { - offlineSortedItems = downloadsHelper.downloadedParents - .where((element) => - element.item.type == - _includeItemTypes(widget.tabContentType) && - element.viewId == - _finampUserHelper.currentUser!.currentViewId) - .map((e) => e.item) - .toList(); - } - } else { - if (widget.tabContentType == TabContentType.songs) { - offlineSortedItems = downloadsHelper.downloadedItems - .where( - (element) { - return _offlineSearch( - item: element.song, - searchTerm: widget.searchTerm!, - tabContentType: widget.tabContentType); - }, - ) - .map((e) => e.song) - .toList(); - } else { - offlineSortedItems = downloadsHelper.downloadedParents - .where( - (element) { - return _offlineSearch( - item: element.item, - searchTerm: widget.searchTerm!, - tabContentType: widget.tabContentType); - }, - ) - .map((e) => e.item) - .toList(); - } - } + if (widget.tabContentType == TabContentType.artists) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.cloud_off, + size: 64, + color: Colors.white.withOpacity(0.5), + ), + const Padding(padding: EdgeInsets.all(8.0)), + const Text( + "Offline artists view hasn't been implemented") + ], + ), + ); + } - offlineSortedItems!.sort((a, b) { - // if (a.name == null || b.name == null) { - // // Returning 0 is the same as both being the same - // return 0; - // } else { - // return a.name!.compareTo(b.name!); - // } - if (a.name == null || b.name == null) { - // Returning 0 is the same as both being the same - return 0; - } else { - switch (widget.sortBy) { - case SortBy.sortName: - if (a.name == null || b.name == null) { - // Returning 0 is the same as both being the same - return 0; + if (widget.searchTerm == null) { + if (widget.tabContentType == TabContentType.songs) { + // If we're on the songs tab, just get all of the downloaded items + offlineSortedItems = downloadsHelper.downloadedItems + .where((element) => + element.viewId == + _finampUserHelper.currentUser!.currentViewId) + .map((e) => e.song) + .toList(); } else { - return a.name!.compareTo(b.name!); + offlineSortedItems = downloadsHelper.downloadedParents + .where((element) => + element.item.type == + _includeItemTypes(widget.tabContentType) && + element.viewId == + _finampUserHelper.currentUser!.currentViewId) + .map((e) => e.item) + .toList(); } - case SortBy.albumArtist: - if (a.albumArtist == null || b.albumArtist == null) { - return 0; - } else { - return a.albumArtist!.compareTo(b.albumArtist!); - } - case SortBy.communityRating: - if (a.communityRating == null || - b.communityRating == null) { - return 0; - } else { - return a.communityRating!.compareTo(b.communityRating!); - } - case SortBy.criticRating: - if (a.criticRating == null || b.criticRating == null) { - return 0; + } else { + if (widget.tabContentType == TabContentType.songs) { + offlineSortedItems = downloadsHelper.downloadedItems + .where( + (element) { + return _offlineSearch( + item: element.song, + searchTerm: widget.searchTerm!, + tabContentType: widget.tabContentType); + }, + ) + .map((e) => e.song) + .toList(); } else { - return a.criticRating!.compareTo(b.criticRating!); + offlineSortedItems = downloadsHelper.downloadedParents + .where( + (element) { + return _offlineSearch( + item: element.item, + searchTerm: widget.searchTerm!, + tabContentType: widget.tabContentType); + }, + ) + .map((e) => e.item) + .toList(); } - case SortBy.dateCreated: - if (a.dateCreated == null || b.dateCreated == null) { - return 0; - } else { - return a.dateCreated!.compareTo(b.dateCreated!); - } - case SortBy.premiereDate: - if (a.premiereDate == null || b.premiereDate == null) { + } + + offlineSortedItems!.sort((a, b) { + // if (a.name == null || b.name == null) { + // // Returning 0 is the same as both being the same + // return 0; + // } else { + // return a.name!.compareTo(b.name!); + // } + if (a.name == null || b.name == null) { + // Returning 0 is the same as both being the same return 0; } else { - return a.premiereDate!.compareTo(b.premiereDate!); - } - case SortBy.random: - // We subtract the result by one so that we can get -1 values - // (see comareTo documentation) - return Random().nextInt(2) - 1; - default: - throw UnimplementedError( - "Unimplemented offline sort mode ${widget.sortBy}"); - } - } - }); - - if (widget.sortOrder == SortOrder.descending) { - // The above sort functions sort in ascending order, so we swap them - // when sorting in descending order. - offlineSortedItems = offlineSortedItems!.reversed.toList(); - } - } - - return Scrollbar( - child: box.get("FinampSettings")!.contentViewType == - ContentViewType.list - ? ListView.builder( - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - itemCount: offlineSortedItems!.length, - key: UniqueKey(), - itemBuilder: (context, index) { - if (widget.tabContentType == TabContentType.songs) { - return SongListTile( - item: offlineSortedItems![index], - isSong: true, - ); - } else { - return AlbumItem( - album: offlineSortedItems![index], - parentType: _getParentType(), - ); - } - }, - ) - : GridView.builder( - itemCount: offlineSortedItems!.length, - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context).size.width > - MediaQuery.of(context).size.height - ? box - .get("FinampSettings")! - .contentGridViewCrossAxisCountLandscape - : box - .get("FinampSettings")! - .contentGridViewCrossAxisCountPortrait, - ), - itemBuilder: (context, index) { - if (widget.tabContentType == TabContentType.songs) { - return SongListTile( - item: offlineSortedItems![index], - isSong: true, - ); - } else { - return AlbumItem( - album: offlineSortedItems![index], - parentType: _getParentType(), - isGrid: true, - gridAddSettingsListener: false, - ); - } - }, - )); - } else { - // If the searchTerm argument is different to lastSearch, the user has changed their search input. - // This makes albumViewFuture search again so that results with the search are shown. - // This also means we don't redo a search unless we actaully need to. - if (widget.searchTerm != _lastSearch || - _pagingController.itemList == null || - widget.isFavourite != _oldIsFavourite || - widget.sortBy != _oldSortBy || - widget.sortOrder != _oldSortOrder || - widget.view != _oldView) { - _lastSearch = widget.searchTerm; - _oldIsFavourite = widget.isFavourite; - _oldSortBy = widget.sortBy; - _oldSortOrder = widget.sortOrder; - _oldView = widget.view; - _pagingController.refresh(); - } - - return RefreshIndicator( - // RefreshIndicator wants an async function, so we use Future.sync() - // to run refresh() inside an async function - onRefresh: () => Future.sync(() => _pagingController.refresh()), - child: Scrollbar( - child: box.get("FinampSettings")!.contentViewType == - ContentViewType.list - ? PagedListView( - pagingController: _pagingController, - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - if (widget.tabContentType == TabContentType.songs) { - return SongListTile( - item: item, - isSong: true, - ); - } else if (widget.tabContentType == - TabContentType.artists) { - return ArtistListTile(item: item); + switch (widget.sortBy) { + case SortBy.sortName: + if (a.name == null || b.name == null) { + // Returning 0 is the same as both being the same + return 0; } else { - return AlbumItem( - album: item, - parentType: _getParentType(), - ); + return a.name!.compareTo(b.name!); } - }, - firstPageProgressIndicatorBuilder: (_) => - const FirstPageProgressIndicator(), - newPageProgressIndicatorBuilder: (_) => - const NewPageProgressIndicator(), - ), - ) - : PagedGridView( - pagingController: _pagingController, - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - builderDelegate: PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - if (widget.tabContentType == TabContentType.songs) { - return SongListTile( - item: item, - isSong: true, - ); + case SortBy.albumArtist: + if (a.albumArtist == null || b.albumArtist == null) { + return 0; } else { - return AlbumItem( - album: item, - parentType: _getParentType(), - isGrid: true, - gridAddSettingsListener: false, - ); + return a.albumArtist!.compareTo(b.albumArtist!); } - }, - firstPageProgressIndicatorBuilder: (_) => - const FirstPageProgressIndicator(), - newPageProgressIndicatorBuilder: (_) => - const NewPageProgressIndicator(), - ), - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context).size.width > - MediaQuery.of(context).size.height - ? box - .get("FinampSettings")! - .contentGridViewCrossAxisCountLandscape - : box - .get("FinampSettings")! - .contentGridViewCrossAxisCountPortrait, - ), - ), - ), + case SortBy.communityRating: + if (a.communityRating == null || + b.communityRating == null) { + return 0; + } else { + return a.communityRating! + .compareTo(b.communityRating!); + } + case SortBy.criticRating: + if (a.criticRating == null || + b.criticRating == null) { + return 0; + } else { + return a.criticRating!.compareTo(b.criticRating!); + } + case SortBy.dateCreated: + if (a.dateCreated == null || b.dateCreated == null) { + return 0; + } else { + return a.dateCreated!.compareTo(b.dateCreated!); + } + case SortBy.premiereDate: + if (a.premiereDate == null || + b.premiereDate == null) { + return 0; + } else { + return a.premiereDate!.compareTo(b.premiereDate!); + } + case SortBy.random: + // We subtract the result by one so that we can get -1 values + // (see comareTo documentation) + return Random().nextInt(2) - 1; + default: + throw UnimplementedError( + "Unimplemented offline sort mode ${widget.sortBy}"); + } + } + }); + + if (widget.sortOrder == SortOrder.descending) { + // The above sort functions sort in ascending order, so we swap them + // when sorting in descending order. + offlineSortedItems = offlineSortedItems!.reversed.toList(); + } + } + + return Scrollbar( + child: box.get("FinampSettings")!.contentViewType == + ContentViewType.list + ? ListView.builder( + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + itemCount: offlineSortedItems!.length, + key: UniqueKey(), + itemBuilder: (context, index) { + if (widget.tabContentType == + TabContentType.songs) { + return SongListTile( + item: offlineSortedItems![index], + isSong: true, + currentlyPlaying: currentlyPlayingItem != null + ? currentlyPlayingItem.id == offlineSortedItems![index].id + : false, + ); + } else { + return AlbumItem( + album: offlineSortedItems![index], + parentType: _getParentType(), + ); + } + }, + ) + : GridView.builder( + itemCount: offlineSortedItems!.length, + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context) + .size + .width > + MediaQuery.of(context).size.height + ? box + .get("FinampSettings")! + .contentGridViewCrossAxisCountLandscape + : box + .get("FinampSettings")! + .contentGridViewCrossAxisCountPortrait, + ), + itemBuilder: (context, index) { + if (widget.tabContentType == + TabContentType.songs) { + return SongListTile( + item: offlineSortedItems![index], + isSong: true, + currentlyPlaying: currentlyPlayingItem != null + ? currentlyPlayingItem.id == offlineSortedItems![index].id + : false + ); + } else { + return AlbumItem( + album: offlineSortedItems![index], + parentType: _getParentType(), + isGrid: true, + gridAddSettingsListener: false, + ); + } + }, + )); + } else { + // If the searchTerm argument is different to lastSearch, the user has changed their search input. + // This makes albumViewFuture search again so that results with the search are shown. + // This also means we don't redo a search unless we actaully need to. + if (widget.searchTerm != _lastSearch || + _pagingController.itemList == null || + widget.isFavourite != _oldIsFavourite || + widget.sortBy != _oldSortBy || + widget.sortOrder != _oldSortOrder || + widget.view != _oldView) { + _lastSearch = widget.searchTerm; + _oldIsFavourite = widget.isFavourite; + _oldSortBy = widget.sortBy; + _oldSortOrder = widget.sortOrder; + _oldView = widget.view; + _pagingController.refresh(); + } + + return RefreshIndicator( + // RefreshIndicator wants an async function, so we use Future.sync() + // to run refresh() inside an async function + onRefresh: () => + Future.sync(() => _pagingController.refresh()), + child: Scrollbar( + child: box.get("FinampSettings")!.contentViewType == + ContentViewType.list + ? PagedListView( + pagingController: _pagingController, + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + builderDelegate: + PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + if (widget.tabContentType == + TabContentType.songs) { + return SongListTile( + item: item, + isSong: true, + currentlyPlaying: currentlyPlayingItem != null + ? currentlyPlayingItem.id == item.id + : false + ); + } else if (widget.tabContentType == + TabContentType.artists) { + return ArtistListTile(item: item); + } else { + return AlbumItem( + album: item, + parentType: _getParentType(), + ); + } + }, + firstPageProgressIndicatorBuilder: (_) => + const FirstPageProgressIndicator(), + newPageProgressIndicatorBuilder: (_) => + const NewPageProgressIndicator(), + ), + ) + : PagedGridView( + pagingController: _pagingController, + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + builderDelegate: + PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + if (widget.tabContentType == + TabContentType.songs) { + return SongListTile( + item: item, + isSong: true, + currentlyPlaying: currentlyPlayingItem != null + ? currentlyPlayingItem.id == item.id + : false, + ); + } else { + return AlbumItem( + album: item, + parentType: _getParentType(), + isGrid: true, + gridAddSettingsListener: false, + ); + } + }, + firstPageProgressIndicatorBuilder: (_) => + const FirstPageProgressIndicator(), + newPageProgressIndicatorBuilder: (_) => + const NewPageProgressIndicator(), + ), + gridDelegate: + SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context) + .size + .width > + MediaQuery.of(context).size.height + ? box + .get("FinampSettings")! + .contentGridViewCrossAxisCountLandscape + : box + .get("FinampSettings")! + .contentGridViewCrossAxisCountPortrait, + ), + ), + ), + ); + } + }, ); - } - }, - ); + }); } } From bc2b698d5f0c4345582b930afd9bf0df1ed5d942 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 2 Feb 2023 19:01:32 +0000 Subject: [PATCH 003/172] Update pubspec --- pubspec.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.lock b/pubspec.lock index 6ee7bf50c..1b37cf63f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1140,5 +1140,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <4.0.0" + dart: ">=2.19.0 <3.0.0" flutter: ">=3.3.0" From 109fbd4f22b1fe3f2259bf55c9c8ce003c39a705 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 2 Feb 2023 19:01:45 +0000 Subject: [PATCH 004/172] Actually fix Chopper concatenating path incorrectly --- lib/services/jellyfin_api.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/services/jellyfin_api.dart b/lib/services/jellyfin_api.dart index 91d7c205c..48173dbef 100644 --- a/lib/services/jellyfin_api.dart +++ b/lib/services/jellyfin_api.dart @@ -383,8 +383,9 @@ abstract class JellyfinApi extends ChopperService { Uri.parse(finampUserHelper.currentUser!.baseUrl); // Add the request path on to the baseUrl - baseUri = - baseUri.replace(path: path.join(baseUri.path, request.uri.path)); + baseUri = baseUri.replace( + pathSegments: + baseUri.pathSegments.followedBy(request.uri.pathSegments)); // tokenHeader will be null if the user isn't logged in. // If we send a null tokenHeader while logging in, the login will always fail. From 531625096ac23f2d2b67777f101d6c2f6a80cbdc Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 10 Feb 2023 17:28:34 +0000 Subject: [PATCH 005/172] Fix transcoding for media with lower than transcode bitrate --- .../music_player_background_task.dart | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index f493706a4..71ef25c62 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -564,7 +564,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { "universal", ]); - var x = Uri( + Uri uri = Uri( host: parsedBaseUrl.host, port: parsedBaseUrl.port, scheme: parsedBaseUrl.scheme, @@ -572,21 +572,23 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { queryParameters: { "UserId": _finampUserHelper.currentUser!.id, "DeviceId": androidId ?? iosDeviceInfo!.identifierForVendor, - // TODO: Do platform checks for this - "Container": - "opus,webm|opus,mp3,aac,m4a|aac,m4a|alac,m4b|aac,flac,webma,webm|webma,wav,ogg", - "MaxStreamingBitrate": mediaItem.extras!["shouldTranscode"] - ? FinampSettingsHelper.finampSettings.transcodeBitrate.toString() - : "999999999", - "AudioCodec": "aac", - "TranscodingContainer": "ts", - "TranscodingProtocol": - mediaItem.extras!["shouldTranscode"] ? "hls" : "http", "ApiKey": _finampUserHelper.currentUser!.accessToken, }, ); - return x; + if (mediaItem.extras!["shouldTranscode"]) { + final queryParameters = Map.of(uri.queryParameters); + + queryParameters.addAll({ + "TranscodingProtocol": "hls", + "AudioBitRate": + FinampSettingsHelper.finampSettings.transcodeBitrate.toString(), + }); + + uri = uri.replace(queryParameters: queryParameters); + } + + return uri; } } From 20e0249d672dbdee5954ac4b118d28db8b76aa08 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Tue, 21 Feb 2023 11:53:32 +0100 Subject: [PATCH 006/172] Trim searchTerm before searching --- lib/components/MusicScreen/music_screen_tab_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/MusicScreen/music_screen_tab_view.dart b/lib/components/MusicScreen/music_screen_tab_view.dart index cb7e429c2..ac1887e67 100644 --- a/lib/components/MusicScreen/music_screen_tab_view.dart +++ b/lib/components/MusicScreen/music_screen_tab_view.dart @@ -94,7 +94,7 @@ class _MusicScreenTabViewState extends State : "SortName"), sortOrder: widget.sortOrder?.toString() ?? SortOrder.ascending.toString(), - searchTerm: widget.searchTerm, + searchTerm: widget.searchTerm?.trim(), // If this is the genres tab, tell getItems to get genres. isGenres: widget.tabContentType == TabContentType.genres, filters: widget.isFavourite ? "IsFavorite" : null, From bef4e3a365ead65850368700073fd7f6437f89a7 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Tue, 21 Feb 2023 18:37:39 +0100 Subject: [PATCH 007/172] :bug: Fix crash in ErrorApp --- lib/main.dart | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 44df4488c..7ba29eb1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -371,10 +371,22 @@ class FinampErrorApp extends StatelessWidget { GlobalCupertinoLocalizations.delegate, ], supportedLocales: AppLocalizations.supportedLocales, - home: Scaffold( - body: Center( - child: Text( - AppLocalizations.of(context)!.startupError(error.toString())), + home: ErrorScreen(error: error), + ); + } +} + +class ErrorScreen extends StatelessWidget { + const ErrorScreen({super.key, this.error}); + + final dynamic error; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Center( + child: Text( + AppLocalizations.of(context)!.startupError(error.toString()), ), ), ); From 816ec671e20de8ca6d3684351018c49ce6629cc4 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 22 Feb 2023 11:59:31 +0100 Subject: [PATCH 008/172] :sparkles: Change sort options to apply per tab --- .../MusicScreen/sort_by_menu_button.dart | 15 +++++--- .../MusicScreen/sort_order_button.dart | 12 ++++--- lib/main.dart | 28 +++++++++++++++ lib/models/finamp_models.dart | 26 +++++++++++--- lib/models/finamp_models.g.dart | 14 ++++++-- lib/screens/music_screen.dart | 36 ++++++++++--------- lib/services/finamp_settings_helper.dart | 8 ++--- 7 files changed, 101 insertions(+), 38 deletions(-) diff --git a/lib/components/MusicScreen/sort_by_menu_button.dart b/lib/components/MusicScreen/sort_by_menu_button.dart index 83243ed50..b9d881859 100644 --- a/lib/components/MusicScreen/sort_by_menu_button.dart +++ b/lib/components/MusicScreen/sort_by_menu_button.dart @@ -1,3 +1,4 @@ +import 'package:finamp/models/finamp_models.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; @@ -5,7 +6,9 @@ import '../../models/jellyfin_models.dart'; import '../../services/finamp_settings_helper.dart'; class SortByMenuButton extends StatelessWidget { - const SortByMenuButton({Key? key}) : super(key: key); + const SortByMenuButton(this.tabType, {Key? key}) : super(key: key); + + final TabContentType tabType; @override Widget build(BuildContext context) { @@ -19,14 +22,16 @@ class SortByMenuButton extends StatelessWidget { child: Text( sortBy.toLocalisedString(context), style: TextStyle( - color: FinampSettingsHelper.finampSettings.sortBy == sortBy - ? Theme.of(context).colorScheme.secondary - : null, + color: + FinampSettingsHelper.finampSettings.getTabSortBy(tabType) == + sortBy + ? Theme.of(context).colorScheme.secondary + : null, ), ), ) ], - onSelected: (value) => FinampSettingsHelper.setSortBy(value), + onSelected: (value) => FinampSettingsHelper.setSortBy(tabType, value), ); } } diff --git a/lib/components/MusicScreen/sort_order_button.dart b/lib/components/MusicScreen/sort_order_button.dart index ffc2e9d64..09d8cab71 100644 --- a/lib/components/MusicScreen/sort_order_button.dart +++ b/lib/components/MusicScreen/sort_order_button.dart @@ -7,7 +7,9 @@ import '../../models/finamp_models.dart'; import '../../services/finamp_settings_helper.dart'; class SortOrderButton extends StatelessWidget { - const SortOrderButton({Key? key}) : super(key: key); + const SortOrderButton(this.tabType, {Key? key}) : super(key: key); + + final TabContentType tabType; @override Widget build(BuildContext context) { @@ -18,14 +20,14 @@ class SortOrderButton extends StatelessWidget { return IconButton( tooltip: AppLocalizations.of(context)!.sortOrder, - icon: finampSettings!.sortOrder == SortOrder.ascending + icon: finampSettings!.getSortOrder(tabType) == SortOrder.ascending ? const Icon(Icons.arrow_downward) : const Icon(Icons.arrow_upward), onPressed: () { - if (finampSettings.sortOrder == SortOrder.ascending) { - FinampSettingsHelper.setSortOrder(SortOrder.descending); + if (finampSettings.getSortOrder(tabType) == SortOrder.ascending) { + FinampSettingsHelper.setSortOrder(tabType,SortOrder.descending); } else { - FinampSettingsHelper.setSortOrder(SortOrder.ascending); + FinampSettingsHelper.setSortOrder(tabType, SortOrder.ascending); } }, ); diff --git a/lib/main.dart b/lib/main.dart index 7ba29eb1f..14055535f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -54,6 +54,7 @@ void main() async { setupLogging(); await setupHive(); _migrateDownloadLocations(); + _migrateSortOptions(); _setupFinampUserHelper(); _setupJellyfinApiData(); await _setupDownloader(); @@ -218,6 +219,33 @@ void _migrateDownloadLocations() { } } +/// Migrates the old SortBy/SortOrder to a map indexed by tab content type +void _migrateSortOptions() { + final finampSettings = FinampSettingsHelper.finampSettings; + + var changed = false; + + if (finampSettings.tabSortBy.isEmpty) { + for (var type in TabContentType.values) { + // ignore: deprecated_member_use_from_same_package + finampSettings.tabSortBy[type] = finampSettings.sortBy; + } + changed = true; + } + + if (finampSettings.tabSortOrder.isEmpty) { + for (var type in TabContentType.values) { + // ignore: deprecated_member_use_from_same_package + finampSettings.tabSortOrder[type] = finampSettings.sortOrder; + } + changed = true; + } + + if (changed) { + FinampSettingsHelper.overwriteFinampSettings(finampSettings); + } +} + void _setupFinampUserHelper() { GetIt.instance.registerSingleton(FinampUserHelper()); } diff --git a/lib/models/finamp_models.dart b/lib/models/finamp_models.dart index 9ed7874b5..178f40624 100644 --- a/lib/models/finamp_models.dart +++ b/lib/models/finamp_models.dart @@ -82,6 +82,8 @@ class FinampSettings { this.hideSongArtistsIfSameAsAlbumArtists = _hideSongArtistsIfSameAsAlbumArtists, this.bufferDurationSeconds = _bufferDurationSeconds, + required this.tabSortBy, + required this.tabSortOrder, }); @HiveField(0) @@ -106,10 +108,12 @@ class FinampSettings { bool isFavourite; /// Current sort by setting. + @Deprecated("Use per-tab sort by instead") @HiveField(7) SortBy sortBy; /// Current sort order setting. + @Deprecated("Use per-tab sort order instead") @HiveField(8) SortOrder sortOrder; @@ -157,6 +161,12 @@ class FinampSettings { @HiveField(19, defaultValue: _disableGesture) bool disableGesture = _disableGesture; + @HiveField(20, defaultValue: {}) + Map tabSortBy; + + @HiveField(21, defaultValue: {}) + Map tabSortOrder; + static Future create() async { final internalSongDir = await getInternalSongDir(); final downloadLocation = DownloadLocation.create( @@ -174,6 +184,8 @@ class FinampSettings { ), ), downloadLocationsMap: {downloadLocation.id: downloadLocation}, + tabSortBy: {}, + tabSortOrder: {}, ); } @@ -187,6 +199,14 @@ class FinampSettings { set bufferDuration(Duration duration) => bufferDurationSeconds = duration.inSeconds; + + SortBy getTabSortBy(TabContentType tabType) { + return tabSortBy[tabType] ?? SortBy.sortName; + } + + SortOrder getSortOrder(TabContentType tabType) { + return tabSortOrder[tabType] ?? SortOrder.ascending; + } } /// Custom storage locations for storing music. @@ -262,16 +282,12 @@ class NewDownloadLocation { enum TabContentType { @HiveField(0) albums, - @HiveField(1) artists, - @HiveField(2) playlists, - @HiveField(3) genres, - @HiveField(4) songs; @@ -321,7 +337,6 @@ enum TabContentType { enum ContentViewType { @HiveField(0) list, - @HiveField(1) grid; @@ -445,6 +460,7 @@ class DownloadedSong { factory DownloadedSong.fromJson(Map json) => _$DownloadedSongFromJson(json); + Map toJson() => _$DownloadedSongToJson(this); } diff --git a/lib/models/finamp_models.g.dart b/lib/models/finamp_models.g.dart index ac8038641..a9935509e 100644 --- a/lib/models/finamp_models.g.dart +++ b/lib/models/finamp_models.g.dart @@ -93,13 +93,19 @@ class FinampSettingsAdapter extends TypeAdapter { hideSongArtistsIfSameAsAlbumArtists: fields[17] == null ? true : fields[17] as bool, bufferDurationSeconds: fields[18] == null ? 50 : fields[18] as int, + tabSortBy: fields[20] == null + ? {} + : (fields[20] as Map).cast(), + tabSortOrder: fields[21] == null + ? {} + : (fields[21] as Map).cast(), )..disableGesture = fields[19] == null ? false : fields[19] as bool; } @override void write(BinaryWriter writer, FinampSettings obj) { writer - ..writeByte(20) + ..writeByte(22) ..writeByte(0) ..write(obj.isOffline) ..writeByte(1) @@ -139,7 +145,11 @@ class FinampSettingsAdapter extends TypeAdapter { ..writeByte(18) ..write(obj.bufferDurationSeconds) ..writeByte(19) - ..write(obj.disableGesture); + ..write(obj.disableGesture) + ..writeByte(20) + ..write(obj.tabSortBy) + ..writeByte(21) + ..write(obj.tabSortOrder); } @override diff --git a/lib/screens/music_screen.dart b/lib/screens/music_screen.dart index 5256dea6e..0743558c4 100644 --- a/lib/screens/music_screen.dart +++ b/lib/screens/music_screen.dart @@ -165,13 +165,13 @@ class _MusicScreenState extends State valueListenable: FinampSettingsHelper.finampSettingsListener, builder: (context, value, _) { final finampSettings = value.get("FinampSettings"); + final tabs = finampSettings!.showTabs.entries + .where((e) => e.value) + .map((e) => e.key); - if (finampSettings!.showTabs.entries - .where((element) => element.value) - .length != - _tabController?.length) { + if (tabs.length != _tabController?.length) { _musicScreenLogger.info( - "Rebuilding MusicScreen tab controller (${finampSettings.showTabs.entries.where((element) => element.value).length} != ${_tabController?.length})"); + "Rebuilding MusicScreen tab controller (${tabs.length} != ${_tabController?.length})"); _buildTabController(); } @@ -202,10 +202,9 @@ class _MusicScreenState extends State AppLocalizations.of(context)!.music), bottom: TabBar( controller: _tabController, - tabs: finampSettings.showTabs.entries - .where((element) => element.value) - .map((e) => Tab( - text: e.key + tabs: tabs + .map((tabType) => Tab( + text: tabType .toLocalisedString(context) .toUpperCase(), )) @@ -232,8 +231,12 @@ class _MusicScreenState extends State ) ] : [ - const SortOrderButton(), - const SortByMenuButton(), + SortOrderButton( + tabs.elementAt(_tabController!.index), + ), + SortByMenuButton( + tabs.elementAt(_tabController!.index), + ), IconButton( icon: finampSettings.isFavourite ? const Icon(Icons.favorite) @@ -259,14 +262,13 @@ class _MusicScreenState extends State floatingActionButton: getFloatingActionButton(), body: TabBarView( controller: _tabController, - children: finampSettings.showTabs.entries - .where((element) => element.value) - .map((e) => MusicScreenTabView( - tabContentType: e.key, + children: tabs + .map((tabType) => MusicScreenTabView( + tabContentType: tabType, searchTerm: searchQuery, isFavourite: finampSettings.isFavourite, - sortBy: finampSettings.sortBy, - sortOrder: finampSettings.sortOrder, + sortBy: finampSettings.getTabSortBy(tabType), + sortOrder: finampSettings.getSortOrder(tabType), view: _finampUserHelper.currentUser?.currentView, )) .toList(), diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index a6d271276..86666d2dc 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -85,16 +85,16 @@ class FinampSettingsHelper { .put("FinampSettings", finampSettingsTemp); } - static void setSortBy(SortBy sortBy) { + static void setSortBy(TabContentType tabType, SortBy sortBy) { FinampSettings finampSettingsTemp = finampSettings; - finampSettingsTemp.sortBy = sortBy; + finampSettingsTemp.tabSortBy[tabType] = sortBy; Hive.box("FinampSettings") .put("FinampSettings", finampSettingsTemp); } - static void setSortOrder(SortOrder sortOrder) { + static void setSortOrder(TabContentType tabType, SortOrder sortOrder) { FinampSettings finampSettingsTemp = finampSettings; - finampSettingsTemp.sortOrder = sortOrder; + finampSettingsTemp.tabSortOrder[tabType] = sortOrder; Hive.box("FinampSettings") .put("FinampSettings", finampSettingsTemp); } From 39fafc35a67c99629a6961026aef6a2b035fde15 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 22 Feb 2023 12:41:27 +0100 Subject: [PATCH 009/172] :bug: Fix vertical padding in artist/genre lists --- lib/components/MusicScreen/music_screen_tab_view.dart | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/components/MusicScreen/music_screen_tab_view.dart b/lib/components/MusicScreen/music_screen_tab_view.dart index ac1887e67..b4ccab862 100644 --- a/lib/components/MusicScreen/music_screen_tab_view.dart +++ b/lib/components/MusicScreen/music_screen_tab_view.dart @@ -365,7 +365,7 @@ class _MusicScreenTabViewState extends State child: Scrollbar( child: box.get("FinampSettings")!.contentViewType == ContentViewType.list - ? PagedListView( + ? PagedListView.separated( pagingController: _pagingController, keyboardDismissBehavior: ScrollViewKeyboardDismissBehavior.onDrag, @@ -391,6 +391,13 @@ class _MusicScreenTabViewState extends State newPageProgressIndicatorBuilder: (_) => const NewPageProgressIndicator(), ), + separatorBuilder: (context, index) => SizedBox( + height: widget.tabContentType == + TabContentType.artists || + widget.tabContentType == TabContentType.genres + ? 16.0 + : 0.0, + ), ) : PagedGridView( pagingController: _pagingController, From 0811fbc9c63b12991b6aca470c1daeb74c86a317 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 1 Mar 2023 02:40:56 +0100 Subject: [PATCH 010/172] :bug: Fix singles not having any songs in album screen --- lib/components/AlbumScreen/album_screen_content.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/AlbumScreen/album_screen_content.dart b/lib/components/AlbumScreen/album_screen_content.dart index 74f30e317..fd96755df 100644 --- a/lib/components/AlbumScreen/album_screen_content.dart +++ b/lib/components/AlbumScreen/album_screen_content.dart @@ -104,7 +104,7 @@ class _AlbumScreenContentState extends State { onDelete: onDelete, ), ) - else if (widget.children.length > 1) + else if (widget.children.isNotEmpty) SongsSliverList( childrenForList: widget.children, childrenForQueue: widget.children, From f986a726a401f9790603a757e9108faa532da9ff Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Thu, 9 Mar 2023 16:58:15 +0100 Subject: [PATCH 011/172] :eyes: Download original images instead of always converting them to png --- lib/services/downloads_helper.dart | 7 ++++--- lib/services/jellyfin_api_helper.dart | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 8bab94041..bee53d142 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -880,13 +880,14 @@ class DownloadsHelper { final imageUrl = _jellyfinApiData.getImageUrl( item: item, - quality: 100, - format: "png", + // Download original file + quality: null, + format: null, ); final tokenHeader = _jellyfinApiData.getTokenHeader(); final relativePath = path_helper.relative(downloadDir.path, from: downloadLocation.path); - final fileName = "${item.imageId}.png"; + final fileName = item.imageId; final imageDownloadId = await FlutterDownloader.enqueue( url: imageUrl.toString(), diff --git a/lib/services/jellyfin_api_helper.dart b/lib/services/jellyfin_api_helper.dart index 5d5c953fc..37bbedd00 100644 --- a/lib/services/jellyfin_api_helper.dart +++ b/lib/services/jellyfin_api_helper.dart @@ -445,8 +445,8 @@ class JellyfinApiHelper { required BaseItemDto item, int? maxWidth, int? maxHeight, - int quality = 90, - String format = "jpg", + int? quality = 90, + String? format = "jpg", }) { if (item.imageId == null) { return null; @@ -466,8 +466,8 @@ class JellyfinApiHelper { scheme: parsedBaseUrl.scheme, pathSegments: builtPath, queryParameters: { - "format": format, - "quality": quality.toString(), + if (format != null) "format": format, + if (quality != null) "quality": quality.toString(), if (maxWidth != null) "MaxWidth": maxWidth.toString(), if (maxHeight != null) "MaxHeight": maxHeight.toString(), }); From ec81816eaddb3fbb2d30637fac06e07b1c664d7c Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Thu, 9 Mar 2023 20:42:49 +0100 Subject: [PATCH 012/172] :sparkles: Sort downloaded items --- .../downloaded_albums_list.dart | 8 ------ lib/services/downloads_helper.dart | 27 ++++++++++++++++--- 2 files changed, 24 insertions(+), 11 deletions(-) diff --git a/lib/components/DownloadsScreen/downloaded_albums_list.dart b/lib/components/DownloadsScreen/downloaded_albums_list.dart index 43f97c8c6..4263865dd 100644 --- a/lib/components/DownloadsScreen/downloaded_albums_list.dart +++ b/lib/components/DownloadsScreen/downloaded_albums_list.dart @@ -36,14 +36,6 @@ class _DownloadedAlbumsListState extends State { final Iterable downloadedParents = downloadsHelper.downloadedParents; - // downloadedParents.sort((a, b) { - // // This may not work, haven't tested it :) - // if (a.item.name != null && b.item.name != null) { - // return a.item.name!.compareTo(b.item.name!); - // } - // return 0; - // }); - return SliverList( delegate: SliverChildBuilderDelegate( (context, index) { diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index bee53d142..2d9791936 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -28,6 +28,29 @@ class DownloadsHelper { final _downloadsLogger = Logger("DownloadsHelper"); + List? _downloadedParentsCache; + + Iterable get downloadedParents => + _downloadedParentsCache ?? _loadSortedDownloadedParents(); + + DownloadsHelper() { + _downloadedParentsBox.watch().listen((event) { + _downloadedParentsCache = null; + }); + } + + List _loadSortedDownloadedParents() { + return _downloadedParentsCache = _downloadedParentsBox.values.toList() + ..sort((a, b) { + final nameA = a.item.name; + final nameB = b.item.name; + + return nameA != null && nameB != null + ? nameA.toLowerCase().compareTo(nameB.toLowerCase()) + : 0; + }); + } + Future addDownloads({ required List items, required BaseItemDto parent, @@ -820,10 +843,8 @@ class DownloadsHelper { return deleteFutures.length; } - Iterable get downloadedParents => - _downloadedParentsBox.values; - Iterable get downloadedItems => _downloadedItemsBox.values; + Iterable get downloadedImages => _downloadedImagesBox.values; ValueListenable> getDownloadedItemsListenable( From aee1efc088ef182dcfd03081074dbd5ccc9a7a44 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sun, 19 Mar 2023 03:49:44 +0000 Subject: [PATCH 013/172] Add timeout to logout --- lib/services/jellyfin_api_helper.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/services/jellyfin_api_helper.dart b/lib/services/jellyfin_api_helper.dart index 37bbedd00..35fc835ae 100644 --- a/lib/services/jellyfin_api_helper.dart +++ b/lib/services/jellyfin_api_helper.dart @@ -406,7 +406,11 @@ class JellyfinApiHelper { // user can still log out during scenarios like wrong IP, no internet etc. try { - response = await jellyfinApi.logout(); + response = await jellyfinApi.logout().timeout( + const Duration(seconds: 3), + onTimeout: () => _jellyfinApiHelperLogger.warning( + "Logout request timed out. Logging out anyway, but be aware that Jellyfin may have not got the signal."), + ); } catch (e) { _jellyfinApiHelperLogger.warning( "Jellyfin logout failed. Logging out anyway, but be aware that Jellyfin may have not got the signal.", From 93267a478772b9995cf7bf2ce9e352ab658e8a08 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 10 Apr 2023 02:53:59 +0100 Subject: [PATCH 014/172] Revert "Fix transcoding for media with lower than transcode bitrate" This reverts commit 531625096ac23f2d2b67777f101d6c2f6a80cbdc. --- .../music_player_background_task.dart | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 71ef25c62..f493706a4 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -564,7 +564,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { "universal", ]); - Uri uri = Uri( + var x = Uri( host: parsedBaseUrl.host, port: parsedBaseUrl.port, scheme: parsedBaseUrl.scheme, @@ -572,23 +572,21 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { queryParameters: { "UserId": _finampUserHelper.currentUser!.id, "DeviceId": androidId ?? iosDeviceInfo!.identifierForVendor, + // TODO: Do platform checks for this + "Container": + "opus,webm|opus,mp3,aac,m4a|aac,m4a|alac,m4b|aac,flac,webma,webm|webma,wav,ogg", + "MaxStreamingBitrate": mediaItem.extras!["shouldTranscode"] + ? FinampSettingsHelper.finampSettings.transcodeBitrate.toString() + : "999999999", + "AudioCodec": "aac", + "TranscodingContainer": "ts", + "TranscodingProtocol": + mediaItem.extras!["shouldTranscode"] ? "hls" : "http", "ApiKey": _finampUserHelper.currentUser!.accessToken, }, ); - if (mediaItem.extras!["shouldTranscode"]) { - final queryParameters = Map.of(uri.queryParameters); - - queryParameters.addAll({ - "TranscodingProtocol": "hls", - "AudioBitRate": - FinampSettingsHelper.finampSettings.transcodeBitrate.toString(), - }); - - uri = uri.replace(queryParameters: queryParameters); - } - - return uri; + return x; } } From b03c119a757340ff1f4b669c4b0a28472932bc4e Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 10 Apr 2023 03:08:06 +0100 Subject: [PATCH 015/172] Use Items/{itemId}/File for direct play --- .../music_player_background_task.dart | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index f493706a4..dccfcb7e5 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -558,32 +558,47 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { final parsedBaseUrl = Uri.parse(_finampUserHelper.currentUser!.baseUrl); List builtPath = List.from(parsedBaseUrl.pathSegments); - builtPath.addAll([ - "Audio", - mediaItem.extras!["itemJson"]["Id"], - "universal", - ]); + + if (mediaItem.extras!["shouldTranscode"]) { + builtPath.addAll([ + "Audio", + mediaItem.extras!["itemJson"]["Id"], + "universal", + ]); + } else { + builtPath.addAll([ + "Items", + mediaItem.extras!["itemJson"]["Id"], + "File", + ]); + } var x = Uri( host: parsedBaseUrl.host, port: parsedBaseUrl.port, scheme: parsedBaseUrl.scheme, pathSegments: builtPath, - queryParameters: { - "UserId": _finampUserHelper.currentUser!.id, - "DeviceId": androidId ?? iosDeviceInfo!.identifierForVendor, - // TODO: Do platform checks for this - "Container": - "opus,webm|opus,mp3,aac,m4a|aac,m4a|alac,m4b|aac,flac,webma,webm|webma,wav,ogg", - "MaxStreamingBitrate": mediaItem.extras!["shouldTranscode"] - ? FinampSettingsHelper.finampSettings.transcodeBitrate.toString() - : "999999999", - "AudioCodec": "aac", - "TranscodingContainer": "ts", - "TranscodingProtocol": - mediaItem.extras!["shouldTranscode"] ? "hls" : "http", - "ApiKey": _finampUserHelper.currentUser!.accessToken, - }, + queryParameters: mediaItem.extras!["shouldTranscode"] + ? { + "UserId": _finampUserHelper.currentUser!.id, + "DeviceId": androidId ?? iosDeviceInfo!.identifierForVendor, + // TODO: Do platform checks for this + "Container": + "opus,webm|opus,mp3,aac,m4a|aac,m4a|alac,m4b|aac,flac,webma,webm|webma,wav,ogg", + "MaxStreamingBitrate": mediaItem.extras!["shouldTranscode"] + ? FinampSettingsHelper.finampSettings.transcodeBitrate + .toString() + : "999999999", + "AudioBitRate": FinampSettingsHelper + .finampSettings.transcodeBitrate + .toString(), + "AudioCodec": "aac", + "TranscodingContainer": "ts", + "TranscodingProtocol": + mediaItem.extras!["shouldTranscode"] ? "hls" : "http", + "ApiKey": _finampUserHelper.currentUser!.accessToken, + } + : {"ApiKey": _finampUserHelper.currentUser!.accessToken}, ); return x; From 2da988aa4420e2bf8424e7ab7a5bcb0eeccdfc68 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:17:13 +0100 Subject: [PATCH 016/172] Use /Audio/{itemId}/main.m3u8 for transcoded audio This will always transcode with HLS, hopefully solving many edge cases around universal audio not transcoding when it should --- .../music_player_background_task.dart | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index dccfcb7e5..fe7110fb8 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -557,14 +557,31 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { final parsedBaseUrl = Uri.parse(_finampUserHelper.currentUser!.baseUrl); - List builtPath = List.from(parsedBaseUrl.pathSegments); + List builtPath = List.from(parsedBaseUrl.pathSegments); + + Map queryParameters = + Map.from(parsedBaseUrl.queryParameters); + + // We include the user token as a query parameter because just_audio used to + // have issues with headers in HLS, and this solution still works fine + queryParameters["ApiKey"] = _finampUserHelper.currentUser!.accessToken; if (mediaItem.extras!["shouldTranscode"]) { builtPath.addAll([ "Audio", mediaItem.extras!["itemJson"]["Id"], - "universal", + "main.m3u8", ]); + + queryParameters.addAll({ + "audioCodec": "aac", + // Ideally we'd use 48kHz when the source is, realistically it doesn't + // matter too much + "audioSampleRate": "44100", + "maxAudioBitDepth": "16", + "audioBitRate": + FinampSettingsHelper.finampSettings.transcodeBitrate.toString(), + }); } else { builtPath.addAll([ "Items", @@ -573,35 +590,13 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { ]); } - var x = Uri( + return Uri( host: parsedBaseUrl.host, port: parsedBaseUrl.port, scheme: parsedBaseUrl.scheme, pathSegments: builtPath, - queryParameters: mediaItem.extras!["shouldTranscode"] - ? { - "UserId": _finampUserHelper.currentUser!.id, - "DeviceId": androidId ?? iosDeviceInfo!.identifierForVendor, - // TODO: Do platform checks for this - "Container": - "opus,webm|opus,mp3,aac,m4a|aac,m4a|alac,m4b|aac,flac,webma,webm|webma,wav,ogg", - "MaxStreamingBitrate": mediaItem.extras!["shouldTranscode"] - ? FinampSettingsHelper.finampSettings.transcodeBitrate - .toString() - : "999999999", - "AudioBitRate": FinampSettingsHelper - .finampSettings.transcodeBitrate - .toString(), - "AudioCodec": "aac", - "TranscodingContainer": "ts", - "TranscodingProtocol": - mediaItem.extras!["shouldTranscode"] ? "hls" : "http", - "ApiKey": _finampUserHelper.currentUser!.accessToken, - } - : {"ApiKey": _finampUserHelper.currentUser!.accessToken}, + queryParameters: queryParameters, ); - - return x; } } From 7f0b3e4dded06081f824d252c8867a0cf118e278 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 30 Jan 2023 14:32:14 +0100 Subject: [PATCH 017/172] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ --- lib/l10n/app_ar.arb | 4 ---- lib/l10n/app_bg.arb | 4 ---- lib/l10n/app_cs.arb | 2 -- lib/l10n/app_de.arb | 2 -- lib/l10n/app_es.arb | 4 ---- lib/l10n/app_et.arb | 2 -- lib/l10n/app_fr.arb | 6 +----- lib/l10n/app_hu.arb | 4 ---- lib/l10n/app_it.arb | 2 -- lib/l10n/app_nb.arb | 4 ---- lib/l10n/app_nl.arb | 2 -- lib/l10n/app_pl.arb | 4 ---- lib/l10n/app_pt.arb | 2 -- lib/l10n/app_pt_BR.arb | 2 -- lib/l10n/app_ru.arb | 4 ---- lib/l10n/app_sv.arb | 2 -- lib/l10n/app_szl.arb | 2 -- lib/l10n/app_zh.arb | 4 ---- lib/l10n/app_zh_Hant.arb | 2 -- 19 files changed, 1 insertion(+), 57 deletions(-) diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index 668f1a4d5..ca39987c5 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -327,8 +327,6 @@ "@queueReplaced": {}, "startingInstantMix": "ابتداء خلط فوري.", "@startingInstantMix": {}, - "favouriteRemoved": "تم إزالة من المفضلات.", - "@favouriteRemoved": {}, "addDownloadLocation": "إضافة مكان التنزيل", "@addDownloadLocation": {}, "errorScreenError": "حدث خطأ اثناء حصول على قائمة الأخطأ! في هذه المرحلة ربما يجب عليك ان تفتح تعليقة على <‎GitHub> و تمسح تخزين التطبيق", @@ -413,8 +411,6 @@ "@downloadsDeleted": {}, "failedToGetSongFromDownloadId": "فشل ان يجد الاغنية من رقم التنزيل", "@failedToGetSongFromDownloadId": {}, - "favouriteAdded": "تم اضافة إلى المفضلات.", - "@favouriteAdded": {}, "internalExternalIpExplanation": "إذا تريد ان توصل خدامة \"جلي فين\" من بعيد, يجيب ان تستعمل آي بي(IP) الخارجي.\n\nإذا خادمك يستعمل منافذ الويب(80 أو 443), لا يجب ان تحدد منفذ. هذه محتمل جدا إذا خادمك بستعمل\"Reverse proxy\".", "@internalExternalIpExplanation": { "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." diff --git a/lib/l10n/app_bg.arb b/lib/l10n/app_bg.arb index 9cb37ae13..cdcbe2f94 100644 --- a/lib/l10n/app_bg.arb +++ b/lib/l10n/app_bg.arb @@ -309,8 +309,6 @@ "@queueReplaced": {}, "startingInstantMix": "Стартиране на незабавен микс.", "@startingInstantMix": {}, - "favouriteAdded": "Добавено в любими.", - "@favouriteAdded": {}, "anErrorHasOccured": "Появи се грешка.", "@anErrorHasOccured": {}, "responseError": "{error} Код на грешката {statusCode}.", @@ -425,8 +423,6 @@ "@instantMix": {}, "addedToQueue": "Добавено към опашката.", "@addedToQueue": {}, - "favouriteRemoved": "Премахване на любими.", - "@favouriteRemoved": {}, "responseError401": "{error} Код на грешката {statusCode}. Това вероятно означава, че сте използвали грешно потребителско име/парола, или вашият клиент вече не е удостоверен.", "@responseError401": { "placeholders": { diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index b4a9c7ffc..4ef069f0f 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -142,8 +142,6 @@ "@anErrorHasOccured": {}, "downloaded": "STAŽENO", "@downloaded": {}, - "favouriteAdded": "Přidáno do oblíbených", - "@favouriteAdded": {}, "favouriteRemoved": "Odebráno z oblíbených.", "@favouriteRemoved": {}, "goToAlbum": "Přejít na album", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index be39f811c..b067c879a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -111,8 +111,6 @@ "@createButtonLabel": {}, "direct": "DIREKT", "@direct": {}, - "favouriteAdded": "Favorit hinzugefügt.", - "@favouriteAdded": {}, "favouriteRemoved": "Favorit entfernt.", "@favouriteRemoved": {}, "newPlaylist": "Neue Wiedergabeliste", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 875b6c053..485da59bd 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -108,8 +108,6 @@ "@downloads": {}, "goToAlbum": "Ir al álbum", "@goToAlbum": {}, - "favouriteRemoved": "Se eliminó de favoritos.", - "@favouriteRemoved": {}, "finamp": "Finamp", "@finamp": {}, "landscape": "Horizontal", @@ -182,8 +180,6 @@ "@error": {}, "favourite": "Favorito", "@favourite": {}, - "favouriteAdded": "Se añadió a favoritos.", - "@favouriteAdded": {}, "jellyfinUsesAACForTranscoding": "Jellyfin usa AAC para transcodificar", "@jellyfinUsesAACForTranscoding": {}, "list": "Lista", diff --git a/lib/l10n/app_et.arb b/lib/l10n/app_et.arb index 3308de7d2..ce4a0cf5e 100644 --- a/lib/l10n/app_et.arb +++ b/lib/l10n/app_et.arb @@ -360,8 +360,6 @@ "@startingInstantMix": {}, "instantMix": "Kiirmiks", "@instantMix": {}, - "favouriteAdded": "Lemmik lisatud.", - "@favouriteAdded": {}, "favouriteRemoved": "Lemmik eemaldatud.", "@favouriteRemoved": {}, "anErrorHasOccured": "Ilmnes tõrge.", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e20d30f12..d6b039c42 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -66,8 +66,6 @@ } } }, - "favouriteAdded": "Favoris ajouté.", - "@favouriteAdded": {}, "couldNotFindLibraries": "Aucune bibliothèque trouvée.", "@couldNotFindLibraries": { "description": "Error message when the user does not have any libraries" @@ -80,8 +78,6 @@ "@favourite": {}, "favourites": "Favoris", "@favourites": {}, - "favouriteRemoved": "Favoris supprimé.", - "@favouriteRemoved": {}, "album": "Album", "@album": {}, "grid": "Mosaïque", @@ -474,4 +470,4 @@ "@bufferDuration": {}, "bufferDurationSubtitle": "Combien de temps le lecteur doit mettre en mémoire tampon, en secondes. Nécessite un redémarrage.", "@bufferDurationSubtitle": {} -} \ No newline at end of file +} diff --git a/lib/l10n/app_hu.arb b/lib/l10n/app_hu.arb index f5cfc0134..c173583aa 100644 --- a/lib/l10n/app_hu.arb +++ b/lib/l10n/app_hu.arb @@ -329,8 +329,6 @@ "@direct": {}, "downloaded": "LETÖLTVE", "@downloaded": {}, - "favouriteAdded": "Kedvenc hozzáadva.", - "@favouriteAdded": {}, "goToAlbum": "Ugrás az albumra", "@goToAlbum": {}, "instantMix": "Instant Keverés", @@ -391,8 +389,6 @@ } } }, - "favouriteRemoved": "Kedvenc eltávolítva.", - "@favouriteRemoved": {}, "invalidNumber": "Érvénytelen szám", "@invalidNumber": {}, "downloads": "Letöltések", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index b1870fdad..c349c4a6a 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -261,8 +261,6 @@ "@statusError": {}, "transcode": "TRANSCODIFICA", "@transcode": {}, - "favouriteAdded": "Preferito aggiunto.", - "@favouriteAdded": {}, "favouriteRemoved": "Preferito rimosso.", "@favouriteRemoved": {}, "queueReplaced": "Coda sostituita.", diff --git a/lib/l10n/app_nb.arb b/lib/l10n/app_nb.arb index ae5a853ba..efd0eb851 100644 --- a/lib/l10n/app_nb.arb +++ b/lib/l10n/app_nb.arb @@ -96,8 +96,6 @@ "@addedToQueue": {}, "queueReplaced": "Kø erstattet.", "@queueReplaced": {}, - "favouriteRemoved": "Favoritt fjernet.", - "@favouriteRemoved": {}, "anErrorHasOccured": "Noe gikk galt.", "@anErrorHasOccured": {}, "removeFromMix": "Fjern fra miks", @@ -206,8 +204,6 @@ "@direct": {}, "startingInstantMix": "Starter umiddelbar miks …", "@startingInstantMix": {}, - "favouriteAdded": "Favorittmerket.", - "@favouriteAdded": {}, "responseError401": "{error} Statuskode {statusCode}. Dette betyr antagelig at du bruker feil brukernavn eller passord, eller at klienten din ikke lenger er innloget.", "@responseError401": { "placeholders": { diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 197a3044d..86facd98b 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -402,8 +402,6 @@ "@queueReplaced": {}, "startingInstantMix": "Instantmix starten.", "@startingInstantMix": {}, - "favouriteAdded": "Favoriet toegevoegd.", - "@favouriteAdded": {}, "favouriteRemoved": "Favoriet verwijderd.", "@favouriteRemoved": {}, "removeFromMix": "Uit mix verwijderen", diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 0bc4998c6..2045d2f89 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -95,8 +95,6 @@ "@direct": {}, "startingInstantMix": "Rozpocznij szybki miks.", "@startingInstantMix": {}, - "favouriteAdded": "Dodano do ulubionych.", - "@favouriteAdded": {}, "anErrorHasOccured": "Wystąpił błąd.", "@anErrorHasOccured": {}, "serverUrl": "Adres URL serwera", @@ -183,8 +181,6 @@ } } }, - "favouriteRemoved": "Usunięto z ulubionych.", - "@favouriteRemoved": {}, "downloaded": "Z POBRANYCH", "@downloaded": {}, "addDownloadLocation": "Dodaj lokalizację pobierania", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 8e05d75f5..8818d70ae 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -65,8 +65,6 @@ "@queueReplaced": {}, "startingInstantMix": "Iniciando mistura instantânea.", "@startingInstantMix": {}, - "favouriteAdded": "Favorito adicionado.", - "@favouriteAdded": {}, "favouriteRemoved": "Favorito removido.", "@favouriteRemoved": {}, "anErrorHasOccured": "Ocorreu um erro.", diff --git a/lib/l10n/app_pt_BR.arb b/lib/l10n/app_pt_BR.arb index 57116b80c..822bd9199 100644 --- a/lib/l10n/app_pt_BR.arb +++ b/lib/l10n/app_pt_BR.arb @@ -424,8 +424,6 @@ "@queueReplaced": {}, "startingInstantMix": "Iniciando mistura instantânea.", "@startingInstantMix": {}, - "favouriteAdded": "Favorito adicionado.", - "@favouriteAdded": {}, "favouriteRemoved": "Favorito removido.", "@favouriteRemoved": {}, "anErrorHasOccured": "Ocorreu um erro.", diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 307128240..2e598223e 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -269,8 +269,6 @@ "@noAlbum": {}, "addFavourite": "В избранное", "@addFavourite": {}, - "favouriteAdded": "Добавлено.", - "@favouriteAdded": {}, "anErrorHasOccured": "Произошла ошибка.", "@anErrorHasOccured": {}, "clear": "Очистить", @@ -279,8 +277,6 @@ "@settings": {}, "removeFavourite": "Удалить из избранного", "@removeFavourite": {}, - "favouriteRemoved": "Удалено.", - "@favouriteRemoved": {}, "addToMix": "Добавить в микс", "@addToMix": {}, "downloadMissingImages": "Скачать отсутствующие изображения", diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 1769411bf..5b84303a7 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -269,8 +269,6 @@ }, "editPlaylistNameTitle": "Ändra Namn På Spellista", "@editPlaylistNameTitle": {}, - "favouriteAdded": "Favorit tillagd.", - "@favouriteAdded": {}, "favouriteRemoved": "Favorit borttagen.", "@favouriteRemoved": {}, "grid": "Rutnät", diff --git a/lib/l10n/app_szl.arb b/lib/l10n/app_szl.arb index 6c0dee11a..b3d99fee4 100644 --- a/lib/l10n/app_szl.arb +++ b/lib/l10n/app_szl.arb @@ -440,8 +440,6 @@ "@startingInstantMix": {}, "queueReplaced": "Raja zastōmpiōno.", "@queueReplaced": {}, - "favouriteAdded": "Dodane do ôblubiōnych.", - "@favouriteAdded": {}, "favouriteRemoved": "Wymazane ze ôblubiōnych.", "@favouriteRemoved": {}, "addToMix": "Dodej do miksu", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index ae5dc656c..122f26693 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -329,12 +329,8 @@ "@failedToGetSongFromDownloadId": {}, "favourite": "收藏夹", "@favourite": {}, - "favouriteAdded": "已添加收藏夹。", - "@favouriteAdded": {}, "grid": "网格", "@grid": {}, - "favouriteRemoved": "已删除收藏夹。", - "@favouriteRemoved": {}, "finamp": "Finamp", "@finamp": {}, "enableTranscodingSubtitle": "音乐流将由服务器转码。", diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index 01502ed6f..0069fa2e8 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -426,8 +426,6 @@ "@queueReplaced": {}, "startingInstantMix": "開始即時混音。", "@startingInstantMix": {}, - "favouriteAdded": "已添加最愛。", - "@favouriteAdded": {}, "favouriteRemoved": "已刪除最愛。", "@favouriteRemoved": {}, "anErrorHasOccured": "發生了一個錯誤。", From 0a77eaff760d74213979699b69acefd7a5bbc09f Mon Sep 17 00:00:00 2001 From: Sam Karanja Date: Tue, 31 Jan 2023 22:51:08 +0100 Subject: [PATCH 018/172] Added translation using Weblate (Swahili) --- lib/l10n/app_sw.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_sw.arb diff --git a/lib/l10n/app_sw.arb b/lib/l10n/app_sw.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_sw.arb @@ -0,0 +1 @@ +{} From 8162611e84d4b5c68d04308037c552cdec424a04 Mon Sep 17 00:00:00 2001 From: Antoine Galluet Date: Tue, 31 Jan 2023 02:00:27 +0000 Subject: [PATCH 019/172] Translated using Weblate (French) Currently translated at 99.4% (169 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/fr/ --- lib/l10n/app_fr.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index d6b039c42..e148ce33a 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -469,5 +469,13 @@ "bufferDuration": "Durée du tampon", "@bufferDuration": {}, "bufferDurationSubtitle": "Combien de temps le lecteur doit mettre en mémoire tampon, en secondes. Nécessite un redémarrage.", - "@bufferDurationSubtitle": {} + "@bufferDurationSubtitle": {}, + "disableGesture": "Désactiver les gestes", + "@disableGesture": {}, + "removeFromPlaylistTooltip": "Retirer de la playlist", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Retirer de la playlist", + "@removeFromPlaylistTitle": {}, + "removedFromPlaylist": "Retirer de la playlist.", + "@removedFromPlaylist": {} } From 702199618bb070bcbe73cd3c1be61d2379b936e0 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Thu, 2 Feb 2023 20:02:22 +0100 Subject: [PATCH 020/172] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ --- lib/l10n/app_cs.arb | 2 -- lib/l10n/app_de.arb | 2 -- lib/l10n/app_et.arb | 2 -- lib/l10n/app_it.arb | 2 -- lib/l10n/app_nl.arb | 2 -- lib/l10n/app_pt.arb | 2 -- lib/l10n/app_pt_BR.arb | 2 -- lib/l10n/app_sv.arb | 2 -- lib/l10n/app_szl.arb | 2 -- lib/l10n/app_zh_Hant.arb | 2 -- 10 files changed, 20 deletions(-) diff --git a/lib/l10n/app_cs.arb b/lib/l10n/app_cs.arb index 4ef069f0f..7f2f7d9c0 100644 --- a/lib/l10n/app_cs.arb +++ b/lib/l10n/app_cs.arb @@ -142,8 +142,6 @@ "@anErrorHasOccured": {}, "downloaded": "STAŽENO", "@downloaded": {}, - "favouriteRemoved": "Odebráno z oblíbených.", - "@favouriteRemoved": {}, "goToAlbum": "Přejít na album", "@goToAlbum": {}, "couldNotFindLibraries": "Nebyly nalezeny žádné knihovny.", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index b067c879a..e45a91970 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -111,8 +111,6 @@ "@createButtonLabel": {}, "direct": "DIREKT", "@direct": {}, - "favouriteRemoved": "Favorit entfernt.", - "@favouriteRemoved": {}, "newPlaylist": "Neue Wiedergabeliste", "@newPlaylist": {}, "playlistCreated": "Wiedergabeliste erstellt.", diff --git a/lib/l10n/app_et.arb b/lib/l10n/app_et.arb index ce4a0cf5e..2671f8628 100644 --- a/lib/l10n/app_et.arb +++ b/lib/l10n/app_et.arb @@ -360,8 +360,6 @@ "@startingInstantMix": {}, "instantMix": "Kiirmiks", "@instantMix": {}, - "favouriteRemoved": "Lemmik eemaldatud.", - "@favouriteRemoved": {}, "anErrorHasOccured": "Ilmnes tõrge.", "@anErrorHasOccured": {}, "responseError": "{error} Olekukood {statusCode}.", diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index c349c4a6a..2a5d8ab4c 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -261,8 +261,6 @@ "@statusError": {}, "transcode": "TRANSCODIFICA", "@transcode": {}, - "favouriteRemoved": "Preferito rimosso.", - "@favouriteRemoved": {}, "queueReplaced": "Coda sostituita.", "@queueReplaced": {}, "removeFavourite": "Rimuovi Preferito", diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 86facd98b..41464c660 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -402,8 +402,6 @@ "@queueReplaced": {}, "startingInstantMix": "Instantmix starten.", "@startingInstantMix": {}, - "favouriteRemoved": "Favoriet verwijderd.", - "@favouriteRemoved": {}, "removeFromMix": "Uit mix verwijderen", "@removeFromMix": {}, "addToMix": "Aan mix toevoegen", diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 8818d70ae..6c5bd11e7 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -65,8 +65,6 @@ "@queueReplaced": {}, "startingInstantMix": "Iniciando mistura instantânea.", "@startingInstantMix": {}, - "favouriteRemoved": "Favorito removido.", - "@favouriteRemoved": {}, "anErrorHasOccured": "Ocorreu um erro.", "@anErrorHasOccured": {}, "removeFromMix": "Remover da Mistura", diff --git a/lib/l10n/app_pt_BR.arb b/lib/l10n/app_pt_BR.arb index 822bd9199..d5287ed5f 100644 --- a/lib/l10n/app_pt_BR.arb +++ b/lib/l10n/app_pt_BR.arb @@ -424,8 +424,6 @@ "@queueReplaced": {}, "startingInstantMix": "Iniciando mistura instantânea.", "@startingInstantMix": {}, - "favouriteRemoved": "Favorito removido.", - "@favouriteRemoved": {}, "anErrorHasOccured": "Ocorreu um erro.", "@anErrorHasOccured": {}, "responseError": "{error} Código de condição {statusCode}.", diff --git a/lib/l10n/app_sv.arb b/lib/l10n/app_sv.arb index 5b84303a7..e6f90dfda 100644 --- a/lib/l10n/app_sv.arb +++ b/lib/l10n/app_sv.arb @@ -269,8 +269,6 @@ }, "editPlaylistNameTitle": "Ändra Namn På Spellista", "@editPlaylistNameTitle": {}, - "favouriteRemoved": "Favorit borttagen.", - "@favouriteRemoved": {}, "grid": "Rutnät", "@grid": {}, "enableTranscodingSubtitle": "Koda om musikströmmar på servern.", diff --git a/lib/l10n/app_szl.arb b/lib/l10n/app_szl.arb index b3d99fee4..632a91b1b 100644 --- a/lib/l10n/app_szl.arb +++ b/lib/l10n/app_szl.arb @@ -440,8 +440,6 @@ "@startingInstantMix": {}, "queueReplaced": "Raja zastōmpiōno.", "@queueReplaced": {}, - "favouriteRemoved": "Wymazane ze ôblubiōnych.", - "@favouriteRemoved": {}, "addToMix": "Dodej do miksu", "@addToMix": {}, "responseError401": "{error} Kod statusu {statusCode}. To bezma ôznaczo, że było użyto niynoleżne miano ôd używocza abo hasło, abo tyż twōj kliynt już niy ma autoryzowany.", diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index 0069fa2e8..0a9a49ed9 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -426,8 +426,6 @@ "@queueReplaced": {}, "startingInstantMix": "開始即時混音。", "@startingInstantMix": {}, - "favouriteRemoved": "已刪除最愛。", - "@favouriteRemoved": {}, "anErrorHasOccured": "發生了一個錯誤。", "@anErrorHasOccured": {}, "responseError": "{error} 狀態碼 {statusCode}.", From 8e29340ea9af86ac1774c0334e17328acf59d3c3 Mon Sep 17 00:00:00 2001 From: Lars Gregersen Date: Sun, 5 Feb 2023 22:07:26 +0100 Subject: [PATCH 021/172] Added translation using Weblate (Danish) --- lib/l10n/app_da.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_da.arb diff --git a/lib/l10n/app_da.arb b/lib/l10n/app_da.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_da.arb @@ -0,0 +1 @@ +{} From 7d82c10bc0f5b5882829543e33564ed15c0869ad Mon Sep 17 00:00:00 2001 From: Lars Gregersen Date: Sun, 5 Feb 2023 21:10:01 +0000 Subject: [PATCH 022/172] Translated using Weblate (Danish) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/da/ --- lib/l10n/app_da.arb | 484 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 483 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_da.arb b/lib/l10n/app_da.arb index 0967ef424..038e3dd2d 100644 --- a/lib/l10n/app_da.arb +++ b/lib/l10n/app_da.arb @@ -1 +1,483 @@ -{} +{ + "serverUrl": "Server URL", + "@serverUrl": {}, + "emptyServerUrl": "Server URLen kan ikke være tom", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "urlStartWithHttps": "En URL skal starte med http:// eller https://", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "urlTrailingSlash": "En URL må ikke slutte med en stråstreg", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "username": "Brugernavn", + "@username": {}, + "password": "Password", + "@password": {}, + "logs": "Log", + "@logs": {}, + "next": "Næste", + "@next": {}, + "selectMusicLibraries": "Vælg musikbiblioteker", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "couldNotFindLibraries": "Kunne ikke finde nogle biblioteker.", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "songs": "Sange", + "@songs": {}, + "artists": "Kunstnere", + "@artists": {}, + "genres": "Genrer", + "@genres": {}, + "startMix": "Start blanding", + "@startMix": {}, + "music": "Musik", + "@music": {}, + "clear": "Fjern", + "@clear": {}, + "favourites": "Favoritter", + "@favourites": {}, + "shuffleAll": "Bland alle", + "@shuffleAll": {}, + "finamp": "Finamp", + "@finamp": {}, + "downloads": "Downloads", + "@downloads": {}, + "settings": "Indstillinger", + "@settings": {}, + "offlineMode": "Offline tilstand", + "@offlineMode": {}, + "sortOrder": "Sortering", + "@sortOrder": {}, + "sortBy": "Sortér efter", + "@sortBy": {}, + "album": "Album", + "@album": {}, + "albumArtist": "Album kunstner", + "@albumArtist": {}, + "artist": "Kunstner", + "@artist": {}, + "criticRating": "Kritikerbedømmelse", + "@criticRating": {}, + "communityRating": "Brugerbedømmelse", + "@communityRating": {}, + "dateAdded": "Tilføjet dato", + "@dateAdded": {}, + "datePlayed": "Spillet dato", + "@datePlayed": {}, + "playCount": "Antal afspillinger", + "@playCount": {}, + "premiereDate": "Premiere dato", + "@premiereDate": {}, + "productionYear": "Produktionsår", + "@productionYear": {}, + "name": "Navn", + "@name": {}, + "random": "Tilfældig", + "@random": {}, + "revenue": "Omsætning", + "@revenue": {}, + "budget": "ignore-same Budget", + "@budget": {}, + "downloadMissingImages": "Download manglende billeder", + "@downloadMissingImages": {}, + "downloadErrors": "Downloadfejl", + "@downloadErrors": {}, + "downloadedItemsCount": "{count,plural,=1{{count} emne} other{{count} emner}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedImagesCount": "{count,plural,=1{{count} billede} other{{count} billeder}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} færdig", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlFailed": "{count} fejlet", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlEnqueued": "{count} i kø", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "noErrors": "Ingen fejl!", + "@noErrors": {}, + "failedToGetSongFromDownloadId": "Kunne ikke hente sang fra download ID", + "@failedToGetSongFromDownloadId": {}, + "error": "Fejl", + "@error": {}, + "discNumber": "Disk {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "playButtonLabel": "AFSPIL", + "@playButtonLabel": {}, + "editPlaylistNameTooltip": "Ret playlistens navn", + "@editPlaylistNameTooltip": {}, + "editPlaylistNameTitle": "Ret Playlistens Navn", + "@editPlaylistNameTitle": {}, + "required": "Obligatorisk", + "@required": {}, + "updateButtonLabel": "OPDATÉR", + "@updateButtonLabel": {}, + "playlistNameUpdated": "Playlistenavn opdateret.", + "@playlistNameUpdated": {}, + "favourite": "Favorit", + "@favourite": {}, + "addDownloads": "Tilføj downloads", + "@addDownloads": {}, + "location": "Placering", + "@location": {}, + "downloadsAdded": "Downloads tilføjet.", + "@downloadsAdded": {}, + "shareLogs": "Del logs", + "@shareLogs": {}, + "stackTrace": "Stack trace", + "@stackTrace": {}, + "transcoding": "Transkodning", + "@transcoding": {}, + "downloadLocations": "Download placeringer", + "@downloadLocations": {}, + "audioService": "Lydservice", + "@audioService": {}, + "layoutAndTheme": "Layout og tema", + "@layoutAndTheme": {}, + "notAvailableInOfflineMode": "Ikke tilgængelig i offline tilstand", + "@notAvailableInOfflineMode": {}, + "logOut": "Log out", + "@logOut": {}, + "downloadedSongsWillNotBeDeleted": "Downloadede sange vil ikke blive slettet", + "@downloadedSongsWillNotBeDeleted": {}, + "areYouSure": "Er du sikker?", + "@areYouSure": {}, + "jellyfinUsesAACForTranscoding": "Jellyfin bruger AAC til transkodning", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscoding": "Aktivér transkodning", + "@enableTranscoding": {}, + "enableTranscodingSubtitle": "Transkodede musikstrømme på serveren.", + "@enableTranscodingSubtitle": {}, + "bitrateSubtitle": "En højere bithastighed giver bedre musikkvalitet men bruger mere båndbredde.", + "@bitrateSubtitle": {}, + "customLocation": "Valgt placering", + "@customLocation": {}, + "appDirectory": "App katalog", + "@appDirectory": {}, + "addDownloadLocation": "Tilføj download placering", + "@addDownloadLocation": {}, + "selectDirectory": "Vælg katalog", + "@selectDirectory": {}, + "unknownError": "Ukendt fejl", + "@unknownError": {}, + "directoryMustBeEmpty": "Katalog skal være tomt", + "@directoryMustBeEmpty": {}, + "enterLowPriorityStateOnPause": "Indtast lav prioritet tilstanden for pause", + "@enterLowPriorityStateOnPause": {}, + "enterLowPriorityStateOnPauseSubtitle": "Tillader notifikationer at bliver strøget i pausetilstand. Tillader også Android at standse services i pausetilstand.", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "shuffleAllSongCount": "Bland alle sange antal", + "@shuffleAllSongCount": {}, + "viewType": "Visning type", + "@viewType": {}, + "viewTypeSubtitle": "Visning type for musikskærmen", + "@viewTypeSubtitle": {}, + "list": "Liste", + "@list": {}, + "grid": "Tabel", + "@grid": {}, + "portrait": "Portræt", + "@portrait": {}, + "landscape": "Landskab", + "@landscape": {}, + "gridCrossAxisCountSubtitle": "Antal tabelfelter som skal anvendes per række i {value} visning.", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "showTextOnGridView": "Vis tekst i tabelvisning", + "@showTextOnGridView": {}, + "showTextOnGridViewSubtitle": "Vælg visning af tekst (titel, kunstner osv.) i tabellen på musikskærmen.", + "@showTextOnGridViewSubtitle": {}, + "showCoverAsPlayerBackground": "Vis sløret album som baggrund", + "@showCoverAsPlayerBackground": {}, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Vælg om sangens kunstner skal vises på albummet, hvis den ikke er forskellig fra albummets kunstner.", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "disableGesture": "Deaktivér gestus", + "@disableGesture": {}, + "disableGestureSubtitle": "Vælg om gestusgenkendelse skal deaktiveres.", + "@disableGestureSubtitle": {}, + "theme": "Tema", + "@theme": {}, + "light": "Lys", + "@light": {}, + "dark": "Mørk", + "@dark": {}, + "tabs": "Tabs", + "@tabs": {}, + "cancelSleepTimer": "Stands sleep timer?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "JA", + "@yesButtonLabel": {}, + "minutes": "Minutter", + "@minutes": {}, + "sleepTimerTooltip": "Sleep timer", + "@sleepTimerTooltip": {}, + "addToPlaylistTooltip": "Tilføj til playliste", + "@addToPlaylistTooltip": {}, + "addToPlaylistTitle": "Tilføj til playliste", + "@addToPlaylistTitle": {}, + "removeFromPlaylistTooltip": "Fjern fra playliste", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Fjern fra playliste", + "@removeFromPlaylistTitle": {}, + "newPlaylist": "Ny playliste", + "@newPlaylist": {}, + "createButtonLabel": "OPRET", + "@createButtonLabel": {}, + "playlistCreated": "Playliste er oprettet.", + "@playlistCreated": {}, + "noAlbum": "Intet album", + "@noAlbum": {}, + "noItem": "Intet objekt", + "@noItem": {}, + "invalidNumber": "Ugyldigt nummer", + "@invalidNumber": {}, + "streaming": "STREAMER", + "@streaming": {}, + "downloaded": "DOWNLOADET", + "@downloaded": {}, + "transcode": "TRANSKODE", + "@transcode": {}, + "direct": "DIREKTE", + "@direct": {}, + "statusError": "Status fejl", + "@statusError": {}, + "queue": "Kø", + "@queue": {}, + "addToQueue": "Tilføj til kø", + "@addToQueue": {}, + "replaceQueue": "Erstat kø", + "@replaceQueue": {}, + "instantMix": "Umiddelbar blanding", + "@instantMix": {}, + "removeFavourite": "Fjern favorit", + "@removeFavourite": {}, + "addFavourite": "Tilføj favorit", + "@addFavourite": {}, + "addedToQueue": "Tilføjet til kø.", + "@addedToQueue": {}, + "queueReplaced": "Køen er erstattet.", + "@queueReplaced": {}, + "removedFromPlaylist": "Fjernet fra playliste.", + "@removedFromPlaylist": {}, + "startingInstantMix": "Starter umiddelbar blanding.", + "@startingInstantMix": {}, + "downloadCount": "{count,plural, =1{{count} download} other{{count} downloads}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "removeFromMix": "Fjern fra blanding", + "@removeFromMix": {}, + "addToMix": "Tilføj til blanding", + "@addToMix": {}, + "redownloadedItems": "{count,plural, =0{Ingen gendownloads nødvendige.} =1{Gendownloadede {count} emne} other{Gendownloadede {count} emner}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bufferDuration": "Buffer varighed", + "@bufferDuration": {}, + "bufferDurationSubtitle": "Afspillerens bufferlængde i sekunder. Kræver genstart.", + "@bufferDurationSubtitle": {}, + "startupError": "Noget gik galt med opstarten af appen. Fejlen var {error}\n\nVenligt opret en sag på github.com/UnicornsOnLSD/finamp med et screenshot af denne side. Hvis problemet vedvarer, så reset appen ved at fjerne app data.", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "internalExternalIpExplanation": "Hvis du ønsker at skabe forbindelse til din Jellyfin server udefra, skal du bruge din externe IP-adresse.\n\nHvis din server er på HTTP porten (80/443) behøver du ikke angive en port. Dette er som regel tilfældet, hvis din server er bag en reverse proxy.", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "unknownName": "Ukendt navn", + "@unknownName": {}, + "albums": "Album", + "@albums": {}, + "playlists": "Playlister", + "@playlists": {}, + "startMixNoSongsArtist": "Tryk (lang tid) på en kunstner for at tilføje eller fjerne den fra blandingen før du starter en blanding", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "startMixNoSongsAlbum": "Tryk (lang tid) på et album for at tilføje eller fjerne det fra blandingen for du starter en blanding", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "runtime": "Runtime", + "@runtime": {}, + "downloadedMissingImages": "{count,plural, =0{Ingen manglende billeder} =1{Downloadet {count} manglende billede} other{Downloadet {count} manglende billeder}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlRunning": "{count} kører", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrorsTitle": "Download fejl", + "@downloadErrorsTitle": {}, + "shuffleButtonLabel": "BLAND", + "@shuffleButtonLabel": {}, + "errorScreenError": "En fejl skete men listen med fejl blev hentet! På dette tidspunkt er det nok bedst at oprette et issue på Github og slette alle appens data", + "@errorScreenError": {}, + "songCount": "{count,plural,=1{{count} Sang} other{{count} Sange}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadsDeleted": "Downloads slettet.", + "@downloadsDeleted": {}, + "addButtonLabel": "TILFØJ", + "@addButtonLabel": {}, + "bitrate": "Bithastighed", + "@bitrate": {}, + "logsCopied": "Logs kopieret.", + "@logsCopied": {}, + "message": "Besked", + "@message": {}, + "applicationLegalese": "Licenseret med Mozilla Public License 2.0. Kildeteksten er tilgængelig her:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "pathReturnSlashErrorMessage": "Stinavne, der returnerer \"/\", kan ikke anvendes", + "@pathReturnSlashErrorMessage": {}, + "customLocationsBuggy": "Valgfrie placeringer giver ofte anledning til fejl grundet problemer med rettigheder. Jeg prøver at finde måder at løse dette på, men i øjeblikket anbefaler jeg ikke, at de anvendes.", + "@customLocationsBuggy": {}, + "noArtist": "Ingen kunstner", + "@noArtist": {}, + "unknownArtist": "Ukendt kunstner", + "@unknownArtist": {}, + "shuffleAllSongCountSubtitle": "Antal sange som skal indlæses, når bland alle sange knappen trykkes.", + "@shuffleAllSongCountSubtitle": {}, + "hideSongArtistsIfSameAsAlbumArtists": "Skjul sangens kunstner, hvis det er den samme som albummets kunstner", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "gridCrossAxisCount": "{value} kolonneantal", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "setSleepTimer": "Sæt sleep timer", + "@setSleepTimer": {}, + "showCoverAsPlayerBackgroundSubtitle": "Vælg om albummets forside skal vises sløret på skærmen under afspilning.", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "system": "System", + "@system": {}, + "noButtonLabel": "NEJ", + "@noButtonLabel": {}, + "goToAlbum": "Gå til album", + "@goToAlbum": {}, + "anErrorHasOccured": "Der er sket en fejl.", + "@anErrorHasOccured": {}, + "responseError401": "{error} Status kode {statusCode}. Dette betyder måske, at du har anvendt forkert brugernavn/password eller at din klient ikke længere er logget ind.", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } + }, + "responseError": "{error} Status kode {statusCode}.", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + } +} From a3aa08ae3190a0dd95835446dc1bfbc3372659b0 Mon Sep 17 00:00:00 2001 From: K H Date: Sat, 11 Feb 2023 23:42:10 +0000 Subject: [PATCH 023/172] Translated using Weblate (Dutch) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/nl/ --- lib/l10n/app_nl.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 41464c660..7c63ac5eb 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -473,5 +473,11 @@ "urlStartWithHttps": "De URL moet beginnen met http:// of https://", "@urlStartWithHttps": { "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" - } + }, + "removeFromPlaylistTooltip": "Verwijder van afspeellijst", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Verwijder van afspeellijst", + "@removeFromPlaylistTitle": {}, + "removedFromPlaylist": "Verwijderd van afspeellijst.", + "@removedFromPlaylist": {} } From 603ee30f1d709d659bbaa73ad3abe6718fa820df Mon Sep 17 00:00:00 2001 From: Peng Lei Date: Fri, 17 Feb 2023 02:18:51 +0000 Subject: [PATCH 024/172] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hans/ --- lib/l10n/app_zh.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 122f26693..f071232f2 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -473,5 +473,11 @@ } }, "bufferDurationSubtitle": "播放器需要播放多久,以秒为单位。需要重启。", - "@bufferDurationSubtitle": {} + "@bufferDurationSubtitle": {}, + "removedFromPlaylist": "已从播放列表删除。", + "@removedFromPlaylist": {}, + "removeFromPlaylistTitle": "从播放列表删除", + "@removeFromPlaylistTitle": {}, + "removeFromPlaylistTooltip": "从播放列表删除", + "@removeFromPlaylistTooltip": {} } From 61ba7805e124b36a97d30849c3aa0098312e2716 Mon Sep 17 00:00:00 2001 From: jakka Date: Sat, 25 Feb 2023 16:09:03 +0000 Subject: [PATCH 025/172] Translated using Weblate (Russian) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ru/ --- lib/l10n/app_ru.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 2e598223e..12a6051d7 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -473,5 +473,11 @@ "bufferDuration": "Длительность буферизации", "@bufferDuration": {}, "bufferDurationSubtitle": "Сколько секунд плеер будет буферизовать. Необходим перезапуск.", - "@bufferDurationSubtitle": {} + "@bufferDurationSubtitle": {}, + "removedFromPlaylist": "Удалено из Плейлиста.", + "@removedFromPlaylist": {}, + "removeFromPlaylistTooltip": "Удалить из плейлиста", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Удалить из Плейлиста", + "@removeFromPlaylistTitle": {} } From c25abd4b9e2d62ea842c4ac63ea777d70ded615b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Wed, 1 Mar 2023 19:11:02 +0100 Subject: [PATCH 026/172] Added translation using Weblate (Turkish) --- lib/l10n/app_tr.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_tr.arb diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_tr.arb @@ -0,0 +1 @@ +{} From 53effa9de353dd658d3796f8c9f6301a851e625b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Wed, 1 Mar 2023 18:39:33 +0000 Subject: [PATCH 027/172] Translated using Weblate (Turkish) Currently translated at 0.5% (1 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/tr/ --- lib/l10n/app_tr.arb | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 0967ef424..9b147115c 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -1 +1,12 @@ -{} +{ + "startupError": "Uygulama başlarken bir hata meydana geldi. Hata: {error}\n\nLütfen bu sayfanın ekran görüntüsüyle github.com/UnicornsOnLSD/finamp adresinde bir issue oluşturun. Eğer bu sorun devam ederse uygulamayı sıfırlamak için uygulama verisini sıfırlayabilirsiniz.", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + } +} From e9187b300797365b2b41b7f415bb565770719f33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ferit=20Yi=C4=9Fit=20BALABAN?= Date: Wed, 1 Mar 2023 18:39:49 +0000 Subject: [PATCH 028/172] Translated using Weblate (Turkish) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/tr/ --- lib/l10n/app_tr.arb | 471 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index 9b147115c..d439edf60 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -8,5 +8,476 @@ "example": "Failed to open download DB" } } + }, + "serverUrl": "Sunucunun URL'i", + "@serverUrl": {}, + "internalExternalIpExplanation": "Jellyfin sunucunuza uzaktan erişmek istiyorsanız dış IP adresinizi kullanmalısınız.\n\nEğer sunucunuz HTTP portlarından (80/443) birindeyse port belirtmenize gerek yok. Sunucunuz bir reverse proxy'nin arkasındaysa muhtemelen bu durum geçerlidir.", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "emptyServerUrl": "Sunucu URL'i boş bırakılamaz", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "urlStartWithHttps": "URL http:// veya https:// ile başlamalı", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "urlTrailingSlash": "URL'in sonunda eğik çizgi olmamalı", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "username": "Kullanıcı adı", + "@username": {}, + "password": "Parola", + "@password": {}, + "logs": "Uygulama dökümleri", + "@logs": {}, + "next": "Sıradaki", + "@next": {}, + "unknownName": "Bilinmeyen İsim", + "@unknownName": {}, + "songs": "Şarkılar", + "@songs": {}, + "albums": "Albümler", + "@albums": {}, + "artists": "Sanatçılar", + "@artists": {}, + "genres": "Tarzlar", + "@genres": {}, + "playlists": "Çalma listeleri", + "@playlists": {}, + "startMix": "Mix'i başlat", + "@startMix": {}, + "startMixNoSongsArtist": "Mix'i başlatmadan önce bir sanatçıya uzun basarak mix'e ekleyip çıkar", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "startMixNoSongsAlbum": "Mix'i başlatmadan önce bir albüme uzun basarak mix'e ekleyip çıkar", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "music": "Müzik", + "@music": {}, + "clear": "Temizle", + "@clear": {}, + "favourites": "Favoriler", + "@favourites": {}, + "downloads": "İndirilenler", + "@downloads": {}, + "sortOrder": "Sıralama düzeni", + "@sortOrder": {}, + "sortBy": "Sıralama ölçütü", + "@sortBy": {}, + "artist": "Sanatçı", + "@artist": {}, + "budget": "Bütçe", + "@budget": {}, + "communityRating": "Topluluk Puanı", + "@communityRating": {}, + "criticRating": "Eleştirmen Puanı", + "@criticRating": {}, + "dateAdded": "Eklenme Tarihi", + "@dateAdded": {}, + "datePlayed": "Oynatma Tarihi", + "@datePlayed": {}, + "downloadedMissingImages": "{count,plural, =0{Eksik görsel yok} =1{{count} tane eksik görsel indirildi} other{{count} tane eksik görsel indirildi}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrors": "İndirme hataları", + "@downloadErrors": {}, + "downloadedItemsCount": "{count,plural,=1{{count} dosya} other{{count} dosya}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedImagesCount": "{count,plural,=1{{count} tane görsel} other{{count} tane görsel}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + }, + "downloadErrorsTitle": "İndirme Hataları", + "@downloadErrorsTitle": {}, + "noErrors": "Hata yok!", + "@noErrors": {}, + "errorScreenError": "Hata listesi oluşturulurken hata meydana geldi. Bu noktada, pes edip GitHub'da issue oluşturmalı ve uygulama verilerini silmelisin", + "@errorScreenError": {}, + "failedToGetSongFromDownloadId": "İndirme ID'sinden şarkıya ulaşılamadı", + "@failedToGetSongFromDownloadId": {}, + "error": "Hata", + "@error": {}, + "downloadCount": "{count,plural, =1{{count} tane indirme} other{{count} tane indirme{count}}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "discNumber": "Disk {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "playButtonLabel": "OYNAT", + "@playButtonLabel": {}, + "shuffleButtonLabel": "KARIŞTIR", + "@shuffleButtonLabel": {}, + "songCount": "{count,plural,=1{{count} Şarkı} other{{count} Şarkı}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editPlaylistNameTooltip": "Çalma listesinin adını düzenle", + "@editPlaylistNameTooltip": {}, + "editPlaylistNameTitle": "Çalma Listesinin Adını Düzenle", + "@editPlaylistNameTitle": {}, + "required": "Gerekli", + "@required": {}, + "updateButtonLabel": "GÜNCELLE", + "@updateButtonLabel": {}, + "playlistNameUpdated": "Çalma listesinin adı güncellendi.", + "@playlistNameUpdated": {}, + "favourite": "Favori", + "@favourite": {}, + "downloadsDeleted": "İndirmeler silindi.", + "@downloadsDeleted": {}, + "addDownloads": "İndirme Ekle", + "@addDownloads": {}, + "location": "Lokasyon", + "@location": {}, + "downloadsAdded": "İndirilenler eklendi.", + "@downloadsAdded": {}, + "addButtonLabel": "EKLE", + "@addButtonLabel": {}, + "shareLogs": "Uygulama dökümlerini paylaş", + "@shareLogs": {}, + "logsCopied": "Uygulama dökümleri kopyalandı.", + "@logsCopied": {}, + "stackTrace": "Fonksiyon Çağrı Yığını", + "@stackTrace": {}, + "downloadLocations": "İndirilenler Lokasyonları", + "@downloadLocations": {}, + "transcoding": "Yeniden kodlama (transcoding)", + "@transcoding": {}, + "notAvailableInOfflineMode": "Çevrim dışı modda mevcut değil", + "@notAvailableInOfflineMode": {}, + "logOut": "Çıkış Yap", + "@logOut": {}, + "jellyfinUsesAACForTranscoding": "Jellyfin transcoding için AAC kullanıyor", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscoding": "Transcoding'i aktifleştir", + "@enableTranscoding": {}, + "enableTranscodingSubtitle": "Sunucu tarafında müzik akışlarını yeniden kodlar.", + "@enableTranscodingSubtitle": {}, + "bitrate": "Bit oranı", + "@bitrate": {}, + "bitrateSubtitle": "Daha yüksek bir bit oranı, daha fazla bant genişliği kullanır ancak daha kaliteli ses verir.", + "@bitrateSubtitle": {}, + "customLocation": "Farklı Lokasyon", + "@customLocation": {}, + "appDirectory": "Uygulama Klasörü", + "@appDirectory": {}, + "addDownloadLocation": "İndirme Lokasyonu Ekle", + "@addDownloadLocation": {}, + "selectDirectory": "Klasör Seç", + "@selectDirectory": {}, + "unknownError": "Bilinmeyen Hata", + "@unknownError": {}, + "pathReturnSlashErrorMessage": "\"/\" döndüren yollar kullanılamaz", + "@pathReturnSlashErrorMessage": {}, + "directoryMustBeEmpty": "Klasör boş olmalı", + "@directoryMustBeEmpty": {}, + "customLocationsBuggy": "İzinlerden kaynaklanan sorunlar dolayısıyla özel konum seçmek fazlasıyla bug'a yol açmakta. Şimdilik kullanmanızı önermiyorum, çözmenin bir yolunu düşünüyorum.", + "@customLocationsBuggy": {}, + "enterLowPriorityStateOnPauseSubtitle": "Şarkı duraklatıldığında bildirimin temizlenebilmesini sağlar. Ayrıca Android'in hizmeti kapatmasına izin verir.", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "shuffleAllSongCount": "Karıştırılacak Tüm Şarkıların Sayısı", + "@shuffleAllSongCount": {}, + "viewTypeSubtitle": "Müzik ekranı için görüntüleme tipi", + "@viewTypeSubtitle": {}, + "list": "Liste", + "@list": {}, + "grid": "Izgara", + "@grid": {}, + "portrait": "Dikey mod", + "@portrait": {}, + "landscape": "Yatay mod", + "@landscape": {}, + "gridCrossAxisCountSubtitle": "Değer {value} olduğunda satır başına kullanılacak ızgara karosu.", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "showTextOnGridView": "Izgara görünümünde metin göster", + "@showTextOnGridView": {}, + "showCoverAsPlayerBackground": "Oynatıcı arkaplanı olarak bulanık kapak fotoğrafını göster", + "@showCoverAsPlayerBackground": {}, + "hideSongArtistsIfSameAsAlbumArtists": "Albüm ile şarkı sanatçısı aynıysa gösterme", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Eğer albüm sanatçısından farklı değilse şarkı sanatçısını albüm ekranında gösterilip gösterilmeyeceğini belirler.", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "disableGesture": "Jestleri devre dışı bırak", + "@disableGesture": {}, + "disableGestureSubtitle": "Jestleri devre dışı bırakır veya aktifleştirir.", + "@disableGestureSubtitle": {}, + "theme": "Tema", + "@theme": {}, + "gridCrossAxisCount": "{value} Izgara Çapraz Eksen Sayısı", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "system": "Sistem", + "@system": {}, + "light": "Aydınlık", + "@light": {}, + "dark": "Karanlık", + "@dark": {}, + "tabs": "Sekmeler", + "@tabs": {}, + "cancelSleepTimer": "Uyuma Zamanlayıcısını İptal Et?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "EVET", + "@yesButtonLabel": {}, + "noButtonLabel": "HAYIR", + "@noButtonLabel": {}, + "setSleepTimer": "Uyuma Zamanlayıcısını Ayarla", + "@setSleepTimer": {}, + "minutes": "Dakika", + "@minutes": {}, + "invalidNumber": "Geçersiz Sayı", + "@invalidNumber": {}, + "sleepTimerTooltip": "Uyku zamanlayıcısı", + "@sleepTimerTooltip": {}, + "addToPlaylistTooltip": "Çalma listesine ekle", + "@addToPlaylistTooltip": {}, + "addToPlaylistTitle": "Listeye Ekle", + "@addToPlaylistTitle": {}, + "removeFromPlaylistTooltip": "Çalma listesinden çıkart", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Listeden Çıkart", + "@removeFromPlaylistTitle": {}, + "newPlaylist": "Yeni Çalma Listesi", + "@newPlaylist": {}, + "createButtonLabel": "OLUŞTUR", + "@createButtonLabel": {}, + "playlistCreated": "Çalma listesi oluşturuldu.", + "@playlistCreated": {}, + "noAlbum": "Albüm Yok", + "@noAlbum": {}, + "noArtist": "Sanatçı Yok", + "@noArtist": {}, + "noItem": "İçerik Yok", + "@noItem": {}, + "unknownArtist": "Bilinmeyen Sanatçı", + "@unknownArtist": {}, + "streaming": "YAYIN ALINIYOR", + "@streaming": {}, + "downloaded": "İNDİRİLDİ", + "@downloaded": {}, + "transcode": "YENİDEN KODLA", + "@transcode": {}, + "direct": "DİREKT", + "@direct": {}, + "statusError": "DURUM HATASI", + "@statusError": {}, + "queue": "Kuyruk", + "@queue": {}, + "replaceQueue": "Kuyruğu Değiştir", + "@replaceQueue": {}, + "addedToQueue": "Kuyruğa eklendi.", + "@addedToQueue": {}, + "queueReplaced": "Kuyruk değiştirildi.", + "@queueReplaced": {}, + "removedFromPlaylist": "Çalma listesinden çıkarıldı.", + "@removedFromPlaylist": {}, + "startingInstantMix": "Anlık mix başlatılıyor.", + "@startingInstantMix": {}, + "anErrorHasOccured": "Bir hata meydana geldi.", + "@anErrorHasOccured": {}, + "responseError": "{error} Durum kodu {statusCode}.", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "removeFromMix": "Mix'ten Çıkart", + "@removeFromMix": {}, + "addToMix": "Mix'e Ekle", + "@addToMix": {}, + "redownloadedItems": "{count,plural, =0{Yeniden indirmeye gerek yok.} =1{{count} tane yeniden indirildi.} other{{count} tane yeniden indirildi.}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bufferDuration": "Önden Kaydetme Süresi", + "@bufferDuration": {}, + "bufferDurationSubtitle": "Oynatıcının kaç saniye önden kaydetmesi gerektiğini ayarlar. Yeniden başlatmayı gerektirir.", + "@bufferDurationSubtitle": {}, + "album": "Albüm", + "@album": {}, + "selectMusicLibraries": "Müzik Kütüphanelerini Seç", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "couldNotFindLibraries": "Hiçbir kütüphane bulunamadı.", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "finamp": "Finamp", + "@finamp": {}, + "shuffleAll": "Tümünü karıştır", + "@shuffleAll": {}, + "playCount": "Oynatma Sayısı", + "@playCount": {}, + "name": "İsim", + "@name": {}, + "offlineMode": "Çevrim dışı Mod", + "@offlineMode": {}, + "settings": "Ayarlar", + "@settings": {}, + "albumArtist": "Albüm Sanatçısı", + "@albumArtist": {}, + "productionYear": "Prodüksiyon Yılı", + "@productionYear": {}, + "random": "Rastgele", + "@random": {}, + "runtime": "Çalma süresi", + "@runtime": {}, + "dlFailed": "{count} tane başarısız", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "premiereDate": "Gösterim Tarihi", + "@premiereDate": {}, + "revenue": "Gelir", + "@revenue": {}, + "downloadMissingImages": "Eksik görselleri indir", + "@downloadMissingImages": {}, + "dlRunning": "{count} tane devam ediyor", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlEnqueued": "{count} tane kuyruğa alındı", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} tane tamamlandı", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "message": "Mesaj", + "@message": {}, + "areYouSure": "Emin misiniz?", + "@areYouSure": {}, + "downloadedSongsWillNotBeDeleted": "İndirilmiş şarkılar silinmeyecek", + "@downloadedSongsWillNotBeDeleted": {}, + "applicationLegalese": "Mozilla Public License 2.0 ile lisanslandı. Kaynak koda buradan ulaşılabilir:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "audioService": "Ses Servisi", + "@audioService": {}, + "layoutAndTheme": "Düzen & Tema", + "@layoutAndTheme": {}, + "enterLowPriorityStateOnPause": "Bekletmede Düşük Öncelik Durumuna Geç", + "@enterLowPriorityStateOnPause": {}, + "shuffleAllSongCountSubtitle": "Tüm şarkıları karıştır butonuna tıklandığında karıştırılacak şarkıların sayısı.", + "@shuffleAllSongCountSubtitle": {}, + "viewType": "Görüntüleme Tipi", + "@viewType": {}, + "showCoverAsPlayerBackgroundSubtitle": "Oynatıcı arkaplanı olarak bulanık kapak fotoğrafını gösterilip gösterilmeyeceğini belirler.", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "showTextOnGridViewSubtitle": "Izgara müzik ekranında metin (başlık, sanatçı, vs.) gösterilip gösterilmeyeceğini belirler.", + "@showTextOnGridViewSubtitle": {}, + "addToQueue": "Kuyruğa Ekle", + "@addToQueue": {}, + "instantMix": "Anlık Mix", + "@instantMix": {}, + "goToAlbum": "Albüme Git", + "@goToAlbum": {}, + "addFavourite": "Favoriye Ekle", + "@addFavourite": {}, + "removeFavourite": "Favoriden Çıkart", + "@removeFavourite": {}, + "responseError401": "{error} Durum kodu {statusCode}. Muhtemelen yanlış kullanıcı adı veya şifreyi kullandınız ya da uygulamadan çıkış yaptınız.", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } } } From 5877796d089f153704218fb4d7132b1051b3de92 Mon Sep 17 00:00:00 2001 From: Arne Cuperus Date: Fri, 10 Mar 2023 14:09:26 +0000 Subject: [PATCH 029/172] Translated using Weblate (Dutch) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/nl/ --- lib/l10n/app_nl.arb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 7c63ac5eb..0d48bce20 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -248,7 +248,7 @@ } } }, - "downloadErrors": "Downloadfouten", + "downloadErrors": "Download fouten", "@downloadErrors": {}, "dlEnqueued": "{count} gepland", "@dlEnqueued": { @@ -266,25 +266,25 @@ } } }, - "downloadErrorsTitle": "Downloadfouten", + "downloadErrorsTitle": "Download Fouten", "@downloadErrorsTitle": {}, "noErrors": "Geen fouten!", "@noErrors": {}, - "failedToGetSongFromDownloadId": "Het liedje kon niet gevonden worden met deze download ID", + "failedToGetSongFromDownloadId": "Het nummer kon niet gevonden worden met deze download ID", "@failedToGetSongFromDownloadId": {}, "error": "Fout", "@error": {}, "playButtonLabel": "AFSPELEN", "@playButtonLabel": {}, - "editPlaylistNameTooltip": "Playlistnaam aanpassen", + "editPlaylistNameTooltip": "Pas de naam van de playlist aan", "@editPlaylistNameTooltip": {}, - "editPlaylistNameTitle": "Playlistnaam aanpassen", + "editPlaylistNameTitle": "Pas de naam van de Playlist aan", "@editPlaylistNameTitle": {}, "shuffleButtonLabel": "SHUFFLE", "@shuffleButtonLabel": {}, "updateButtonLabel": "UPDATE", "@updateButtonLabel": {}, - "playlistNameUpdated": "Playlistnaam aangepast.", + "playlistNameUpdated": "De naam van de playlist is aangepast.", "@playlistNameUpdated": {}, "favourite": "Favoriet", "@favourite": {}, From b70c3ee4a0313608da2b77efbf04628a80a79388 Mon Sep 17 00:00:00 2001 From: Bastien Didier Date: Fri, 10 Mar 2023 17:37:13 +0000 Subject: [PATCH 030/172] Translated using Weblate (French) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/fr/ --- lib/l10n/app_fr.arb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index e148ce33a..a554659a9 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -357,7 +357,7 @@ } } }, - "showTextOnGridViewSubtitle": "Afficher ou non du texte (titre, artiste...) sur les tuiles en mode mosaïque", + "showTextOnGridViewSubtitle": "Afficher ou non du texte (titre, artiste...) sur les tuiles en mode mosaïque.", "@showTextOnGridViewSubtitle": {}, "showCoverAsPlayerBackground": "Afficher la couverture floutée en arrière-plan du lecteur", "@showCoverAsPlayerBackground": {}, @@ -477,5 +477,7 @@ "removeFromPlaylistTitle": "Retirer de la playlist", "@removeFromPlaylistTitle": {}, "removedFromPlaylist": "Retirer de la playlist.", - "@removedFromPlaylist": {} + "@removedFromPlaylist": {}, + "disableGestureSubtitle": "Option permettant de désactiver les gestes.", + "@disableGestureSubtitle": {} } From 729c89c7a6796638f5526755b1bb8e8c6a4ea384 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Thanh=20S=C6=A1n?= Date: Sun, 12 Mar 2023 17:26:10 +0100 Subject: [PATCH 031/172] Added translation using Weblate (Vietnamese) --- lib/l10n/app_vi.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_vi.arb diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_vi.arb @@ -0,0 +1 @@ +{} From 8bcc1ea2cc7f10d490fab12fc2cd1cc15b71cdb6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Thanh=20S=C6=A1n?= Date: Mon, 13 Mar 2023 13:13:56 +0000 Subject: [PATCH 032/172] Translated using Weblate (Vietnamese) Currently translated at 67.6% (115 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/vi/ --- lib/l10n/app_vi.arb | 330 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 329 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index 0967ef424..667495673 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -1 +1,329 @@ -{} +{ + "serverUrl": "URL của máy chủ", + "@serverUrl": {}, + "emptyServerUrl": "URL của máy chủ không thể để trống", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "urlStartWithHttps": "URL phải bắt đầu bằng http:// hoặc https://", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "urlTrailingSlash": "URL không được có dấu gạch chéo", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "username": "Tên người dùng", + "@username": {}, + "password": "Mật khẩu", + "@password": {}, + "logs": "Ghi chép", + "@logs": {}, + "next": "Tiếp theo", + "@next": {}, + "selectMusicLibraries": "Chọn Thư viện Nhạc", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "couldNotFindLibraries": "Không thể tìm thấy bất kì thư viện nào.", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "unknownName": "Không có tên", + "@unknownName": {}, + "songs": "Bài hát", + "@songs": {}, + "albums": "Albums", + "@albums": {}, + "artists": "Nghệ sĩ", + "@artists": {}, + "genres": "Thể loại", + "@genres": {}, + "startMix": "Bắt đầu Mix", + "@startMix": {}, + "startMixNoSongsArtist": "Bấm giữ một nghệ sĩ để thêm hoặc loại bỏ ra khỏi bộ Mix trước khi bắt đầu một tuyển tập Mix", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "music": "Nhạc", + "@music": {}, + "clear": "Loại bỏ", + "@clear": {}, + "favourites": "Yêu thích", + "@favourites": {}, + "shuffleAll": "Trộn hết", + "@shuffleAll": {}, + "finamp": "Finamp", + "@finamp": {}, + "downloads": "Tải xuống", + "@downloads": {}, + "settings": "Cài đặt", + "@settings": {}, + "offlineMode": "Chế độ ngoại tuyến", + "@offlineMode": {}, + "sortOrder": "Sắp thứ tự", + "@sortOrder": {}, + "sortBy": "Sắp theo", + "@sortBy": {}, + "album": "Album", + "@album": {}, + "albumArtist": "Album Nghệ Sĩ", + "@albumArtist": {}, + "artist": "Nghệ sĩ", + "@artist": {}, + "budget": "Chi phí", + "@budget": {}, + "criticRating": "Đánh giá nhà phê bình", + "@criticRating": {}, + "dateAdded": "Ngày được thêm", + "@dateAdded": {}, + "datePlayed": "Ngày đã chơi", + "@datePlayed": {}, + "playCount": "Số lần chơi", + "@playCount": {}, + "premiereDate": "Ngày phát hành", + "@premiereDate": {}, + "productionYear": "Năm sản xuất", + "@productionYear": {}, + "name": "Tên", + "@name": {}, + "random": "Ngẫu nhiên", + "@random": {}, + "revenue": "Lợi nhuận", + "@revenue": {}, + "runtime": "Thời gian phát", + "@runtime": {}, + "downloadMissingImages": "Tải ảnh thiếu", + "@downloadMissingImages": {}, + "playlists": "Danh sách phát", + "@playlists": {}, + "downloadCount": "{count,plural, =1{{count} đang tải xuống} other{{count} đang tải xuống}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedItemsCount": "{count,plural,=1{{count} vật} other{{count} vật}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedImagesCount": "{count,plural,=1{{count} ảnh} other{{count} ảnh}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} đã hoàn thành", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlFailed": "{count} thất bại", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlEnqueued": "{count} đang hàng đợi", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlRunning": "{count} đang chạy", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrorsTitle": "Lỗi tải xuống", + "@downloadErrorsTitle": {}, + "noErrors": "Không có lỗi!", + "@noErrors": {}, + "errorScreenError": "Đã có lỗi xảy ra khi lấy danh sách lỗi! Bạn nên tạo một Issue trên trang Github và xoá dữ liệu ứng dụng", + "@errorScreenError": {}, + "failedToGetSongFromDownloadId": "Lấy bài hát từ ID tải Thất Bại", + "@failedToGetSongFromDownloadId": {}, + "error": "Lỗi", + "@error": {}, + "discNumber": "Đĩa {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "playButtonLabel": "PHÁT", + "@playButtonLabel": {}, + "songCount": "{count,plural,=1{{count} bài hát} other{{count} bài hát}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "required": "Được yêu cầu", + "@required": {}, + "updateButtonLabel": "CẬP NHẬT", + "@updateButtonLabel": {}, + "favourite": "Yêu thích", + "@favourite": {}, + "downloadsDeleted": "Đã xóa tải xuống.", + "@downloadsDeleted": {}, + "addDownloads": "Thêm tải xuống", + "@addDownloads": {}, + "location": "Vị trí", + "@location": {}, + "addButtonLabel": "THÊM", + "@addButtonLabel": {}, + "shareLogs": "Chia sẻ bản ghi", + "@shareLogs": {}, + "logsCopied": "Đã sao chép bản ghi.", + "@logsCopied": {}, + "message": "Tin nhắn", + "@message": {}, + "stackTrace": "Dấu vết Ngăn xếp", + "@stackTrace": {}, + "transcoding": "Đang chuyển đổi", + "@transcoding": {}, + "downloadLocations": "Nơi tải xuống", + "@downloadLocations": {}, + "audioService": "Dịch vụ âm thanh", + "@audioService": {}, + "layoutAndTheme": "Bố cục & Chủ đề", + "@layoutAndTheme": {}, + "notAvailableInOfflineMode": "Không khả dụng ở chế độ ngoại tuyến", + "@notAvailableInOfflineMode": {}, + "logOut": "Đăng xuất", + "@logOut": {}, + "downloadedSongsWillNotBeDeleted": "Các bài hát được tải xuống sẽ không bị xoá", + "@downloadedSongsWillNotBeDeleted": {}, + "jellyfinUsesAACForTranscoding": "Jellyfin dùng codec AAC để chuyển đổi", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscoding": "Bật chuyển đổi", + "@enableTranscoding": {}, + "enableTranscodingSubtitle": "Chuyển đổi các luồng truyền phát nhạc trên phía máy chủ.", + "@enableTranscodingSubtitle": {}, + "editPlaylistNameTooltip": "Chỉnh sửa tên danh sách phát", + "@editPlaylistNameTooltip": {}, + "customLocation": "Vị trí tuỳ chỉnh", + "@customLocation": {}, + "appDirectory": "Đường dẫn ứng dụng", + "@appDirectory": {}, + "addDownloadLocation": "Thêm đường dẫn tải xuống", + "@addDownloadLocation": {}, + "selectDirectory": "Chọn đường dẫn", + "@selectDirectory": {}, + "pathReturnSlashErrorMessage": "Đường dẫn mà trả về \"/\" không dùng được", + "@pathReturnSlashErrorMessage": {}, + "directoryMustBeEmpty": "Đường dẫn phải trống", + "@directoryMustBeEmpty": {}, + "enterLowPriorityStateOnPause": "Vào trạng thái Ưu Tiên Thấp khi Dừng", + "@enterLowPriorityStateOnPause": {}, + "enterLowPriorityStateOnPauseSubtitle": "Để thông báo được gạt đi khi dừng. Ngoài ra cho phép Android tắt dịch vụ khi dừng.", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "shuffleAllSongCount": "Trộn tất cả bài hát", + "@shuffleAllSongCount": {}, + "shuffleAllSongCountSubtitle": "Số lượng bài hát được tải khi dùng nút trộn tất cả bài hát.", + "@shuffleAllSongCountSubtitle": {}, + "viewTypeSubtitle": "Loại xem cho màn hình nhạc", + "@viewTypeSubtitle": {}, + "list": "Danh sách", + "@list": {}, + "addToPlaylistTooltip": "Thêm vào danh sách phát", + "@addToPlaylistTooltip": {}, + "addToPlaylistTitle": "Thêm vào Danh sách phát", + "@addToPlaylistTitle": {}, + "removeFromPlaylistTooltip": "Loại bỏ khỏi danh sách phát", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Loại bỏ khỏi Danh sách phát", + "@removeFromPlaylistTitle": {}, + "newPlaylist": "Danh sách phát mới", + "@newPlaylist": {}, + "startupError": "Đã xảy ra lỗi khi khởi động ứng dụng. Lỗi: {error}\n\nXin hãy tạo một Issue trên github.com/UnicornsOnLSD/finamp với ảnh chụp màn hình của trang này. Nếu vấn đề tái diễn bạn có thể dọn dữ liệu ứng dụng để thiết lập lại ứng dụng.", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "internalExternalIpExplanation": "Nếu bạn muốn truy cập máy chủ Jellyfin từ xa, bạn cần phải dùng địa chỉ IP bên ngoài.\n\nNếu máy chủ của bạn đang mở cổng HTTP (cổng 80/443), bạn không cần phải chỉ rõ cổng. Đây có thể do máy chủ của bạn được set Proxy ngược.", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "communityRating": "Đánh giá cộng đồng", + "@communityRating": {}, + "downloadedMissingImages": "{count,plural, =0{Không ảnh thiếu được tìm} =1{Đã tải {count} ảnh thiếu} other{Đã tải {count} ảnh thiếu}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "shuffleButtonLabel": "XÁO TRỘN", + "@shuffleButtonLabel": {}, + "startMixNoSongsAlbum": "Bấm giữ một Album để thêm hoặc loại bỏ ra khỏi bộ Mix trước khi bắt đầu một tuyển tập Mix", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "downloadErrors": "Lỗi tải", + "@downloadErrors": {}, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + }, + "downloadsAdded": "Đã thêm tải xuống.", + "@downloadsAdded": {}, + "areYouSure": "Bạn chắc chứ?", + "@areYouSure": {}, + "unknownError": "Lỗi không xác định", + "@unknownError": {}, + "applicationLegalese": "Được cấp giấy phép Mozilla Public License 2.0. Mã nguồn tại:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "bitrate": "Tốc độ bit", + "@bitrate": {}, + "bitrateSubtitle": "Một tốc độ bit cao hơn mang lại âm thanh tốt hơn trong khi dùng băng thông lớn hơn.", + "@bitrateSubtitle": {}, + "customLocationsBuggy": "Vị trí tùy chỉnh khá nhiều lỗi do lỗi với việc cấp quyền. Tôi đang tìm biện pháp để sửa, hiện tại tôi không khuyến khích dùng chúng.", + "@customLocationsBuggy": {}, + "editPlaylistNameTitle": "Chỉnh sửa Tên Danh sách phát", + "@editPlaylistNameTitle": {}, + "viewType": "Loại Xem", + "@viewType": {}, + "playlistNameUpdated": "Tên danh sách phát đã được cập nhật.", + "@playlistNameUpdated": {} +} From 57998464e170b5926d86f698f5f848c09733b2a0 Mon Sep 17 00:00:00 2001 From: ssantos Date: Sat, 18 Mar 2023 10:40:16 +0000 Subject: [PATCH 033/172] Translated using Weblate (Portuguese) Currently translated at 87.6% (149 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/pt/ --- lib/l10n/app_pt.arb | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/lib/l10n/app_pt.arb b/lib/l10n/app_pt.arb index 6c5bd11e7..6be2b5d1c 100644 --- a/lib/l10n/app_pt.arb +++ b/lib/l10n/app_pt.arb @@ -31,9 +31,9 @@ "@shuffleButtonLabel": {}, "location": "Localização", "@location": {}, - "downloadsAdded": "Transferências adicionadas.", + "downloadsAdded": "Descargas adicionadas.", "@downloadsAdded": {}, - "downloadedSongsWillNotBeDeleted": "Músicas transferidas não serão apagadas", + "downloadedSongsWillNotBeDeleted": "Músicas descarregadas não serão apagadas", "@downloadedSongsWillNotBeDeleted": {}, "areYouSure": "Tem certeza?", "@areYouSure": {}, @@ -115,7 +115,7 @@ "@shuffleAll": {}, "finamp": "Finamp", "@finamp": {}, - "downloads": "Downloads", + "downloads": "Descargas", "@downloads": {}, "settings": "Configurações", "@settings": {}, @@ -157,7 +157,7 @@ "@runtime": {}, "downloadMissingImages": "Descarregar imagens ausentes", "@downloadMissingImages": {}, - "downloadErrors": "Erros de transferência", + "downloadErrors": "Erros de descarga", "@downloadErrors": {}, "downloadedMissingImages": "{count,plural, =0{Nenhuma imagem ausente encontrada} =1{{count} imagem ausente descarregada} other{{count} imagens ausentes descarregadas}}", "@downloadedMissingImages": { @@ -222,9 +222,9 @@ } } }, - "downloadErrorsTitle": "Erros de transferência", + "downloadErrorsTitle": "Erros de descarga", "@downloadErrorsTitle": {}, - "failedToGetSongFromDownloadId": "Falha em adquirir música do ID da transferência", + "failedToGetSongFromDownloadId": "Falha em adquirir música do ID da descarga", "@failedToGetSongFromDownloadId": {}, "error": "Erro", "@error": {}, @@ -250,9 +250,9 @@ "@playlistNameUpdated": {}, "favourite": "Favorito", "@favourite": {}, - "downloadsDeleted": "Transferências apagadas.", + "downloadsDeleted": "Descargas apagadas.", "@downloadsDeleted": {}, - "addDownloads": "Adicionar Transferências", + "addDownloads": "Adicionar Descargas", "@addDownloads": {}, "message": "Mensagem", "@message": {}, @@ -268,7 +268,7 @@ "@applicationLegalese": {}, "transcoding": "Transcodificando", "@transcoding": {}, - "downloadLocations": "Locais de Transferências", + "downloadLocations": "Locais de Descargas", "@downloadLocations": {}, "audioService": "Serviço de Áudio", "@audioService": {}, @@ -286,7 +286,7 @@ "@customLocation": {}, "appDirectory": "Diretório de apps", "@appDirectory": {}, - "addDownloadLocation": "Adicionar Localização de Transferências", + "addDownloadLocation": "Adicionar Localização de Descargas", "@addDownloadLocation": {}, "selectDirectory": "Selecione Diretório", "@selectDirectory": {}, @@ -364,7 +364,7 @@ "@noArtist": {}, "streaming": "TRANSMITINDO", "@streaming": {}, - "downloaded": "TRANSFERIDO", + "downloaded": "DESCARREGADO", "@downloaded": {}, "transcode": "TRANSCODIFICAR", "@transcode": {}, From b728a80d49d79b3a3b3a6d4846c4fcc93fff2066 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Luis=20Hern=C3=A1ndez=20Martin?= Date: Tue, 4 Apr 2023 11:48:28 +0200 Subject: [PATCH 034/172] Added translation using Weblate (Catalan) --- lib/l10n/app_ca.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_ca.arb diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_ca.arb @@ -0,0 +1 @@ +{} From aedb9439a2d642fbb90578ade57c03958adf9943 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Luis=20Hern=C3=A1ndez=20Martin?= Date: Tue, 4 Apr 2023 09:56:12 +0000 Subject: [PATCH 035/172] Translated using Weblate (Catalan) Currently translated at 13.5% (23 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ca/ --- lib/l10n/app_ca.arb | 81 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 80 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_ca.arb b/lib/l10n/app_ca.arb index 0967ef424..a976687e6 100644 --- a/lib/l10n/app_ca.arb +++ b/lib/l10n/app_ca.arb @@ -1 +1,80 @@ -{} +{ + "serverUrl": "URL del servidor", + "@serverUrl": {}, + "downloaded": "DESCARREGAT", + "@downloaded": {}, + "removeFavourite": "Suprimeix el favorit", + "@removeFavourite": {}, + "addFavourite": "Afegeix favorit", + "@addFavourite": {}, + "addedToQueue": "S'ha afegit a la cua.", + "@addedToQueue": {}, + "addToMix": "Afegir a la barreja", + "@addToMix": {}, + "statusError": "ERROR D'ESTAT", + "@statusError": {}, + "startupError": "S'ha produït un error durant l'inici de l'aplicació. L'error va ser: {error}\n\nCreeu un problema a github.com/UnicornsOnLSD/finamp amb una captura de pantalla d'aquesta pàgina. Si aquest problema persisteix, podeu esborrar les dades de l'aplicació per restablir-la.", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "queue": "Cua", + "@queue": {}, + "replaceQueue": "Substitueix la cua", + "@replaceQueue": {}, + "internalExternalIpExplanation": "Si voleu poder accedir al vostre servidor Jellyfin de forma remota, heu d'utilitzar la vostra IP externa.\n\nSi el vostre servidor està en un port HTTP (80/443), no cal que especifiqueu cap port. És probable que aquest sigui el cas si el vostre servidor està darrere d'un servidor intermediari invers.", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "transcode": "TRANSCODIFICACIÓ", + "@transcode": {}, + "direct": "DIRECTE", + "@direct": {}, + "instantMix": "Barreja instantània", + "@instantMix": {}, + "addToQueue": "Afegir a la cua", + "@addToQueue": {}, + "goToAlbum": "Vés a l'àlbum", + "@goToAlbum": {}, + "removedFromPlaylist": "S'ha suprimit de la llista de reproducció.", + "@removedFromPlaylist": {}, + "queueReplaced": "S'ha substituït la cua.", + "@queueReplaced": {}, + "startingInstantMix": "Inici de la barreja instantània.", + "@startingInstantMix": {}, + "anErrorHasOccured": "S'ha produït un error.", + "@anErrorHasOccured": {}, + "responseError": "{error} Codi d'estat {statusCode}.", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "responseError401": "{error} Codi d'estat {statusCode}. Això probablement vol dir que heu utilitzat un nom d'usuari/contrasenya incorrecte o que el vostre client ha tancat la sessió.", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } + }, + "removeFromMix": "Eliminar de la barreja", + "@removeFromMix": {} +} From c1d4c2f8350af39c938570773aa1684d8d377d46 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 12 Apr 2023 16:42:35 +0100 Subject: [PATCH 036/172] Update dependencies --- pubspec.lock | 209 ++++++++++++++++++++++++++------------------------- pubspec.yaml | 31 ++++---- 2 files changed, 122 insertions(+), 118 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 1b37cf63f..185fa7a56 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,42 +5,42 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: "0c80aeab9bc807ab10022cd3b2f4cf2ecdf231949dc1ddd9442406a003f19201" + sha256: a36ec4843dc30ea6bf652bf25e3448db6c5e8bcf4aa55f063a5d1dad216d8214 url: "https://pub.dev" source: hosted - version: "52.0.0" + version: "58.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: cd8ee83568a77f3ae6b913a36093a1c9b1264e7cb7f834d9ddd2311dade9c1f4 + sha256: cc4242565347e98424ce9945c819c192ec0838cb9d1f6aa4a97cc96becbc5b27 url: "https://pub.dev" source: hosted - version: "5.4.0" + version: "5.10.0" android_id: dependency: "direct main" description: name: android_id - sha256: e60b3c96bf256014d6c1d0a03e2712ef49d717817c13cbdb4c72b939199a4993 + sha256: f417b2fe86f93a1184662eaae582082c02c57d50c5a1048e5a66e09b2bdf87db url: "https://pub.dev" source: hosted - version: "0.1.3+1" + version: "0.2.0" archive: dependency: transitive description: name: archive - sha256: ed7cc591a948744994714375caf9a2ce89e1d82e8243997c8a2994d57181c212 + sha256: "0c8368c9b3f0abbc193b9d6133649a614204b528982bebc7026372d61677ce3a" url: "https://pub.dev" source: hosted - version: "3.3.5" + version: "3.3.7" args: dependency: transitive description: name: args - sha256: "139d809800a412ebb26a3892da228b2d0ba36f0ef5d9a82166e5e52ec8d61611" + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" async: dependency: transitive description: @@ -117,18 +117,18 @@ packages: dependency: transitive description: name: build_daemon - sha256: "6bc5544ea6ce4428266e7ea680e945c68806c4aae2da0eb5e9ccf38df8d6acbf" + sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.1" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: "7c35a3a7868626257d8aee47b51c26b9dba11eaddf3431117ed2744951416aab" + sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.2.0" build_runner: dependency: "direct dev" description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: built_value - sha256: "169565c8ad06adb760c3645bf71f00bff161b00002cace266cad42c5d22a7725" + sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0" url: "https://pub.dev" source: hosted - version: "8.4.3" + version: "8.4.4" characters: dependency: transitive description: @@ -181,10 +181,10 @@ packages: dependency: "direct main" description: name: chopper - sha256: ea41a0bafd5f4d4d735a90a722e00bb1d80e830073cb380edfa1979901d6d2df + sha256: b2645618fa760df06d7609c96b092d7b3e7a8f23639d34269f62f45a5edabc7d url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.1.1" chopper_generator: dependency: "direct dev" description: @@ -197,10 +197,10 @@ packages: dependency: transitive description: name: cli_util - sha256: "66f86e916d285c1a93d3b79587d94bd71984a66aac4ff74e524cfa7877f1395c" + sha256: b8db3080e59b2503ca9e7922c3df2072cf13992354d5e944074ffa836fba43b7 url: "https://pub.dev" source: hosted - version: "0.3.5" + version: "0.4.0" clipboard: dependency: "direct main" description: @@ -245,10 +245,10 @@ packages: dependency: transitive description: name: cross_file - sha256: f71079978789bc2fe78d79227f1f8cfe195b31bbd8db2399b0d15a4b96fb843b + sha256: "0b0036e8cccbfbe0555fd83c1d31a6f30b77a96b598b35a5d36dd41f718695e9" url: "https://pub.dev" source: hosted - version: "0.3.3+2" + version: "0.3.3+4" crypto: dependency: transitive description: @@ -269,18 +269,18 @@ packages: dependency: transitive description: name: dart_style - sha256: "7a03456c3490394c8e7665890333e91ae8a49be43542b616e414449ac358acd4" + sha256: "6d691edde054969f0e0f26abb1b30834b5138b963793e56f69d3a9a4435e6352" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.0" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: "7ff671ed0a6356fa8f2e1ae7d3558d3fb7b6a41e24455e4f8df75b811fb8e4ab" + sha256: "435383ca05f212760b0a70426b5a90354fe6bd65992b3a5e27ab6ede74c02f5c" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "8.2.0" device_info_plus_platform_interface: dependency: transitive description: @@ -289,6 +289,14 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + equatable: + dependency: transitive + description: + name: equatable + sha256: c2b87cb7756efdf69892005af546c56c0b5037f54d2a88269b4f347a505e3ca2 + url: "https://pub.dev" + source: hosted + version: "2.0.5" fake_async: dependency: transitive description: @@ -317,10 +325,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: d090ae03df98b0247b82e5928f44d1b959867049d18d73635e2e0bc3f49542b9 + sha256: dd328189f2f4ccea042bb5b382d5e981691cc74b5a3429b9317bff2b19704489 url: "https://pub.dev" source: hosted - version: "5.2.5" + version: "5.2.8" file_sizes: dependency: "direct main" description: @@ -333,10 +341,10 @@ packages: dependency: transitive description: name: fixnum - sha256: "04be3e934c52e082558cc9ee21f42f5c1cd7a1262f4c63cd0357c08d5bba81ec" + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -361,20 +369,19 @@ packages: flutter_downloader: dependency: "direct main" description: - path: "." - ref: "2517721" - resolved-ref: "25177215a0afba6f4ed83b6382874fa8e219358e" - url: "https://github.com/UnicornsOnLSD/flutter_downloader.git" - source: git - version: "1.7.3" + name: flutter_downloader + sha256: "6f2836668f33d0cd3ed275c3198d30967c42af53fa9b18c6b0edbf5fc12b599a" + url: "https://pub.dev" + source: hosted + version: "1.10.2" flutter_launcher_icons: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: ce0e501cfc258907842238e4ca605e74b7fd1cdf04b3b43e86c43f3e40a1592c + sha256: "8546a9b9510e1a260b8d55fb2d07096e8a8552c6a2c2bf529100344894b2b41a" url: "https://pub.dev" source: hosted - version: "0.11.0" + version: "0.13.0" flutter_lints: dependency: "direct dev" description: @@ -392,18 +399,18 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "60fc7b78455b94e6de2333d2f95196d32cf5c22f4b0b0520a628804cb463503b" + sha256: c224ac897bed083dabf11f238dd11a239809b446740be0c2044608c50029ffdf url: "https://pub.dev" source: hosted - version: "2.0.7" + version: "2.0.9" flutter_riverpod: dependency: "direct main" description: name: flutter_riverpod - sha256: "0c997763ce06359ee4686553b74def84062e9d6929ac63f61fa02465c1f8e32c" + sha256: "812dfbb87af51e73e68ea038bcfd1c732078d6838d3388d03283db7dec0d1e5f" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.4" flutter_sticky_header: dependency: "direct main" description: @@ -506,10 +513,10 @@ packages: dependency: transitive description: name: image - sha256: "8e9d133755c3e84c73288363e6343157c383a0c6c56fc51afcc5d4d7180306d6" + sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "4.0.15" infinite_scroll_pagination: dependency: "direct main" description: @@ -554,18 +561,18 @@ packages: dependency: "direct dev" description: name: json_serializable - sha256: "040088d9eb2337f3a51ddabe35261e2fea1c351e3dd29d755ed1290b6b700a75" + sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a url: "https://pub.dev" source: hosted - version: "6.6.0" + version: "6.6.1" just_audio: dependency: "direct main" description: name: just_audio - sha256: "7a5057a4d05c8f88ee968cec6fdfe1015577d5184e591d5ac15ab16d8f5ecb17" + sha256: "7e6d31508dacd01a066e3889caf6282e5f1eb60707c230203b21a83af5c55586" url: "https://pub.dev" source: hosted - version: "0.9.31" + version: "0.9.32" just_audio_platform_interface: dependency: transitive description: @@ -594,10 +601,10 @@ packages: dependency: "direct main" description: name: logging - sha256: c0bbfe94d46aedf9b8b3e695cf3bd48c8e14b35e3b2c639e0aa7755d589ba946 + sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" matcher: dependency: transitive description: @@ -658,10 +665,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: f619162573096d428ccde2e33f92e05b5a179cd6f0e3120c1005f181bee8ed16 + sha256: cbff87676c352d97116af6dbea05aa28c4d65eb0f6d5677a520c11a69ca9a24d url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -682,50 +689,50 @@ packages: dependency: "direct main" description: name: path_provider - sha256: dcea5feb97d8abf90cab9e9030b497fb7c3cbf26b7a1fe9e3ef7dcb0a1ddec95 + sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 url: "https://pub.dev" source: hosted - version: "2.0.12" + version: "2.0.14" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: a776c088d671b27f6e3aa8881d64b87b3e80201c64e8869b811325de7a76c15e + sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" url: "https://pub.dev" source: hosted - version: "2.0.22" + version: "2.0.24" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "62a68e7e1c6c459f9289859e2fae58290c981ce21d1697faf54910fe1faa4c74" + sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183 url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.2" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ab0987bf95bc591da42dffb38c77398fc43309f0b9b894dcc5d6f40c4b26c379 + sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.1.10" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: f0abc8ebd7253741f05488b4813d936b4d07c6bae3e86148a09e342ee4b08e76 + sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" url: "https://pub.dev" source: hosted - version: "2.0.5" + version: "2.0.6" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: bcabbe399d4042b8ee687e17548d5d3f527255253b4a639f5f8d2094a9c2b45c + sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.5" pedantic: dependency: transitive description: @@ -754,10 +761,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: "9c370ef6a18b1c4b2f7f35944d644a56aa23576f23abee654cf73968de93f163" + sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 url: "https://pub.dev" source: hosted - version: "9.0.7" + version: "9.0.8" permission_handler_platform_interface: dependency: transitive description: @@ -794,18 +801,18 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: dbf0f707c78beedc9200146ad3cb0ab4d5da13c246336987be6940f026500d3a + sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pointycastle: dependency: transitive description: name: pointycastle - sha256: db7306cf0249f838d1a24af52b5a5887c5bf7f31d8bb4e827d071dc0939ad346 + sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c url: "https://pub.dev" source: hosted - version: "3.6.2" + version: "3.7.2" pool: dependency: transitive description: @@ -842,18 +849,18 @@ packages: dependency: transitive description: name: pubspec_parse - sha256: "75f6614d6dde2dc68948dffbaa4fe5dae32cd700eb9fb763fe11dfb45a3c4d0a" + sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" riverpod: dependency: transitive description: name: riverpod - sha256: "0f43c64f1f79c2112c843305a879a746587fb7c1e388f1d4717737796756e2c4" + sha256: "77ab3bcd084bb19fa8717a526217787c725d7f5be938404c7839cd760fdf6ae5" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.3.4" rxdart: dependency: "direct main" description: @@ -866,18 +873,18 @@ packages: dependency: "direct main" description: name: share_plus - sha256: e387077716f80609bb979cd199331033326033ecd1c8f200a90c5f57b1c9f55e + sha256: "692261968a494e47323dcc8bc66d8d52e81bc27cb4b808e4e8d7e8079d4cc01a" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.2" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "82ddd4ab9260c295e6e39612d4ff00390b9a7a21f1bb1da771e2f232d80ab8a1" + sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" url: "https://pub.dev" source: hosted - version: "3.2.0" + version: "3.2.1" shelf: dependency: transitive description: @@ -911,18 +918,18 @@ packages: dependency: transitive description: name: sliver_tools - sha256: edf005f1a47c2ffa6f1e1a4f24dd99c45b8bccfff9b928d39170d36dc6fda871 + sha256: ccdc502098a8bfa07b3ec582c282620031481300035584e1bb3aca296a505e8c url: "https://pub.dev" source: hosted - version: "0.2.8" + version: "0.2.10" source_gen: dependency: transitive description: name: source_gen - sha256: "2d79738b6bbf38a43920e2b8d189e9a3ce6cc201f4b8fc76be5e4fe377b1c38d" + sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298 url: "https://pub.dev" source: hosted - version: "1.2.6" + version: "1.2.7" source_helper: dependency: transitive description: @@ -943,18 +950,18 @@ packages: dependency: transitive description: name: sqflite - sha256: "067ab48dbc66bae05e18073a604443baa35957101bd3905b94f65e764c6d0688" + sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.2.6" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: b2ed22d1d62c944ec0dac5cc687ae99cb3331c3ebe146d726ed24704634b5ccd + sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" stack_trace: dependency: transitive description: @@ -1039,34 +1046,34 @@ packages: dependency: transitive description: name: url_launcher_linux - sha256: "318c42cba924e18180c029be69caf0a1a710191b9ec49bb42b5998fdcccee3cc" + sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.4" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "4eae912628763eb48fc214522e58e942fd16ce195407dbf45638239523c759a6" + sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "44d79408ce9f07052095ef1f9a693c258d6373dc3944249374e30eff7219ccb0" + sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.16" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: b6217370f8eb1fd85c8890c539f5a639a01ab209a36db82c921ebeacefc7a615 + sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" uuid: dependency: "direct main" description: @@ -1103,26 +1110,26 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: ca49c0bc209c687b887f30527fb6a9d80040b072cc2990f34b9bec3e7663101b + sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.4.0" win32: dependency: transitive description: name: win32 - sha256: c9ebe7ee4ab0c2194e65d3a07d8c54c5d00bb001b76081c4a04cdb8448b59e46 + sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 url: "https://pub.dev" source: hosted - version: "3.1.3" + version: "3.1.4" xdg_directories: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.0" xml: dependency: transitive description: @@ -1140,5 +1147,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.0 <3.0.0" + dart: ">=2.19.4 <3.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 74d9fb2e1..927495044 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 0.6.10+28 environment: - sdk: ">=2.19.0 <3.0.0" + sdk: ">=2.19.4 <3.0.0" dependencies: flutter: @@ -27,42 +27,39 @@ dependencies: sdk: flutter json_annotation: ^4.8.0 - chopper: ^6.0.0 + chopper: ^6.1.1 get_it: ^7.2.0 - just_audio: ^0.9.31 + just_audio: ^0.9.32 audio_service: ^0.18.9 audio_session: ^0.1.13 rxdart: ^0.27.7 simple_gesture_detector: ^0.2.0 - flutter_downloader: - git: - url: https://github.com/UnicornsOnLSD/flutter_downloader.git - ref: "2517721" - path_provider: ^2.0.12 + flutter_downloader: ^1.10.2 + path_provider: ^2.0.14 hive: ^2.2.3 hive_flutter: ^1.1.0 file_sizes: ^1.0.6 - logging: ^1.1.0 + logging: ^1.1.1 clipboard: ^0.1.3 - file_picker: ^5.2.5 + file_picker: ^5.2.8 permission_handler: ^10.2.0 provider: ^6.0.5 uuid: ^3.0.7 infinite_scroll_pagination: ^3.2.0 flutter_sticky_header: ^0.6.5 - device_info_plus: ^8.0.0 - package_info_plus: ^3.0.2 + device_info_plus: ^8.2.0 + package_info_plus: ^3.1.0 octo_image: ^1.0.2 - share_plus: ^6.3.0 + share_plus: ^6.3.2 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.5 path: ^1.8.2 - android_id: ^0.1.3+1 + android_id: ^0.2.0 intl: ^0.17.0 auto_size_text: ^3.0.0 - flutter_riverpod: ^2.1.3 + flutter_riverpod: ^2.3.4 dev_dependencies: flutter_test: @@ -70,8 +67,8 @@ dev_dependencies: build_runner: ^2.3.3 chopper_generator: ^6.0.0 hive_generator: ^2.0.0 - json_serializable: ^6.6.0 - flutter_launcher_icons: ^0.11.0 + json_serializable: ^6.6.1 + flutter_launcher_icons: ^0.13.0 flutter_lints: ^2.0.1 # For information on the generic Dart part of this file, see the From 464a4de9cce958fa5b3154c1b38a91cc5bb09cf4 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:07:20 +0100 Subject: [PATCH 037/172] Add flutterw for F-Droid --- .flutter | 1 + .gitmodules | 4 ++ flutterw | 113 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 118 insertions(+) create mode 160000 .flutter create mode 100644 .gitmodules create mode 100755 flutterw diff --git a/.flutter b/.flutter new file mode 160000 index 000000000..4b1264501 --- /dev/null +++ b/.flutter @@ -0,0 +1 @@ +Subproject commit 4b12645012342076800eb701bcdfe18f87da21cf diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 000000000..ee9d7761a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,4 @@ +[submodule ".flutter"] + path = .flutter + url = https://github.com/flutter/flutter.git + branch = stable diff --git a/flutterw b/flutterw new file mode 100755 index 000000000..419302129 --- /dev/null +++ b/flutterw @@ -0,0 +1,113 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Flutter start up script for UN*X +## Version: v1.3.1 +## Date: 2023-04-12 16:58:43 +## +## Use this flutter wrapper to bundle Flutter within your project to make +## sure everybody builds with the same version. +## +## Read about the install and uninstall process in the README on GitHub +## https://github.com/passsy/flutter_wrapper +## +## Inspired by gradle-wrapper. +## +############################################################################## + +echoerr() { echo "$@" 1>&2; } + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ]; do + ls=$(ls -ld "$PRG") + link=$(expr "$ls" : '.*-> \(.*\)$') + if expr "$link" : '/.*' >/dev/null; then + PRG="$link" + else + PRG=$(dirname "$PRG")"/$link" + fi +done +SAVED="$(pwd)" +cd "$(dirname "$PRG")/" >/dev/null +APP_HOME="$(pwd -P)" +cd "$SAVED" >/dev/null + +FLUTTER_SUBMODULE_NAME='.flutter' +GIT_HOME=$(git -C "${APP_HOME}" rev-parse --show-toplevel) +FLUTTER_DIR="${GIT_HOME}/${FLUTTER_SUBMODULE_NAME}" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +# Fix not initialized flutter submodule +if [ ! -f "${FLUTTER_DIR}/bin/flutter" ]; then + echoerr "$FLUTTER_SUBMODULE_NAME submodule not initialized. Initializing..." + git submodule update --init "${FLUTTER_DIR}" +fi + +# Detect detach HEAD and fix it. commands like upgrade expect a valid branch, not a detached HEAD +FLUTTER_SYMBOLIC_REF=$(git -C "${FLUTTER_DIR}" symbolic-ref -q HEAD) +if [ -z "${FLUTTER_SYMBOLIC_REF}" ]; then + FLUTTER_REV=$(git -C "${FLUTTER_DIR}" rev-parse HEAD) + FLUTTER_CHANNEL=$(git -C "${GIT_HOME}" config -f .gitmodules submodule.${FLUTTER_SUBMODULE_NAME}.branch) + + if [ -z "${FLUTTER_CHANNEL}" ]; then + echoerr "Warning: Submodule '$FLUTTER_SUBMODULE_NAME' doesn't point to an official Flutter channel \ +(one of stable|beta|dev|master). './flutterw upgrade' will fail without a channel." + echoerr "Fix this by adding a specific channel with:" + echoerr " - './flutterw channel ' or" + echoerr " - Add 'branch = ' to '$FLUTTER_SUBMODULE_NAME' submodule in .gitmodules" + else + echoerr "Fixing detached HEAD: '$FLUTTER_SUBMODULE_NAME' submodule points to a specific commit $FLUTTER_REV, not channel '$FLUTTER_CHANNEL' (as defined in .gitmodules)." + # Make sure channel is fetched + # Remove old channel branch because it might be moved to an unrelated commit where fast-forward pull isn't possible + git -C "${FLUTTER_DIR}" branch -q -D "${FLUTTER_CHANNEL}" 2> /dev/null || true + git -C "${FLUTTER_DIR}" fetch -q origin + + # bind current HEAD to channel defined in .gitmodules + git -C "${FLUTTER_DIR}" checkout -q -b "${FLUTTER_CHANNEL}" "${FLUTTER_REV}" + git -C "${FLUTTER_DIR}" branch -q -u "origin/${FLUTTER_CHANNEL}" "${FLUTTER_CHANNEL}" + echoerr "Fixed! Migrated to channel '$FLUTTER_CHANNEL' while staying at commit $FLUTTER_REV. './flutterw upgrade' now works without problems!" + git -C "${FLUTTER_DIR}" status -bs + fi +fi + +# Wrapper tasks done, call flutter binary with all args +set -e +"$FLUTTER_DIR/bin/flutter" "$@" +set +e + +# Post flutterw tasks. exit code from /bin/flutterw will be used as final exit +FLUTTER_EXIT_STATUS=$? +if [ ${FLUTTER_EXIT_STATUS} -eq 0 ]; then + + # ./flutterw channel CHANNEL + if echo "$@" | grep -q "channel"; then + if [ -n "$2" ]; then + # make sure .gitmodules is updated as well + CHANNEL=${2} # second arg + git config -f "${GIT_HOME}/.gitmodules" "submodule.${FLUTTER_SUBMODULE_NAME}.branch" "${CHANNEL}" + # makes sure nobody forgets to do commit all changed files + git add "${GIT_HOME}/.gitmodules" + git add "${FLUTTER_DIR}" + fi + fi + + # ./flutterw upgrade + if echo "$@" | grep -q "upgrade"; then + # makes sure nobody forgets to do commit the changed submodule + git add "${FLUTTER_DIR}" + # flutter packages get runs automatically. Stage those changes as well + if [ -f pubspec.lock ]; then + git add pubspec.lock + fi + fi +fi + +exit ${FLUTTER_EXIT_STATUS} From 95ce0256f5615bbbdcd11bfc3e7aaed06b2dd733 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:08:58 +0100 Subject: [PATCH 038/172] Bump F-Droid version --- fastlane/metadata/android/en-US/changelogs/29.txt | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/29.txt diff --git a/fastlane/metadata/android/en-US/changelogs/29.txt b/fastlane/metadata/android/en-US/changelogs/29.txt new file mode 100644 index 000000000..e98083a33 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/29.txt @@ -0,0 +1 @@ +Lots of bug fixes. Full changelog at https://github.com/jmshrv/finamp/releases/tag/0.6.11 diff --git a/pubspec.yaml b/pubspec.yaml index 927495044..0f6dd5c73 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.10+28 +version: 0.6.11+29 environment: sdk: ">=2.19.4 <3.0.0" From b17a55d8f203722a2ae4f0feb22602f2c792fba1 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 12 Apr 2023 17:36:15 +0100 Subject: [PATCH 039/172] Revert "Log album image sizes" This reverts commit 4e31658a276ce3360cc7328befd13a6bc908736d. Logging this spammed the logs too much --- lib/components/album_image.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/components/album_image.dart b/lib/components/album_image.dart index 4d794be16..35ea1904a 100644 --- a/lib/components/album_image.dart +++ b/lib/components/album_image.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:logging/logging.dart'; import 'package:octo_image/octo_image.dart'; import '../models/jellyfin_models.dart'; @@ -7,8 +6,6 @@ import '../services/album_image_provider.dart'; typedef ImageProviderCallback = void Function(ImageProvider? imageProvider); -final _albumImageLogger = Logger("AlbumImage"); - /// This widget provides the default look for album images throughout Finamp - /// Aspect ratio 1 with a circular border radius of 4. If you don't want these /// customisations, use [BareAlbumImage] or get an [ImageProvider] directly @@ -64,9 +61,6 @@ class AlbumImage extends StatelessWidget { final int physicalHeight = (constraints.maxHeight * mediaQuery.devicePixelRatio).toInt(); - _albumImageLogger.info( - "Loading item ${item!.id} with max size ${physicalWidth}x$physicalHeight"); - return BareAlbumImage( item: item!, maxWidth: physicalWidth, From b4c330e034a67069d6f656c669f6b5e1156356a0 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 12 Apr 2023 18:04:10 +0100 Subject: [PATCH 040/172] Update iOS translation languages --- ios/Podfile.lock | 4 +-- ios/Runner.xcodeproj/project.pbxproj | 35 ++++++++++++++++--- ios/Runner/ca.lproj/LaunchScreen.strings | 1 + ios/Runner/ca.lproj/Main.strings | 1 + ios/Runner/da.lproj/LaunchScreen.strings | 1 + ios/Runner/da.lproj/Main.strings | 1 + ios/Runner/pt-PT.lproj/LaunchScreen.strings | 1 + ios/Runner/pt-PT.lproj/Main.strings | 1 + ios/Runner/tr.lproj/LaunchScreen.strings | 1 + ios/Runner/tr.lproj/Main.strings | 1 + ios/Runner/vi.lproj/LaunchScreen.strings | 1 + ios/Runner/vi.lproj/Main.strings | 1 + ios/Runner/zh-Hant.lproj/LaunchScreen.strings | 1 + ios/Runner/zh-Hant.lproj/Main.strings | 1 + 14 files changed, 44 insertions(+), 7 deletions(-) create mode 100644 ios/Runner/ca.lproj/LaunchScreen.strings create mode 100644 ios/Runner/ca.lproj/Main.strings create mode 100644 ios/Runner/da.lproj/LaunchScreen.strings create mode 100644 ios/Runner/da.lproj/Main.strings create mode 100644 ios/Runner/pt-PT.lproj/LaunchScreen.strings create mode 100644 ios/Runner/pt-PT.lproj/Main.strings create mode 100644 ios/Runner/tr.lproj/LaunchScreen.strings create mode 100644 ios/Runner/tr.lproj/Main.strings create mode 100644 ios/Runner/vi.lproj/LaunchScreen.strings create mode 100644 ios/Runner/vi.lproj/Main.strings create mode 100644 ios/Runner/zh-Hant.lproj/LaunchScreen.strings create mode 100644 ios/Runner/zh-Hant.lproj/Main.strings diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a2ca4d91d..b1a335a2b 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -144,11 +144,11 @@ SPEC CHECKSUMS: DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 file_picker: ce3938a0df3cc1ef404671531facef740d03f920 Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 - flutter_downloader: 058b9c41564a90500f67f3e432e3524613a7fd83 + flutter_downloader: b7301ae057deadd4b1650dc7c05375f10ff12c39 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: 37748e03f12783f9de2cb2c4eadfaa25fe6d4852 + path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index cfb0f2f07..10f01484d 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -42,6 +42,18 @@ 1A1D18E22964B15800AFE66F /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = ""; }; 1A1D18E32964B18F00AFE66F /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Main.strings; sourceTree = ""; }; 1A1D18E42964B18F00AFE66F /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1A3D4FB929E71AE500C17E1B /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Main.strings; sourceTree = ""; }; + 1A3D4FBA29E71AE500C17E1B /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1A3D4FBB29E71B0500C17E1B /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/Main.strings"; sourceTree = ""; }; + 1A3D4FBC29E71B0500C17E1B /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant"; path = "zh-Hant.lproj/LaunchScreen.strings"; sourceTree = ""; }; + 1A3D4FBD29E71B1200C17E1B /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Main.strings; sourceTree = ""; }; + 1A3D4FBE29E71B1200C17E1B /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1A3D4FBF29E71B2C00C17E1B /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/Main.strings"; sourceTree = ""; }; + 1A3D4FC029E71B2C00C17E1B /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "pt-PT"; path = "pt-PT.lproj/LaunchScreen.strings"; sourceTree = ""; }; + 1A3D4FC129E71B4700C17E1B /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/Main.strings; sourceTree = ""; }; + 1A3D4FC229E71B4700C17E1B /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1A3D4FC329E71B4B00C17E1B /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Main.strings; sourceTree = ""; }; + 1A3D4FC429E71B4B00C17E1B /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/LaunchScreen.strings; sourceTree = ""; }; 1A7A01C725617F74005D4731 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 1A9072D528AEB75800DB7A21 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; 1A9072D628AEB75800DB7A21 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; @@ -53,8 +65,6 @@ 1A9072DC28AEB7DB00DB7A21 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/LaunchScreen.strings; sourceTree = ""; }; 1A9072DD28AEB7E600DB7A21 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/Main.strings; sourceTree = ""; }; 1A9072DE28AEB7E600DB7A21 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/LaunchScreen.strings; sourceTree = ""; }; - 1A9072DF28AEB83200DB7A21 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/Main.strings; sourceTree = ""; }; - 1A9072E028AEB83200DB7A21 /* zh */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zh; path = zh.lproj/LaunchScreen.strings; sourceTree = ""; }; 1AA9061928BB90D900DF9B20 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/Main.strings; sourceTree = ""; }; 1AA9061A28BB90D900DF9B20 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/LaunchScreen.strings; sourceTree = ""; }; 1AA9061B28BB911A00DF9B20 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Main.strings; sourceTree = ""; }; @@ -223,7 +233,6 @@ nl, pl, sv, - zh, ar, fr, it, @@ -236,6 +245,12 @@ bg, "zh-Hans", et, + ca, + "zh-Hant", + da, + "pt-PT", + tr, + vi, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; @@ -358,7 +373,6 @@ 1A9072D928AEB7A500DB7A21 /* nl */, 1A9072DB28AEB7DB00DB7A21 /* pl */, 1A9072DD28AEB7E600DB7A21 /* sv */, - 1A9072DF28AEB83200DB7A21 /* zh */, 1AA9061928BB90D900DF9B20 /* ar */, 1AA9061B28BB911A00DF9B20 /* fr */, 1AA9061D28BB912A00DF9B20 /* it */, @@ -371,6 +385,12 @@ 1A1D18DF2964B12600AFE66F /* bg */, 1A1D18E12964B15800AFE66F /* zh-Hans */, 1A1D18E32964B18F00AFE66F /* et */, + 1A3D4FB929E71AE500C17E1B /* ca */, + 1A3D4FBB29E71B0500C17E1B /* zh-Hant */, + 1A3D4FBD29E71B1200C17E1B /* da */, + 1A3D4FBF29E71B2C00C17E1B /* pt-PT */, + 1A3D4FC129E71B4700C17E1B /* tr */, + 1A3D4FC329E71B4B00C17E1B /* vi */, ); name = Main.storyboard; sourceTree = ""; @@ -384,7 +404,6 @@ 1A9072DA28AEB7A500DB7A21 /* nl */, 1A9072DC28AEB7DB00DB7A21 /* pl */, 1A9072DE28AEB7E600DB7A21 /* sv */, - 1A9072E028AEB83200DB7A21 /* zh */, 1AA9061A28BB90D900DF9B20 /* ar */, 1AA9061C28BB911A00DF9B20 /* fr */, 1AA9061E28BB912A00DF9B20 /* it */, @@ -397,6 +416,12 @@ 1A1D18E02964B12600AFE66F /* bg */, 1A1D18E22964B15800AFE66F /* zh-Hans */, 1A1D18E42964B18F00AFE66F /* et */, + 1A3D4FBA29E71AE500C17E1B /* ca */, + 1A3D4FBC29E71B0500C17E1B /* zh-Hant */, + 1A3D4FBE29E71B1200C17E1B /* da */, + 1A3D4FC029E71B2C00C17E1B /* pt-PT */, + 1A3D4FC229E71B4700C17E1B /* tr */, + 1A3D4FC429E71B4B00C17E1B /* vi */, ); name = LaunchScreen.storyboard; sourceTree = ""; diff --git a/ios/Runner/ca.lproj/LaunchScreen.strings b/ios/Runner/ca.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/ca.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/ca.lproj/Main.strings b/ios/Runner/ca.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/ca.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/da.lproj/LaunchScreen.strings b/ios/Runner/da.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/da.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/da.lproj/Main.strings b/ios/Runner/da.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/da.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/pt-PT.lproj/LaunchScreen.strings b/ios/Runner/pt-PT.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/pt-PT.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/pt-PT.lproj/Main.strings b/ios/Runner/pt-PT.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/pt-PT.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/tr.lproj/LaunchScreen.strings b/ios/Runner/tr.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/tr.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/tr.lproj/Main.strings b/ios/Runner/tr.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/tr.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/vi.lproj/LaunchScreen.strings b/ios/Runner/vi.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/vi.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/vi.lproj/Main.strings b/ios/Runner/vi.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/vi.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/zh-Hant.lproj/LaunchScreen.strings b/ios/Runner/zh-Hant.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/zh-Hant.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/zh-Hant.lproj/Main.strings b/ios/Runner/zh-Hant.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/zh-Hant.lproj/Main.strings @@ -0,0 +1 @@ + From e23c826201016fb40efe21158fff3d28381e7e8b Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 14 Apr 2023 12:54:12 +0100 Subject: [PATCH 041/172] Revert flutter_downloader upgrade --- pubspec.lock | 11 ++++++----- pubspec.yaml | 5 ++++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 185fa7a56..4c2a9d6ee 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -369,11 +369,12 @@ packages: flutter_downloader: dependency: "direct main" description: - name: flutter_downloader - sha256: "6f2836668f33d0cd3ed275c3198d30967c42af53fa9b18c6b0edbf5fc12b599a" - url: "https://pub.dev" - source: hosted - version: "1.10.2" + path: "." + ref: "2517721" + resolved-ref: "25177215a0afba6f4ed83b6382874fa8e219358e" + url: "https://github.com/UnicornsOnLSD/flutter_downloader.git" + source: git + version: "1.7.3" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 0f6dd5c73..b67e6e3ba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -34,7 +34,10 @@ dependencies: audio_session: ^0.1.13 rxdart: ^0.27.7 simple_gesture_detector: ^0.2.0 - flutter_downloader: ^1.10.2 + flutter_downloader: + git: + url: https://github.com/UnicornsOnLSD/flutter_downloader.git + ref: "2517721" path_provider: ^2.0.14 hive: ^2.2.3 hive_flutter: ^1.1.0 From 1a3f8e0767785cdfacb163368542d9455a3bf42f Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 14 Apr 2023 13:17:30 +0100 Subject: [PATCH 042/172] Add HTTP fix for flutter_downloader --- .../current_downloads_list.dart | 3 +- lib/main.dart | 73 ++++++++++++------- lib/services/download_update_stream.dart | 2 +- pubspec.lock | 12 +-- pubspec.yaml | 4 +- 5 files changed, 58 insertions(+), 36 deletions(-) diff --git a/lib/components/DownloadsScreen/current_downloads_list.dart b/lib/components/DownloadsScreen/current_downloads_list.dart index 02b95416d..bedcd68a6 100644 --- a/lib/components/DownloadsScreen/current_downloads_list.dart +++ b/lib/components/DownloadsScreen/current_downloads_list.dart @@ -40,8 +40,7 @@ class _CurrentDownloadsListState extends State { super.dispose(); } - static void downloadCallback( - String id, DownloadTaskStatus status, int progress) { + static void downloadCallback(String id, int status, int progress) { final SendPort? send = IsolateNameServer.lookupPortByName('downloader_send_port'); send?.send([id, status, progress]); diff --git a/lib/main.dart b/lib/main.dart index 14055535f..0a52e992b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -338,30 +338,53 @@ class Finamp extends StatelessWidget { brightness: Brightness.dark, accentColor: accentColor, ), - indicatorColor: accentColor, checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return generateMaterialColor(accentColor).shade200; } - return null; - }), - ), radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return generateMaterialColor(accentColor).shade200; } - return null; - }), - ), switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return generateMaterialColor(accentColor).shade200; } - return null; - }), - trackColor: MaterialStateProperty.resolveWith((Set states) { - if (states.contains(MaterialState.disabled)) { return null; } - if (states.contains(MaterialState.selected)) { return generateMaterialColor(accentColor).shade200; } - return null; - }), - ), + indicatorColor: accentColor, + checkboxTheme: CheckboxThemeData( + fillColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } + return null; + }), + ), + radioTheme: RadioThemeData( + fillColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } + return null; + }), + ), + switchTheme: SwitchThemeData( + thumbColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } + return null; + }), + trackColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } + return null; + }), + ), ), themeMode: box.get("ThemeMode"), localizationsDelegates: const [ @@ -422,7 +445,7 @@ class ErrorScreen extends StatelessWidget { } class _DummyCallback { - static void callback(String id, DownloadTaskStatus status, int progress) { + static void callback(String id, int status, int progress) { // Add the event to the DownloadUpdateStream instance. final SendPort? send = IsolateNameServer.lookupPortByName('downloader_send_port'); diff --git a/lib/services/download_update_stream.dart b/lib/services/download_update_stream.dart index 8f89cc3cd..d7d1a2cc0 100644 --- a/lib/services/download_update_stream.dart +++ b/lib/services/download_update_stream.dart @@ -31,7 +31,7 @@ class DownloadUpdateStream { _port.sendPort, 'downloader_send_port'); _port.listen((dynamic data) { String id = data[0]; - DownloadTaskStatus status = data[1]; + DownloadTaskStatus status = DownloadTaskStatus(data[1]); int progress = data[2]; add(DownloadUpdate( diff --git a/pubspec.lock b/pubspec.lock index 4c2a9d6ee..599128315 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -325,10 +325,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: dd328189f2f4ccea042bb5b382d5e981691cc74b5a3429b9317bff2b19704489 + sha256: dcde5ad1a0cebcf3715ea3f24d0db1888bf77027a26c77d7779e8ef63b8ade62 url: "https://pub.dev" source: hosted - version: "5.2.8" + version: "5.2.9" file_sizes: dependency: "direct main" description: @@ -370,11 +370,11 @@ packages: dependency: "direct main" description: path: "." - ref: "2517721" - resolved-ref: "25177215a0afba6f4ed83b6382874fa8e219358e" - url: "https://github.com/UnicornsOnLSD/flutter_downloader.git" + ref: ae1beecbbe4f80c70a9b57876dd9f29ad1ee944d + resolved-ref: ae1beecbbe4f80c70a9b57876dd9f29ad1ee944d + url: "https://github.com/jmshrv/flutter_downloader.git" source: git - version: "1.7.3" + version: "1.10.2" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index b67e6e3ba..fa46445eb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,8 +36,8 @@ dependencies: simple_gesture_detector: ^0.2.0 flutter_downloader: git: - url: https://github.com/UnicornsOnLSD/flutter_downloader.git - ref: "2517721" + url: https://github.com/jmshrv/flutter_downloader.git + ref: "ae1beecbbe4f80c70a9b57876dd9f29ad1ee944d" path_provider: ^2.0.14 hive: ^2.2.3 hive_flutter: ^1.1.0 From 194c1db14ef7a3a793b41299809ed1f0d5c785cd Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 14 Apr 2023 13:18:28 +0100 Subject: [PATCH 043/172] Bump version, update F-Droid metadata --- fastlane/metadata/android/en-US/changelogs/30.txt | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/30.txt diff --git a/fastlane/metadata/android/en-US/changelogs/30.txt b/fastlane/metadata/android/en-US/changelogs/30.txt new file mode 100644 index 000000000..370f4d17e --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/30.txt @@ -0,0 +1 @@ +Fixed issue that broke downloading from HTTP servers \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index fa46445eb..745d47953 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.11+29 +version: 0.6.12+30 environment: sdk: ">=2.19.4 <3.0.0" From 797535d9739bb50f5d6eb5a25a6b3b3cda71789b Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sat, 22 Apr 2023 22:55:42 +0100 Subject: [PATCH 044/172] Specify userInfo when returning Uri --- lib/services/jellyfin_api_helper.dart | 1 + lib/services/music_player_background_task.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/lib/services/jellyfin_api_helper.dart b/lib/services/jellyfin_api_helper.dart index 35fc835ae..e55504c99 100644 --- a/lib/services/jellyfin_api_helper.dart +++ b/lib/services/jellyfin_api_helper.dart @@ -468,6 +468,7 @@ class JellyfinApiHelper { host: parsedBaseUrl.host, port: parsedBaseUrl.port, scheme: parsedBaseUrl.scheme, + userInfo: parsedBaseUrl.userInfo, pathSegments: builtPath, queryParameters: { if (format != null) "format": format, diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index fe7110fb8..e6231075c 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -594,6 +594,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { host: parsedBaseUrl.host, port: parsedBaseUrl.port, scheme: parsedBaseUrl.scheme, + userInfo: parsedBaseUrl.userInfo, pathSegments: builtPath, queryParameters: queryParameters, ); From 26754a731ef8f845448c5b7fd7137e9ccf507feb Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sun, 23 Apr 2023 14:09:42 +0100 Subject: [PATCH 045/172] Initial locale selector --- .../language_list.dart | 82 ++++++ lib/l10n/app_en.arb | 5 +- lib/main.dart | 267 ++++++++++-------- lib/models/locale_adapter.dart | 33 +++ lib/screens/language_selection_screen.dart | 21 ++ lib/screens/settings_screen.dart | 11 + lib/services/finamp_settings_helper.dart | 6 +- lib/services/locale_helper.dart | 16 ++ pubspec.lock | 9 + pubspec.yaml | 4 + 10 files changed, 323 insertions(+), 131 deletions(-) create mode 100644 lib/components/LanguageSelectionScreen/language_list.dart create mode 100644 lib/models/locale_adapter.dart create mode 100644 lib/screens/language_selection_screen.dart create mode 100644 lib/services/locale_helper.dart diff --git a/lib/components/LanguageSelectionScreen/language_list.dart b/lib/components/LanguageSelectionScreen/language_list.dart new file mode 100644 index 000000000..cc3073469 --- /dev/null +++ b/lib/components/LanguageSelectionScreen/language_list.dart @@ -0,0 +1,82 @@ +import 'dart:collection'; + +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:locale_names/locale_names.dart'; + +import '../../services/finamp_settings_helper.dart'; +import '../../services/locale_helper.dart'; + +class LanguageList extends StatefulWidget { + const LanguageList({super.key}); + + @override + State createState() => _LanguageListState(); +} + +class _LanguageListState extends State { + // yeah I'm a computer science student how could you tell + // (sorts locales without having to copy them into a list first) + final locales = SplayTreeMap.fromIterable( + AppLocalizations.supportedLocales, + key: (element) => (element as Locale).defaultDisplayLanguage, + value: (element) => element, + ); + + @override + Widget build(BuildContext context) { + return Scrollbar( + // We have a ValueListenableBuilder here to rebuild all the ListTiles when + // the language changes + child: ValueListenableBuilder( + valueListenable: LocaleHelper.localeListener, + builder: (_, __, ___) { + return CustomScrollView( + slivers: [ + const SliverList( + delegate: SliverChildListDelegate.fixed([ + LanguageListTile(), + Divider(), + ]), + ), + SliverList( + delegate: SliverChildBuilderDelegate( + (context, index) { + final locale = locales.values.elementAt(index); + + return LanguageListTile(locale: locale); + }, + childCount: locales.length, + ), + ) + ], + ); + }, + ), + ); + } +} + +class LanguageListTile extends StatelessWidget { + const LanguageListTile({ + super.key, + this.locale, + }); + + final Locale? locale; + + @override + Widget build(BuildContext context) { + return RadioListTile( + title: Text(locale?.nativeDisplayLanguage ?? + AppLocalizations.of(context)!.system), + subtitle: locale == null ? null : Text(locale!.defaultDisplayLanguage), + value: locale, + groupValue: LocaleHelper.locale, + onChanged: (_) { + LocaleHelper.setLocale(locale); + }, + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index dc8bb87cc..5f88e39fc 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -479,5 +479,6 @@ "bufferDuration": "Buffer Duration", "@bufferDuration": {}, "bufferDurationSubtitle": "How much the player should buffer, in seconds. Requires a restart.", - "@bufferDurationSubtitle": {} -} + "@bufferDurationSubtitle": {}, + "language": "Language" +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 0a52e992b..77f2e18fa 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -18,7 +18,10 @@ import 'package:logging/logging.dart'; import 'package:uuid/uuid.dart'; import 'generate_material_color.dart'; +import 'models/locale_adapter.dart'; import 'models/theme_mode_adapter.dart'; +import 'screens/language_selection_screen.dart'; +import 'services/locale_helper.dart'; import 'services/theme_mode_helper.dart'; import 'setup_logging.dart'; import 'screens/user_selector.dart'; @@ -150,6 +153,7 @@ Future setupHive() async { Hive.registerAdapter(ContentViewTypeAdapter()); Hive.registerAdapter(DownloadedImageAdapter()); Hive.registerAdapter(ThemeModeAdapter()); + Hive.registerAdapter(LocaleAdapter()); await Future.wait([ Hive.openBox("DownloadedParents"), Hive.openBox("DownloadedItems"), @@ -159,7 +163,8 @@ Future setupHive() async { Hive.openBox("FinampSettings"), Hive.openBox("DownloadedImages"), Hive.openBox("DownloadedImageIds"), - Hive.openBox("ThemeMode") + Hive.openBox("ThemeMode"), + Hive.openBox(LocaleHelper.boxName), ]); // If the settings box is empty, we add an initial settings value here. @@ -268,139 +273,151 @@ class Finamp extends StatelessWidget { FocusManager.instance.primaryFocus?.unfocus(); } }, - child: ValueListenableBuilder>( - valueListenable: ThemeModeHelper.themeModeListener, - builder: (_, box, __) { - return MaterialApp( - title: "Finamp", - routes: { - SplashScreen.routeName: (context) => const SplashScreen(), - UserSelector.routeName: (context) => const UserSelector(), - ViewSelector.routeName: (context) => const ViewSelector(), - MusicScreen.routeName: (context) => const MusicScreen(), - AlbumScreen.routeName: (context) => const AlbumScreen(), - ArtistScreen.routeName: (context) => const ArtistScreen(), - AddToPlaylistScreen.routeName: (context) => - const AddToPlaylistScreen(), - PlayerScreen.routeName: (context) => const PlayerScreen(), - DownloadsScreen.routeName: (context) => - const DownloadsScreen(), - DownloadsErrorScreen.routeName: (context) => - const DownloadsErrorScreen(), - LogsScreen.routeName: (context) => const LogsScreen(), - SettingsScreen.routeName: (context) => - const SettingsScreen(), - TranscodingSettingsScreen.routeName: (context) => - const TranscodingSettingsScreen(), - DownloadsSettingsScreen.routeName: (context) => - const DownloadsSettingsScreen(), - AddDownloadLocationScreen.routeName: (context) => - const AddDownloadLocationScreen(), - AudioServiceSettingsScreen.routeName: (context) => - const AudioServiceSettingsScreen(), - TabsSettingsScreen.routeName: (context) => - const TabsSettingsScreen(), - LayoutSettingsScreen.routeName: (context) => - const LayoutSettingsScreen(), - }, - initialRoute: SplashScreen.routeName, - theme: ThemeData( + // We awkwardly have two ValueListenableBuilders for the locale and + // theme because I didn't want every FinampSettings change to rebuild + // the whole app + child: ValueListenableBuilder( + valueListenable: LocaleHelper.localeListener, + builder: (_, __, ___) { + return ValueListenableBuilder>( + valueListenable: ThemeModeHelper.themeModeListener, + builder: (_, box, __) { + return MaterialApp( + title: "Finamp", + routes: { + SplashScreen.routeName: (context) => const SplashScreen(), + UserSelector.routeName: (context) => const UserSelector(), + ViewSelector.routeName: (context) => const ViewSelector(), + MusicScreen.routeName: (context) => const MusicScreen(), + AlbumScreen.routeName: (context) => const AlbumScreen(), + ArtistScreen.routeName: (context) => const ArtistScreen(), + AddToPlaylistScreen.routeName: (context) => + const AddToPlaylistScreen(), + PlayerScreen.routeName: (context) => const PlayerScreen(), + DownloadsScreen.routeName: (context) => + const DownloadsScreen(), + DownloadsErrorScreen.routeName: (context) => + const DownloadsErrorScreen(), + LogsScreen.routeName: (context) => const LogsScreen(), + SettingsScreen.routeName: (context) => + const SettingsScreen(), + TranscodingSettingsScreen.routeName: (context) => + const TranscodingSettingsScreen(), + DownloadsSettingsScreen.routeName: (context) => + const DownloadsSettingsScreen(), + AddDownloadLocationScreen.routeName: (context) => + const AddDownloadLocationScreen(), + AudioServiceSettingsScreen.routeName: (context) => + const AudioServiceSettingsScreen(), + TabsSettingsScreen.routeName: (context) => + const TabsSettingsScreen(), + LayoutSettingsScreen.routeName: (context) => + const LayoutSettingsScreen(), + LanguageSelectionScreen.routeName: (context) => + const LanguageSelectionScreen(), + }, + initialRoute: SplashScreen.routeName, + theme: ThemeData( + colorScheme: ColorScheme.fromSwatch( + primarySwatch: generateMaterialColor(accentColor), + brightness: Brightness.light, + accentColor: accentColor, + ), + appBarTheme: const AppBarTheme( + color: Colors.white, + foregroundColor: Colors.black, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.light), + ), + tabBarTheme: const TabBarTheme( + labelColor: Colors.black, + )), + darkTheme: ThemeData( + brightness: Brightness.dark, + scaffoldBackgroundColor: backgroundColor, + appBarTheme: const AppBarTheme( + color: raisedDarkColor, + systemOverlayStyle: SystemUiOverlayStyle( + statusBarBrightness: Brightness.dark), + ), + cardColor: raisedDarkColor, + bottomNavigationBarTheme: + const BottomNavigationBarThemeData( + backgroundColor: raisedDarkColor), + canvasColor: raisedDarkColor, + visualDensity: VisualDensity.adaptivePlatformDensity, colorScheme: ColorScheme.fromSwatch( primarySwatch: generateMaterialColor(accentColor), - brightness: Brightness.light, + brightness: Brightness.dark, accentColor: accentColor, ), - appBarTheme: const AppBarTheme( - color: Colors.white, - foregroundColor: Colors.black, - systemOverlayStyle: SystemUiOverlayStyle( - statusBarBrightness: Brightness.light), - ), - tabBarTheme: const TabBarTheme( - labelColor: Colors.black, - )), - darkTheme: ThemeData( - brightness: Brightness.dark, - scaffoldBackgroundColor: backgroundColor, - appBarTheme: const AppBarTheme( - color: raisedDarkColor, - systemOverlayStyle: SystemUiOverlayStyle( - statusBarBrightness: Brightness.dark), - ), - cardColor: raisedDarkColor, - bottomNavigationBarTheme: - const BottomNavigationBarThemeData( - backgroundColor: raisedDarkColor), - canvasColor: raisedDarkColor, - visualDensity: VisualDensity.adaptivePlatformDensity, - colorScheme: ColorScheme.fromSwatch( - primarySwatch: generateMaterialColor(accentColor), - brightness: Brightness.dark, - accentColor: accentColor, - ), - indicatorColor: accentColor, - checkboxTheme: CheckboxThemeData( - fillColor: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.disabled)) { + indicatorColor: accentColor, + checkboxTheme: CheckboxThemeData( + fillColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } return null; - } - if (states.contains(MaterialState.selected)) { - return generateMaterialColor(accentColor).shade200; - } - return null; - }), - ), - radioTheme: RadioThemeData( - fillColor: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.disabled)) { + }), + ), + radioTheme: RadioThemeData( + fillColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } return null; - } - if (states.contains(MaterialState.selected)) { - return generateMaterialColor(accentColor).shade200; - } - return null; - }), - ), - switchTheme: SwitchThemeData( - thumbColor: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.disabled)) { + }), + ), + switchTheme: SwitchThemeData( + thumbColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } return null; - } - if (states.contains(MaterialState.selected)) { - return generateMaterialColor(accentColor).shade200; - } - return null; - }), - trackColor: MaterialStateProperty.resolveWith( - (Set states) { - if (states.contains(MaterialState.disabled)) { + }), + trackColor: MaterialStateProperty.resolveWith( + (Set states) { + if (states.contains(MaterialState.disabled)) { + return null; + } + if (states.contains(MaterialState.selected)) { + return generateMaterialColor(accentColor).shade200; + } return null; - } - if (states.contains(MaterialState.selected)) { - return generateMaterialColor(accentColor).shade200; - } - return null; - }), + }), + ), ), - ), - themeMode: box.get("ThemeMode"), - localizationsDelegates: const [ - AppLocalizations.delegate, - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - supportedLocales: AppLocalizations.supportedLocales, - // We awkwardly put English as the first supported locale so - // that basicLocaleListResolution falls back to it instead of - // the first language in supportedLocales (Arabic as of writing) - localeListResolutionCallback: (locales, supportedLocales) => - basicLocaleListResolution(locales, - [const Locale("en")].followedBy(supportedLocales))); - }), + themeMode: box.get("ThemeMode"), + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + // We awkwardly put English as the first supported locale so + // that basicLocaleListResolution falls back to it instead of + // the first language in supportedLocales (Arabic as of writing) + localeListResolutionCallback: (locales, supportedLocales) => + basicLocaleListResolution(locales, + [const Locale("en")].followedBy(supportedLocales)), + locale: LocaleHelper.locale, + ); + }); + }, + ), ), ); } diff --git a/lib/models/locale_adapter.dart b/lib/models/locale_adapter.dart new file mode 100644 index 000000000..5d3b807a3 --- /dev/null +++ b/lib/models/locale_adapter.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; +import 'package:hive/hive.dart'; + +class LocaleAdapter extends TypeAdapter { + @override + int get typeId => 42; + + @override + Locale read(BinaryReader reader) { + final localeList = reader.readStringList(); + + final languageCode = localeList[0]; + + // scriptCode and countryCode are empty strings when null (see write) + final scriptCode = localeList[1] == "" ? null : localeList[1]; + final countryCode = localeList[2] == "" ? null : localeList[2]; + + return Locale.fromSubtags( + languageCode: languageCode, + scriptCode: scriptCode, + countryCode: countryCode, + ); + } + + @override + void write(BinaryWriter writer, Locale obj) { + writer.writeStringList([ + obj.languageCode, + obj.scriptCode ?? "", + obj.countryCode ?? "", + ]); + } +} diff --git a/lib/screens/language_selection_screen.dart b/lib/screens/language_selection_screen.dart new file mode 100644 index 000000000..e58324b52 --- /dev/null +++ b/lib/screens/language_selection_screen.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +import '../components/LanguageSelectionScreen/language_list.dart'; + +class LanguageSelectionScreen extends StatelessWidget { + const LanguageSelectionScreen({super.key}); + + static const routeName = "/settings/language"; + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(AppLocalizations.of(context)!.language), + ), + body: const LanguageList(), + ); + } +} diff --git a/lib/screens/settings_screen.dart b/lib/screens/settings_screen.dart index 89ca4b27b..f12612764 100644 --- a/lib/screens/settings_screen.dart +++ b/lib/screens/settings_screen.dart @@ -1,14 +1,17 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:locale_names/locale_names.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../services/finamp_settings_helper.dart'; +import '../services/locale_helper.dart'; import 'transcoding_settings_screen.dart'; import 'downloads_settings_screen.dart'; import 'audio_service_settings_screen.dart'; import 'layout_settings_screen.dart'; import '../components/SettingsScreen/logout_list_tile.dart'; import 'view_selector.dart'; +import 'language_selection_screen.dart'; class SettingsScreen extends StatelessWidget { const SettingsScreen({Key? key}) : super(key: key); @@ -76,6 +79,14 @@ class SettingsScreen extends StatelessWidget { onTap: () => Navigator.of(context).pushNamed(ViewSelector.routeName), ), + ListTile( + leading: const Icon(Icons.language), + title: Text(AppLocalizations.of(context)!.language), + subtitle: Text(LocaleHelper.locale?.nativeDisplayLanguage ?? + AppLocalizations.of(context)!.system), + onTap: () => Navigator.of(context) + .pushNamed(LanguageSelectionScreen.routeName), + ), const LogoutListTile(), ], ), diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index 86666d2dc..696a70dbc 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -176,11 +176,9 @@ class FinampSettingsHelper { .put("FinampSettings", finampSettingsTemp); } - static void setDisableGesture( - bool disableGesture) { + static void setDisableGesture(bool disableGesture) { FinampSettings finampSettingsTemp = finampSettings; - finampSettingsTemp.disableGesture = - disableGesture; + finampSettingsTemp.disableGesture = disableGesture; Hive.box("FinampSettings") .put("FinampSettings", finampSettingsTemp); } diff --git a/lib/services/locale_helper.dart b/lib/services/locale_helper.dart new file mode 100644 index 000000000..34f401d30 --- /dev/null +++ b/lib/services/locale_helper.dart @@ -0,0 +1,16 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; + +class LocaleHelper { + static const boxName = "Locale"; + + static ValueListenable> get localeListener => + Hive.box(boxName).listenable(); + + static Locale? get locale => Hive.box(boxName).get(boxName); + + static void setLocale(Locale? locale) { + Hive.box(boxName).put(boxName, locale); + } +} diff --git a/pubspec.lock b/pubspec.lock index 599128315..32fa3bba4 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -598,6 +598,15 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + locale_names: + dependency: "direct main" + description: + path: "." + ref: "74e55fd" + resolved-ref: "74e55fd2997cc5a0d5283604a09c307036cac020" + url: "https://github.com/Mantano/locale_names.git" + source: git + version: "0.0.1" logging: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 745d47953..93494c30b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,10 @@ dependencies: intl: ^0.17.0 auto_size_text: ^3.0.0 flutter_riverpod: ^2.3.4 + locale_names: + git: + url: https://github.com/Mantano/locale_names.git + ref: 74e55fd dev_dependencies: flutter_test: From a0b481602e558df623ea0e3ca6b189bc31faa03d Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 17:54:05 +0100 Subject: [PATCH 046/172] Add '@pragma('vm:entry-point')' to download callbacks (fixes #433) https://github.com/fluttercommunity/flutter_downloader/issues/629 --- lib/components/DownloadsScreen/current_downloads_list.dart | 2 ++ lib/main.dart | 2 ++ 2 files changed, 4 insertions(+) diff --git a/lib/components/DownloadsScreen/current_downloads_list.dart b/lib/components/DownloadsScreen/current_downloads_list.dart index bedcd68a6..77290a099 100644 --- a/lib/components/DownloadsScreen/current_downloads_list.dart +++ b/lib/components/DownloadsScreen/current_downloads_list.dart @@ -40,6 +40,8 @@ class _CurrentDownloadsListState extends State { super.dispose(); } + // https://github.com/fluttercommunity/flutter_downloader/issues/629 + @pragma('vm:entry-point') static void downloadCallback(String id, int status, int progress) { final SendPort? send = IsolateNameServer.lookupPortByName('downloader_send_port'); diff --git a/lib/main.dart b/lib/main.dart index 77f2e18fa..0655b8ae1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -462,6 +462,8 @@ class ErrorScreen extends StatelessWidget { } class _DummyCallback { + // https://github.com/fluttercommunity/flutter_downloader/issues/629 + @pragma('vm:entry-point') static void callback(String id, int status, int progress) { // Add the event to the DownloadUpdateStream instance. final SendPort? send = From 1cfcd7e8884af1c266fdf055aa3ead105576a692 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 22:39:26 +0100 Subject: [PATCH 047/172] Fix system locale option not changing properly, show subtitle language in current locale --- .../LanguageSelectionScreen/language_list.dart | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/components/LanguageSelectionScreen/language_list.dart b/lib/components/LanguageSelectionScreen/language_list.dart index cc3073469..4d408adfd 100644 --- a/lib/components/LanguageSelectionScreen/language_list.dart +++ b/lib/components/LanguageSelectionScreen/language_list.dart @@ -32,12 +32,19 @@ class _LanguageListState extends State { child: ValueListenableBuilder( valueListenable: LocaleHelper.localeListener, builder: (_, __, ___) { + debugPrint(LocaleHelper.locale.toString()); return CustomScrollView( slivers: [ - const SliverList( + // For some reason, setting the null (system) LanguageListTile to + // const stops it from switching when going to/from the same + // language as the system language (e.g., system to English on a + // device set to English) + // ignore: prefer_const_constructors + SliverList( + // ignore: prefer_const_constructors delegate: SliverChildListDelegate.fixed([ LanguageListTile(), - Divider(), + const Divider(), ]), ), SliverList( @@ -71,7 +78,11 @@ class LanguageListTile extends StatelessWidget { return RadioListTile( title: Text(locale?.nativeDisplayLanguage ?? AppLocalizations.of(context)!.system), - subtitle: locale == null ? null : Text(locale!.defaultDisplayLanguage), + subtitle: locale == null + ? null + : Text(LocaleHelper.locale == null + ? locale!.defaultDisplayLanguage + : locale!.displayLanguageIn(LocaleHelper.locale!)), value: locale, groupValue: LocaleHelper.locale, onChanged: (_) { From c61de3bae450d510fbf11c4942cb89e51ebe453b Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 22:42:37 +0100 Subject: [PATCH 048/172] Add language selector in login screen --- lib/screens/user_selector.dart | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/screens/user_selector.dart b/lib/screens/user_selector.dart index 73cd70532..d71127ae9 100644 --- a/lib/screens/user_selector.dart +++ b/lib/screens/user_selector.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import '../components/UserSelector/private_user_sign_in.dart'; +import 'language_selection_screen.dart'; class UserSelector extends StatelessWidget { const UserSelector({Key? key}) : super(key: key); @@ -9,8 +10,19 @@ class UserSelector extends StatelessWidget { @override Widget build(BuildContext context) { - return const Scaffold( - body: Center(child: PrivateUserSignIn()), + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.transparent, + elevation: 0, + actions: [ + IconButton( + onPressed: () => Navigator.of(context) + .pushNamed(LanguageSelectionScreen.routeName), + icon: const Icon(Icons.language), + ) + ], + ), + body: const Center(child: PrivateUserSignIn()), ); } } From 323265d15a65b88437185e25345194561c3c2fc7 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 22:44:08 +0100 Subject: [PATCH 049/172] Bump version --- fastlane/metadata/android/en-US/changelogs/31.txt | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/31.txt diff --git a/fastlane/metadata/android/en-US/changelogs/31.txt b/fastlane/metadata/android/en-US/changelogs/31.txt new file mode 100644 index 000000000..d6c13b331 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/31.txt @@ -0,0 +1 @@ +Downloader fixes, and a language selector. Full changelog at https://github.com/jmshrv/finamp/releases/tag/0.6.13 \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 93494c30b..eed0c99f5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.12+30 +version: 0.6.13+31 environment: sdk: ">=2.19.4 <3.0.0" From 17365b2973f4f06de6d64fdfaf6524d0ddfbe500 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 22:47:18 +0100 Subject: [PATCH 050/172] Update Flutter in flutterw --- .flutter | 2 +- pubspec.lock | 40 ++++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/.flutter b/.flutter index 4b1264501..4d9e56e69 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit 4b12645012342076800eb701bcdfe18f87da21cf +Subproject commit 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf diff --git a/pubspec.lock b/pubspec.lock index 32fa3bba4..21df4167b 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -325,10 +325,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: dcde5ad1a0cebcf3715ea3f24d0db1888bf77027a26c77d7779e8ef63b8ade62 + sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff url: "https://pub.dev" source: hosted - version: "5.2.9" + version: "5.2.10" file_sizes: dependency: "direct main" description: @@ -379,10 +379,10 @@ packages: dependency: "direct dev" description: name: flutter_launcher_icons - sha256: "8546a9b9510e1a260b8d55fb2d07096e8a8552c6a2c2bf529100344894b2b41a" + sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea" url: "https://pub.dev" source: hosted - version: "0.13.0" + version: "0.13.1" flutter_lints: dependency: "direct dev" description: @@ -408,10 +408,10 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: "812dfbb87af51e73e68ea038bcfd1c732078d6838d3388d03283db7dec0d1e5f" + sha256: b83ac5827baadefd331ea1d85110f34645827ea234ccabf53a655f41901a9bf4 url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" flutter_sticky_header: dependency: "direct main" description: @@ -442,10 +442,10 @@ packages: dependency: "direct main" description: name: get_it - sha256: "290fde3a86072e4b37dbb03c07bec6126f0ecc28dad403c12ffe2e5a2d751ab7" + sha256: f9982979e3d2f286a957c04d2c3a98f55b0f0a06ffd6c5c4abbb96f06937f463 url: "https://pub.dev" source: hosted - version: "7.2.0" + version: "7.3.0" glob: dependency: transitive description: @@ -707,10 +707,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "019f18c9c10ae370b08dce1f3e3b73bc9f58e7f087bb5e921f06529438ac0ae7" + sha256: da97262be945a72270513700a92b39dd2f4a54dad55d061687e2e37a6390366a url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.25" path_provider_foundation: dependency: transitive description: @@ -819,10 +819,10 @@ packages: dependency: transitive description: name: pointycastle - sha256: c3120a968135aead39699267f4c74bc9a08e4e909e86bc1b0af5bfd78691123c + sha256: "7c1e5f0d23c9016c5bbd8b1473d0d3fb3fc851b876046039509e18e0c7485f2c" url: "https://pub.dev" source: hosted - version: "3.7.2" + version: "3.7.3" pool: dependency: transitive description: @@ -867,10 +867,10 @@ packages: dependency: transitive description: name: riverpod - sha256: "77ab3bcd084bb19fa8717a526217787c725d7f5be938404c7839cd760fdf6ae5" + sha256: "80e48bebc83010d5e67a11c9514af6b44bbac1ec77b4333c8ea65dbc79e2d8ef" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" rxdart: dependency: "direct main" description: @@ -960,18 +960,18 @@ packages: dependency: transitive description: name: sqflite - sha256: "500d6fec583d2c021f2d25a056d96654f910662c64f836cd2063167b8f1fa758" + sha256: e7dfb6482d5d02b661d0b2399efa72b98909e5aa7b8336e1fb37e226264ade00 url: "https://pub.dev" source: hosted - version: "2.2.6" + version: "2.2.7" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "963dad8c4aa2f814ce7d2d5b1da2f36f31bd1a439d8f27e3dc189bb9d26bc684" + sha256: "220831bf0bd5333ff2445eee35ec131553b804e6b5d47a4a37ca6f5eb66e282c" url: "https://pub.dev" source: hosted - version: "2.4.3" + version: "2.4.4" stack_trace: dependency: transitive description: @@ -1016,10 +1016,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "33b31b6beb98100bf9add464a36a8dd03eb10c7a8cf15aeec535e9b054aaf04b" + sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" term_glyph: dependency: transitive description: From bda54e5bfcf4f2bf55451be18a72fa28e81b0e3f Mon Sep 17 00:00:00 2001 From: James Harvey Date: Fri, 14 Apr 2023 15:56:13 +0200 Subject: [PATCH 051/172] Added translation using Weblate (Chinese (Traditional, Hong Kong)) --- lib/l10n/app_zh_Hant_HK.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_zh_Hant_HK.arb diff --git a/lib/l10n/app_zh_Hant_HK.arb b/lib/l10n/app_zh_Hant_HK.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_zh_Hant_HK.arb @@ -0,0 +1 @@ +{} From e729602fea59b2aed487b0bd2b2f2729396f5eb6 Mon Sep 17 00:00:00 2001 From: Paul Richter Date: Fri, 14 Apr 2023 07:32:58 +0000 Subject: [PATCH 052/172] Translated using Weblate (Japanese) Currently translated at 90.0% (153 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ja/ --- lib/l10n/app_ja.arb | 412 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 409 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 01aee168b..da081a93c 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -1,6 +1,412 @@ { - "noErrors": "エラーなし", + "noErrors": "エラーなし!", "@noErrors": {}, - "error": "間違い", - "@error": {} + "error": "エラー", + "@error": {}, + "serverUrl": "サーバー URL", + "@serverUrl": {}, + "internalExternalIpExplanation": "Jellyfinサーバーをリモートでアクセスするには、外部 IP を指定して下さい。\n\nサーバーが HTTP ポート(80/443)で動いてる場合はポートを指定する必要はありません。サーバーがリバースプロクシの後段にある場合これが一般的です。", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "emptyServerUrl": "サーバー URL は空であってはなりません", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "password": "パスワード", + "@password": {}, + "logs": "ログ", + "@logs": {}, + "username": "ユーザー名", + "@username": {}, + "next": "次", + "@next": {}, + "urlStartWithHttps": "URL は http:// か https:// で始まらなければなりません", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "urlTrailingSlash": "URL の末尾にスラッシュがあってはなりません", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "unknownName": "タイトル不明", + "@unknownName": {}, + "couldNotFindLibraries": "ライブラリが見つかりません。", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "songs": "曲", + "@songs": {}, + "albums": "アルバム", + "@albums": {}, + "artists": "アーティスト", + "@artists": {}, + "genres": "ジャンル", + "@genres": {}, + "selectMusicLibraries": "ミュージック・ライブラリを選択", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "playlists": "プレイリスト", + "@playlists": {}, + "music": "ミュージック", + "@music": {}, + "clear": "クリア", + "@clear": {}, + "shuffleAll": "全てシャッフル", + "@shuffleAll": {}, + "downloads": "ダウンロード", + "@downloads": {}, + "startMix": "ミックス開始", + "@startMix": {}, + "favourites": "お気に入り", + "@favourites": {}, + "startMixNoSongsArtist": "アーティスト名をロングプレスして、ミックス開始前にミックスビルダーに追加や外すことができます", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "startMixNoSongsAlbum": "アーティスト名をロングプレスして、ミックス開始前にミックスビルダーに追加や外すことができます", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "settings": "設定", + "@settings": {}, + "offlineMode": "オフライン・モード", + "@offlineMode": {}, + "sortOrder": "並び順", + "@sortOrder": {}, + "album": "アルバム", + "@album": {}, + "sortBy": "並び基準", + "@sortBy": {}, + "albumArtist": "アルバム・アーティスト", + "@albumArtist": {}, + "artist": "アーティスト", + "@artist": {}, + "budget": "予算", + "@budget": {}, + "productionYear": "作成年", + "@productionYear": {}, + "dateAdded": "追加日", + "@dateAdded": {}, + "playCount": "再生回数", + "@playCount": {}, + "datePlayed": "再生日", + "@datePlayed": {}, + "premiereDate": "公開日", + "@premiereDate": {}, + "criticRating": "評論家評価", + "@criticRating": {}, + "communityRating": "コミュニティ評価", + "@communityRating": {}, + "random": "ランダム", + "@random": {}, + "downloadErrors": "ダウンロード・エラー", + "@downloadErrors": {}, + "downloadMissingImages": "欠けている画像をダウンロード", + "@downloadMissingImages": {}, + "revenue": "収入", + "@revenue": {}, + "downloadCount": "{count,plural, =1{{count} 個ダウンロード} other{{count}個ダウンロード}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedItemsCount": "{count,plural, =1{{count} アイテム} other{{count}アイテム}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedImagesCount": "{count,plural, =1{{count} 枚の画像} other{{count}枚の画像}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} 個完了", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrorsTitle": "ダウンロード・エラー", + "@downloadErrorsTitle": {}, + "dlEnqueued": "{count} 個キューに追加", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlRunning": "{count} 個再生中", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlFailed": "{count}個失敗", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "playButtonLabel": "再生", + "@playButtonLabel": {}, + "songCount": "{count,plural,=1{{count}曲} other{{count}曲}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editPlaylistNameTooltip": "プレイリスト名を編集", + "@editPlaylistNameTooltip": {}, + "updateButtonLabel": "更新", + "@updateButtonLabel": {}, + "downloadsDeleted": "ダウンロードを削除しました。", + "@downloadsDeleted": {}, + "addButtonLabel": "追加", + "@addButtonLabel": {}, + "shareLogs": "ログ共有", + "@shareLogs": {}, + "location": "場所", + "@location": {}, + "favourite": "お気に入り", + "@favourite": {}, + "downloadsAdded": "ダウンロードを追加しました。", + "@downloadsAdded": {}, + "stackTrace": "スタックトレース", + "@stackTrace": {}, + "transcoding": "トランスコード", + "@transcoding": {}, + "layoutAndTheme": "レイアウト&テーマ", + "@layoutAndTheme": {}, + "notAvailableInOfflineMode": "オフライン・モードでは無効", + "@notAvailableInOfflineMode": {}, + "downloadLocations": "ダウンロード場所", + "@downloadLocations": {}, + "audioService": "オーディオ・サービス", + "@audioService": {}, + "logOut": "ログアウト", + "@logOut": {}, + "downloadedSongsWillNotBeDeleted": "ダウロード済みの曲は削除されません", + "@downloadedSongsWillNotBeDeleted": {}, + "jellyfinUsesAACForTranscoding": "Jelllyfin はトランスコードに AAC を利用します", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscoding": "トランスコード有効", + "@enableTranscoding": {}, + "bitrate": "ビットレート", + "@bitrate": {}, + "addDownloadLocation": "ダウンロード場所を追加", + "@addDownloadLocation": {}, + "bitrateSubtitle": "高いビットレートではオーディオの質が高くなりますが、転送容量も高くなります。", + "@bitrateSubtitle": {}, + "selectDirectory": "ディレクトリを選択", + "@selectDirectory": {}, + "unknownError": "不明なエラー", + "@unknownError": {}, + "pathReturnSlashErrorMessage": "\"/\" を返すパスは利用できません", + "@pathReturnSlashErrorMessage": {}, + "directoryMustBeEmpty": "ディレクトリは空でなければなりません", + "@directoryMustBeEmpty": {}, + "list": "リスト", + "@list": {}, + "grid": "グリッド", + "@grid": {}, + "portrait": "縦表示", + "@portrait": {}, + "landscape": "横表示", + "@landscape": {}, + "enterLowPriorityStateOnPause": "一時停止時は低優先度状態", + "@enterLowPriorityStateOnPause": {}, + "showTextOnGridView": "グリッド・ビューでテキストを表示する", + "@showTextOnGridView": {}, + "showCoverAsPlayerBackground": "プレイヤー背景にジャケット画像をぼかして表示する", + "@showCoverAsPlayerBackground": {}, + "showTextOnGridViewSubtitle": "グリッド音楽画面でテキスト(タイトル、アーティスト等)を表示させるか。", + "@showTextOnGridViewSubtitle": {}, + "showCoverAsPlayerBackgroundSubtitle": "プレイヤー画面で背景にジャケット画像をぼかして表示させるか。", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "theme": "テーマ", + "@theme": {}, + "system": "システム", + "@system": {}, + "disableGesture": "ジェスチャーを無効", + "@disableGesture": {}, + "light": "明るい", + "@light": {}, + "disableGestureSubtitle": "ジェスチャーを無効にするか。", + "@disableGestureSubtitle": {}, + "tabs": "タブ", + "@tabs": {}, + "invalidNumber": "無効な数値", + "@invalidNumber": {}, + "sleepTimerTooltip": "スリープ・タイマー", + "@sleepTimerTooltip": {}, + "addToPlaylistTooltip": "プレイリストに追加", + "@addToPlaylistTooltip": {}, + "dark": "暗い", + "@dark": {}, + "cancelSleepTimer": "スリープ・タイマーをキャンセルしますか?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "はい", + "@yesButtonLabel": {}, + "noButtonLabel": "いいえ", + "@noButtonLabel": {}, + "setSleepTimer": "スリープ・タイマーを設定", + "@setSleepTimer": {}, + "minutes": "分", + "@minutes": {}, + "addToPlaylistTitle": "プレイリストに追加", + "@addToPlaylistTitle": {}, + "addedToQueue": "キューに追加。", + "@addedToQueue": {}, + "queueReplaced": "キューを置き換えました。", + "@queueReplaced": {}, + "addToMix": "ミックスに追加", + "@addToMix": {}, + "removeFromMix": "ミックスから外す", + "@removeFromMix": {}, + "enableTranscodingSubtitle": "サーバー側の音楽ストリームをトランスコードします。", + "@enableTranscodingSubtitle": {}, + "hideSongArtistsIfSameAsAlbumArtists": "曲のアーティストがアルバム・アーティストと同じの場合、表示しない", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "newPlaylist": "新規プレイリスト", + "@newPlaylist": {}, + "removeFromPlaylistTooltip": "プレイリストから外す", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "プレイリストから外す", + "@removeFromPlaylistTitle": {}, + "createButtonLabel": "作成", + "@createButtonLabel": {}, + "playlistCreated": "プレイリストを作成しました。", + "@playlistCreated": {}, + "noAlbum": "アルバム無し", + "@noAlbum": {}, + "noItem": "アイテム無し", + "@noItem": {}, + "noArtist": "アーティスト無し", + "@noArtist": {}, + "unknownArtist": "アーティスト不明", + "@unknownArtist": {}, + "streaming": "ストリーミング", + "@streaming": {}, + "downloaded": "ダウンロード済み", + "@downloaded": {}, + "transcode": "トランスコード", + "@transcode": {}, + "statusError": "ステータス・エラー", + "@statusError": {}, + "removeFavourite": "お気に入りを削除", + "@removeFavourite": {}, + "addFavourite": "お気に入りを追加", + "@addFavourite": {}, + "removedFromPlaylist": "プレイリストから外しました。", + "@removedFromPlaylist": {}, + "startingInstantMix": "インスタント・ミックス開始。", + "@startingInstantMix": {}, + "anErrorHasOccured": "エラーが発生しました。", + "@anErrorHasOccured": {}, + "instantMix": "インスタント・ミックス", + "@instantMix": {}, + "direct": "直接", + "@direct": {}, + "queue": "キュー", + "@queue": {}, + "addToQueue": "キューに追加", + "@addToQueue": {}, + "replaceQueue": "キューを置き換え", + "@replaceQueue": {}, + "responseError": "{error} ステータスコード {statusCode}.", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "required": "必須", + "@required": {}, + "discNumber": "ディスク {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "shuffleButtonLabel": "シャッフル", + "@shuffleButtonLabel": {}, + "editPlaylistNameTitle": "プレイリスト名を編集", + "@editPlaylistNameTitle": {}, + "playlistNameUpdated": "プレイリスト名を更新しました。", + "@playlistNameUpdated": {}, + "addDownloads": "ダウンロードを追加", + "@addDownloads": {}, + "logsCopied": "ログをコピーしました。", + "@logsCopied": {}, + "message": "メッセージ", + "@message": {}, + "failedToGetSongFromDownloadId": "ダウンロード ID から曲が取得できません", + "@failedToGetSongFromDownloadId": {}, + "downloadedMissingImages": "{count,plural, =0{欠けている画像が見つかりません} =1{欠けている画像を {count} 枚ダウンロード} other{欠けている画像を {count} 枚ダウンロード}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "goToAlbum": "アルバムに行く", + "@goToAlbum": {}, + "errorScreenError": "エラーリスト取得時にエラーが発生しました!この時点では、GitHub でイシューを作成し、アプリデータを削除することをお勧めします", + "@errorScreenError": {}, + "startupError": "起動時に問題が起こりました。エラー: {error}\n\ngithub.com/UnicornsOnLSD/finamp でイシューを作成し、このページのスクリーンショットを付けてください。問題が継続した場合はアプリデータをクリアしてアプリを初期化して下さい。", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "曲のアーティストがアルバムのアーティストと一致した場合、アルバム画面に表示させるか。", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "enterLowPriorityStateOnPauseSubtitle": "一時停止時に通知をスワイプで除去できます。また Android では一時停止時にサービスを停止できます。", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "gridCrossAxisCountSubtitle": "{value}の場合一行当たりのグリッドタイル数。", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "bufferDurationSubtitle": "プレイヤーがバッファする容量(秒単位)。リスタートが必要です。", + "@bufferDurationSubtitle": {} } From a667e9e10fce018b8bbfa5ff9a372cfafdc98ea1 Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Sat, 15 Apr 2023 10:25:34 +0000 Subject: [PATCH 053/172] Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 97.6% (166 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hant_HK/ --- lib/l10n/app_zh_Hant_HK.arb | 484 +++++++++++++++++++++++++++++++++++- 1 file changed, 483 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_zh_Hant_HK.arb b/lib/l10n/app_zh_Hant_HK.arb index 0967ef424..01e1d8962 100644 --- a/lib/l10n/app_zh_Hant_HK.arb +++ b/lib/l10n/app_zh_Hant_HK.arb @@ -1 +1,483 @@ -{} +{ + "serverUrl": "伺服器 URL", + "@serverUrl": {}, + "emptyServerUrl": "伺服器 URL 並不能漏空", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "urlStartWithHttps": "URL 必須以 http:// 或 https:// 開頭", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "urlTrailingSlash": "URL 不能以「/」結尾", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "username": "用戶名稱", + "@username": {}, + "password": "密碼", + "@password": {}, + "unknownName": "未知的名稱", + "@unknownName": {}, + "songs": "歌曲", + "@songs": {}, + "albums": "專輯", + "@albums": {}, + "artists": "歌手", + "@artists": {}, + "genres": "曲風", + "@genres": {}, + "playlists": "播放清單", + "@playlists": {}, + "startMix": "開始混音", + "@startMix": {}, + "startMixNoSongsArtist": "在開始混音之前,長按歌手以添加至混音器或從混音器移除", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "music": "音樂", + "@music": {}, + "clear": "清除", + "@clear": {}, + "downloads": "下載", + "@downloads": {}, + "settings": "設定", + "@settings": {}, + "offlineMode": "離線模式", + "@offlineMode": {}, + "sortBy": "排序方法", + "@sortBy": {}, + "sortOrder": "順序", + "@sortOrder": {}, + "album": "專輯", + "@album": {}, + "albumArtist": "專輯歌手", + "@albumArtist": {}, + "artist": "歌手", + "@artist": {}, + "communityRating": "聽眾評分", + "@communityRating": {}, + "criticRating": "樂評人評分", + "@criticRating": {}, + "dateAdded": "添加日期", + "@dateAdded": {}, + "datePlayed": "播放日期", + "@datePlayed": {}, + "playCount": "播放次數", + "@playCount": {}, + "premiereDate": "首次播放日期", + "@premiereDate": {}, + "productionYear": "推出年份", + "@productionYear": {}, + "name": "名稱", + "@name": {}, + "random": "隨機", + "@random": {}, + "runtime": "執行時", + "@runtime": {}, + "downloadMissingImages": "下載缺失的圖片", + "@downloadMissingImages": {}, + "budget": "預算", + "@budget": {}, + "revenue": "收入", + "@revenue": {}, + "downloadCount": "{count,plural, =1{{count} 個下載} other{{count} 個下載}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedItemsCount": "{count,plural,=1{{count} 個項目} other{{count} 個項目}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} 完成", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlEnqueued": "{count} 等待中", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlRunning": "{count} 正在下載", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrorsTitle": "下載錯誤", + "@downloadErrorsTitle": {}, + "noErrors": "沒有錯誤!", + "@noErrors": {}, + "errorScreenError": "在讀取錯誤資訊時出現錯誤!建議在 GitHub 上回報此問題及重設應用程式。", + "@errorScreenError": {}, + "failedToGetSongFromDownloadId": "無法從下載 ID 中取得歌曲", + "@failedToGetSongFromDownloadId": {}, + "error": "錯誤", + "@error": {}, + "discNumber": "CD {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "playButtonLabel": "播放", + "@playButtonLabel": {}, + "shuffleButtonLabel": "隨機播放", + "@shuffleButtonLabel": {}, + "songCount": "{count,plural,=1{{count} 首歌曲} other{{count} 首歌曲}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editPlaylistNameTooltip": "編輯播放清單名稱", + "@editPlaylistNameTooltip": {}, + "editPlaylistNameTitle": "編輯播放清單名稱", + "@editPlaylistNameTitle": {}, + "updateButtonLabel": "更新", + "@updateButtonLabel": {}, + "playlistNameUpdated": "已更新播放清單名稱。", + "@playlistNameUpdated": {}, + "favourite": "我的最愛", + "@favourite": {}, + "location": "位置", + "@location": {}, + "addButtonLabel": "加入", + "@addButtonLabel": {}, + "shareLogs": "分享所有紀錄檔", + "@shareLogs": {}, + "logsCopied": "已複製所有紀錄。", + "@logsCopied": {}, + "stackTrace": "Stack Trace", + "@stackTrace": {}, + "applicationLegalese": "採用 Mozilla Public License 2.0。源碼:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "transcoding": "轉碼(Transcoding)", + "@transcoding": {}, + "downloadLocations": "下載位置", + "@downloadLocations": {}, + "audioService": "播放設定", + "@audioService": {}, + "layoutAndTheme": "版面設計及主題", + "@layoutAndTheme": {}, + "notAvailableInOfflineMode": "不能在離線模式下使用", + "@notAvailableInOfflineMode": {}, + "logOut": "登出", + "@logOut": {}, + "areYouSure": "你確定嗎?", + "@areYouSure": {}, + "jellyfinUsesAACForTranscoding": "Jellyfin 使用 ACC 進行轉碼", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscodingSubtitle": "啟用後,音訊會先在伺服器轉碼。", + "@enableTranscodingSubtitle": {}, + "bitrate": "位元速率(Bitrate)", + "@bitrate": {}, + "bitrateSubtitle": "越高的位元速率帶來越好的音質,但亦會使用更多的流量。", + "@bitrateSubtitle": {}, + "addDownloadLocation": "添加下載位置", + "@addDownloadLocation": {}, + "unknownError": "未知的錯誤", + "@unknownError": {}, + "directoryMustBeEmpty": "所選的資料夾必須為空的", + "@directoryMustBeEmpty": {}, + "selectDirectory": "選擇資料夾", + "@selectDirectory": {}, + "appDirectory": "應用程式資料夾", + "@appDirectory": {}, + "enterLowPriorityStateOnPause": "暫停播放時會進入「低優先」狀態", + "@enterLowPriorityStateOnPause": {}, + "enterLowPriorityStateOnPauseSubtitle": "在停止播放時,允許「通知」能被「滑動移除」及關閉應用程式(適用於 Android 裝置)。", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "shuffleAllSongCount": "隨機播放歌曲數量", + "@shuffleAllSongCount": {}, + "shuffleAllSongCountSubtitle": "當使用隨機播放所有歌曲功能時,播放歌曲的數量上限。", + "@shuffleAllSongCountSubtitle": {}, + "message": "訊息", + "@message": {}, + "pathReturnSlashErrorMessage": "並不能使用「/」路徑", + "@pathReturnSlashErrorMessage": {}, + "portrait": "垂直", + "@portrait": {}, + "gridCrossAxisCountSubtitle": "當電話使用{value}顯示模式時,每行的方格(歌曲)數量。", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "showTextOnGridView": "在方格內顯示文字", + "@showTextOnGridView": {}, + "showTextOnGridViewSubtitle": "是否在使用格狀題示方式時,在方格內顯示文字(名稱、歌手等)。", + "@showTextOnGridViewSubtitle": {}, + "showCoverAsPlayerBackground": "顯示模糊化的封面作為播放器的背景", + "@showCoverAsPlayerBackground": {}, + "showCoverAsPlayerBackgroundSubtitle": "是否以模糊化的專輯封面圖片作為應用程式內的播放頁面的背景。", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "hideSongArtistsIfSameAsAlbumArtists": "隱藏與專輯歌手相同的歌曲歌手名稱", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "是否在當專輯的歌手名稱與專輯內的歌曲的歌手名稱相同時,隱藏歌曲的歌手名稱。", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "disableGesture": "禁用「手勢」功能", + "@disableGesture": {}, + "disableGestureSubtitle": "是否禁用「手勢」功能。", + "@disableGestureSubtitle": {}, + "theme": "色彩主題", + "@theme": {}, + "system": "系統", + "@system": {}, + "dark": "深色", + "@dark": {}, + "tabs": "分頁", + "@tabs": {}, + "cancelSleepTimer": "取消睡眠定時器?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "是", + "@yesButtonLabel": {}, + "noButtonLabel": "否", + "@noButtonLabel": {}, + "setSleepTimer": "設定睡眠定時器", + "@setSleepTimer": {}, + "minutes": "分鐘", + "@minutes": {}, + "invalidNumber": "無效的數字", + "@invalidNumber": {}, + "sleepTimerTooltip": "睡眠定時器", + "@sleepTimerTooltip": {}, + "addToPlaylistTooltip": "新增歌曲至播放清單", + "@addToPlaylistTooltip": {}, + "removeFromPlaylistTooltip": "從播放清單中移除歌曲", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "從播放清單中移除", + "@removeFromPlaylistTitle": {}, + "newPlaylist": "新的播放清單", + "@newPlaylist": {}, + "createButtonLabel": "建立", + "@createButtonLabel": {}, + "playlistCreated": "已建立播放清單。", + "@playlistCreated": {}, + "unknownArtist": "未知的歌手", + "@unknownArtist": {}, + "direct": "直接播放", + "@direct": {}, + "streaming": "串流中", + "@streaming": {}, + "statusError": "錯誤", + "@statusError": {}, + "addToQueue": "加入至播放佇列", + "@addToQueue": {}, + "queue": "播放佇列", + "@queue": {}, + "replaceQueue": "取代現時的播放佇列", + "@replaceQueue": {}, + "instantMix": "即時混音", + "@instantMix": {}, + "goToAlbum": "檢視專輯", + "@goToAlbum": {}, + "removeFavourite": "從我的最愛中移除", + "@removeFavourite": {}, + "addFavourite": "加入至我的最愛", + "@addFavourite": {}, + "addedToQueue": "已加入至播放佇列。", + "@addedToQueue": {}, + "queueReplaced": "已取代現有的播放佇列。", + "@queueReplaced": {}, + "removedFromPlaylist": "已從播放清單中移除。", + "@removedFromPlaylist": {}, + "startingInstantMix": "開始即時混音中。", + "@startingInstantMix": {}, + "anErrorHasOccured": "出現錯誤。", + "@anErrorHasOccured": {}, + "responseError": "{error}(代碼:{statusCode})。", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "removeFromMix": "從混音中刪除", + "@removeFromMix": {}, + "addToMix": "加入至混音", + "@addToMix": {}, + "redownloadedItems": "{count,plural, =0{沒有需要重新下載的項目。} =1{已重新下載{count}個項目} other{已重新下載{count}個項目}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bufferDuration": "緩衝時長", + "@bufferDuration": {}, + "bufferDurationSubtitle": "播放器可以預先載入多少的音訊數據(秒)。需重啟以套用設定。", + "@bufferDurationSubtitle": {}, + "startupError": "應用程式啟動時出錯({error})\n\n你可以在 github.com/UnicornsOnLSD/finamp 回報有關問題並附上截圖。如果問題持續,你可以嘗試清除應用程式的資料。", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "downloadedMissingImages": "{count,plural, =0{沒有缺失的圖片} =1{已下載{count} 張圖片} other{已下載{count} 張圖片}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "selectMusicLibraries": "選擇音樂媒體庫", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "couldNotFindLibraries": "找不到任何媒體庫。", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "internalExternalIpExplanation": "如果你需要在局部區域網絡(LAN)以外的地方連接 Jellyfin,請使用伺服器的區域網絡(WAN)IP。\n\n如果目標伺服器使用的連接埠(port)是 HTTP port(80/433),你毋須填寫連接埠。", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "logs": "紀錄檔", + "@logs": {}, + "next": "下一個", + "@next": {}, + "startMixNoSongsAlbum": "在開始混音之前,長按專輯以添加至混音器或從混音器移除", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "favourites": "我的最愛", + "@favourites": {}, + "shuffleAll": "隨機播放全部", + "@shuffleAll": {}, + "finamp": "Finamp", + "@finamp": {}, + "downloadErrors": "下載錯誤", + "@downloadErrors": {}, + "dlFailed": "{count} 失敗", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + }, + "downloadedImagesCount": "{count,plural,=1{{count} 張圖片} other{{count} 張圖片}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "enableTranscoding": "啟用轉碼", + "@enableTranscoding": {}, + "downloadsDeleted": "已刪除。", + "@downloadsDeleted": {}, + "required": "必填", + "@required": {}, + "addDownloads": "添加至下載", + "@addDownloads": {}, + "downloadsAdded": "已新增至下載。", + "@downloadsAdded": {}, + "downloadedSongsWillNotBeDeleted": "已下載的歌曲並不會被刪除", + "@downloadedSongsWillNotBeDeleted": {}, + "customLocation": "自訂位置", + "@customLocation": {}, + "customLocationsBuggy": "現時,自訂位置功能因權限問題而未能完全正常運作。如非必要,建議不要使用。", + "@customLocationsBuggy": {}, + "viewTypeSubtitle": "題示歌曲的方式", + "@viewTypeSubtitle": {}, + "viewType": "顯示方式", + "@viewType": {}, + "grid": "格狀", + "@grid": {}, + "list": "清單", + "@list": {}, + "landscape": "水平", + "@landscape": {}, + "gridCrossAxisCount": "{value}顯示模式的方格的數量", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "light": "淺色", + "@light": {}, + "addToPlaylistTitle": "新增至播放清單", + "@addToPlaylistTitle": {}, + "noAlbum": "沒有任何專輯", + "@noAlbum": {}, + "noArtist": "沒有任何歌手", + "@noArtist": {}, + "noItem": "沒有任何項目", + "@noItem": {}, + "downloaded": "已下載", + "@downloaded": {}, + "transcode": "轉碼", + "@transcode": {}, + "responseError401": "{error}(代碼:{statusCode})。這很大機會由於用戶名稱/密碼輸入錯誤或你已被登出。", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } + } +} From dcdfc7f656b223409881e47714f1db53d18adc1d Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Mon, 17 Apr 2023 04:53:14 +0000 Subject: [PATCH 054/172] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hant/ --- lib/l10n/app_zh_Hant.arb | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index 0a9a49ed9..a56bf0cf8 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -1,7 +1,7 @@ { "serverUrl": "服務器 URL", "@serverUrl": {}, - "showTextOnGridViewSubtitle": "是否在網格音樂螢幕上顯示文字(標題、藝術家等)。", + "showTextOnGridViewSubtitle": "是否在網格音樂螢幕上顯示文字(標題、歌手等)。", "@showTextOnGridViewSubtitle": {}, "startupError": "應用程序啓動期間出現問題! 錯誤是:{error}\n\n請在 github.com/UnicornsOnLSD/finamp 上創建一個 Github 問題,並附上此頁面的螢幕截圖。 如果此頁面一直顯示,請清除您的應用數據以重置應用。", "@startupError": { @@ -89,13 +89,13 @@ "@sortBy": {}, "album": "專輯", "@album": {}, - "albumArtist": "專輯藝術家", + "albumArtist": "專輯歌手", "@albumArtist": {}, - "artist": "藝術家", + "artist": "歌手", "@artist": {}, "budget": "預算", "@budget": {}, - "communityRating": "社區評級", + "communityRating": "聽眾評級", "@communityRating": {}, "criticRating": "評論家評級", "@criticRating": {}, @@ -352,7 +352,7 @@ "@showCoverAsPlayerBackgroundSubtitle": {}, "hideSongArtistsIfSameAsAlbumArtists": "如果與專輯藝術家相同,則隱藏歌曲藝術家", "@hideSongArtistsIfSameAsAlbumArtists": {}, - "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "是否在專輯螢幕上隱藏歌曲藝術家(如果他們與專輯藝術家沒有區別)。", + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "是否在專輯螢幕上隱藏歌曲歌手(如果他們與專輯歌手沒有區別)。", "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, "theme": "主題", "@theme": {}, @@ -392,9 +392,9 @@ "@noAlbum": {}, "noItem": "沒有項目", "@noItem": {}, - "noArtist": "沒有藝術家", + "noArtist": "沒有歌手", "@noArtist": {}, - "unknownArtist": "未知藝術家", + "unknownArtist": "未知歌手", "@unknownArtist": {}, "streaming": "串流", "@streaming": {}, @@ -457,5 +457,27 @@ "removeFromMix": "從混音中刪除", "@removeFromMix": {}, "addToMix": "添加到混音", - "@addToMix": {} + "@addToMix": {}, + "disableGesture": "禁用手勢", + "@disableGesture": {}, + "disableGestureSubtitle": "是否禁用「手勢」功能。", + "@disableGestureSubtitle": {}, + "removeFromPlaylistTooltip": "從播放列表中移除歌曲", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "從播放列表中移除", + "@removeFromPlaylistTitle": {}, + "removedFromPlaylist": "已從播放列表中移除。", + "@removedFromPlaylist": {}, + "bufferDurationSubtitle": "播放器可以預先載入多少的音樂(秒)。需重啟以套用設定。", + "@bufferDurationSubtitle": {}, + "bufferDuration": "緩衝時長", + "@bufferDuration": {}, + "redownloadedItems": "{count,plural, =0{沒有需要重新下載的項目。} =1{已重新下載{count}個項目} other{已重新下載{count}個項目}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + } } From ca4829194751563aac421526ab1783ec04ee509c Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Mon, 17 Apr 2023 05:03:17 +0000 Subject: [PATCH 055/172] Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (170 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hant_HK/ --- lib/l10n/app_zh_Hant_HK.arb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_zh_Hant_HK.arb b/lib/l10n/app_zh_Hant_HK.arb index 01e1d8962..adf003fed 100644 --- a/lib/l10n/app_zh_Hant_HK.arb +++ b/lib/l10n/app_zh_Hant_HK.arb @@ -169,9 +169,9 @@ "@shareLogs": {}, "logsCopied": "已複製所有紀錄。", "@logsCopied": {}, - "stackTrace": "Stack Trace", + "stackTrace": "除錯資訊(Stack Trace)", "@stackTrace": {}, - "applicationLegalese": "採用 Mozilla Public License 2.0。源碼:\n\ngithub.com/jmshrv/finamp", + "applicationLegalese": "採用 Mozilla Public License 2.0 特許條款。原始碼:\n\ngithub.com/jmshrv/finamp", "@applicationLegalese": {}, "transcoding": "轉碼(Transcoding)", "@transcoding": {}, @@ -215,7 +215,7 @@ "@shuffleAllSongCountSubtitle": {}, "message": "訊息", "@message": {}, - "pathReturnSlashErrorMessage": "並不能使用「/」路徑", + "pathReturnSlashErrorMessage": "不能使用「/」路徑", "@pathReturnSlashErrorMessage": {}, "portrait": "垂直", "@portrait": {}, From 6b925f849bc020a955c06917c5ec8d055e113b0f Mon Sep 17 00:00:00 2001 From: Mrayo Date: Fri, 21 Apr 2023 03:01:49 +0200 Subject: [PATCH 056/172] Added translation using Weblate (Greek) --- lib/l10n/app_el.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_el.arb diff --git a/lib/l10n/app_el.arb b/lib/l10n/app_el.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_el.arb @@ -0,0 +1 @@ +{} From 553b46ca73f5a0a51bae98685d4ca89869d4f5df Mon Sep 17 00:00:00 2001 From: Mrayo Date: Fri, 21 Apr 2023 01:06:37 +0000 Subject: [PATCH 057/172] Translated using Weblate (Greek) Currently translated at 68.2% (116 of 170 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/el/ --- lib/l10n/app_el.arb | 348 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 347 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_el.arb b/lib/l10n/app_el.arb index 0967ef424..7cef69ebb 100644 --- a/lib/l10n/app_el.arb +++ b/lib/l10n/app_el.arb @@ -1 +1,347 @@ -{} +{ + "startupError": "Ωχ, κάτι δεν πήγε καλά κατά την έναρξη της εφαρμογής. Ο αριθμός σφάλματος είναι:{error}\nΔημιούργησε ένα issue στο\ngithub.com/UnicornsOnLSD/finamp μαζί με ένα στιγμιότυπο οθόνης από αυτήν την σελίδα. Εάν το πρόβλημα παραμένει μπορείς να διαγράψεις τα δεδομένα της εφαρμογής με σκοπό την επαναφορά της.", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "internalExternalIpExplanation": "Αν θέλεις να έχεις πρόσβαση στον jellyfin διακομιστή σου εξ-αποστασεως, πρέπει να χρησιμοποιήσεις την εξωτερική Διεύθυνση IP.\n\nΑν ο διακομιστής βρίσκεται σε πύλη HTTP(80/443), δεν είναι απαραίτητος ο ορισμός πύλης. Αυτό είναι πιθανόν εφόσον ο διακομιστής σου βρίσκεται πίσω από reverse proxy.", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "artists": "Καλλιτέχνες", + "@artists": {}, + "next": "Επόμενο", + "@next": {}, + "emptyServerUrl": "Η διεύθυνση διακομιστή δεν μπορεί να είναι κενή", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "songs": "Τραγούδια", + "@songs": {}, + "urlStartWithHttps": "Η διεύθυνση πρέπει να ξεκινά με http:// ή https://", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "serverUrl": "Διεύθυνση διακομιστή", + "@serverUrl": {}, + "downloads": "Λήψεις", + "@downloads": {}, + "selectMusicLibraries": "Επέλεξε τις βιβλιοθήκες μουσικής σου", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "albums": "Άλμπουμ", + "@albums": {}, + "urlTrailingSlash": "Η διεύθυνση δεν πρέπει να περιέχει ακολουθούμενη κάθετο", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "username": "Όνομα χρήστη", + "@username": {}, + "password": "Συνθηματικό χρήστη", + "@password": {}, + "logs": "Αρχείο καταγραφών", + "@logs": {}, + "couldNotFindLibraries": "Δεν βρέθηκαν συλλογές.", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "unknownName": "Άγνωστη όνομα", + "@unknownName": {}, + "genres": "Είδη", + "@genres": {}, + "startMixNoSongsArtist": "Κράτησε πατημένο έναν καλλιτέχνη, προκειμένου να προστεθεί ή να αφαιρεθεί από τον κατασκευαστή μίξεων, προ-εκκινήσεως μίξης", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "playlists": "Λίστες αναπαραγωγής", + "@playlists": {}, + "startMix": "Έναρξη μίξης", + "@startMix": {}, + "startMixNoSongsAlbum": "Κράτησε πατημένο ένα άλμπουμ, προκειμένου να προστεθεί ή να αφαιρεθεί από τον κατασκευαστή μίξεων, προ-εκκινήσεως μίξης", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "music": "Μουσική", + "@music": {}, + "finamp": "Finamp", + "@finamp": {}, + "communityRating": "Βαθμολογία κοινότητας", + "@communityRating": {}, + "dlFailed": "{count} απέτυχαν", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "clear": "Εκκαθάριση", + "@clear": {}, + "favourites": "Αγαπημένα", + "@favourites": {}, + "shuffleAll": "Τυχαία αναπαραγωγή", + "@shuffleAll": {}, + "sortOrder": "Σειρά ταξινόμησης", + "@sortOrder": {}, + "settings": "Ρυθμίσεις", + "@settings": {}, + "offlineMode": "Λειτουργία εκτός σύνδεσης", + "@offlineMode": {}, + "album": "Άλμπουμ", + "@album": {}, + "budget": "Προϋπολογισμός", + "@budget": {}, + "sortBy": "Ταξινόμηση κατά", + "@sortBy": {}, + "criticRating": "Βαθμολογία κριτών", + "@criticRating": {}, + "albumArtist": "Καλλιτέχνης άλμπουμ", + "@albumArtist": {}, + "artist": "Καλλιτέχνης", + "@artist": {}, + "dateAdded": "Ημερομηνία προσθήκης", + "@dateAdded": {}, + "playCount": "Πλήθος αναπαραγωγών", + "@playCount": {}, + "datePlayed": "Ημερομηνία αναπαραγωγής", + "@datePlayed": {}, + "random": "Τυχαίο", + "@random": {}, + "premiereDate": "Ημερομηνία πρεμιέρας", + "@premiereDate": {}, + "productionYear": "Χρονολογία παραγωγής", + "@productionYear": {}, + "revenue": "Έσοδα", + "@revenue": {}, + "downloadMissingImages": "Λήψη ελλείποντων εικόνων", + "@downloadMissingImages": {}, + "downloadedImagesCount": "{count,plural,=1{{count} εικόνα} other{{count} εικόνες}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "name": "Όνομα", + "@name": {}, + "downloadCount": "{count,plural, =1{{count} λήψη} other{{count} λήψεις}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} ολοκληρώθηκαν", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "failedToGetSongFromDownloadId": "Απέτυχε η λήψη τραγουδιού από το download ID", + "@failedToGetSongFromDownloadId": {}, + "playlistNameUpdated": "Ανανεώθηκε το όνομα λίστας αναπαραγωγής.", + "@playlistNameUpdated": {}, + "runtime": "Διάρκεια", + "@runtime": {}, + "noErrors": "Κανένα σφάλμα!", + "@noErrors": {}, + "stackTrace": "Stack Trace", + "@stackTrace": {}, + "downloadedMissingImages": "{count,plural, =0{δεν βρέθηκαν ελλειπείς εικόνες} =1{ελήφθησαν {count} ελλειπής εικόνες} other{ελήφθησαν {count} ελλειπής εικόνες}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrors": "Σφάλμα στην λήψη", + "@downloadErrors": {}, + "downloadedItemsCount": "{count,plural,=1{{count} αντικείμενο} other{{count} αντικείμενα}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + }, + "dlEnqueued": "{count} στην σειρά", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlRunning": "{count} τρέχοντα", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrorsTitle": "Σφάλματα λήψης", + "@downloadErrorsTitle": {}, + "error": "Σφάλμα", + "@error": {}, + "shuffleButtonLabel": "Τυχαία αναπαραγωγή", + "@shuffleButtonLabel": {}, + "errorScreenError": "Προέκυψε σφάλμα κατά την λήψη λίστας σφαλμάτων! Σε αυτό το σημείο, καλό είναι να δημιουργήσεις issue στο GitHub και να διαγράψεις τα δεδομένα της εφαρμογής", + "@errorScreenError": {}, + "discNumber": "Δίσκος {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "playButtonLabel": "Αναπαραγωγή", + "@playButtonLabel": {}, + "addDownloads": "Προσθήκη λήψεων", + "@addDownloads": {}, + "logsCopied": "Αντιγράφηκαν οι καταγραφές.", + "@logsCopied": {}, + "message": "Μύνημα", + "@message": {}, + "required": "Απαραίτητο", + "@required": {}, + "favourite": "Αγαπημένο", + "@favourite": {}, + "downloadsAdded": "Προστέθηκαν λήψεις.", + "@downloadsAdded": {}, + "audioService": "Audio Service", + "@audioService": {}, + "songCount": "{count,plural,=1{{count} τίτλος} other{{count} τίτλοι}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editPlaylistNameTooltip": "Τροποποίηση λίστας αναπαραγωγής", + "@editPlaylistNameTooltip": {}, + "downloadsDeleted": "Διαγράφτηκαν οι λήψεις.", + "@downloadsDeleted": {}, + "editPlaylistNameTitle": "Τροποποίηση ονόματος λίστας", + "@editPlaylistNameTitle": {}, + "updateButtonLabel": "Ανανέωση", + "@updateButtonLabel": {}, + "transcoding": "Μετακωδικοποίηση", + "@transcoding": {}, + "notAvailableInOfflineMode": "Μη διαθέσιμο σε λειτουργία εκτός σύνδεσης", + "@notAvailableInOfflineMode": {}, + "logOut": "Έξοδος", + "@logOut": {}, + "downloadedSongsWillNotBeDeleted": "Κατεβασμένα τραγούδια δεν θα διαγραφούν", + "@downloadedSongsWillNotBeDeleted": {}, + "areYouSure": "Είσαι σίγουρος?", + "@areYouSure": {}, + "location": "Τοποθεσία", + "@location": {}, + "addButtonLabel": "Προσθήκη", + "@addButtonLabel": {}, + "layoutAndTheme": "Διάταξη καί εμφάνιση", + "@layoutAndTheme": {}, + "enableTranscodingSubtitle": "μετακωδικοποιει την ροή μουσικής από την πλευρά του διακομιστή.", + "@enableTranscodingSubtitle": {}, + "shareLogs": "Μοιραστείτε τις καταγραφές", + "@shareLogs": {}, + "applicationLegalese": "Αδειοδοτηθηκε με την Άδεια Δημόσιας Χρήσης της Mozilla Public License2.0. Ο κώδικας πηγής διαθέσιμος στο:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "downloadLocations": "Τοποθεσίες λήψεων", + "@downloadLocations": {}, + "jellyfinUsesAACForTranscoding": "Το Jellyfin χρησιμοποιεί AAC για την μετακωδικοποίηση", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscoding": "Ενεργοποίηση μετακωδικοποίησης", + "@enableTranscoding": {}, + "bitrate": "Bitrate", + "@bitrate": {}, + "bitrateSubtitle": "Ο ψηλότερος bitrate προσφέρει υψηλότερης ποιότητας ήχο σε βάρος εύρους ζώνης.", + "@bitrateSubtitle": {}, + "customLocation": "Εξατομικευμένη τοποθεσία", + "@customLocation": {}, + "customLocationsBuggy": "Οι εξατομικευμένες τοποθεσίες έχουν αρκετά bugs λόγω θεμάτων αδειών.\nΣκέφτομαι τρόπους να φτιάξω τα προβλήμματα, όμως για τώρα δεν προτείνω την χρήση τους.", + "@customLocationsBuggy": {}, + "enterLowPriorityStateOnPause": "Ενεργοποίηση χαμηλής-προτεραιοτητας κατάσταση κατά την παύση", + "@enterLowPriorityStateOnPause": {}, + "landscape": "Οριζόντιο", + "@landscape": {}, + "gridCrossAxisCount": "{value} πλήθος τεμνοντων αξόνων πλέγματος", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "appDirectory": "Κατάλογος εφαρμογής", + "@appDirectory": {}, + "addDownloadLocation": "Προσθήκη τοποθεσίας λήψεως", + "@addDownloadLocation": {}, + "selectDirectory": "Επέλεξε κατάλογο", + "@selectDirectory": {}, + "unknownError": "Άγνωστο σφάλμα", + "@unknownError": {}, + "directoryMustBeEmpty": "Ο κατάλογος πρέπει να είναι άδειος", + "@directoryMustBeEmpty": {}, + "enterLowPriorityStateOnPauseSubtitle": "Επιτρέπει την απόσβεση της ειδοποίησης κατά την παύση. Επίσης επιτρέπει στο σύστημα να \"σκοτώνει\" την υπηρεσία κατά την παύση.", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "shuffleAllSongCountSubtitle": "Πλήθος τραγουδιών να φορτώσουν όταν χρησιμοποιείται το κουμπί τυχαίας αναπαραγωγής όλων.", + "@shuffleAllSongCountSubtitle": {}, + "viewTypeSubtitle": "Τρόπος προβολής για την οθόνη μουσικής", + "@viewTypeSubtitle": {}, + "list": "Λίστα", + "@list": {}, + "pathReturnSlashErrorMessage": "Μονοπάτια που επιστρέφουν \"/\" δεν μπορούν να χρησιμοποιηθούν", + "@pathReturnSlashErrorMessage": {}, + "shuffleAllSongCount": "Τυχαία αναπαραγωγή όλων των Πλήθων τραγουδιών", + "@shuffleAllSongCount": {}, + "viewType": "Τύπος προβολής", + "@viewType": {}, + "grid": "Πλέγμα", + "@grid": {}, + "portrait": "Κάθετο", + "@portrait": {}, + "showTextOnGridView": "Εμφάνιση αναγραφων στην προβολή πλέγματος", + "@showTextOnGridView": {}, + "gridCrossAxisCountSubtitle": "Πλήθος τετραγώνων πλέγματος ανά Πλειάδες{value}.", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + } +} From d203eef46d7e3ead2f375815ddd15d73447ba96f Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Sun, 23 Apr 2023 14:27:52 +0000 Subject: [PATCH 058/172] Translated using Weblate (Spanish) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/es/ --- lib/l10n/app_es.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 485da59bd..008c97d3e 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -479,5 +479,7 @@ "removeFromPlaylistTitle": "Eliminar de la lista de reproducción", "@removeFromPlaylistTitle": {}, "removedFromPlaylist": "Eliminado de la lista de reproducción.", - "@removedFromPlaylist": {} + "@removedFromPlaylist": {}, + "language": "Idioma", + "@language": {} } From e3a1474b14c48ebdf899dbc37cfc0a74116f2373 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Sun, 23 Apr 2023 14:31:02 +0000 Subject: [PATCH 059/172] Translated using Weblate (Turkish) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/tr/ --- lib/l10n/app_tr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index d439edf60..f8b7e7a60 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -479,5 +479,7 @@ "example": "401" } } - } + }, + "language": "Dil", + "@language": {} } From 2a201cca106cc10ffafe4ea37ac2fe869ddd9011 Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Mon, 24 Apr 2023 02:17:51 +0000 Subject: [PATCH 060/172] Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hant_HK/ --- lib/l10n/app_zh_Hant_HK.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_zh_Hant_HK.arb b/lib/l10n/app_zh_Hant_HK.arb index adf003fed..e7a34815b 100644 --- a/lib/l10n/app_zh_Hant_HK.arb +++ b/lib/l10n/app_zh_Hant_HK.arb @@ -479,5 +479,7 @@ "example": "401" } } - } + }, + "language": "語言", + "@language": {} } From 82b9b2a220de9a1dc9f4c1dfde7a94bff9538ed4 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 23:00:13 +0100 Subject: [PATCH 061/172] Actually update flutter_downloader to the fixed version --- pubspec.lock | 4 ++-- pubspec.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pubspec.lock b/pubspec.lock index 21df4167b..8c2ad57f2 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -370,8 +370,8 @@ packages: dependency: "direct main" description: path: "." - ref: ae1beecbbe4f80c70a9b57876dd9f29ad1ee944d - resolved-ref: ae1beecbbe4f80c70a9b57876dd9f29ad1ee944d + ref: ac9b9e917e874f1f86f52ddd74fb57e4e2af3639 + resolved-ref: ac9b9e917e874f1f86f52ddd74fb57e4e2af3639 url: "https://github.com/jmshrv/flutter_downloader.git" source: git version: "1.10.2" diff --git a/pubspec.yaml b/pubspec.yaml index eed0c99f5..280be26be 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -37,7 +37,7 @@ dependencies: flutter_downloader: git: url: https://github.com/jmshrv/flutter_downloader.git - ref: "ae1beecbbe4f80c70a9b57876dd9f29ad1ee944d" + ref: "ac9b9e917e874f1f86f52ddd74fb57e4e2af3639" path_provider: ^2.0.14 hive: ^2.2.3 hive_flutter: ^1.1.0 From 9e4ebf836bdab5d51395e0a0c4b9ce3f9100d2af Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 23:01:38 +0100 Subject: [PATCH 062/172] Bump version (again) --- fastlane/metadata/android/en-US/changelogs/32.txt | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/32.txt diff --git a/fastlane/metadata/android/en-US/changelogs/32.txt b/fastlane/metadata/android/en-US/changelogs/32.txt new file mode 100644 index 000000000..84057a753 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/32.txt @@ -0,0 +1 @@ +Downloader fixes, and a language selector. Full changelog at https://github.com/jmshrv/finamp/releases/tag/0.6.14 \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 280be26be..bd7f754c9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.13+31 +version: 0.6.14+32 environment: sdk: ">=2.19.4 <3.0.0" From 82b36def175d84f656a24e8122a60eeb0d9efd79 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 23:06:48 +0100 Subject: [PATCH 063/172] Add Japanese to iOS language list --- ios/Runner.xcodeproj/project.pbxproj | 5 +++++ ios/Runner/ja.lproj/LaunchScreen.strings | 1 + ios/Runner/ja.lproj/Main.strings | 1 + 3 files changed, 7 insertions(+) create mode 100644 ios/Runner/ja.lproj/LaunchScreen.strings create mode 100644 ios/Runner/ja.lproj/Main.strings diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 10f01484d..5d7393c37 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -54,6 +54,8 @@ 1A3D4FC229E71B4700C17E1B /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/LaunchScreen.strings; sourceTree = ""; }; 1A3D4FC329E71B4B00C17E1B /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/Main.strings; sourceTree = ""; }; 1A3D4FC429E71B4B00C17E1B /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1A5F1B1929F734E500E6F504 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/Main.strings; sourceTree = ""; }; + 1A5F1B1A29F734E500E6F504 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/LaunchScreen.strings; sourceTree = ""; }; 1A7A01C725617F74005D4731 /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libsqlite3.tbd; path = usr/lib/libsqlite3.tbd; sourceTree = SDKROOT; }; 1A9072D528AEB75800DB7A21 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/Main.strings; sourceTree = ""; }; 1A9072D628AEB75800DB7A21 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/LaunchScreen.strings; sourceTree = ""; }; @@ -251,6 +253,7 @@ "pt-PT", tr, vi, + ja, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; @@ -391,6 +394,7 @@ 1A3D4FBF29E71B2C00C17E1B /* pt-PT */, 1A3D4FC129E71B4700C17E1B /* tr */, 1A3D4FC329E71B4B00C17E1B /* vi */, + 1A5F1B1929F734E500E6F504 /* ja */, ); name = Main.storyboard; sourceTree = ""; @@ -422,6 +426,7 @@ 1A3D4FC029E71B2C00C17E1B /* pt-PT */, 1A3D4FC229E71B4700C17E1B /* tr */, 1A3D4FC429E71B4B00C17E1B /* vi */, + 1A5F1B1A29F734E500E6F504 /* ja */, ); name = LaunchScreen.storyboard; sourceTree = ""; diff --git a/ios/Runner/ja.lproj/LaunchScreen.strings b/ios/Runner/ja.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/ja.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/ja.lproj/Main.strings b/ios/Runner/ja.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/ja.lproj/Main.strings @@ -0,0 +1 @@ + From 0932469b1906f7a425d3fc73cbf561f3c58e1760 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 24 Apr 2023 23:18:59 +0100 Subject: [PATCH 064/172] Update to better null-safety conversion of locale_names --- .../LanguageSelectionScreen/language_list.dart | 12 ++++++------ pubspec.lock | 6 +++--- pubspec.yaml | 8 ++++++-- 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/components/LanguageSelectionScreen/language_list.dart b/lib/components/LanguageSelectionScreen/language_list.dart index 4d408adfd..1e6c308d4 100644 --- a/lib/components/LanguageSelectionScreen/language_list.dart +++ b/lib/components/LanguageSelectionScreen/language_list.dart @@ -5,7 +5,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:locale_names/locale_names.dart'; -import '../../services/finamp_settings_helper.dart'; import '../../services/locale_helper.dart'; class LanguageList extends StatefulWidget { @@ -18,7 +17,7 @@ class LanguageList extends StatefulWidget { class _LanguageListState extends State { // yeah I'm a computer science student how could you tell // (sorts locales without having to copy them into a list first) - final locales = SplayTreeMap.fromIterable( + final locales = SplayTreeMap.fromIterable( AppLocalizations.supportedLocales, key: (element) => (element as Locale).defaultDisplayLanguage, value: (element) => element, @@ -32,7 +31,6 @@ class _LanguageListState extends State { child: ValueListenableBuilder( valueListenable: LocaleHelper.localeListener, builder: (_, __, ___) { - debugPrint(LocaleHelper.locale.toString()); return CustomScrollView( slivers: [ // For some reason, setting the null (system) LanguageListTile to @@ -43,6 +41,7 @@ class _LanguageListState extends State { SliverList( // ignore: prefer_const_constructors delegate: SliverChildListDelegate.fixed([ + // ignore: prefer_const_constructors LanguageListTile(), const Divider(), ]), @@ -80,9 +79,10 @@ class LanguageListTile extends StatelessWidget { AppLocalizations.of(context)!.system), subtitle: locale == null ? null - : Text(LocaleHelper.locale == null - ? locale!.defaultDisplayLanguage - : locale!.displayLanguageIn(LocaleHelper.locale!)), + : Text((LocaleHelper.locale == null + ? locale!.defaultDisplayLanguage + : locale!.displayLanguageIn(LocaleHelper.locale!)) ?? + "???"), value: locale, groupValue: LocaleHelper.locale, onChanged: (_) { diff --git a/pubspec.lock b/pubspec.lock index 8c2ad57f2..53726e63e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -602,9 +602,9 @@ packages: dependency: "direct main" description: path: "." - ref: "74e55fd" - resolved-ref: "74e55fd2997cc5a0d5283604a09c307036cac020" - url: "https://github.com/Mantano/locale_names.git" + ref: cea057c220f4ee7e09e8f1fc7036110245770948 + resolved-ref: cea057c220f4ee7e09e8f1fc7036110245770948 + url: "https://github.com/lamarios/locale_names.git" source: git version: "0.0.1" logging: diff --git a/pubspec.yaml b/pubspec.yaml index bd7f754c9..065b348aa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,10 +63,14 @@ dependencies: intl: ^0.17.0 auto_size_text: ^3.0.0 flutter_riverpod: ^2.3.4 + # locale_names: + # git: + # url: https://github.com/Mantano/locale_names.git + # ref: 74e55fd locale_names: git: - url: https://github.com/Mantano/locale_names.git - ref: 74e55fd + url: https://github.com/lamarios/locale_names.git + ref: cea057c220f4ee7e09e8f1fc7036110245770948 dev_dependencies: flutter_test: From ec3be0a9e10ff8717cad5af660e3e0ead612a71f Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Apr 2023 21:32:44 +0100 Subject: [PATCH 065/172] Start work on downloads plan --- DOWNLOADS_PLAN.md | 37 +++++++++++++++++++++++++++++++++++++ README.md | 2 ++ 2 files changed, 39 insertions(+) create mode 100644 DOWNLOADS_PLAN.md diff --git a/DOWNLOADS_PLAN.md b/DOWNLOADS_PLAN.md new file mode 100644 index 000000000..7d97bce35 --- /dev/null +++ b/DOWNLOADS_PLAN.md @@ -0,0 +1,37 @@ +# Finamp 1.0 Plan - Downloads Rewrite + +## Introduction + +This document outlines the core plan and motivation for Finamp 1.0. This change will need a major version bump as it will introduce breaking changes. As such, we will have to ensure that users are notified well in advance. + +## Motivations + +Finamp's downloading infrastructure is currently very poor. It was mostly written before I had any formal computer science experience. Because of this, it is very finnicky, and frequently breaks between updates. Some highlights include: + +* Download items being tracked across five key-value databases that all need to be kept in sync: + * `DownloadedItems` - stores downloaded song data. + * `DownloadedParents` - stores data about parents (albums, playlists), with links to children. + * By link, I mean a copy of the Jellyfin metadata stored in `DownloadedItems`. Syncing between the two is done manually. + * `DownloadIds` - a copy of the data stored in `DownloadedItems`, but indexed by the `flutter_downloader` download ID so that we can track the download ID back to the actual song. + * `DownloadedImages` - stores downloaded image data. + * `DownloadedImageIds` - same as `DownloadIds`, but for images. + * `DownloadIds` cannot be used for images, as it takes a `DownloadedSong` data type (yes, a complete copy). +* In the past, I stored absolute paths. The `DownloadedSong` data-type has to account for this, and update as it goes along. + * This is especially problematic on iOS, where the internal app directory changes every update. +* Off the top of my head, there is no clean way to add support for downloaded artists, or especially genres + +The download system also makes use of the [flutter_downloader](https://pub.dev/packages/flutter_downloader) package. In the new system, I will be replacing it with [background_downloader](https://pub.dev/packages/background_downloader) for the following reasons: + +* Desktop support - While downloading isn't 100% essential for desktop, it will be nice to have a complete version of Finamp on desktop. Desktop support will also be required for Linux phones. +* Better looking API - `flutter_downloader` is extremely finnicky, especially around listening for updates. This is why download indicators have never really worked in Finamp. +* Scoped storage support - Finamp's support for external directories is currently practically broken. It seems to be a pretty niche feature, but it would be nice to have working again. +* Relative path handling by default - this will solve the absolute path issue I mentioned earlier. +* Better stability - `flutter_downloader` has had a history of spurious bugs causing crashes and other issues. Most recently, [downloads on iOS failed to get marked as complete](https://github.com/jmshrv/finamp/issues/433), causing downloads to effectively break on iOS. I haven't tried `background_downloader` myself yet, but it has a much more rigid testing setup which should help it catch regressions like this. + +Finamp also uses [Hive](https://pub.dev/packages/hive) for all of its databases. Hive is a great database, but it isn't intended for complex use-cases like this. The same people behind Hive have created [Isar](https://pub.dev/packages/isar), which supports more advanced use-cases. I didn't use this initially as it was still in early development during Finamp's initial development, but it is now stable. For downloads, it will allow me to actually model relations in the database to make adding and removing downloads more reliable. I will also likely replace Finamp's other Hive databases with Isar databases. + +## Impacts + +Doing all of this will be an extremely damaging breaking change. Finamp's last breaking change was [0.5.0](https://github.com/jmshrv/finamp/releases/tag/0.5.0), which required all users to clear their app data (loading databases from before 0.5.0 failed). This was almost acceptable back then - Finamp was still in its early days and hadn't yet been released on the App Store or Play Store (0.5.0 was the first release). Now, Finamp has roughly 25,000 downloads (thank you!). I can't just release an update that breaks everything without giving months of notice, especially since the app will be auto-updated to 1.0. + +While the app won't fail to launch like 0.5.0 did, it will effectively log the user out (as the user database will be replaced). Downloads will also be wiped by deleting the internal song directory. Users must be aware that this is happening - one of the first "actions" of this will be to add a visible warning to the app linking here (with a brief explanation at the top). \ No newline at end of file diff --git a/README.md b/README.md index 5bbf31dbd..c25b294b9 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Finamp is a Jellyfin music player for Android and iOS. Its main feature is the ability to download songs for offline listening. +**Breaking changes are coming** - see the [plan](DOWNLOADS_PLAN.md) + ## Downloading [ Date: Sun, 30 Apr 2023 21:02:21 +0800 Subject: [PATCH 066/172] Fix shuffle always starts on first song(issues:#61) and remove redundant code --- .../album_screen_content_flexible_space_bar.dart | 3 +++ lib/services/audio_service_helper.dart | 16 ++++++++-------- lib/services/music_player_background_task.dart | 1 - 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart b/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart index ef32a06af..e5e4cf012 100644 --- a/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart +++ b/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart @@ -1,3 +1,5 @@ +import 'dart:math'; + import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; @@ -70,6 +72,7 @@ class AlbumScreenContentFlexibleSpaceBar extends StatelessWidget { audioServiceHelper.replaceQueueWithItem( itemList: items, shuffle: true, + initialIndex: Random().nextInt(items.length), ), icon: const Icon(Icons.shuffle), label: Text( diff --git a/lib/services/audio_service_helper.dart b/lib/services/audio_service_helper.dart index 34265f5b0..989ee97a3 100644 --- a/lib/services/audio_service_helper.dart +++ b/lib/services/audio_service_helper.dart @@ -40,14 +40,14 @@ class AudioServiceHelper { } } - if (!shuffle) { - // Give the audio service our next initial index so that playback starts - // at that index. We don't do this if shuffling because it causes the - // queue to always start at the start (although you could argue that we - // still should if initialIndex is not 0, but that doesn't happen - // anywhere in this app so oh well). - _audioHandler.setNextInitialIndex(initialIndex); - } + // if (!shuffle) { + // // Give the audio service our next initial index so that playback starts + // // at that index. We don't do this if shuffling because it causes the + // // queue to always start at the start (although you could argue that we + // // still should if initialIndex is not 0, but that doesn't happen + // // anywhere in this app so oh well). + _audioHandler.setNextInitialIndex(initialIndex); + // } await _audioHandler.updateQueue(queue); diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index e6231075c..72a071a33 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -312,7 +312,6 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { try { switch (shuffleMode) { case AudioServiceShuffleMode.all: - await _player.shuffle(); await _player.setShuffleModeEnabled(true); shuffleNextQueue = true; break; From 666f35c9a64929bab20b56673ca60779786fdb55 Mon Sep 17 00:00:00 2001 From: Cameron Wichman Date: Tue, 9 May 2023 23:05:24 -0700 Subject: [PATCH 067/172] Improved skipToPrevious() functionality --- lib/services/music_player_background_task.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index e6231075c..6aa6e57b1 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -271,7 +271,11 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { @override Future skipToPrevious() async { try { - await _player.seekToPrevious(); + if (!_player.hasPrevious || _player.position.inSeconds >= 5) { + await _player.seek(Duration.zero, index: _player.currentIndex); + } else { + await _player.seek(Duration.zero, index: _player.previousIndex); + } } catch (e) { _audioServiceBackgroundTaskLogger.severe(e); return Future.error(e); From 78601fd3c4bd358d0169ba8b249abf6234c2d308 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 11 May 2023 01:40:11 +0100 Subject: [PATCH 068/172] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 065b348aa..03f4c58cd 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.14+32 +version: 0.6.15+33 environment: sdk: ">=2.19.4 <3.0.0" From 80d4d10caedbb4131cf579af7e76fe29fabfdf0b Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 11 May 2023 02:06:01 +0100 Subject: [PATCH 069/172] Set blur sigma to 85 (fixes Impeller blur) --- android/build.gradle | 2 +- ios/Podfile.lock | 4 +-- ios/Runner.xcodeproj/project.pbxproj | 1 + lib/screens/player_screen.dart | 10 +++++--- pubspec.lock | 38 ++++++++++++++-------------- pubspec.yaml | 2 +- 6 files changed, 31 insertions(+), 26 deletions(-) diff --git a/android/build.gradle b/android/build.gradle index 58a8c74b1..713d7f6e6 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -26,6 +26,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index b1a335a2b..075acad49 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -93,7 +93,7 @@ DEPENDENCIES: - flutter_downloader (from `.symlinks/plugins/flutter_downloader/ios`) - just_audio (from `.symlinks/plugins/just_audio/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) @@ -126,7 +126,7 @@ EXTERNAL SOURCES: package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: - :path: ".symlinks/plugins/path_provider_foundation/ios" + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" share_plus: diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 5d7393c37..ec56283fb 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -290,6 +290,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( diff --git a/lib/screens/player_screen.dart b/lib/screens/player_screen.dart index 7c8cedf97..860d80d09 100644 --- a/lib/screens/player_screen.dart +++ b/lib/screens/player_screen.dart @@ -35,12 +35,13 @@ class PlayerScreen extends StatelessWidget { return SimpleGestureDetector( onVerticalSwipe: (direction) { - if (!FinampSettingsHelper.finampSettings.disableGesture && direction == SwipeDirection.down) { + if (!FinampSettingsHelper.finampSettings.disableGesture && + direction == SwipeDirection.down) { Navigator.of(context).pop(); } }, onHorizontalSwipe: (direction) { - if(!FinampSettingsHelper.finampSettings.disableGesture){ + if (!FinampSettingsHelper.finampSettings.disableGesture) { switch (direction) { case SwipeDirection.left: audioHandler.skipToNext(); @@ -192,7 +193,10 @@ class _BlurredPlayerScreenBackground extends ConsumerWidget { BlendMode.srcOver), child: ImageFiltered( imageFilter: ImageFilter.blur( - sigmaX: 100, sigmaY: 100, tileMode: TileMode.mirror), + sigmaX: 85, + sigmaY: 85, + tileMode: TileMode.mirror, + ), child: SizedBox.expand(child: child), ), ), diff --git a/pubspec.lock b/pubspec.lock index 53726e63e..d815bc5e3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -45,10 +45,10 @@ packages: dependency: transitive description: name: async - sha256: bfe67ef28df125b7dddcea62755991f807aa39a2492a23e1550161692950bbe0 + sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.0" audio_service: dependency: "direct main" description: @@ -165,10 +165,10 @@ packages: dependency: transitive description: name: characters - sha256: e6a326c8af69605aec75ed6c187d06b349707a27fbff8222ca9cc2cff167975c + sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.3.0" checked_yaml: dependency: transitive description: @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: collection - sha256: cfc915e6923fe5ce6e153b0723c753045de46de1b4d63771530504004a45fae0 + sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.17.1" convert: dependency: transitive description: @@ -530,10 +530,10 @@ packages: dependency: "direct main" description: name: intl - sha256: "910f85bce16fb5c6f614e117efa303e85a1731bb0081edf3604a2ae6e9a3cc91" + sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 url: "https://pub.dev" source: hosted - version: "0.17.0" + version: "0.18.0" io: dependency: transitive description: @@ -546,10 +546,10 @@ packages: dependency: transitive description: name: js - sha256: "5528c2f391ededb7775ec1daa69e65a2d61276f7552de2b5f7b8d34ee9fd4ab7" + sha256: f2c445dce49627136094980615a031419f7f3eb393237e4ecd97ac15dea343f3 url: "https://pub.dev" source: hosted - version: "0.6.5" + version: "0.6.7" json_annotation: dependency: "direct main" description: @@ -619,10 +619,10 @@ packages: dependency: transitive description: name: matcher - sha256: "16db949ceee371e9b99d22f88fa3a73c4e59fd0afed0bd25fc336eb76c198b72" + sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" url: "https://pub.dev" source: hosted - version: "0.12.13" + version: "0.12.15" material_color_utilities: dependency: transitive description: @@ -635,10 +635,10 @@ packages: dependency: transitive description: name: meta - sha256: "6c268b42ed578a53088d834796959e4a1814b5e9e164f147f580a386e5decf42" + sha256: "3c74dbf8763d36539f114c799d8a2d87343b5067e9d796ca22b5eb8437090ee3" url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" mime: dependency: transitive description: @@ -691,10 +691,10 @@ packages: dependency: "direct main" description: name: path - sha256: db9d4f58c908a4ba5953fcee2ae317c94889433e5024c27ce74a37f94267945b + sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" url: "https://pub.dev" source: hosted - version: "1.8.2" + version: "1.8.3" path_provider: dependency: "direct main" description: @@ -1032,10 +1032,10 @@ packages: dependency: transitive description: name: test_api - sha256: ad540f65f92caa91bf21dfc8ffb8c589d6e4dc0c2267818b4cc2792857706206 + sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb url: "https://pub.dev" source: hosted - version: "0.4.16" + version: "0.5.1" timing: dependency: transitive description: @@ -1157,5 +1157,5 @@ packages: source: hosted version: "3.1.1" sdks: - dart: ">=2.19.4 <3.0.0" + dart: ">=3.0.0-0 <4.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index 03f4c58cd..f5983829a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,7 +60,7 @@ dependencies: cupertino_icons: ^1.0.5 path: ^1.8.2 android_id: ^0.2.0 - intl: ^0.17.0 + intl: ^0.18.0 auto_size_text: ^3.0.0 flutter_riverpod: ^2.3.4 # locale_names: From 7c257cc013d09d9b2e743810b0bac420079f842b Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 11 May 2023 15:03:44 +0100 Subject: [PATCH 070/172] Apply Dart Fix --- lib/components/AlbumScreen/song_list_tile.dart | 6 +++--- lib/components/now_playing_bar.dart | 6 +++--- lib/screens/player_screen.dart | 14 +++++++------- lib/services/jellyfin_api.dart | 1 - 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index 3d8162cd1..610fdf7f6 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -399,10 +399,10 @@ class _SongListTileState extends State { background: Container( color: Theme.of(context).colorScheme.secondary, alignment: Alignment.centerLeft, - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + child: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), child: Row( - children: const [ + children: [ AspectRatio( aspectRatio: 1, child: FittedBox( diff --git a/lib/components/now_playing_bar.dart b/lib/components/now_playing_bar.dart index c5b68cfca..07fa73deb 100644 --- a/lib/components/now_playing_bar.dart +++ b/lib/components/now_playing_bar.dart @@ -68,11 +68,11 @@ class NowPlayingBar extends StatelessWidget { } return false; }, - background: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16.0), + background: const Padding( + padding: EdgeInsets.symmetric(horizontal: 16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ + children: [ AspectRatio( aspectRatio: 1, child: FittedBox( diff --git a/lib/screens/player_screen.dart b/lib/screens/player_screen.dart index 860d80d09..bfa0e5c8c 100644 --- a/lib/screens/player_screen.dart +++ b/lib/screens/player_screen.dart @@ -70,28 +70,28 @@ class PlayerScreen extends StatelessWidget { children: [ if (FinampSettingsHelper.finampSettings.showCoverAsPlayerBackground) const _BlurredPlayerScreenBackground(), - SafeArea( + const SafeArea( child: Center( child: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ - const Expanded( + Expanded( child: _PlayerScreenAlbumImage(), ), Expanded( child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), + padding: EdgeInsets.symmetric(horizontal: 16), child: Column( mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.spaceEvenly, crossAxisAlignment: CrossAxisAlignment.start, children: [ - const SongName(), - const ProgressSlider(), - const PlayerButtons(), + SongName(), + ProgressSlider(), + PlayerButtons(), Stack( alignment: Alignment.center, - children: const [ + children: [ Align( alignment: Alignment.centerLeft, child: PlaybackMode(), diff --git a/lib/services/jellyfin_api.dart b/lib/services/jellyfin_api.dart index 48173dbef..fca6fee19 100644 --- a/lib/services/jellyfin_api.dart +++ b/lib/services/jellyfin_api.dart @@ -5,7 +5,6 @@ import 'package:chopper/chopper.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:get_it/get_it.dart'; import 'package:package_info_plus/package_info_plus.dart'; -import 'package:path/path.dart' as path; import '../models/jellyfin_models.dart'; import 'finamp_user_helper.dart'; From 2d5a208df76c2c02e52fc4b0679a2b6c0318abab Mon Sep 17 00:00:00 2001 From: Max Rumpf Date: Thu, 18 May 2023 13:30:29 +0200 Subject: [PATCH 071/172] Sort artist albums by PremiereDate instead of ProductionYear If an artist has multiple albums in one year, albums may not necessarily follow the release order. Sorting by PremiereDate instead fixes that. If no PremiereDate is set on the item, the server will automatically fall back to ProductionYear. --- lib/components/MusicScreen/music_screen_tab_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/MusicScreen/music_screen_tab_view.dart b/lib/components/MusicScreen/music_screen_tab_view.dart index b4ccab862..c8231bed5 100644 --- a/lib/components/MusicScreen/music_screen_tab_view.dart +++ b/lib/components/MusicScreen/music_screen_tab_view.dart @@ -90,7 +90,7 @@ class _MusicScreenTabViewState extends State : widget.parentItem == null ? "SortName" : widget.parentItem?.type == "MusicArtist" - ? "ProductionYear" + ? "PremiereDate" : "SortName"), sortOrder: widget.sortOrder?.toString() ?? SortOrder.ascending.toString(), From 205b3a3e716e361ad8a1cede11fe6e22631c1e7e Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sun, 13 Mar 2022 22:25:43 +0100 Subject: [PATCH 072/172] :bug: Fix playback stop events using an incorrect position --- lib/services/music_player_background_task.dart | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 8d6227dc3..1e9c6f79b 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -100,6 +100,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { final playbackData = generatePlaybackProgressInfo( item: _previousItem, includeNowPlayingQueue: true, + isStopEvent: true, ); if (playbackData != null) { @@ -373,6 +374,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { PlaybackProgressInfo? generatePlaybackProgressInfo({ MediaItem? item, required bool includeNowPlayingQueue, + bool isStopEvent = false, }) { if (_queueAudioSource.length == 0 && item == null) { // This function relies on _queue having items, so we return null if it's @@ -386,7 +388,9 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { _getQueueItem(_player.currentIndex ?? 0).extras!["itemJson"]["Id"], isPaused: !_player.playing, isMuted: _player.volume == 0, - positionTicks: _player.position.inMicroseconds * 10, + positionTicks: isStopEvent + ? (item?.duration?.inMicroseconds ?? 0) * 10 + : _player.position.inMicroseconds * 10, repeatMode: _jellyfinRepeatMode(_player.loopMode), playMethod: item?.extras!["shouldTranscode"] ?? _getQueueItem(_player.currentIndex ?? 0) From 5ed5fc9d866cbc3a929e765d66889e54500bedb1 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 26 May 2023 20:32:16 +0100 Subject: [PATCH 073/172] Remove breaking changes note from README They're still coming, just not for a while --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index c25b294b9..5bbf31dbd 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ Finamp is a Jellyfin music player for Android and iOS. Its main feature is the ability to download songs for offline listening. -**Breaking changes are coming** - see the [plan](DOWNLOADS_PLAN.md) - ## Downloading [ Date: Sun, 11 Jun 2023 21:09:02 +0100 Subject: [PATCH 074/172] Censor login information in logs screen --- lib/components/LogsScreen/log_tile.dart | 74 ++++++++++++++++++---- lib/l10n/app_en.arb | 4 +- lib/services/case_insensitive_pattern.dart | 19 ++++++ lib/services/censored_log.dart | 33 ++++++++++ lib/services/contains_login.dart | 6 ++ lib/services/finamp_logs_helper.dart | 47 +------------- 6 files changed, 123 insertions(+), 60 deletions(-) create mode 100644 lib/services/case_insensitive_pattern.dart create mode 100644 lib/services/censored_log.dart create mode 100644 lib/services/contains_login.dart diff --git a/lib/components/LogsScreen/log_tile.dart b/lib/components/LogsScreen/log_tile.dart index 548fb1c9a..790201f82 100644 --- a/lib/components/LogsScreen/log_tile.dart +++ b/lib/components/LogsScreen/log_tile.dart @@ -1,49 +1,59 @@ import 'package:clipboard/clipboard.dart'; +import 'package:finamp/services/censored_log.dart'; +import 'package:finamp/services/contains_login.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:get_it/get_it.dart'; import 'package:logging/logging.dart'; -import '../../services/finamp_logs_helper.dart'; import '../error_snackbar.dart'; -class LogTile extends StatelessWidget { +class LogTile extends StatefulWidget { const LogTile({Key? key, required this.logRecord}) : super(key: key); final LogRecord logRecord; @override - Widget build(BuildContext context) { - final finampLogsHelper = GetIt.instance(); + State createState() => _LogTileState(); +} + +class _LogTileState extends State { + final _controller = ExpansionTileController(); + /// Whether the user has confirmed. This is used to stop onExpansionChanged + /// from infinitely asking the user to confirm. + bool hasConfirmed = false; + + @override + Widget build(BuildContext context) { return GestureDetector( onLongPress: () async { try { - await FlutterClipboard.copy(finampLogsHelper.sanitiseLog(logRecord)); + await FlutterClipboard.copy(widget.logRecord.censoredMessage); } catch (e) { errorSnackbar(e, context); } }, child: Card( - color: _logColor(logRecord.level, context), + color: _logColor(widget.logRecord.level, context), child: ExpansionTile( - leading: _LogIcon(level: logRecord.level), - key: PageStorageKey(logRecord.time), + controller: _controller, + leading: _LogIcon(level: widget.logRecord.level), + key: PageStorageKey(widget.logRecord.time), title: RichText( maxLines: 3, overflow: TextOverflow.ellipsis, text: TextSpan( children: [ TextSpan( - text: "[${logRecord.loggerName}] ", + text: "[${widget.logRecord.loggerName}] ", style: const TextStyle(fontWeight: FontWeight.bold), ), TextSpan( - text: "[${logRecord.time}] ", + text: "[${widget.logRecord.time}] ", style: const TextStyle(fontWeight: FontWeight.bold), ), TextSpan( - text: logRecord.message, + text: widget.logRecord.loginCensoredMessage, ), ], ), @@ -57,7 +67,7 @@ class LogTile extends StatelessWidget { style: Theme.of(context).primaryTextTheme.headlineSmall, ), Text( - "${logRecord.message}\n", + "${widget.logRecord.message}\n", style: Theme.of(context).primaryTextTheme.bodyMedium, ), Text( @@ -65,10 +75,46 @@ class LogTile extends StatelessWidget { style: Theme.of(context).primaryTextTheme.headlineSmall, ), Text( - logRecord.stackTrace.toString(), + widget.logRecord.stackTrace.toString(), style: Theme.of(context).primaryTextTheme.bodyMedium, ) ], + onExpansionChanged: (value) async { + if (value && !hasConfirmed && widget.logRecord.containsLogin) { + _controller.collapse(); + + final confirmed = await showDialog( + context: context, + barrierDismissible: false, + builder: (context) { + return AlertDialog( + title: Text(AppLocalizations.of(context)!.confirm), + content: Text( + AppLocalizations.of(context)!.showUncensoredLogMessage), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text(MaterialLocalizations.of(context) + .cancelButtonLabel), + ), + TextButton( + onPressed: () => Navigator.of(context).pop(true), + child: Text(AppLocalizations.of(context)!.confirm), + ), + ], + ); + }, + ); + + hasConfirmed = confirmed!; + + if (confirmed) { + _controller.expand(); + } + } else if (hasConfirmed) { + hasConfirmed = false; + } + }, ), ), ); diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5f88e39fc..40fe18a5e 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -480,5 +480,7 @@ "@bufferDuration": {}, "bufferDurationSubtitle": "How much the player should buffer, in seconds. Requires a restart.", "@bufferDurationSubtitle": {}, - "language": "Language" + "language": "Language", + "confirm": "Confirm", + "showUncensoredLogMessage": "This log contains your login information. Show?" } \ No newline at end of file diff --git a/lib/services/case_insensitive_pattern.dart b/lib/services/case_insensitive_pattern.dart new file mode 100644 index 000000000..ae58093a5 --- /dev/null +++ b/lib/services/case_insensitive_pattern.dart @@ -0,0 +1,19 @@ +/// A pattern for case-insensitive matching. Used when sanitising logs as +/// Chopper logs the base URL in lowercase. +class CaseInsensitivePattern implements Pattern { + late String matcher; + + CaseInsensitivePattern(String matcher) { + this.matcher = matcher.toLowerCase(); + } + + @override + Iterable allMatches(String string, [int start = 0]) { + return matcher.allMatches(string.toLowerCase(), start); + } + + @override + Match? matchAsPrefix(String string, [int start = 0]) { + return matcher.matchAsPrefix(string.toLowerCase(), start); + } +} diff --git a/lib/services/censored_log.dart b/lib/services/censored_log.dart new file mode 100644 index 000000000..3a52ff6be --- /dev/null +++ b/lib/services/censored_log.dart @@ -0,0 +1,33 @@ +import 'package:finamp/services/contains_login.dart'; +import 'package:get_it/get_it.dart'; +import 'package:logging/logging.dart'; + +import 'case_insensitive_pattern.dart'; +import 'finamp_user_helper.dart'; + +extension CensoredMessage on LogRecord { + /// The uncensored log string to be shown to the user + String get logString => + "[$loggerName/${level.name}] $time: $message\n\n${stackTrace.toString()}"; + + String get loginCensoredMessage => containsLogin ? "LOGIN BODY" : message; + + String get censoredMessage { + if (containsLogin) { + return loginCensoredMessage; + } + + final finampUserHelper = GetIt.instance(); + + String workingLogString = logString; + + for (final user in finampUserHelper.finampUsers) { + workingLogString = + logString.replaceAll(CaseInsensitivePattern(user.baseUrl), "BASEURL"); + workingLogString = logString.replaceAll( + CaseInsensitivePattern(user.accessToken), "TOKEN"); + } + + return workingLogString; + } +} diff --git a/lib/services/contains_login.dart b/lib/services/contains_login.dart new file mode 100644 index 000000000..e01a18c4b --- /dev/null +++ b/lib/services/contains_login.dart @@ -0,0 +1,6 @@ +import 'package:logging/logging.dart'; + +extension ContainsLogin on LogRecord { + /// Whether or not the log record contains the user's login details. + bool get containsLogin => message.contains('{"Username'); +} diff --git a/lib/services/finamp_logs_helper.dart b/lib/services/finamp_logs_helper.dart index 96c486ef5..d9f2a6144 100644 --- a/lib/services/finamp_logs_helper.dart +++ b/lib/services/finamp_logs_helper.dart @@ -1,14 +1,12 @@ import 'dart:io'; import 'package:clipboard/clipboard.dart'; -import 'package:get_it/get_it.dart'; +import 'package:finamp/services/censored_log.dart'; import 'package:logging/logging.dart'; import 'package:path_provider/path_provider.dart'; import 'package:share_plus/share_plus.dart'; import 'package:path/path.dart' as path_helper; -import 'finamp_user_helper.dart'; - class FinampLogsHelper { final List logs = []; @@ -26,33 +24,12 @@ class FinampLogsHelper { final logsStringBuffer = StringBuffer(); for (final log in logs) { - logsStringBuffer.writeln(sanitiseLog(log)); + logsStringBuffer.writeln(log.censoredMessage); } return logsStringBuffer.toString(); } - /// Sanitise the given log record (censor base url and tokens) - String sanitiseLog(LogRecord log) { - if (log.message.contains('{"Username')) { - return "LOGIN BODY"; - } - - final finampUserHelper = GetIt.instance(); - - var logString = - "[${log.loggerName}/${log.level.name}] ${log.time}: ${log.message}\n\n${log.stackTrace.toString()}"; - - for (final user in finampUserHelper.finampUsers) { - logString = - logString.replaceAll(CaseInsensitivePattern(user.baseUrl), "BASEURL"); - logString = logString.replaceAll( - CaseInsensitivePattern(user.accessToken), "TOKEN"); - } - - return logString; - } - Future copyLogs() async => await FlutterClipboard.copy(getSanitisedLogs()); @@ -70,23 +47,3 @@ class FinampLogsHelper { await tempFile.delete(); } } - -/// A pattern for case-insensitive matching. Used when sanitising logs as -/// Chopper logs the base URL in lowercase. -class CaseInsensitivePattern implements Pattern { - late String matcher; - - CaseInsensitivePattern(String matcher) { - this.matcher = matcher.toLowerCase(); - } - - @override - Iterable allMatches(String string, [int start = 0]) { - return matcher.allMatches(string.toLowerCase(), start); - } - - @override - Match? matchAsPrefix(String string, [int start = 0]) { - return matcher.matchAsPrefix(string.toLowerCase(), start); - } -} From 77583d0aa6f62c9bb913e9509e7694cbb8b82621 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sun, 11 Jun 2023 21:13:01 +0100 Subject: [PATCH 075/172] Fix baseurl showing in shared logs --- lib/services/censored_log.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/services/censored_log.dart b/lib/services/censored_log.dart index 3a52ff6be..16d106f2f 100644 --- a/lib/services/censored_log.dart +++ b/lib/services/censored_log.dart @@ -22,9 +22,9 @@ extension CensoredMessage on LogRecord { String workingLogString = logString; for (final user in finampUserHelper.finampUsers) { - workingLogString = - logString.replaceAll(CaseInsensitivePattern(user.baseUrl), "BASEURL"); - workingLogString = logString.replaceAll( + workingLogString = workingLogString.replaceAll( + CaseInsensitivePattern(user.baseUrl), "BASEURL"); + workingLogString = workingLogString.replaceAll( CaseInsensitivePattern(user.accessToken), "TOKEN"); } From 33d0a0ec3e698ab92d8c31f867f9478c3ae4c1c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Sch=C3=BCle?= Date: Fri, 16 Jun 2023 16:25:43 +0200 Subject: [PATCH 076/172] try to fix the redownload --- lib/services/downloads_helper.dart | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 2d9791936..9db97d713 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -773,23 +773,25 @@ class DownloadsHelper { /// creating new ones with the same settings. Returns number of songs /// redownloaded Future redownloadFailed() async { - final loadedDownloadTasks = + final failedDownloadTasks = await getDownloadsWithStatus(DownloadTaskStatus.failed); - if (loadedDownloadTasks?.isEmpty ?? true) { + if (failedDownloadTasks?.isEmpty ?? true) { + _downloadsLogger.info("Failed downloads list is empty -> not redownloading anything"); return 0; } - List> items = []; + int redownloadCount = 0; Map> parentItems = {}; List deleteFutures = []; List downloadedSongs = []; - for (DownloadTask downloadTask in loadedDownloadTasks!) { + for (DownloadTask downloadTask in failedDownloadTasks!) { DownloadedSong? downloadedSong = getJellyfinItemFromDownloadId(downloadTask.taskId); if (downloadedSong == null) { + _downloadsLogger.info("Could not get Jellyfin item for failed task"); continue; } @@ -812,7 +814,6 @@ class DownloadsHelper { parentItems[downloadedSong.song.id]! .add(await _jellyfinApiData.getItemById(parent)); - items.add([downloadedSong.song]); } } @@ -837,10 +838,11 @@ class DownloadsHelper { downloadLocation: downloadedSong.downloadLocation!, viewId: downloadedSong.viewId, ); + redownloadCount++; } } - return deleteFutures.length; + return redownloadCount; } Iterable get downloadedItems => _downloadedItemsBox.values; From 8d8eb8729b77197471410b6df30d23f84de0665e Mon Sep 17 00:00:00 2001 From: lyaschuchenko Date: Sat, 29 Apr 2023 07:29:11 +0200 Subject: [PATCH 077/172] Added translation using Weblate (Ukrainian) --- lib/l10n/app_uk.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_uk.arb diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_uk.arb @@ -0,0 +1 @@ +{} From 3e193846793442d4044c3f5248761c2562cb5edf Mon Sep 17 00:00:00 2001 From: lyaschuchenko Date: Sat, 29 Apr 2023 05:56:34 +0000 Subject: [PATCH 078/172] Translated using Weblate (Ukrainian) Currently translated at 99.4% (170 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/uk/ --- lib/l10n/app_uk.arb | 472 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 471 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 0967ef424..4b7d75716 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -1 +1,471 @@ -{} +{ + "audioService": "Служба аудіовідтворення", + "@audioService": {}, + "layoutAndTheme": "Вигляд і тема", + "@layoutAndTheme": {}, + "notAvailableInOfflineMode": "Недоступне в оффлайн режимі", + "@notAvailableInOfflineMode": {}, + "jellyfinUsesAACForTranscoding": "Jellyfin використовує ААС для транскодування", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscodingSubtitle": "Перекодування музичного потоку відбувається зі сторони сервера.", + "@enableTranscodingSubtitle": {}, + "customLocation": "Власна папка", + "@customLocation": {}, + "pathReturnSlashErrorMessage": "Шляхи, які повертають \"/\", не можна використовувати", + "@pathReturnSlashErrorMessage": {}, + "enterLowPriorityStateOnPause": "Перейти у стан низького пріоритету під час паузи", + "@enterLowPriorityStateOnPause": {}, + "shuffleAllSongCount": "Перемішати всі пісні", + "@shuffleAllSongCount": {}, + "shuffleAllSongCountSubtitle": "Кількість пісень для завантаження при використанні кнопки перемішування всіх пісень.", + "@shuffleAllSongCountSubtitle": {}, + "viewTypeSubtitle": "Тип перегляду для музичного екрану", + "@viewTypeSubtitle": {}, + "list": "Список", + "@list": {}, + "landscape": "Альбомний", + "@landscape": {}, + "gridCrossAxisCount": "{value} Підрахунок поперечних осей сітки", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "showTextOnGridView": "Показати текст при перегляді сіткою", + "@showTextOnGridView": {}, + "showCoverAsPlayerBackgroundSubtitle": "Використовувати або ні розмиту обкладинку як фон на екрані програвача.", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "disableGestureSubtitle": "Чи вимикати жести.", + "@disableGestureSubtitle": {}, + "theme": "Тема", + "@theme": {}, + "system": "Системна", + "@system": {}, + "tabs": "Закладки", + "@tabs": {}, + "cancelSleepTimer": "Скасувати таймер сну?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "ТАК", + "@yesButtonLabel": {}, + "noButtonLabel": "НІ", + "@noButtonLabel": {}, + "minutes": "Хвилини", + "@minutes": {}, + "addToPlaylistTitle": "Додати до плейлисту", + "@addToPlaylistTitle": {}, + "removeFromPlaylistTooltip": "Видалити з плейлисту", + "@removeFromPlaylistTooltip": {}, + "createButtonLabel": "СТВОРИТИ", + "@createButtonLabel": {}, + "playlistCreated": "Плейлист створено.", + "@playlistCreated": {}, + "noAlbum": "Альбом невідомий", + "@noAlbum": {}, + "noItem": "Невідомий елемент", + "@noItem": {}, + "noArtist": "Невідомий виконавець", + "@noArtist": {}, + "unknownArtist": "Невідомий виконавець", + "@unknownArtist": {}, + "downloaded": "ЗАВАНТАЖЕНО", + "@downloaded": {}, + "direct": "ПРЯМЕ ВІДТВОРЕННЯ", + "@direct": {}, + "streaming": "ВІДТВОРЮЄТЬСЯ ЗАРАЗ", + "@streaming": {}, + "statusError": "ПОМИЛКОВИЙ СТАТУС", + "@statusError": {}, + "queue": "Черга", + "@queue": {}, + "replaceQueue": "Замінити чергу", + "@replaceQueue": {}, + "instantMix": "Швидкий мікс", + "@instantMix": {}, + "goToAlbum": "До альбому", + "@goToAlbum": {}, + "removeFavourite": "Видалити з вибраного", + "@removeFavourite": {}, + "addedToQueue": "Додати до черги.", + "@addedToQueue": {}, + "queueReplaced": "Черга змінена.", + "@queueReplaced": {}, + "startingInstantMix": "Починається швидкий мікс.", + "@startingInstantMix": {}, + "anErrorHasOccured": "Сталася помилка.", + "@anErrorHasOccured": {}, + "responseError": "{error} Код статусу {statusCode}.", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "removeFromMix": "Видалити з міксу", + "@removeFromMix": {}, + "addToMix": "Додати до міксу", + "@addToMix": {}, + "bufferDurationSubtitle": "Скільки плеєр має буферизувати, у секундах. Вимагає перезавантаження.", + "@bufferDurationSubtitle": {}, + "language": "Мова", + "@language": {}, + "startupError": "Щось пішло не так під час запуску програми. помилка: {error}\n\nБудь ласка, створіть проблему на github.com/UnicornsOnLSD/finamp із знімком екрана цієї сторінки. Якщо проблема не зникне, ви можете очистити дані програми, щоб скинути налаштування програми.", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "serverUrl": "URL серверу", + "@serverUrl": {}, + "couldNotFindLibraries": "Неможливо знайти будь-яку музичну бібліотеку.", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "playlists": "Списки відтворення", + "@playlists": {}, + "album": "Альбомами", + "@album": {}, + "songCount": "{count,plural,=1{{count} Пісня} other{{count Пісні}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dateAdded": "Датою додавання", + "@dateAdded": {}, + "startMixNoSongsArtist": "Затисніть виконавця, щоб додати або видалити його з конструктора міксів перед початком міксування", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "internalExternalIpExplanation": "Якщо ви хочете мати віддалений доступ до свого сервера Jellyfin, вам потрібно використовувати зовнішню IP-адресу.\n\nЯкщо ваш сервер використовує порт HTTP (80/443), вам не потрібно вказувати порт. Ймовірно, це буде необхідно, якщо ваш сервер знаходиться за зворотним проксі.", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "startMix": "Почати перемішування", + "@startMix": {}, + "artist": "Виконавцями", + "@artist": {}, + "startMixNoSongsAlbum": "Затисніть альбом, щоб додати або видалити його з конструктора міксів перед початком міксування", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "sortBy": "Сортування за", + "@sortBy": {}, + "albumArtist": "Виконавцями альбомів", + "@albumArtist": {}, + "communityRating": "Оцінкою спільноти", + "@communityRating": {}, + "playCount": "Кількістю відтворень", + "@playCount": {}, + "premiereDate": "Датою прем'єри", + "@premiereDate": {}, + "downloadErrors": "Завантажити помилки", + "@downloadErrors": {}, + "downloadCount": "{count,plural, =1{{count} Завантажено} other{{count} Завантажень}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "datePlayed": "Датою відтворення", + "@datePlayed": {}, + "name": "Ім'ям", + "@name": {}, + "downloadedMissingImages": "{count,plural, =0{Відсутніх зображень не знайдено} =1{Завантажено {count} відсутніх зображень} other{Завантажено {count} відсутніх зображень}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} виконано", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "errorScreenError": "Під час отримання списку помилок сталася помилка! На цьому етапі вам, ймовірно, слід просто створити проблему на GitHub і видалити дані програми", + "@errorScreenError": {}, + "error": "Помилка", + "@error": {}, + "dlFailed": "{count} не вдалося", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "required": "Обов'язково", + "@required": {}, + "dlEnqueued": "{count} поставлено в чергу", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "failedToGetSongFromDownloadId": "Не вдалося отримати пісню з ідентифікатора завантаження", + "@failedToGetSongFromDownloadId": {}, + "discNumber": "Платівка {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "editPlaylistNameTooltip": "Редагувати назву плейлисту", + "@editPlaylistNameTooltip": {}, + "updateButtonLabel": "ОНОВИТИ", + "@updateButtonLabel": {}, + "dlRunning": "{count} запущено", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrorsTitle": "Завантажити помилки", + "@downloadErrorsTitle": {}, + "editPlaylistNameTitle": "Редагувати назву плейлисту", + "@editPlaylistNameTitle": {}, + "transcoding": "Транскодування", + "@transcoding": {}, + "logsCopied": "Логи скопійовано.", + "@logsCopied": {}, + "downloadedSongsWillNotBeDeleted": "Завантажені пісні не будуть видалені", + "@downloadedSongsWillNotBeDeleted": {}, + "shareLogs": "Поділитися логами", + "@shareLogs": {}, + "downloadLocations": "Місце завантаження", + "@downloadLocations": {}, + "addButtonLabel": "ДОДАТИ", + "@addButtonLabel": {}, + "applicationLegalese": "Ліцензовано за Mozilla Public License 2.0. Вихідний код доступний за адресою:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "bitrate": "Бітрейт", + "@bitrate": {}, + "logOut": "Вийти", + "@logOut": {}, + "enableTranscoding": "Ввімкнути транскодування", + "@enableTranscoding": {}, + "areYouSure": "Ви впевнені ?", + "@areYouSure": {}, + "dark": "Темна", + "@dark": {}, + "appDirectory": "Тека застосунку", + "@appDirectory": {}, + "bitrateSubtitle": "Вищий бітрейт забезпечує вищу якість звуку за рахунок вищої пропускної здатності.", + "@bitrateSubtitle": {}, + "addDownloadLocation": "Додати теку завантаження", + "@addDownloadLocation": {}, + "unknownError": "Невідома помилка", + "@unknownError": {}, + "selectDirectory": "Вибрати теку", + "@selectDirectory": {}, + "directoryMustBeEmpty": "Тека повинна бути порожньою", + "@directoryMustBeEmpty": {}, + "enterLowPriorityStateOnPauseSubtitle": "Дозволяє змахнути сповіщення під час паузи. Також дозволяє Android вимикати службу під час паузи.", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "viewType": "Тип перегляду", + "@viewType": {}, + "gridCrossAxisCountSubtitle": "Кількість плиток сітки для використання в рядку, коли {value}.", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "customLocationsBuggy": "Користувацька текс має багато проблем через проблеми з дозволами. Я думаю, як це виправити, але поки що я б не рекомендував ними користуватися.", + "@customLocationsBuggy": {}, + "grid": "Таблиця", + "@grid": {}, + "portrait": "Портретний", + "@portrait": {}, + "showTextOnGridViewSubtitle": "Відображати чи ні текст (назву, виконавця тощо) на музичному екрані сітки.", + "@showTextOnGridViewSubtitle": {}, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Чи показувати виконавців пісень на екрані альбому, якщо вони не відрізняються від виконавців альбому.", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "disableGesture": "Вимкнути жести", + "@disableGesture": {}, + "showCoverAsPlayerBackground": "Показати розмиту обкладинку як фон гравця", + "@showCoverAsPlayerBackground": {}, + "light": "Світла", + "@light": {}, + "hideSongArtistsIfSameAsAlbumArtists": "Приховати виконавців пісень, якщо вони збігаються з виконавцями альбомів", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "setSleepTimer": "Встановити таймер сну", + "@setSleepTimer": {}, + "sleepTimerTooltip": "Таймер сну", + "@sleepTimerTooltip": {}, + "invalidNumber": "Некоректне число", + "@invalidNumber": {}, + "addToPlaylistTooltip": "Додати до плейлисту", + "@addToPlaylistTooltip": {}, + "transcode": "ПЕРЕКОДОВУЄТЬСЯ", + "@transcode": {}, + "removeFromPlaylistTitle": "Видалити з плейлисту", + "@removeFromPlaylistTitle": {}, + "newPlaylist": "Новий плейлист", + "@newPlaylist": {}, + "addToQueue": "Додати до черги", + "@addToQueue": {}, + "addFavourite": "Додати в вибране", + "@addFavourite": {}, + "responseError401": "{error} Код статусу {statusCode}. Можливо, це означає, що ви використали неправильне ім’я користувача/пароль або ваш клієнт не ввійшов в систему.", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } + }, + "removedFromPlaylist": "Видалено з плейлисту.", + "@removedFromPlaylist": {}, + "redownloadedItems": "{count,plural, =0{Повторне завантаження не потрібне.} =1{Повторно завантажено {count} елемент} other{Повторно завантажено {count} елементів}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bufferDuration": "Тривалість буферу", + "@bufferDuration": {}, + "downloadedItemsCount": "{count,plural,=1{{count} елементів} other{{count} елементів}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedImagesCount": "{count,plural,=1{{count} Зображення} other{{count} зображень}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "offlineMode": "Оффлайн режим", + "@offlineMode": {}, + "sortOrder": "Порядок сортування", + "@sortOrder": {}, + "emptyServerUrl": "URL серверу не може бути порожнім", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "urlStartWithHttps": "URL має починатися з http:// або http://", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "urlTrailingSlash": "URL-адреса не повинна містити косу риску в кінці", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "username": "Логін", + "@username": {}, + "password": "Пароль", + "@password": {}, + "logs": "Логи", + "@logs": {}, + "next": "Далі", + "@next": {}, + "selectMusicLibraries": "Виберіть музичну бібліотеку", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "unknownName": "Невідома назва", + "@unknownName": {}, + "songs": "Пісні", + "@songs": {}, + "albums": "Альбоми", + "@albums": {}, + "artists": "Виконавці", + "@artists": {}, + "genres": "Жанри", + "@genres": {}, + "music": "Музика", + "@music": {}, + "clear": "Очистити", + "@clear": {}, + "favourites": "Вибране", + "@favourites": {}, + "shuffleAll": "Перемішати все", + "@shuffleAll": {}, + "finamp": "Finamp", + "@finamp": {}, + "downloads": "Завантаження", + "@downloads": {}, + "settings": "Налаштування", + "@settings": {}, + "budget": "Бюджет", + "@budget": {}, + "criticRating": "Оцінкою критиків", + "@criticRating": {}, + "productionYear": "Роком виходу", + "@productionYear": {}, + "random": "Випадково", + "@random": {}, + "revenue": "Дохід", + "@revenue": {}, + "runtime": "Тривалістю", + "@runtime": {}, + "downloadMissingImages": "Завантажити відсутні зображення", + "@downloadMissingImages": {}, + "noErrors": "Немає помилок!", + "@noErrors": {}, + "playButtonLabel": "ГРАТИ", + "@playButtonLabel": {}, + "shuffleButtonLabel": "ПЕРЕМІШАТИ", + "@shuffleButtonLabel": {}, + "playlistNameUpdated": "Ім'я плейлисту змінено.", + "@playlistNameUpdated": {}, + "favourite": "Улюблене", + "@favourite": {}, + "downloadsDeleted": "Завантаження видалено.", + "@downloadsDeleted": {}, + "addDownloads": "Додати завантаження", + "@addDownloads": {}, + "location": "Збережено в", + "@location": {}, + "downloadsAdded": "Завантаження додано.", + "@downloadsAdded": {}, + "message": "Повідомлення", + "@message": {}, + "stackTrace": "Трасування стека", + "@stackTrace": {} +} From cb2d404ed739ac64fbcd0bc629f71f185da2a1a4 Mon Sep 17 00:00:00 2001 From: Eryk Michalak Date: Tue, 2 May 2023 19:39:37 +0000 Subject: [PATCH 079/172] Translated using Weblate (Polish) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/pl/ --- lib/l10n/app_pl.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 2045d2f89..528356db4 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -479,5 +479,7 @@ "removeFromPlaylistTitle": "Usuń z Listy odtwarzania", "@removeFromPlaylistTitle": {}, "removedFromPlaylist": "Usunięto z listy odtwarzania.", - "@removedFromPlaylist": {} + "@removedFromPlaylist": {}, + "language": "Język", + "@language": {} } From d463b9e8c060716e2a485e79af6283caddf9ca91 Mon Sep 17 00:00:00 2001 From: Paul Richter Date: Tue, 2 May 2023 01:15:54 +0000 Subject: [PATCH 080/172] Translated using Weblate (Japanese) Currently translated at 91.8% (157 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ja/ --- lib/l10n/app_ja.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index da081a93c..7dfc4aaa9 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -408,5 +408,13 @@ } }, "bufferDurationSubtitle": "プレイヤーがバッファする容量(秒単位)。リスタートが必要です。", - "@bufferDurationSubtitle": {} + "@bufferDurationSubtitle": {}, + "runtime": "再生時間", + "@runtime": {}, + "name": "曲名", + "@name": {}, + "bufferDuration": "バッファ容量", + "@bufferDuration": {}, + "language": "言語", + "@language": {} } From 2133892117828ed5feddd57472c3bf6acfa8e32c Mon Sep 17 00:00:00 2001 From: jakka Date: Wed, 3 May 2023 16:16:05 +0000 Subject: [PATCH 081/172] Translated using Weblate (Russian) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ru/ --- lib/l10n/app_ru.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 12a6051d7..2b3d4b685 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -479,5 +479,7 @@ "removeFromPlaylistTooltip": "Удалить из плейлиста", "@removeFromPlaylistTooltip": {}, "removeFromPlaylistTitle": "Удалить из Плейлиста", - "@removeFromPlaylistTitle": {} + "@removeFromPlaylistTitle": {}, + "language": "Язык", + "@language": {} } From 6258f3e5bcb8ec2244639e89ec8efd36177634ea Mon Sep 17 00:00:00 2001 From: Kenneth Schack Banner Date: Mon, 1 May 2023 14:06:48 +0000 Subject: [PATCH 082/172] Translated using Weblate (Danish) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/da/ --- lib/l10n/app_da.arb | 136 ++++++++++++++++++++++---------------------- 1 file changed, 69 insertions(+), 67 deletions(-) diff --git a/lib/l10n/app_da.arb b/lib/l10n/app_da.arb index 038e3dd2d..53337e257 100644 --- a/lib/l10n/app_da.arb +++ b/lib/l10n/app_da.arb @@ -1,27 +1,27 @@ { "serverUrl": "Server URL", "@serverUrl": {}, - "emptyServerUrl": "Server URLen kan ikke være tom", + "emptyServerUrl": "Server URL kan ikke være tom", "@emptyServerUrl": { "description": "Error message that shows when the user submits a login without a server URL" }, - "urlStartWithHttps": "En URL skal starte med http:// eller https://", + "urlStartWithHttps": "URL skal starte med http:// eller https://", "@urlStartWithHttps": { "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" }, - "urlTrailingSlash": "En URL må ikke slutte med en stråstreg", + "urlTrailingSlash": "URL må ikke indeholde en efterfølgnede stråstreg", "@urlTrailingSlash": { "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" }, "username": "Brugernavn", "@username": {}, - "password": "Password", + "password": "Adgangskode", "@password": {}, "logs": "Log", "@logs": {}, "next": "Næste", "@next": {}, - "selectMusicLibraries": "Vælg musikbiblioteker", + "selectMusicLibraries": "Vælg musik biblioteker", "@selectMusicLibraries": { "description": "App bar title for library select screen" }, @@ -39,7 +39,7 @@ "@startMix": {}, "music": "Musik", "@music": {}, - "clear": "Fjern", + "clear": "Ryd", "@clear": {}, "favourites": "Favoritter", "@favourites": {}, @@ -47,7 +47,7 @@ "@shuffleAll": {}, "finamp": "Finamp", "@finamp": {}, - "downloads": "Downloads", + "downloads": "Overførelser", "@downloads": {}, "settings": "Indstillinger", "@settings": {}, @@ -63,17 +63,17 @@ "@albumArtist": {}, "artist": "Kunstner", "@artist": {}, - "criticRating": "Kritikerbedømmelse", + "criticRating": "Kritiker bedømmelse", "@criticRating": {}, - "communityRating": "Brugerbedømmelse", + "communityRating": "Fællesskab bedømmelse", "@communityRating": {}, "dateAdded": "Tilføjet dato", "@dateAdded": {}, - "datePlayed": "Spillet dato", + "datePlayed": "Afspillet dato", "@datePlayed": {}, - "playCount": "Antal afspillinger", + "playCount": "Antal afspilninger", "@playCount": {}, - "premiereDate": "Premiere dato", + "premiereDate": "Udgivelsesdato", "@premiereDate": {}, "productionYear": "Produktionsår", "@productionYear": {}, @@ -83,11 +83,11 @@ "@random": {}, "revenue": "Omsætning", "@revenue": {}, - "budget": "ignore-same Budget", + "budget": "Budget", "@budget": {}, - "downloadMissingImages": "Download manglende billeder", + "downloadMissingImages": "Overførelse mangler billeder", "@downloadMissingImages": {}, - "downloadErrors": "Downloadfejl", + "downloadErrors": "Overførelsesfejl", "@downloadErrors": {}, "downloadedItemsCount": "{count,plural,=1{{count} emne} other{{count} emner}}", "@downloadedItemsCount": { @@ -105,7 +105,7 @@ } } }, - "dlComplete": "{count} færdig", + "dlComplete": "{count} færdige", "@dlComplete": { "placeholders": { "count": { @@ -113,7 +113,7 @@ } } }, - "dlFailed": "{count} fejlet", + "dlFailed": "{count} fejlede", "@dlFailed": { "placeholders": { "count": { @@ -131,7 +131,7 @@ }, "noErrors": "Ingen fejl!", "@noErrors": {}, - "failedToGetSongFromDownloadId": "Kunne ikke hente sang fra download ID", + "failedToGetSongFromDownloadId": "Fejlede under hentning af sang fra overførelse ID", "@failedToGetSongFromDownloadId": {}, "error": "Fejl", "@error": {}, @@ -145,57 +145,57 @@ }, "playButtonLabel": "AFSPIL", "@playButtonLabel": {}, - "editPlaylistNameTooltip": "Ret playlistens navn", + "editPlaylistNameTooltip": "Ændre afspilningslistens navn", "@editPlaylistNameTooltip": {}, - "editPlaylistNameTitle": "Ret Playlistens Navn", + "editPlaylistNameTitle": "Ændre afspilningslistens navn", "@editPlaylistNameTitle": {}, - "required": "Obligatorisk", + "required": "Påkrævet", "@required": {}, "updateButtonLabel": "OPDATÉR", "@updateButtonLabel": {}, - "playlistNameUpdated": "Playlistenavn opdateret.", + "playlistNameUpdated": "Afspilningslistens navn er opdateret.", "@playlistNameUpdated": {}, "favourite": "Favorit", "@favourite": {}, - "addDownloads": "Tilføj downloads", + "addDownloads": "Tilføj overførelser", "@addDownloads": {}, "location": "Placering", "@location": {}, - "downloadsAdded": "Downloads tilføjet.", + "downloadsAdded": "Overførelse tilføjet.", "@downloadsAdded": {}, - "shareLogs": "Del logs", + "shareLogs": "Del log", "@shareLogs": {}, - "stackTrace": "Stack trace", + "stackTrace": "Bunke spor", "@stackTrace": {}, - "transcoding": "Transkodning", + "transcoding": "Omkoder", "@transcoding": {}, - "downloadLocations": "Download placeringer", + "downloadLocations": "Overførelse placering", "@downloadLocations": {}, - "audioService": "Lydservice", + "audioService": "Lyd tjeneste", "@audioService": {}, - "layoutAndTheme": "Layout og tema", + "layoutAndTheme": "Udseende & tema", "@layoutAndTheme": {}, "notAvailableInOfflineMode": "Ikke tilgængelig i offline tilstand", "@notAvailableInOfflineMode": {}, - "logOut": "Log out", + "logOut": "Log af", "@logOut": {}, - "downloadedSongsWillNotBeDeleted": "Downloadede sange vil ikke blive slettet", + "downloadedSongsWillNotBeDeleted": "Overførte sange vil ikke blive slettet", "@downloadedSongsWillNotBeDeleted": {}, "areYouSure": "Er du sikker?", "@areYouSure": {}, - "jellyfinUsesAACForTranscoding": "Jellyfin bruger AAC til transkodning", + "jellyfinUsesAACForTranscoding": "Jellyfin bruger AAC under omkodning", "@jellyfinUsesAACForTranscoding": {}, - "enableTranscoding": "Aktivér transkodning", + "enableTranscoding": "Aktivér omkodning", "@enableTranscoding": {}, - "enableTranscodingSubtitle": "Transkodede musikstrømme på serveren.", + "enableTranscodingSubtitle": "Omkoder musikken på serveren før det bliver sendes.", "@enableTranscodingSubtitle": {}, - "bitrateSubtitle": "En højere bithastighed giver bedre musikkvalitet men bruger mere båndbredde.", + "bitrateSubtitle": "En højere bithastighed giver højere lydkvalitet men bruger mere båndbredde.", "@bitrateSubtitle": {}, - "customLocation": "Valgt placering", + "customLocation": "Vælg placering", "@customLocation": {}, "appDirectory": "App katalog", "@appDirectory": {}, - "addDownloadLocation": "Tilføj download placering", + "addDownloadLocation": "Tilføj overførelses placering", "@addDownloadLocation": {}, "selectDirectory": "Vælg katalog", "@selectDirectory": {}, @@ -203,25 +203,25 @@ "@unknownError": {}, "directoryMustBeEmpty": "Katalog skal være tomt", "@directoryMustBeEmpty": {}, - "enterLowPriorityStateOnPause": "Indtast lav prioritet tilstanden for pause", + "enterLowPriorityStateOnPause": "Gå i lav prioritet tilstand når musik afspilning er på pause", "@enterLowPriorityStateOnPause": {}, - "enterLowPriorityStateOnPauseSubtitle": "Tillader notifikationer at bliver strøget i pausetilstand. Tillader også Android at standse services i pausetilstand.", + "enterLowPriorityStateOnPauseSubtitle": "Tillader notifikationer bliver strøget væk, når afspilning er sat på pause. Dette tillader også Android, at afbryde servicen når den er på pause.", "@enterLowPriorityStateOnPauseSubtitle": {}, - "shuffleAllSongCount": "Bland alle sange antal", + "shuffleAllSongCount": "Bland alle sange tæl", "@shuffleAllSongCount": {}, - "viewType": "Visning type", + "viewType": "Visning tilstand", "@viewType": {}, - "viewTypeSubtitle": "Visning type for musikskærmen", + "viewTypeSubtitle": "Visningstype for musik skærmen", "@viewTypeSubtitle": {}, "list": "Liste", "@list": {}, - "grid": "Tabel", + "grid": "Gitter", "@grid": {}, "portrait": "Portræt", "@portrait": {}, "landscape": "Landskab", "@landscape": {}, - "gridCrossAxisCountSubtitle": "Antal tabelfelter som skal anvendes per række i {value} visning.", + "gridCrossAxisCountSubtitle": "Antal gitrebrikker som bruges per række {value}.", "@gridCrossAxisCountSubtitle": { "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", "placeholders": { @@ -231,11 +231,11 @@ } } }, - "showTextOnGridView": "Vis tekst i tabelvisning", + "showTextOnGridView": "Vis tekst i gitter visning", "@showTextOnGridView": {}, - "showTextOnGridViewSubtitle": "Vælg visning af tekst (titel, kunstner osv.) i tabellen på musikskærmen.", + "showTextOnGridViewSubtitle": "Vælg om tekst skal vises (titel, kunstner osv.) på gitteret musik skærm.", "@showTextOnGridViewSubtitle": {}, - "showCoverAsPlayerBackground": "Vis sløret album som baggrund", + "showCoverAsPlayerBackground": "Vis sløret cover som afspiller baggrund", "@showCoverAsPlayerBackground": {}, "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Vælg om sangens kunstner skal vises på albummet, hvis den ikke er forskellig fra albummets kunstner.", "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, @@ -309,7 +309,7 @@ "@removedFromPlaylist": {}, "startingInstantMix": "Starter umiddelbar blanding.", "@startingInstantMix": {}, - "downloadCount": "{count,plural, =1{{count} download} other{{count} downloads}}", + "downloadCount": "{count,plural, =1{{count} overført} other{{count} overførelser}}", "@downloadCount": { "placeholders": { "count": { @@ -333,7 +333,7 @@ "@bufferDuration": {}, "bufferDurationSubtitle": "Afspillerens bufferlængde i sekunder. Kræver genstart.", "@bufferDurationSubtitle": {}, - "startupError": "Noget gik galt med opstarten af appen. Fejlen var {error}\n\nVenligt opret en sag på github.com/UnicornsOnLSD/finamp med et screenshot af denne side. Hvis problemet vedvarer, så reset appen ved at fjerne app data.", + "startupError": "Noget gik galt med opstarten af appen. Fejlen var {error}\n\nVenligst opret en sag på github.com/UnicornsOnLSD/finamp med et skærmbillede af denne side. Hvis dette problem forbliver, så kan du fjerne dine app data for at nulstille appen.", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -343,15 +343,15 @@ } } }, - "internalExternalIpExplanation": "Hvis du ønsker at skabe forbindelse til din Jellyfin server udefra, skal du bruge din externe IP-adresse.\n\nHvis din server er på HTTP porten (80/443) behøver du ikke angive en port. Dette er som regel tilfældet, hvis din server er bag en reverse proxy.", + "internalExternalIpExplanation": "Hvis du vil være i stand til at tilgå din Jellyfin server udefra, skal du bruge din eksterne IP.\n\nHvis din server er på HTTP porten (80/443), behøver du ikke angive en port. Dette er som regel tilfældet, hvis din server er bag en reverse proxy.", "@internalExternalIpExplanation": { "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." }, "unknownName": "Ukendt navn", "@unknownName": {}, - "albums": "Album", + "albums": "Albummer", "@albums": {}, - "playlists": "Playlister", + "playlists": "Afspilningslister", "@playlists": {}, "startMixNoSongsArtist": "Tryk (lang tid) på en kunstner for at tilføje eller fjerne den fra blandingen før du starter en blanding", "@startMixNoSongsArtist": { @@ -361,9 +361,9 @@ "@startMixNoSongsAlbum": { "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" }, - "runtime": "Runtime", + "runtime": "Spilletid", "@runtime": {}, - "downloadedMissingImages": "{count,plural, =0{Ingen manglende billeder} =1{Downloadet {count} manglende billede} other{Downloadet {count} manglende billeder}}", + "downloadedMissingImages": "{count,plural, =0{Ingen manglende billeder blev fundet} =1{Overførte {count} manglende billeder} other{Overførte {count} manglende billeder}}", "@downloadedMissingImages": { "description": "Message that shows when the user downloads missing images", "placeholders": { @@ -372,7 +372,7 @@ } } }, - "dlRunning": "{count} kører", + "dlRunning": "{count} er igang", "@dlRunning": { "placeholders": { "count": { @@ -380,11 +380,11 @@ } } }, - "downloadErrorsTitle": "Download fejl", + "downloadErrorsTitle": "Overførelsesfejl", "@downloadErrorsTitle": {}, "shuffleButtonLabel": "BLAND", "@shuffleButtonLabel": {}, - "errorScreenError": "En fejl skete men listen med fejl blev hentet! På dette tidspunkt er det nok bedst at oprette et issue på Github og slette alle appens data", + "errorScreenError": "Der opstod en fejl under hentningen af listen med fejl! På dette tidspunkt, bedes du oprette en fejl på Github og slette appens data", "@errorScreenError": {}, "songCount": "{count,plural,=1{{count} Sang} other{{count} Sange}}", "@songCount": { @@ -394,31 +394,31 @@ } } }, - "downloadsDeleted": "Downloads slettet.", + "downloadsDeleted": "Overførelser slettet.", "@downloadsDeleted": {}, "addButtonLabel": "TILFØJ", "@addButtonLabel": {}, "bitrate": "Bithastighed", "@bitrate": {}, - "logsCopied": "Logs kopieret.", + "logsCopied": "Log kopieret.", "@logsCopied": {}, "message": "Besked", "@message": {}, - "applicationLegalese": "Licenseret med Mozilla Public License 2.0. Kildeteksten er tilgængelig her:\n\ngithub.com/jmshrv/finamp", + "applicationLegalese": "Licenseret med Mozilla Public License 2.0. Kildeteksten er tilgængelig på:\n\ngithub.com/jmshrv/finamp", "@applicationLegalese": {}, - "pathReturnSlashErrorMessage": "Stinavne, der returnerer \"/\", kan ikke anvendes", + "pathReturnSlashErrorMessage": "Stier, der returnerer \"/\", kan ikke anvendes", "@pathReturnSlashErrorMessage": {}, - "customLocationsBuggy": "Valgfrie placeringer giver ofte anledning til fejl grundet problemer med rettigheder. Jeg prøver at finde måder at løse dette på, men i øjeblikket anbefaler jeg ikke, at de anvendes.", + "customLocationsBuggy": "Valgfrie placeringer er ekstremt fejlramte grundet manglende rettigheder. Jeg prøver at finde måder at løse dette, men for nu anbefaler jeg ikke, at de anvendes.", "@customLocationsBuggy": {}, "noArtist": "Ingen kunstner", "@noArtist": {}, "unknownArtist": "Ukendt kunstner", "@unknownArtist": {}, - "shuffleAllSongCountSubtitle": "Antal sange som skal indlæses, når bland alle sange knappen trykkes.", + "shuffleAllSongCountSubtitle": "Mængde af sange der skal indlæses, når bland alle sange knappen bruges.", "@shuffleAllSongCountSubtitle": {}, "hideSongArtistsIfSameAsAlbumArtists": "Skjul sangens kunstner, hvis det er den samme som albummets kunstner", "@hideSongArtistsIfSameAsAlbumArtists": {}, - "gridCrossAxisCount": "{value} kolonneantal", + "gridCrossAxisCount": "{value} Gitter tværakse antal", "@gridCrossAxisCount": { "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", "placeholders": { @@ -430,7 +430,7 @@ }, "setSleepTimer": "Sæt sleep timer", "@setSleepTimer": {}, - "showCoverAsPlayerBackgroundSubtitle": "Vælg om albummets forside skal vises sløret på skærmen under afspilning.", + "showCoverAsPlayerBackgroundSubtitle": "Vælg om der skal bruges sløret cover kunst som vises som baggrund på afspiller skærmen.", "@showCoverAsPlayerBackgroundSubtitle": {}, "system": "System", "@system": {}, @@ -479,5 +479,7 @@ "example": "1 image" } } - } + }, + "language": "Sprog", + "@language": {} } From 7a7f60e52966496463e77bbdc7269e04bd49a6a9 Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Tue, 2 May 2023 02:52:40 +0000 Subject: [PATCH 083/172] Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hant_HK/ --- lib/l10n/app_zh_Hant_HK.arb | 54 ++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/lib/l10n/app_zh_Hant_HK.arb b/lib/l10n/app_zh_Hant_HK.arb index e7a34815b..31849d329 100644 --- a/lib/l10n/app_zh_Hant_HK.arb +++ b/lib/l10n/app_zh_Hant_HK.arb @@ -45,7 +45,7 @@ "@settings": {}, "offlineMode": "離線模式", "@offlineMode": {}, - "sortBy": "排序方法", + "sortBy": "排序方式", "@sortBy": {}, "sortOrder": "順序", "@sortOrder": {}, @@ -65,7 +65,7 @@ "@datePlayed": {}, "playCount": "播放次數", "@playCount": {}, - "premiereDate": "首次播放日期", + "premiereDate": "推出日期", "@premiereDate": {}, "productionYear": "推出年份", "@productionYear": {}, @@ -81,7 +81,7 @@ "@budget": {}, "revenue": "收入", "@revenue": {}, - "downloadCount": "{count,plural, =1{{count} 個下載} other{{count} 個下載}}", + "downloadCount": "{count,plural, =1{{count}個下載} other{{count}個下載}}", "@downloadCount": { "placeholders": { "count": { @@ -89,7 +89,7 @@ } } }, - "downloadedItemsCount": "{count,plural,=1{{count} 個項目} other{{count} 個項目}}", + "downloadedItemsCount": "{count,plural,=1{{count}個項目} other{{count}個項目}}", "@downloadedItemsCount": { "placeholders": { "count": { @@ -143,7 +143,7 @@ "@playButtonLabel": {}, "shuffleButtonLabel": "隨機播放", "@shuffleButtonLabel": {}, - "songCount": "{count,plural,=1{{count} 首歌曲} other{{count} 首歌曲}}", + "songCount": "{count,plural,=1{{count}首歌曲} other{{count}首歌曲}}", "@songCount": { "placeholders": { "count": { @@ -179,13 +179,13 @@ "@downloadLocations": {}, "audioService": "播放設定", "@audioService": {}, - "layoutAndTheme": "版面設計及主題", + "layoutAndTheme": "顯示及主題", "@layoutAndTheme": {}, "notAvailableInOfflineMode": "不能在離線模式下使用", "@notAvailableInOfflineMode": {}, "logOut": "登出", "@logOut": {}, - "areYouSure": "你確定嗎?", + "areYouSure": "您確定嗎?", "@areYouSure": {}, "jellyfinUsesAACForTranscoding": "Jellyfin 使用 ACC 進行轉碼", "@jellyfinUsesAACForTranscoding": {}, @@ -209,9 +209,9 @@ "@enterLowPriorityStateOnPause": {}, "enterLowPriorityStateOnPauseSubtitle": "在停止播放時,允許「通知」能被「滑動移除」及關閉應用程式(適用於 Android 裝置)。", "@enterLowPriorityStateOnPauseSubtitle": {}, - "shuffleAllSongCount": "隨機播放歌曲數量", + "shuffleAllSongCount": "隨機播放上限", "@shuffleAllSongCount": {}, - "shuffleAllSongCountSubtitle": "當使用隨機播放所有歌曲功能時,播放歌曲的數量上限。", + "shuffleAllSongCountSubtitle": "使用「隨機播放全部」時,播放歌曲的數量上限。", "@shuffleAllSongCountSubtitle": {}, "message": "訊息", "@message": {}, @@ -219,7 +219,7 @@ "@pathReturnSlashErrorMessage": {}, "portrait": "垂直", "@portrait": {}, - "gridCrossAxisCountSubtitle": "當電話使用{value}顯示模式時,每行的方格(歌曲)數量。", + "gridCrossAxisCountSubtitle": "使用{value}顯示模式時,每行的方格(歌曲、歌手等)數量。", "@gridCrossAxisCountSubtitle": { "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", "placeholders": { @@ -231,15 +231,15 @@ }, "showTextOnGridView": "在方格內顯示文字", "@showTextOnGridView": {}, - "showTextOnGridViewSubtitle": "是否在使用格狀題示方式時,在方格內顯示文字(名稱、歌手等)。", + "showTextOnGridViewSubtitle": "使用格狀顯示方式時,是否在方格內顯示歌曲資訊(名稱、歌手等)。", "@showTextOnGridViewSubtitle": {}, - "showCoverAsPlayerBackground": "顯示模糊化的封面作為播放器的背景", + "showCoverAsPlayerBackground": "模糊化封面作為播放器的背景", "@showCoverAsPlayerBackground": {}, "showCoverAsPlayerBackgroundSubtitle": "是否以模糊化的專輯封面圖片作為應用程式內的播放頁面的背景。", "@showCoverAsPlayerBackgroundSubtitle": {}, - "hideSongArtistsIfSameAsAlbumArtists": "隱藏與專輯歌手相同的歌曲歌手名稱", + "hideSongArtistsIfSameAsAlbumArtists": "隱藏與專輯歌手同名的歌手名稱", "@hideSongArtistsIfSameAsAlbumArtists": {}, - "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "是否在當專輯的歌手名稱與專輯內的歌曲的歌手名稱相同時,隱藏歌曲的歌手名稱。", + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "當專輯的歌手與專輯歌曲的歌手名稱相同時,是否隱藏歌曲的歌手名稱。", "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, "disableGesture": "禁用「手勢」功能", "@disableGesture": {}, @@ -267,13 +267,13 @@ "@invalidNumber": {}, "sleepTimerTooltip": "睡眠定時器", "@sleepTimerTooltip": {}, - "addToPlaylistTooltip": "新增歌曲至播放清單", + "addToPlaylistTooltip": "將歌曲加入至播放清單", "@addToPlaylistTooltip": {}, "removeFromPlaylistTooltip": "從播放清單中移除歌曲", "@removeFromPlaylistTooltip": {}, "removeFromPlaylistTitle": "從播放清單中移除", "@removeFromPlaylistTitle": {}, - "newPlaylist": "新的播放清單", + "newPlaylist": "建立播放清單", "@newPlaylist": {}, "createButtonLabel": "建立", "@createButtonLabel": {}, @@ -324,7 +324,7 @@ } } }, - "removeFromMix": "從混音中刪除", + "removeFromMix": "從混音中移除", "@removeFromMix": {}, "addToMix": "加入至混音", "@addToMix": {}, @@ -338,9 +338,9 @@ }, "bufferDuration": "緩衝時長", "@bufferDuration": {}, - "bufferDurationSubtitle": "播放器可以預先載入多少的音訊數據(秒)。需重啟以套用設定。", + "bufferDurationSubtitle": "播放器可以預先載入多少的音訊數據(秒)。重啟以套用設定。", "@bufferDurationSubtitle": {}, - "startupError": "應用程式啟動時出錯({error})\n\n你可以在 github.com/UnicornsOnLSD/finamp 回報有關問題並附上截圖。如果問題持續,你可以嘗試清除應用程式的資料。", + "startupError": "應用程式啟動時出錯({error})\n\n您可以在 github.com/UnicornsOnLSD/finamp 回報有關問題並附上截圖。如果問題持續,您可以嘗試清除應用程式的資料。", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -350,7 +350,7 @@ } } }, - "downloadedMissingImages": "{count,plural, =0{沒有缺失的圖片} =1{已下載{count} 張圖片} other{已下載{count} 張圖片}}", + "downloadedMissingImages": "{count,plural, =0{沒有缺失的圖片} =1{已下載{count}張圖片} other{已下載{count}張圖片}}", "@downloadedMissingImages": { "description": "Message that shows when the user downloads missing images", "placeholders": { @@ -363,11 +363,11 @@ "@selectMusicLibraries": { "description": "App bar title for library select screen" }, - "couldNotFindLibraries": "找不到任何媒體庫。", + "couldNotFindLibraries": "沒有可用的媒體庫。", "@couldNotFindLibraries": { "description": "Error message when the user does not have any libraries" }, - "internalExternalIpExplanation": "如果你需要在局部區域網絡(LAN)以外的地方連接 Jellyfin,請使用伺服器的區域網絡(WAN)IP。\n\n如果目標伺服器使用的連接埠(port)是 HTTP port(80/433),你毋須填寫連接埠。", + "internalExternalIpExplanation": "如果您需要在局部區域網絡(LAN)以外的地方連接 Jellyfin,請使用伺服器的區域網絡(WAN)IP。\n\n如果目標伺服器使用的連接埠(port)是 HTTP port(80/433),則毋須填寫連接埠。", "@internalExternalIpExplanation": { "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." }, @@ -409,7 +409,7 @@ } } }, - "downloadedImagesCount": "{count,plural,=1{{count} 張圖片} other{{count} 張圖片}}", + "downloadedImagesCount": "{count,plural,=1{{count}張圖片} other{{count}張圖片}}", "@downloadedImagesCount": { "placeholders": { "count": { @@ -425,7 +425,7 @@ "@required": {}, "addDownloads": "添加至下載", "@addDownloads": {}, - "downloadsAdded": "已新增至下載。", + "downloadsAdded": "已添加至下載。", "@downloadsAdded": {}, "downloadedSongsWillNotBeDeleted": "已下載的歌曲並不會被刪除", "@downloadedSongsWillNotBeDeleted": {}, @@ -443,7 +443,7 @@ "@list": {}, "landscape": "水平", "@landscape": {}, - "gridCrossAxisCount": "{value}顯示模式的方格的數量", + "gridCrossAxisCount": "{value}顯示模式的方格數量", "@gridCrossAxisCount": { "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", "placeholders": { @@ -455,7 +455,7 @@ }, "light": "淺色", "@light": {}, - "addToPlaylistTitle": "新增至播放清單", + "addToPlaylistTitle": "加入至播放清單", "@addToPlaylistTitle": {}, "noAlbum": "沒有任何專輯", "@noAlbum": {}, @@ -467,7 +467,7 @@ "@downloaded": {}, "transcode": "轉碼", "@transcode": {}, - "responseError401": "{error}(代碼:{statusCode})。這很大機會由於用戶名稱/密碼輸入錯誤或你已被登出。", + "responseError401": "{error}(代碼:{statusCode})。此錯誤有可能因為用戶名稱/密碼輸入錯誤或您已被登出而導致。", "@responseError401": { "placeholders": { "error": { From 3cfaf82edb08b615e995fecce20ea2098d03fb21 Mon Sep 17 00:00:00 2001 From: Kenneth Schack Banner Date: Fri, 5 May 2023 11:27:12 +0000 Subject: [PATCH 084/172] Translated using Weblate (Danish) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/da/ --- lib/l10n/app_da.arb | 74 ++++++++++++++++++++++----------------------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/lib/l10n/app_da.arb b/lib/l10n/app_da.arb index 53337e257..d11f76a71 100644 --- a/lib/l10n/app_da.arb +++ b/lib/l10n/app_da.arb @@ -33,7 +33,7 @@ "@songs": {}, "artists": "Kunstnere", "@artists": {}, - "genres": "Genrer", + "genres": "Genre", "@genres": {}, "startMix": "Start blanding", "@startMix": {}, @@ -53,7 +53,7 @@ "@settings": {}, "offlineMode": "Offline tilstand", "@offlineMode": {}, - "sortOrder": "Sortering", + "sortOrder": "Sorter efter", "@sortOrder": {}, "sortBy": "Sortér efter", "@sortBy": {}, @@ -67,9 +67,9 @@ "@criticRating": {}, "communityRating": "Fællesskab bedømmelse", "@communityRating": {}, - "dateAdded": "Tilføjet dato", + "dateAdded": "Dato tilføjet", "@dateAdded": {}, - "datePlayed": "Afspillet dato", + "datePlayed": "Afspillet den", "@datePlayed": {}, "playCount": "Antal afspilninger", "@playCount": {}, @@ -145,9 +145,9 @@ }, "playButtonLabel": "AFSPIL", "@playButtonLabel": {}, - "editPlaylistNameTooltip": "Ændre afspilningslistens navn", + "editPlaylistNameTooltip": "Rediger afspilningslistens navn", "@editPlaylistNameTooltip": {}, - "editPlaylistNameTitle": "Ændre afspilningslistens navn", + "editPlaylistNameTitle": "Rediger afspilningslistens navn", "@editPlaylistNameTitle": {}, "required": "Påkrævet", "@required": {}, @@ -183,11 +183,11 @@ "@downloadedSongsWillNotBeDeleted": {}, "areYouSure": "Er du sikker?", "@areYouSure": {}, - "jellyfinUsesAACForTranscoding": "Jellyfin bruger AAC under omkodning", + "jellyfinUsesAACForTranscoding": "Jellyfin bruger AAC ved omkodning", "@jellyfinUsesAACForTranscoding": {}, "enableTranscoding": "Aktivér omkodning", "@enableTranscoding": {}, - "enableTranscodingSubtitle": "Omkoder musikken på serveren før det bliver sendes.", + "enableTranscodingSubtitle": "Omkoder musik under afspilning på serveren.", "@enableTranscodingSubtitle": {}, "bitrateSubtitle": "En højere bithastighed giver højere lydkvalitet men bruger mere båndbredde.", "@bitrateSubtitle": {}, @@ -195,7 +195,7 @@ "@customLocation": {}, "appDirectory": "App katalog", "@appDirectory": {}, - "addDownloadLocation": "Tilføj overførelses placering", + "addDownloadLocation": "Tilføj overførelse placering", "@addDownloadLocation": {}, "selectDirectory": "Vælg katalog", "@selectDirectory": {}, @@ -207,7 +207,7 @@ "@enterLowPriorityStateOnPause": {}, "enterLowPriorityStateOnPauseSubtitle": "Tillader notifikationer bliver strøget væk, når afspilning er sat på pause. Dette tillader også Android, at afbryde servicen når den er på pause.", "@enterLowPriorityStateOnPauseSubtitle": {}, - "shuffleAllSongCount": "Bland alle sange tæl", + "shuffleAllSongCount": "Bland alle sange antal", "@shuffleAllSongCount": {}, "viewType": "Visning tilstand", "@viewType": {}, @@ -239,9 +239,9 @@ "@showCoverAsPlayerBackground": {}, "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Vælg om sangens kunstner skal vises på albummet, hvis den ikke er forskellig fra albummets kunstner.", "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, - "disableGesture": "Deaktivér gestus", + "disableGesture": "Deaktivér bevægelser", "@disableGesture": {}, - "disableGestureSubtitle": "Vælg om gestusgenkendelse skal deaktiveres.", + "disableGestureSubtitle": "Vælg om bevægelser skal deaktiveres.", "@disableGestureSubtitle": {}, "theme": "Tema", "@theme": {}, @@ -249,41 +249,41 @@ "@light": {}, "dark": "Mørk", "@dark": {}, - "tabs": "Tabs", + "tabs": "Faner", "@tabs": {}, - "cancelSleepTimer": "Stands sleep timer?", + "cancelSleepTimer": "Vil du annullere sove timeren?", "@cancelSleepTimer": {}, "yesButtonLabel": "JA", "@yesButtonLabel": {}, "minutes": "Minutter", "@minutes": {}, - "sleepTimerTooltip": "Sleep timer", + "sleepTimerTooltip": "Sove timer", "@sleepTimerTooltip": {}, - "addToPlaylistTooltip": "Tilføj til playliste", + "addToPlaylistTooltip": "Tilføj til afspilningsliste", "@addToPlaylistTooltip": {}, - "addToPlaylistTitle": "Tilføj til playliste", + "addToPlaylistTitle": "Tilføj til afspilningsliste", "@addToPlaylistTitle": {}, - "removeFromPlaylistTooltip": "Fjern fra playliste", + "removeFromPlaylistTooltip": "Fjern fra afspilningsliste", "@removeFromPlaylistTooltip": {}, - "removeFromPlaylistTitle": "Fjern fra playliste", + "removeFromPlaylistTitle": "Fjern fra afspilningsliste", "@removeFromPlaylistTitle": {}, - "newPlaylist": "Ny playliste", + "newPlaylist": "Ny afspilningsliste", "@newPlaylist": {}, "createButtonLabel": "OPRET", "@createButtonLabel": {}, - "playlistCreated": "Playliste er oprettet.", + "playlistCreated": "Afspilningsliste er oprettet.", "@playlistCreated": {}, "noAlbum": "Intet album", "@noAlbum": {}, "noItem": "Intet objekt", "@noItem": {}, - "invalidNumber": "Ugyldigt nummer", + "invalidNumber": "Ugyldig nummer", "@invalidNumber": {}, "streaming": "STREAMER", "@streaming": {}, - "downloaded": "DOWNLOADET", + "downloaded": "OVERFØRELSER", "@downloaded": {}, - "transcode": "TRANSKODE", + "transcode": "OMKOD", "@transcode": {}, "direct": "DIREKTE", "@direct": {}, @@ -293,9 +293,9 @@ "@queue": {}, "addToQueue": "Tilføj til kø", "@addToQueue": {}, - "replaceQueue": "Erstat kø", + "replaceQueue": "Udskift køen", "@replaceQueue": {}, - "instantMix": "Umiddelbar blanding", + "instantMix": "Hurtig blanding", "@instantMix": {}, "removeFavourite": "Fjern favorit", "@removeFavourite": {}, @@ -303,11 +303,11 @@ "@addFavourite": {}, "addedToQueue": "Tilføjet til kø.", "@addedToQueue": {}, - "queueReplaced": "Køen er erstattet.", + "queueReplaced": "Køen er udskiftet.", "@queueReplaced": {}, - "removedFromPlaylist": "Fjernet fra playliste.", + "removedFromPlaylist": "Fjernet fra afspilningslisten.", "@removedFromPlaylist": {}, - "startingInstantMix": "Starter umiddelbar blanding.", + "startingInstantMix": "Starter hurtig blanding.", "@startingInstantMix": {}, "downloadCount": "{count,plural, =1{{count} overført} other{{count} overførelser}}", "@downloadCount": { @@ -321,7 +321,7 @@ "@removeFromMix": {}, "addToMix": "Tilføj til blanding", "@addToMix": {}, - "redownloadedItems": "{count,plural, =0{Ingen gendownloads nødvendige.} =1{Gendownloadede {count} emne} other{Gendownloadede {count} emner}}", + "redownloadedItems": "{count,plural, =0{Ingen genoverførsel er nødvendig.} =1{Genoverførte {count} objekt} other{Genoverfører {count} objekter}}", "@redownloadedItems": { "placeholders": { "count": { @@ -331,9 +331,9 @@ }, "bufferDuration": "Buffer varighed", "@bufferDuration": {}, - "bufferDurationSubtitle": "Afspillerens bufferlængde i sekunder. Kræver genstart.", + "bufferDurationSubtitle": "Hvor meget afspilleren skal forudindlæse i sekunder. En genstart er nødvendig.", "@bufferDurationSubtitle": {}, - "startupError": "Noget gik galt med opstarten af appen. Fejlen var {error}\n\nVenligst opret en sag på github.com/UnicornsOnLSD/finamp med et skærmbillede af denne side. Hvis dette problem forbliver, så kan du fjerne dine app data for at nulstille appen.", + "startupError": "Noget gik galt under app opstarten. Fejlen er {error}\n\nVenligst opret en sag på github.com/UnicornsOnLSD/finamp med et skærmbillede af denne side. Hvis dette problem forbliver, så kan du fjerne dine app data for at nulstille appen.", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -416,7 +416,7 @@ "@unknownArtist": {}, "shuffleAllSongCountSubtitle": "Mængde af sange der skal indlæses, når bland alle sange knappen bruges.", "@shuffleAllSongCountSubtitle": {}, - "hideSongArtistsIfSameAsAlbumArtists": "Skjul sangens kunstner, hvis det er den samme som albummets kunstner", + "hideSongArtistsIfSameAsAlbumArtists": "Skjul sang kunstner, hvis det er den samme som albummets kunstner", "@hideSongArtistsIfSameAsAlbumArtists": {}, "gridCrossAxisCount": "{value} Gitter tværakse antal", "@gridCrossAxisCount": { @@ -428,9 +428,9 @@ } } }, - "setSleepTimer": "Sæt sleep timer", + "setSleepTimer": "Indtil sove timer", "@setSleepTimer": {}, - "showCoverAsPlayerBackgroundSubtitle": "Vælg om der skal bruges sløret cover kunst som vises som baggrund på afspiller skærmen.", + "showCoverAsPlayerBackgroundSubtitle": "Vælg om der skal bruges sløret omslagskunst som vises som baggrund på afspiller skærmen.", "@showCoverAsPlayerBackgroundSubtitle": {}, "system": "System", "@system": {}, @@ -438,9 +438,9 @@ "@noButtonLabel": {}, "goToAlbum": "Gå til album", "@goToAlbum": {}, - "anErrorHasOccured": "Der er sket en fejl.", + "anErrorHasOccured": "Der opstod en fejl.", "@anErrorHasOccured": {}, - "responseError401": "{error} Status kode {statusCode}. Dette betyder måske, at du har anvendt forkert brugernavn/password eller at din klient ikke længere er logget ind.", + "responseError401": "{error} Status kode {statusCode}. Dette betyder, at du har anvendt et forkert brugernavn/adgangskode eller at din klient ikke længere er logget på.", "@responseError401": { "placeholders": { "error": { From 688e98e4a5db8e09a8de0a8a0ee97dc0465f8aec Mon Sep 17 00:00:00 2001 From: NicKoehler Date: Mon, 8 May 2023 12:34:36 +0000 Subject: [PATCH 085/172] Translated using Weblate (Italian) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/it/ --- lib/l10n/app_it.arb | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_it.arb b/lib/l10n/app_it.arb index 2a5d8ab4c..bb2a4d90a 100644 --- a/lib/l10n/app_it.arb +++ b/lib/l10n/app_it.arb @@ -473,5 +473,13 @@ "type": "int" } } - } + }, + "removeFromPlaylistTooltip": "Rimuovi dalla playlist", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Rimuovi dalla playlist", + "@removeFromPlaylistTitle": {}, + "removedFromPlaylist": "Rimosso dalla playlist.", + "@removedFromPlaylist": {}, + "language": "Lingua", + "@language": {} } From 472f824eae62594401eae16562ffbc25df37f321 Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Wed, 10 May 2023 02:45:37 +0000 Subject: [PATCH 086/172] Translated using Weblate (Chinese (Traditional)) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hant/ --- lib/l10n/app_zh_Hant.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_zh_Hant.arb b/lib/l10n/app_zh_Hant.arb index a56bf0cf8..32ee1ce5c 100644 --- a/lib/l10n/app_zh_Hant.arb +++ b/lib/l10n/app_zh_Hant.arb @@ -479,5 +479,7 @@ "type": "int" } } - } + }, + "language": "語言", + "@language": {} } From 3b4e1933a016651a1dc2a10ecd59e7d92df168b1 Mon Sep 17 00:00:00 2001 From: Ekapol Tassaneeyasin Date: Thu, 11 May 2023 14:34:27 +0200 Subject: [PATCH 087/172] Added translation using Weblate (Thai) --- lib/l10n/app_th.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_th.arb diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_th.arb @@ -0,0 +1 @@ +{} From 2114d55fd59424f5854369c7a9bcccf207eafa94 Mon Sep 17 00:00:00 2001 From: Ekapol Tassaneeyasin Date: Thu, 11 May 2023 13:07:30 +0000 Subject: [PATCH 088/172] Translated using Weblate (Thai) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/th/ --- lib/l10n/app_th.arb | 486 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 485 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb index 0967ef424..ba454dbbe 100644 --- a/lib/l10n/app_th.arb +++ b/lib/l10n/app_th.arb @@ -1 +1,485 @@ -{} +{ + "showCoverAsPlayerBackgroundSubtitle": "แสดงหรือไม่แสดงภาพเบลอของปกเพลงเป็นพื้นหลังของตัวเล่นเพลง", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "music": "เพลง", + "@music": {}, + "clear": "เคลียร์", + "@clear": {}, + "hideSongArtistsIfSameAsAlbumArtists": "ซ่อนศิลปินของเพลงหากเป็นชื่อเดียวกับศิลปินของอัลบั้ม", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "favourites": "รายการโปรด", + "@favourites": {}, + "setSleepTimer": "ตั้งเวลาปิด", + "@setSleepTimer": {}, + "minutes": "นาที", + "@minutes": {}, + "sleepTimerTooltip": "ตั้งเวลาปิด", + "@sleepTimerTooltip": {}, + "addToPlaylistTitle": "เพิ่มไปยังเพลย์ลิสต์", + "@addToPlaylistTitle": {}, + "newPlaylist": "สร้างเพลย์ลิสต์ใหม่", + "@newPlaylist": {}, + "noArtist": "ไม่มีศิลปิน", + "@noArtist": {}, + "removeFavourite": "นำออกจากรายการโปรด", + "@removeFavourite": {}, + "statusError": "สถานะผิดพลาด", + "@statusError": {}, + "queueReplaced": "คิวถูกแทนที่แล้ว", + "@queueReplaced": {}, + "responseError": "{error} รหัสข้อผิดพลาด {statusCode}", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "serverUrl": "URL ของเซิร์ฟเวอร์", + "@serverUrl": {}, + "logs": "ล็อก", + "@logs": {}, + "next": "ถัดไป", + "@next": {}, + "selectMusicLibraries": "เลือกคลังเพลง", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "artists": "ศิลปิน", + "@artists": {}, + "genres": "ประเภท", + "@genres": {}, + "startMixNoSongsAlbum": "แตะค้างที่อัลบั้มเพื่อเพิ่มหรือนำออกจากตัวสร้างมิกซ์ก่อนที่จะเริ่มมิกซ์", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "albumArtist": "ศิลปินของอัลบั้ม", + "@albumArtist": {}, + "artist": "ศิลปิน", + "@artist": {}, + "budget": "งบ", + "@budget": {}, + "communityRating": "เรตติ้งจากชุมชน", + "@communityRating": {}, + "criticRating": "เรตติ้งจาก Critic", + "@criticRating": {}, + "datePlayed": "วันที่เล่น", + "@datePlayed": {}, + "premiereDate": "วันที่เปิดตัว", + "@premiereDate": {}, + "productionYear": "ปีที่จัดทำ", + "@productionYear": {}, + "name": "ชื่อ", + "@name": {}, + "revenue": "รายได้", + "@revenue": {}, + "downloadMissingImages": "ดาวน์โหลดไฟล์ภาพที่ขาด", + "@downloadMissingImages": {}, + "downloadCount": "{count,plural, =1{ดาวน์โหลดแล้ว {count}} other{ดาวน์โหลดแล้ว {count}}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + }, + "dlComplete": "เสร็จแล้ว {count}", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlFailed": "ล้มเหลว {count}", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlEnqueued": "เข้าคิว {count}", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "noErrors": "ไม่มีข้อผิดพลาด!", + "@noErrors": {}, + "favourite": "รายการโปรด", + "@favourite": {}, + "downloadsDeleted": "ลบการดาวน์โหลดแล้ว", + "@downloadsDeleted": {}, + "addDownloads": "เพิ่มการดาวน์โหลด", + "@addDownloads": {}, + "location": "สถานที่", + "@location": {}, + "downloadsAdded": "เพิ่มการดาวน์โหลดแล้ว", + "@downloadsAdded": {}, + "addButtonLabel": "เพิ่ม", + "@addButtonLabel": {}, + "shareLogs": "แชร์ล็อก", + "@shareLogs": {}, + "logsCopied": "คัดลอกล็อกแล้ว", + "@logsCopied": {}, + "transcoding": "การแปลงไฟล์", + "@transcoding": {}, + "downloadLocations": "ตำแหน่งที่ดาวน์โหลด", + "@downloadLocations": {}, + "audioService": "บริการเสียง", + "@audioService": {}, + "stackTrace": "ร่องรอย", + "@stackTrace": {}, + "layoutAndTheme": "หน้าตา และ ธีม", + "@layoutAndTheme": {}, + "notAvailableInOfflineMode": "ไม่พร้อมใช้งานในโหมดออฟไลน์", + "@notAvailableInOfflineMode": {}, + "jellyfinUsesAACForTranscoding": "Jellyfin ใช้รหัส AAC สำหรับการแปลงไฟล์", + "@jellyfinUsesAACForTranscoding": {}, + "enableTranscoding": "เปิดใช้งานการแปลงไฟล์", + "@enableTranscoding": {}, + "enableTranscodingSubtitle": "การแปลงไฟล์เพลงจะถูกดำเนินการจากฝั่งเซิร์ฟเวอร์", + "@enableTranscodingSubtitle": {}, + "bitrate": "บิทเรต", + "@bitrate": {}, + "customLocation": "ตำแหน่งแบบเลือกเอง", + "@customLocation": {}, + "appDirectory": "โฟลเดอร์ของแอป", + "@appDirectory": {}, + "directoryMustBeEmpty": "โฟลเดอร์นั้นต้องว่าง", + "@directoryMustBeEmpty": {}, + "enterLowPriorityStateOnPause": "เมื่อหยุดพักจะเข้าสู่โหมดความสำคัญต่ำ", + "@enterLowPriorityStateOnPause": {}, + "shuffleAllSongCount": "จำนวนครั้งที่สุ่มเพลงทั้งหมด", + "@shuffleAllSongCount": {}, + "shuffleAllSongCountSubtitle": "จำนวนเพลงที่โหลดขึ้นมาเมื่อใช้ปุ่ม สุ่มทั้งหมด", + "@shuffleAllSongCountSubtitle": {}, + "viewTypeSubtitle": "ประเภทการแสดงผลของหน้าจอเพลง", + "@viewTypeSubtitle": {}, + "list": "รายการ", + "@list": {}, + "grid": "ตาราง", + "@grid": {}, + "portrait": "แนวตั้ง", + "@portrait": {}, + "landscape": "แนวนอน", + "@landscape": {}, + "gridCrossAxisCount": "จำนวนรายการใน {value}", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "gridCrossAxisCountSubtitle": "จำนวนของไอเท็มในตารางต่อแถวของ {value}", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "showTextOnGridView": "แสดงตัวอักษรในโหมดตาราง", + "@showTextOnGridView": {}, + "showCoverAsPlayerBackground": "แสดงภาพเบลอของปกเพลงเป็นภาพพื้นหลังของตัวเล่นเพลง", + "@showCoverAsPlayerBackground": {}, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "แสดงหรือไม่แสดงชื่อศิลปินเพลงบนหน้าจออัลบั้มหากทั้งสองชื่อไม่ต่างกัน", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "disableGesture": "ปิดการใช้งาน gestures", + "@disableGesture": {}, + "disableGestureSubtitle": "ใช้หรือไม่ใช้งาน gestures", + "@disableGestureSubtitle": {}, + "theme": "ธีม", + "@theme": {}, + "system": "ตามระบบ", + "@system": {}, + "light": "สว่าง", + "@light": {}, + "dark": "มืด", + "@dark": {}, + "tabs": "แท็บ", + "@tabs": {}, + "cancelSleepTimer": "ยกเลิกการตั้งเวลาปิด?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "ใช่", + "@yesButtonLabel": {}, + "noButtonLabel": "ไม่ใช่", + "@noButtonLabel": {}, + "invalidNumber": "ตัวเลขไม่ถูกต้อง", + "@invalidNumber": {}, + "addToPlaylistTooltip": "เพิ่มไปยังเพลย์ลิสต์", + "@addToPlaylistTooltip": {}, + "removeFromPlaylistTooltip": "นำออกจากเพลย์ลิสต์", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "นำออกจากเพลย์ลิสต์", + "@removeFromPlaylistTitle": {}, + "createButtonLabel": "สร้าง", + "@createButtonLabel": {}, + "playlistCreated": "สร้างเพลย์ลิสต์แล้ว", + "@playlistCreated": {}, + "noAlbum": "ไม่มีอัลบั้ม", + "@noAlbum": {}, + "noItem": "ไม่มีรายการ", + "@noItem": {}, + "unknownArtist": "ศิลปินที่ไม่รู้จัก", + "@unknownArtist": {}, + "streaming": "กำลังสตรีม", + "@streaming": {}, + "downloaded": "ดาวน์โหลดแล้ว", + "@downloaded": {}, + "direct": "โดยตรง", + "@direct": {}, + "queue": "คิว", + "@queue": {}, + "addToQueue": "เพิ่มไปยังคิว", + "@addToQueue": {}, + "replaceQueue": "แทนที่คิว", + "@replaceQueue": {}, + "instantMix": "มิกซ์ทันที", + "@instantMix": {}, + "goToAlbum": "ไปยังอัลบั้ม", + "@goToAlbum": {}, + "addFavourite": "เพิ่มไปยังรายการโปรด", + "@addFavourite": {}, + "addedToQueue": "เพิ่มลงคิวแล้ว", + "@addedToQueue": {}, + "removedFromPlaylist": "นำออกจากเพลย์ลิสต์แล้ว", + "@removedFromPlaylist": {}, + "startingInstantMix": "กำลังเริ่มมิกซ์ทันที", + "@startingInstantMix": {}, + "anErrorHasOccured": "พบข้อผิดพลาด", + "@anErrorHasOccured": {}, + "removeFromMix": "นำออกจากมิกซ์", + "@removeFromMix": {}, + "settings": "ตั้งค่า", + "@settings": {}, + "addToMix": "เพิ่มไปยังมิกซ์", + "@addToMix": {}, + "redownloadedItems": "{count,plural, =0{ไม่ต้องดาวน์โหลดซ้ำ} =1{ดาวน์โหลดอีกครั้ง {count} รายการ} other{ดาวน์โหลดอีกครั้ง{count} รายการ}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bufferDuration": "ระยะเวลาที่บัฟเฟอร์", + "@bufferDuration": {}, + "language": "ภาษา", + "@language": {}, + "offlineMode": "โหมดออฟไลน์", + "@offlineMode": {}, + "album": "อัลบั้ม", + "@album": {}, + "startupError": "พบข้อผิดพลาดขณะเริ่มต้นแอป ข้อผิดพลาด: {error}\n\nโปรดสร้าง Issue บน github.com/UnicornsOnLSD/finamp พร้อมแนบภาพหน้าจอของหน้านี้ หากปัญหานี้ยังคงเป็นอยู่ให้ลองเคลียร์ข้อมูลของแอปเพื่อรีเซ็ต", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + }, + "selectDirectory": "เลือกโฟลเดอร์", + "@selectDirectory": {}, + "dateAdded": "วันที่เพิ่ม", + "@dateAdded": {}, + "playCount": "จำนวนครั้งที่เล่น", + "@playCount": {}, + "random": "สุ่ม", + "@random": {}, + "runtime": "เวลา", + "@runtime": {}, + "downloadedItemsCount": "{count,plural,=1{{count} รายการ} other{{count} รายการ}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrors": "ดาวน์โหลดผิดพลาด", + "@downloadErrors": {}, + "downloadedMissingImages": "{count,plural, =0{ไม่มีภาพที่ขาด} =1{ดาวน์โหลดภาพที่ขาดแล้ว {count} รูป} other{ดาวน์โหลดภาพที่ขาดแล้ว {count} รูป}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedImagesCount": "{count,plural,=1{{count} รูป} other{{count} รูป}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "updateButtonLabel": "อัปเดต", + "@updateButtonLabel": {}, + "bitrateSubtitle": "บิตเรตที่สูงขึ้นจะช่วยให้เสียงดีขึ้นแต่ก็แลกมาด้วยการใช้งานเน็ตเวิร์กที่มากขึ้น", + "@bitrateSubtitle": {}, + "songCount": "{count,plural,=1{{count} เพลง} other{{count} เพลง}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlRunning": "กำลังทำงาน {count}", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editPlaylistNameTooltip": "แก้ไขชื่อเพลย์ลิสต์", + "@editPlaylistNameTooltip": {}, + "downloadErrorsTitle": "ดาวน์โหลดผิดพลาด", + "@downloadErrorsTitle": {}, + "discNumber": "แผ่นที่ {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "editPlaylistNameTitle": "แก้ไขชื่อเพลย์ลิสต์", + "@editPlaylistNameTitle": {}, + "errorScreenError": "พบข้อผิดพลาดในการดึงรายการข้อผิดพลาด! ถ้าถึงขั้นนี้แล้ว คุณน่าจะต้องไปสร้าง Issue บน Github แล้วลบข้อมูลแอปทิ้งซะ", + "@errorScreenError": {}, + "error": "ผิดพลาด", + "@error": {}, + "playButtonLabel": "เล่น", + "@playButtonLabel": {}, + "required": "จำเป็น", + "@required": {}, + "failedToGetSongFromDownloadId": "ล้มเหลวในการเรียกข้อมูลเพลงจากไอดีสำหรับดาวน์โหลด", + "@failedToGetSongFromDownloadId": {}, + "shuffleButtonLabel": "สุ่ม", + "@shuffleButtonLabel": {}, + "playlistNameUpdated": "อัปเดตชื่อเพลย์ลิสต์แล้ว", + "@playlistNameUpdated": {}, + "message": "ข้อความ", + "@message": {}, + "responseError401": "{error} รหัสข้อผิดพลาด {statusCode} เป็นไปได้ว่าคุณอาจจะใช้ ชื่อผู้ใช้หรือรหัสผ่านที่ไม่ถูกต้อง หรือตัวโปรแกรมไม่ได้อยู่ในสถานะล็อกอินแล้ว", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } + }, + "applicationLegalese": "ลิขสิทธิ์โดย Mozilla Public License 2.0 สามารถดาวน์โหลด Source Code ได้ที่:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "logOut": "ออกจากระบบ", + "@logOut": {}, + "areYouSure": "แน่ใจหรือไม่?", + "@areYouSure": {}, + "addDownloadLocation": "เพิ่มตำแหน่งสำหรับดาวน์โหลด", + "@addDownloadLocation": {}, + "downloadedSongsWillNotBeDeleted": "เพลงที่ดาวน์โหลดแล้วจะไม่ถูกลบ", + "@downloadedSongsWillNotBeDeleted": {}, + "bufferDurationSubtitle": "ตัวเล่นเพลงควรจะอ่านไฟล์ล่วงหน้าเท่าไร ระบุเป็นวินาที หากเปลี่ยนต้องรีสตาร์ท", + "@bufferDurationSubtitle": {}, + "unknownError": "ข้อผิดพลาดที่ไม่รู้จัก", + "@unknownError": {}, + "pathReturnSlashErrorMessage": "ตำแหน่งที่ได้ \"/\" ไม่สามารถใช้ได้", + "@pathReturnSlashErrorMessage": {}, + "enterLowPriorityStateOnPauseSubtitle": "ให้แถบแจ้งเตือนสามารถปัดทิ้งไปได้เมื่อหยุดชั่วคราว รวมถึงให้แอนดรอยสามารถปิดเซอร์วิสนี้ได้เมื่อหยุดชั่วคราว", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "customLocationsBuggy": "ตำแหน่งแบบเลือกเองนั้นค่อนข้างไม่สเถียรเนื่องจากปัญหาด้านสิทธิ์ ตอนนี้ยังหาวิธีแก้เรื่องนี้อยู่ ทางที่ดีตอนนี้ไม่ค่อยแนะนำให้ใช้ฟีเจอร์นี้", + "@customLocationsBuggy": {}, + "viewType": "ประเภทการแสดงผล", + "@viewType": {}, + "transcode": "แปลงไฟล์", + "@transcode": {}, + "internalExternalIpExplanation": "หากคุณต้องการเข้าถึงเซิร์ฟเวอร์ Jellyfin จากภายนอก คุณจำเป็นต้องมี IP ภายนอก\n\nหากเซิร์ฟเวอร์ของคุณรันบนพอร์ต HTTP (80/443) คุณไม่จำเป็นต้องระบุพอร์ต ในเคสนี้เซิร์ฟเวอร์ของคุณอาจจะอยู่หลัง Reverse Proxy", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "emptyServerUrl": "จำเป็นต้องระบุ URL ของเซิร์ฟเวอร์", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "urlStartWithHttps": "URL ต้องขึ้นต้นด้วย http:// หรือ https://", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "urlTrailingSlash": "URL ต้องไม่มีเครื่องหมาย / ต่อหลัง", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "username": "ชื่อผู้ใช้", + "@username": {}, + "password": "รหัสผ่าน", + "@password": {}, + "playlists": "เพลย์ลิสต์", + "@playlists": {}, + "startMixNoSongsArtist": "แตะค้างที่ศิลปินเพื่อเพิ่มหรือนำออกจากตัวสร้างมิกซ์ก่อนที่จะเริ่มมิกซ์", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "couldNotFindLibraries": "ไม่พบคลังใดๆ", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "albums": "อัลบั้ม", + "@albums": {}, + "downloads": "ดาวน์โหลด", + "@downloads": {}, + "sortOrder": "เรียงลำดับ", + "@sortOrder": {}, + "sortBy": "เรียงด้วย", + "@sortBy": {}, + "unknownName": "ไม่รู้จักชื่อ", + "@unknownName": {}, + "finamp": "ฟินแอมป์", + "@finamp": {}, + "songs": "เพลง", + "@songs": {}, + "startMix": "เริ่มมิกซ์", + "@startMix": {}, + "shuffleAll": "สุ่มทั้งหมด", + "@shuffleAll": {}, + "showTextOnGridViewSubtitle": "แสดงหรือไม่แสดงข้อความ (ชื่อเพลง, ศิลปิน, อื่น ๆ) บนตารางแสดงเพลง", + "@showTextOnGridViewSubtitle": {} +} From c32553b6b2551ebe96980f95c556ff4247b98836 Mon Sep 17 00:00:00 2001 From: Ekapol Tassaneeyasin Date: Fri, 12 May 2023 08:11:04 +0000 Subject: [PATCH 089/172] Translated using Weblate (Ukrainian) Currently translated at 99.4% (170 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/uk/ --- lib/l10n/app_uk.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 4b7d75716..fbcf09cf3 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -138,7 +138,7 @@ "@playlists": {}, "album": "Альбомами", "@album": {}, - "songCount": "{count,plural,=1{{count} Пісня} other{{count Пісні}}", + "songCount": "{count,plural,=1{{count} Пісня} other{{count} Пісні}", "@songCount": { "placeholders": { "count": { From 7ea1cad45b37e167924b792a1a5b77f448693227 Mon Sep 17 00:00:00 2001 From: Ekapol Tassaneeyasin Date: Fri, 12 May 2023 08:04:55 +0000 Subject: [PATCH 090/172] Translated using Weblate (Thai) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/th/ --- lib/l10n/app_th.arb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb index ba454dbbe..13a2fe118 100644 --- a/lib/l10n/app_th.arb +++ b/lib/l10n/app_th.arb @@ -72,7 +72,7 @@ "@datePlayed": {}, "premiereDate": "วันที่เปิดตัว", "@premiereDate": {}, - "productionYear": "ปีที่จัดทำ", + "productionYear": "ปีที่สร้าง", "@productionYear": {}, "name": "ชื่อ", "@name": {}, @@ -150,7 +150,7 @@ "@downloadLocations": {}, "audioService": "บริการเสียง", "@audioService": {}, - "stackTrace": "ร่องรอย", + "stackTrace": "ตามรอย", "@stackTrace": {}, "layoutAndTheme": "หน้าตา และ ธีม", "@layoutAndTheme": {}, @@ -408,7 +408,7 @@ } } }, - "applicationLegalese": "ลิขสิทธิ์โดย Mozilla Public License 2.0 สามารถดาวน์โหลด Source Code ได้ที่:\n\ngithub.com/jmshrv/finamp", + "applicationLegalese": "สงวนลิขสิทธิ์โดย Mozilla Public License 2.0 สามารถดาวน์โหลด Source Code ได้ที่:\n\ngithub.com/jmshrv/finamp", "@applicationLegalese": {}, "logOut": "ออกจากระบบ", "@logOut": {}, From 576b83375550bf1ab0933e85c6b8536cd99d677d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tr=E1=BA=A7n=20Thanh=20S=C6=A1n?= Date: Sat, 13 May 2023 09:57:00 +0000 Subject: [PATCH 091/172] Translated using Weblate (Vietnamese) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/vi/ --- lib/l10n/app_vi.arb | 170 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 163 insertions(+), 7 deletions(-) diff --git a/lib/l10n/app_vi.arb b/lib/l10n/app_vi.arb index 667495673..5cefd6d0b 100644 --- a/lib/l10n/app_vi.arb +++ b/lib/l10n/app_vi.arb @@ -1,5 +1,5 @@ { - "serverUrl": "URL của máy chủ", + "serverUrl": "URL của Máy chủ", "@serverUrl": {}, "emptyServerUrl": "URL của máy chủ không thể để trống", "@emptyServerUrl": { @@ -17,7 +17,7 @@ "@username": {}, "password": "Mật khẩu", "@password": {}, - "logs": "Ghi chép", + "logs": "Nhật kí hoạt động", "@logs": {}, "next": "Tiếp theo", "@next": {}, @@ -77,9 +77,9 @@ "@criticRating": {}, "dateAdded": "Ngày được thêm", "@dateAdded": {}, - "datePlayed": "Ngày đã chơi", + "datePlayed": "Ngày phát", "@datePlayed": {}, - "playCount": "Số lần chơi", + "playCount": "Số lần phát", "@playCount": {}, "premiereDate": "Ngày phát hành", "@premiereDate": {}, @@ -203,9 +203,9 @@ "@message": {}, "stackTrace": "Dấu vết Ngăn xếp", "@stackTrace": {}, - "transcoding": "Đang chuyển đổi", + "transcoding": "Chuyển Mã", "@transcoding": {}, - "downloadLocations": "Nơi tải xuống", + "downloadLocations": "Vị trí tải xuống", "@downloadLocations": {}, "audioService": "Dịch vụ âm thanh", "@audioService": {}, @@ -325,5 +325,161 @@ "viewType": "Loại Xem", "@viewType": {}, "playlistNameUpdated": "Tên danh sách phát đã được cập nhật.", - "@playlistNameUpdated": {} + "@playlistNameUpdated": {}, + "noArtist": "Không có Nghệ sĩ", + "@noArtist": {}, + "direct": "TRỰC TIẾP", + "@direct": {}, + "addFavourite": "Thêm vào Yêu thích", + "@addFavourite": {}, + "startingInstantMix": "Bắt đầu tuyển tập nhạc tức thời.", + "@startingInstantMix": {}, + "responseError401": "{error} Mã {statusCode}. Có thể bạn đã nhập sai tên người dùng/mật khẩu, hoặc client của bạn bị đăng xuất.", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } + }, + "addToMix": "Thêm vào Mix", + "@addToMix": {}, + "noButtonLabel": "KHÔNG", + "@noButtonLabel": {}, + "setSleepTimer": "Đặt đồng hồ hẹn giờ ngủ", + "@setSleepTimer": {}, + "grid": "Lưới", + "@grid": {}, + "portrait": "Dọc", + "@portrait": {}, + "landscape": "Ngang", + "@landscape": {}, + "gridCrossAxisCount": "{value} Số Lưới Trục-Chéo", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "gridCrossAxisCountSubtitle": "Số lượng lưới dùng mỗi hàng khi {value}.", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "showTextOnGridViewSubtitle": "Cho dù có hay không hiện thông tin (tiêu đề, nghệ sĩ,...) trên lưới nhạc.", + "@showTextOnGridViewSubtitle": {}, + "showTextOnGridView": "Hiện thông tin trong khung lưới", + "@showTextOnGridView": {}, + "showCoverAsPlayerBackground": "Dùng ảnh bìa mờ làm nền trình phát", + "@showCoverAsPlayerBackground": {}, + "showCoverAsPlayerBackgroundSubtitle": "Có hay không dùng ảnh bìa mờ làm nền trình phát trên phần phát nhạc.", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "hideSongArtistsIfSameAsAlbumArtists": "Ẩn tên ca sĩ bài hát nếu giống ca sĩ album", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Có hay không hiện nghệ sĩ bài hát trên phần album nếu không khác ca sĩ album.", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "disableGesture": "Tắt cử chỉ", + "@disableGesture": {}, + "disableGestureSubtitle": "Có nên tắt cử chỉ.", + "@disableGestureSubtitle": {}, + "theme": "Chủ đề", + "@theme": {}, + "system": "Hệ thống", + "@system": {}, + "light": "Sáng", + "@light": {}, + "dark": "Tối", + "@dark": {}, + "tabs": "Trang", + "@tabs": {}, + "cancelSleepTimer": "Tắt Đồng hồ Hẹn Giờ Ngủ?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "CÓ", + "@yesButtonLabel": {}, + "minutes": "Phút", + "@minutes": {}, + "invalidNumber": "Số không hợp lệ", + "@invalidNumber": {}, + "sleepTimerTooltip": "Đồng hồ hẹn giờ ngủ", + "@sleepTimerTooltip": {}, + "createButtonLabel": "TẠO", + "@createButtonLabel": {}, + "playlistCreated": "Đã tạo danh sách phát.", + "@playlistCreated": {}, + "noAlbum": "Không có Album", + "@noAlbum": {}, + "noItem": "Không có vật phẩm", + "@noItem": {}, + "unknownArtist": "Nghệ sĩ không rõ", + "@unknownArtist": {}, + "streaming": "STREAMING", + "@streaming": {}, + "downloaded": "ĐÃ TẢI XUỐNG", + "@downloaded": {}, + "transcode": "CHUYỂN MÃ", + "@transcode": {}, + "statusError": "LỖI TRẠNG THÁI", + "@statusError": {}, + "queue": "Hàng chờ", + "@queue": {}, + "addToQueue": "Thêm vào Hàng chờ", + "@addToQueue": {}, + "replaceQueue": "Thay thế Hàng chờ", + "@replaceQueue": {}, + "goToAlbum": "Đi tới Album", + "@goToAlbum": {}, + "removeFavourite": "Xóa yêu thích", + "@removeFavourite": {}, + "addedToQueue": "Đã thêm vào hàng chờ.", + "@addedToQueue": {}, + "queueReplaced": "Đã thay thế hàng chờ.", + "@queueReplaced": {}, + "removedFromPlaylist": "Đã loại bỏ khỏi danh sách.", + "@removedFromPlaylist": {}, + "anErrorHasOccured": "Đã có lỗi xảy ra.", + "@anErrorHasOccured": {}, + "responseError": "{error} Mã {statusCode}.", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "redownloadedItems": "{count,plural, =0{Không cần tải lại.} =1{Đã tải lại {count} bài} other{Đã tải lại {count} bài}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "bufferDuration": "Thời gian bộ đệm", + "@bufferDuration": {}, + "bufferDurationSubtitle": "Trình phát nên tạo bộ đệm trong bao lâu giây. Cần khởi động lại app.", + "@bufferDurationSubtitle": {}, + "instantMix": "Instant Mix", + "@instantMix": {}, + "removeFromMix": "Loại bỏ khỏi Mix", + "@removeFromMix": {}, + "language": "Ngôn ngữ", + "@language": {} } From c5929f2f2c0149e108b2e8c283f9b6b856b8676d Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 17 May 2023 16:38:53 +0000 Subject: [PATCH 092/172] Translated using Weblate (German) Currently translated at 99.4% (170 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/de/ --- lib/l10n/app_de.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index e45a91970..90572bba0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -477,5 +477,7 @@ "removeFromPlaylistTitle": "Aus Wiedergabeliste entfernen", "@removeFromPlaylistTitle": {}, "removedFromPlaylist": "Aus der Wiedergabeliste entfernt.", - "@removedFromPlaylist": {} + "@removedFromPlaylist": {}, + "bufferDurationSubtitle": "Wie viele Sekunden der Player vorladen soll. Benötigt einen Neustart.", + "@bufferDurationSubtitle": {} } From 14b8119ed2a66dcba30930b37157c37e4d6fe64f Mon Sep 17 00:00:00 2001 From: Felipe Silva Date: Tue, 16 May 2023 20:11:28 +0000 Subject: [PATCH 093/172] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/pt_BR/ --- lib/l10n/app_pt_BR.arb | 46 +++++++++++++++++++++++++++++++++--------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/lib/l10n/app_pt_BR.arb b/lib/l10n/app_pt_BR.arb index d5287ed5f..05efcd773 100644 --- a/lib/l10n/app_pt_BR.arb +++ b/lib/l10n/app_pt_BR.arb @@ -146,7 +146,7 @@ "@noErrors": {}, "songs": "Músicas", "@songs": {}, - "startupError": "Catapimbas! Algo deu errado na inicialização. O erro foi: {error}\n\nPor favor, abra uma issue em github.com/UnicornsOnLSD/finamp anexando uma captura de tela dessa página. Se esse erro continuar aparecendo, limpe os dados para restaurar o aplicativo.", + "startupError": "Algo deu errado na inicialização. O erro foi: {error}\n\nPor favor, crie um problema/issue em github.com/UnicornsOnLSD/finamp anexando uma captura de tela dessa página. Se esse erro persistir, limpe os dados para restaurar o aplicativo.", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -204,7 +204,7 @@ "@addDownloadLocation": {}, "location": "Localização", "@location": {}, - "enableTranscodingSubtitle": "Se ativado, transmissão de músicas serão transcodificados pelo servidor.", + "enableTranscodingSubtitle": "A transmissão de músicas será transcodificada pelo servidor.", "@enableTranscodingSubtitle": {}, "songCount": "{count,plural,=1{{count} Música} other{{count} Músicas}}", "@songCount": { @@ -258,9 +258,9 @@ "@selectDirectory": {}, "enterLowPriorityStateOnPause": "Entrar Estado de Baixa-Prioridade durante Pausa", "@enterLowPriorityStateOnPause": {}, - "showCoverAsPlayerBackground": "Mostrar Capas Desfocadas como Fundo do Tocador", + "showCoverAsPlayerBackground": "Mostrar capas desfocadas como fundo do tocador", "@showCoverAsPlayerBackground": {}, - "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Esconder ou não artistas da música na tela do álbum se eles não forem diferentes dos artistas do álbum.", + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Indica se deve mostrar os artistas da música na tela do álbum se eles não forem diferentes dos artistas do álbum.", "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, "bitrateSubtitle": "Uma taxa de bits mais alta resulta em áudio de maior qualidade, ao custo de maior largura de banda.", "@bitrateSubtitle": {}, @@ -278,11 +278,11 @@ "@customLocationsBuggy": {}, "shuffleAllSongCount": "Contagem de Misturar Todas as Músicas", "@shuffleAllSongCount": {}, - "enterLowPriorityStateOnPauseSubtitle": "Quando ativado, a notificação pode ser dispensada quando pausado. Ativando isso também permite ao Android terminar o serviço quando pausado.", + "enterLowPriorityStateOnPauseSubtitle": "Permite que a notificação seja dispensada quando pausado. Também permite ao Android terminar o serviço quando pausado.", "@enterLowPriorityStateOnPauseSubtitle": {}, "portrait": "Retrato", "@portrait": {}, - "showTextOnGridView": "Mostrar Texto na Visualização Grade", + "showTextOnGridView": "Mostrar texto na visualização de grade", "@showTextOnGridView": {}, "landscape": "Paisagem", "@landscape": {}, @@ -308,7 +308,7 @@ }, "showCoverAsPlayerBackgroundSubtitle": "Usar ou não usar arte de capas desfocadas como fundo da tela to tocador.", "@showCoverAsPlayerBackgroundSubtitle": {}, - "hideSongArtistsIfSameAsAlbumArtists": "Esconder Artistas da Musica se for o mesmo que Artistas do Álbum", + "hideSongArtistsIfSameAsAlbumArtists": "Esconder o nome dos artistas da música se for o mesmo que os artistas do álbum", "@hideSongArtistsIfSameAsAlbumArtists": {}, "light": "Claro", "@light": {}, @@ -370,11 +370,11 @@ "@noArtist": {}, "transcode": "TRANSCODIFICAR", "@transcode": {}, - "startMixNoSongsArtist": "Aperte e segure em um artista para adicionar ou remover do construtor de miscelânea antes de iniciar a miscelânea", + "startMixNoSongsArtist": "Aperte e segure num artista para adicionar ou remover da mistura antes de iniciá-la", "@startMixNoSongsArtist": { "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" }, - "startMixNoSongsAlbum": "Aperte e segure no álbum para adicionar ou remover do construtor de miscelânea antes de iniciar a miscelânea", + "startMixNoSongsAlbum": "Aperte e segure num álbum para adicionar ou remover da mistura antes de iniciá-la", "@startMixNoSongsAlbum": { "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" }, @@ -455,5 +455,31 @@ "removeFromMix": "Remover da Mistura", "@removeFromMix": {}, "addToMix": "Adicionar à Mistura", - "@addToMix": {} + "@addToMix": {}, + "minutes": "Minutos", + "@minutes": {}, + "disableGesture": "Desativar gestos", + "@disableGesture": {}, + "disableGestureSubtitle": "Indica se deve desativar gestos.", + "@disableGestureSubtitle": {}, + "removeFromPlaylistTooltip": "Remover da playlist", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Remover da Playlist", + "@removeFromPlaylistTitle": {}, + "removedFromPlaylist": "Removido da playlist.", + "@removedFromPlaylist": {}, + "bufferDuration": "Duração do buffer", + "@bufferDuration": {}, + "bufferDurationSubtitle": "Quanto o reprodutor deve armazenar de buffer, em segundos. Requer uma reinicialização.", + "@bufferDurationSubtitle": {}, + "redownloadedItems": "{count,plural, =0{Sem necessidade de baixar novamente.} =1{{count} item foi baixado novamente} other{{count} itens foram baixados novamente}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "language": "Idioma", + "@language": {} } From 27326d75757f9036f24397d24104b841fa3ddd47 Mon Sep 17 00:00:00 2001 From: Felipe Silva Date: Tue, 16 May 2023 20:44:43 +0000 Subject: [PATCH 094/172] Translated using Weblate (Ukrainian) Currently translated at 99.4% (170 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/uk/ --- lib/l10n/app_uk.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index fbcf09cf3..70ac79a6f 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -138,7 +138,7 @@ "@playlists": {}, "album": "Альбомами", "@album": {}, - "songCount": "{count,plural,=1{{count} Пісня} other{{count} Пісні}", + "songCount": "{count,plural,=1{{count} Пісня} other{{count} Пісні}}", "@songCount": { "placeholders": { "count": { From fc578e20306187598f31af437034005c22f7f32c Mon Sep 17 00:00:00 2001 From: Hannes Braun Date: Sun, 21 May 2023 20:47:59 +0000 Subject: [PATCH 095/172] Translated using Weblate (German) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/de/ --- lib/l10n/app_de.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 90572bba0..f73d5f9fc 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -479,5 +479,7 @@ "removedFromPlaylist": "Aus der Wiedergabeliste entfernt.", "@removedFromPlaylist": {}, "bufferDurationSubtitle": "Wie viele Sekunden der Player vorladen soll. Benötigt einen Neustart.", - "@bufferDurationSubtitle": {} + "@bufferDurationSubtitle": {}, + "language": "Sprache", + "@language": {} } From 7f48dfef1b96854da894fff52bf7ca6e4046d718 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A1=E6=83=85=E5=A4=A9?= Date: Sun, 28 May 2023 01:26:27 +0000 Subject: [PATCH 096/172] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hans/ --- lib/l10n/app_zh.arb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index f071232f2..3efe8899a 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -472,12 +472,14 @@ } } }, - "bufferDurationSubtitle": "播放器需要播放多久,以秒为单位。需要重启。", + "bufferDurationSubtitle": "播放器需要提前缓冲多长时间的音乐,以秒为单位。需要重启才能生效。", "@bufferDurationSubtitle": {}, "removedFromPlaylist": "已从播放列表删除。", "@removedFromPlaylist": {}, "removeFromPlaylistTitle": "从播放列表删除", "@removeFromPlaylistTitle": {}, "removeFromPlaylistTooltip": "从播放列表删除", - "@removeFromPlaylistTooltip": {} + "@removeFromPlaylistTooltip": {}, + "language": "语言", + "@language": {} } From 1e88f1b06a74cf5fbae2d1c6ebcbf68bf965d76f Mon Sep 17 00:00:00 2001 From: Filip Cuk Date: Wed, 7 Jun 2023 15:50:07 +0200 Subject: [PATCH 097/172] Added translation using Weblate (Croatian) --- lib/l10n/app_hr.arb | 1 + 1 file changed, 1 insertion(+) create mode 100644 lib/l10n/app_hr.arb diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/lib/l10n/app_hr.arb @@ -0,0 +1 @@ +{} From 2b6cdadf2ed179b46d76ce9b23a014e8f6aa1756 Mon Sep 17 00:00:00 2001 From: Filip Cuk Date: Wed, 7 Jun 2023 13:51:46 +0000 Subject: [PATCH 098/172] Translated using Weblate (Croatian) Currently translated at 1.1% (2 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/hr/ --- lib/l10n/app_hr.arb | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index 0967ef424..2bb58e6d0 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -1 +1,14 @@ -{} +{ + "serverUrl": "URL Servera", + "@serverUrl": {}, + "startupError": "Dogodila se greška prilikom pokretanja aplikacije. Greška: {error}\n\nMolimo napravite issue na github.com/UnicornsOnLSD/finamp sa slikom zaslona ove stranice. Ako problem i dalje nastavi, pokušajte očistiti podatke aplikacije i resetirati ju.", + "@startupError": { + "description": "The error message that shows when startup fails.", + "placeholders": { + "error": { + "type": "String", + "example": "Failed to open download DB" + } + } + } +} From 0cbcd7dc71a94b8a518edaffcc9703610d0a576c Mon Sep 17 00:00:00 2001 From: Filip Cuk Date: Wed, 7 Jun 2023 14:02:28 +0000 Subject: [PATCH 099/172] Translated using Weblate (Croatian) Currently translated at 100.0% (171 of 171 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/hr/ --- lib/l10n/app_hr.arb | 471 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 471 insertions(+) diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index 2bb58e6d0..b28b73aec 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -10,5 +10,476 @@ "example": "Failed to open download DB" } } + }, + "sortOrder": "Redoslijed sortiranja", + "@sortOrder": {}, + "selectMusicLibraries": "Odaberi glazbenu knjižnicu", + "@selectMusicLibraries": { + "description": "App bar title for library select screen" + }, + "username": "Korisničko ime", + "@username": {}, + "password": "Lozinka", + "@password": {}, + "logs": "Dnevnik logova", + "@logs": {}, + "next": "Sljedeće", + "@next": {}, + "genres": "Žanrovi", + "@genres": {}, + "music": "Glazba", + "@music": {}, + "name": "Ime", + "@name": {}, + "random": "Nasumično", + "@random": {}, + "revenue": "Prihod", + "@revenue": {}, + "runtime": "Vrijeme trajanja", + "@runtime": {}, + "downloadMissingImages": "Preuzmi nedostajuće fotografije", + "@downloadMissingImages": {}, + "noErrors": "Nema grešaka!", + "@noErrors": {}, + "failedToGetSongFromDownloadId": "Neuspjelo dohvaćanje pjesme s ID preuzimanja", + "@failedToGetSongFromDownloadId": {}, + "error": "Greška", + "@error": {}, + "playButtonLabel": "POKRENI", + "@playButtonLabel": {}, + "favourite": "Omiljeno", + "@favourite": {}, + "location": "Lokacija", + "@location": {}, + "applicationLegalese": "Licencirano sa Mozilla Public License 2.0. Izvorni kod dostupan na:\n\ngithub.com/jmshrv/finamp", + "@applicationLegalese": {}, + "transcoding": "Transkodiranje", + "@transcoding": {}, + "logOut": "Odjavi se", + "@logOut": {}, + "unknownError": "Nepoznata greška", + "@unknownError": {}, + "pathReturnSlashErrorMessage": "Putovi koji vraćaju \"/\" ne mogu se koristiti", + "@pathReturnSlashErrorMessage": {}, + "shuffleAllSongCount": "Izmiješaj sve pjesme", + "@shuffleAllSongCount": {}, + "list": "Lista", + "@list": {}, + "portrait": "Portret", + "@portrait": {}, + "landscape": "Pejzaž", + "@landscape": {}, + "gridCrossAxisCount": "{value} broj rešetkastih linija", + "@gridCrossAxisCount": { + "description": "List tile title for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "Portrait" + } + } + }, + "showTextOnGridView": "Prikaži tekst u rešetkastom pregledu", + "@showTextOnGridView": {}, + "showTextOnGridViewSubtitle": "Da li prikazati tekst (naslov, umjetnik itd..) na rešetkastom glazbenom ekranu.", + "@showTextOnGridViewSubtitle": {}, + "showCoverAsPlayerBackground": "Prikaži mutan cover kao pozadinu playera", + "@showCoverAsPlayerBackground": {}, + "tabs": "Tabovi", + "@tabs": {}, + "streaming": "STRUJANJE", + "@streaming": {}, + "statusError": "STATUS GREŠKE", + "@statusError": {}, + "removedFromPlaylist": "Uklonjeno iz popisa za reprodukciju.", + "@removedFromPlaylist": {}, + "startingInstantMix": "Pokretanje instant miksa.", + "@startingInstantMix": {}, + "anErrorHasOccured": "Dogodila se greška.", + "@anErrorHasOccured": {}, + "responseError401": "{error} Status kod {statusCode}. Ovo vjerojatno znači da ste koristili pokrešno korisničko ime/lozinku, ili vaš kljent nije više prijavljen.", + "@responseError401": { + "placeholders": { + "error": { + "type": "String", + "example": "Unauthorized" + }, + "statusCode": { + "type": "int", + "example": "401" + } + } + }, + "removeFromMix": "Ukloni iz miksa", + "@removeFromMix": {}, + "bufferDuration": "Trajanje međuspremnika", + "@bufferDuration": {}, + "bufferDurationSubtitle": "Koliko playera treba spremiti u međuspremnik, u sekundama. Potreban je reset.", + "@bufferDurationSubtitle": {}, + "language": "Jezik", + "@language": {}, + "unknownName": "Nepoznato ime", + "@unknownName": {}, + "songs": "Pjesme", + "@songs": {}, + "startMixNoSongsArtist": "Pritisni i drži na umjetnika da dodate ili uklonite iz graditelja mixa prije početka mixa", + "@startMixNoSongsArtist": { + "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" + }, + "urlStartWithHttps": "URL mora početi sa http:// ili https://", + "@urlStartWithHttps": { + "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" + }, + "artists": "Umjetnici", + "@artists": {}, + "internalExternalIpExplanation": "Ako želite pristupiti Jellyfin serveru udaljeno (remotely), morate korisititi externu IP adresu.\n\nAko je Vaš server na HTTP portu (80/443), ne morate navesti port. Ovo je vrlo vjerojatno slučaj ako je vaš server iza obrnute proxy.", + "@internalExternalIpExplanation": { + "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." + }, + "emptyServerUrl": "URL Servera je smije biti prazan", + "@emptyServerUrl": { + "description": "Error message that shows when the user submits a login without a server URL" + }, + "urlTrailingSlash": "URL ne smije sadržavati kosu crtu na kraju", + "@urlTrailingSlash": { + "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" + }, + "albums": "Albumi", + "@albums": {}, + "couldNotFindLibraries": "Nemoguće pronaći knjižnicu.", + "@couldNotFindLibraries": { + "description": "Error message when the user does not have any libraries" + }, + "playlists": "Popis za reprodukciju", + "@playlists": {}, + "startMix": "Pokreni Mix", + "@startMix": {}, + "playCount": "Broj reprodukcija", + "@playCount": {}, + "datePlayed": "Datum reprodukcije", + "@datePlayed": {}, + "startMixNoSongsAlbum": "Pritisni i drži na album da dodate ili uklonite iz graditelja mixa prije početka mixa", + "@startMixNoSongsAlbum": { + "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" + }, + "shuffleAll": "Izmiješaj sve", + "@shuffleAll": {}, + "album": "Album", + "@album": {}, + "communityRating": "Ocjena zajednice", + "@communityRating": {}, + "clear": "Očisti", + "@clear": {}, + "finamp": "Finamp", + "@finamp": {}, + "favourites": "Omiljeno", + "@favourites": {}, + "downloads": "Preuzimanja", + "@downloads": {}, + "settings": "Postavke", + "@settings": {}, + "offlineMode": "Način izvan mreže", + "@offlineMode": {}, + "sortBy": "Sortiraj po", + "@sortBy": {}, + "productionYear": "Godina produkcije", + "@productionYear": {}, + "albumArtist": "Album Umjetnik", + "@albumArtist": {}, + "dateAdded": "Datum dodavanja", + "@dateAdded": {}, + "artist": "Umjetnik", + "@artist": {}, + "budget": "Budžet", + "@budget": {}, + "premiereDate": "Datum premijere", + "@premiereDate": {}, + "criticRating": "Ocjena kritičara", + "@criticRating": {}, + "downloadedMissingImages": "{count,plural, =0{Nema nedostajućih fotografija} =1{Preuzeta {count} nedostajuća fotografija} other{Preuzeto {count} nedostajućih fotografija}}", + "@downloadedMissingImages": { + "description": "Message that shows when the user downloads missing images", + "placeholders": { + "count": { + "type": "int" + } + } + }, + "editPlaylistNameTitle": "Uredi ime popisa za reprodukciju", + "@editPlaylistNameTitle": {}, + "customLocation": "Prilagođena lokacija", + "@customLocation": {}, + "viewTypeSubtitle": "Vidi tip za glazbeni ekran", + "@viewTypeSubtitle": {}, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + }, + "discNumber": "Disk {number}", + "@discNumber": { + "placeholders": { + "number": { + "type": "int" + } + } + }, + "downloadCount": "{count,plural, =1{{count} preuzimanje} other{{count} preuzimanja}}", + "@downloadCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrors": "Preuzmi greške", + "@downloadErrors": {}, + "downloadedItemsCount": "{count,plural,=1{{count} stvar} other{{count} stvari}}", + "@downloadedItemsCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadedImagesCount": "{count,plural,=1{{count} fotografija} other{{count} fotografija}}", + "@downloadedImagesCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlFailed": "{count} neuspjelo", + "@dlFailed": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlComplete": "{count} dovršeno", + "@dlComplete": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "downloadErrorsTitle": "Preuzmi greške", + "@downloadErrorsTitle": {}, + "editPlaylistNameTooltip": "Uredi ime popisa za reprodukciju", + "@editPlaylistNameTooltip": {}, + "playlistNameUpdated": "Ime popisa za reprodukciju ažurirano.", + "@playlistNameUpdated": {}, + "dlEnqueued": "{count} u redu čekanja", + "@dlEnqueued": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "dlRunning": "{count} pokrenuto", + "@dlRunning": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "errorScreenError": "Dogodila se greška prilikom dohvaćanja liste grešaka! Jedino što preostaje je napraviti issue na GitHub-u i izbrisati podatke aplikacije", + "@errorScreenError": {}, + "shuffleButtonLabel": "IZMIJEŠAJ", + "@shuffleButtonLabel": {}, + "addDownloads": "Dodaj preuzimanja", + "@addDownloads": {}, + "songCount": "{count,plural,=1{{count} Pjesma} other{{count} Pjesama}}", + "@songCount": { + "placeholders": { + "count": { + "type": "int" + } + } + }, + "required": "Obavezno", + "@required": {}, + "downloadsAdded": "Preuzimanja dodana.", + "@downloadsAdded": {}, + "updateButtonLabel": "AŽURIRAJ", + "@updateButtonLabel": {}, + "downloadsDeleted": "Preuzimanja izbrisana.", + "@downloadsDeleted": {}, + "addButtonLabel": "DODAJ", + "@addButtonLabel": {}, + "shareLogs": "Podijeli zapise logova", + "@shareLogs": {}, + "logsCopied": "Zapisi logova kopirani.", + "@logsCopied": {}, + "message": "Poruka", + "@message": {}, + "stackTrace": "Trag Stacka", + "@stackTrace": {}, + "layoutAndTheme": "Izgled i tema", + "@layoutAndTheme": {}, + "customLocationsBuggy": "Prilagođene lokacije su izrazito pune grešaka zbog problema oko dozvola. Razmišljam o načinima da ovo ispravim, ali za sada ne bih preporučio korištenje.", + "@customLocationsBuggy": {}, + "shuffleAllSongCountSubtitle": "Količina pjesama da se učita kada koristite izmiješaj sve pjesme tipku.", + "@shuffleAllSongCountSubtitle": {}, + "noArtist": "Nema umjetnika", + "@noArtist": {}, + "downloadLocations": "Lokacija preuzimanja", + "@downloadLocations": {}, + "audioService": "Audio Servis", + "@audioService": {}, + "areYouSure": "Jeste li sigurni?", + "@areYouSure": {}, + "jellyfinUsesAACForTranscoding": "Jellyfin koristi AAC za transkodiranje", + "@jellyfinUsesAACForTranscoding": {}, + "notAvailableInOfflineMode": "Nije dostupno u načinu izvan mreže", + "@notAvailableInOfflineMode": {}, + "addDownloadLocation": "Dodaj lokaciju preuzimanja", + "@addDownloadLocation": {}, + "directoryMustBeEmpty": "Direktorij mora biti prazan", + "@directoryMustBeEmpty": {}, + "enterLowPriorityStateOnPauseSubtitle": "Omogućuje brisanje obavijesti kada je pauzirano. Također omogućuje Androidu da prekine uslugu kada je pauzirana.", + "@enterLowPriorityStateOnPauseSubtitle": {}, + "grid": "Rešetka", + "@grid": {}, + "showCoverAsPlayerBackgroundSubtitle": "Da li koristiti mutni cover kao pozadinu na ekranu playera.", + "@showCoverAsPlayerBackgroundSubtitle": {}, + "disableGesture": "Ugasi geste", + "@disableGesture": {}, + "theme": "Tema", + "@theme": {}, + "dark": "Tamno", + "@dark": {}, + "downloadedSongsWillNotBeDeleted": "Preuzete pjesme neće biti izbrisane", + "@downloadedSongsWillNotBeDeleted": {}, + "bitrate": "Bitrate", + "@bitrate": {}, + "bitrateSubtitle": "Veći bitrate daje veću kvalitetu zvuka s troškom većeg prometa.", + "@bitrateSubtitle": {}, + "setSleepTimer": "Postavi vrijeme spavanja", + "@setSleepTimer": {}, + "downloaded": "PREUZETO", + "@downloaded": {}, + "selectDirectory": "Odaberi direktorij", + "@selectDirectory": {}, + "enableTranscoding": "Omogući transkodiranje", + "@enableTranscoding": {}, + "enableTranscodingSubtitle": "Transkodira stream glazbe na server strani.", + "@enableTranscodingSubtitle": {}, + "addToPlaylistTooltip": "Dodaj u popis za reprodukciju", + "@addToPlaylistTooltip": {}, + "goToAlbum": "Idi na album", + "@goToAlbum": {}, + "appDirectory": "Direktorij aplikacije", + "@appDirectory": {}, + "enterLowPriorityStateOnPause": "Unesite stanje niskog prioriteta za vrijeme pauze", + "@enterLowPriorityStateOnPause": {}, + "gridCrossAxisCountSubtitle": "Količina rešetkastih polja po redu kada {value}.", + "@gridCrossAxisCountSubtitle": { + "description": "List tile subtitle for grid cross axis count. Value will either be the portrait or landscape key.", + "placeholders": { + "value": { + "type": "String", + "example": "landscape" + } + } + }, + "hideSongArtistsIfSameAsAlbumArtists": "Sakrij umjetnike pjesme ako je isti kao umjetnik albuma", + "@hideSongArtistsIfSameAsAlbumArtists": {}, + "minutes": "Minuta", + "@minutes": {}, + "viewType": "Vidi tip", + "@viewType": {}, + "createButtonLabel": "KREIRAJ", + "@createButtonLabel": {}, + "sleepTimerTooltip": "Vrijeme spavanja", + "@sleepTimerTooltip": {}, + "addToPlaylistTitle": "Dodaj u reprodukciju", + "@addToPlaylistTitle": {}, + "removeFromPlaylistTitle": "Ukloni iz popisa za reprodukciju", + "@removeFromPlaylistTitle": {}, + "newPlaylist": "Novi popis za reprodukciju", + "@newPlaylist": {}, + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Da li prikazati umjetnike pjesme na ekranu albuma ako se ne razlikuje od umjetnika albuma.", + "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, + "disableGestureSubtitle": "Da li ugasiti geste.", + "@disableGestureSubtitle": {}, + "system": "Sistem", + "@system": {}, + "light": "Svijetlo", + "@light": {}, + "cancelSleepTimer": "Ugasi vrijeme spavanja?", + "@cancelSleepTimer": {}, + "yesButtonLabel": "DA", + "@yesButtonLabel": {}, + "noButtonLabel": "NE", + "@noButtonLabel": {}, + "invalidNumber": "Nemoguć broj", + "@invalidNumber": {}, + "removeFromPlaylistTooltip": "Ukloni iz popisa za reprodukciju", + "@removeFromPlaylistTooltip": {}, + "unknownArtist": "Nepoznat umjetnik", + "@unknownArtist": {}, + "transcode": "TRANSKODIRAJ", + "@transcode": {}, + "playlistCreated": "Popis za reprodukciju kreiran.", + "@playlistCreated": {}, + "noAlbum": "Nema albuma", + "@noAlbum": {}, + "noItem": "Nema stvari", + "@noItem": {}, + "queue": "Red čekanja", + "@queue": {}, + "direct": "DIREKTNO", + "@direct": {}, + "addToQueue": "Dodaj u red čekanja", + "@addToQueue": {}, + "instantMix": "Instant miks", + "@instantMix": {}, + "responseError": "{error} status kod {statusCode}.", + "@responseError": { + "placeholders": { + "error": { + "type": "String", + "example": "Forbidden" + }, + "statusCode": { + "type": "int", + "example": "403" + } + } + }, + "replaceQueue": "Zamijeni red čekanja", + "@replaceQueue": {}, + "addedToQueue": "Dodano u red čekanja.", + "@addedToQueue": {}, + "removeFavourite": "Izbriši omiljene", + "@removeFavourite": {}, + "addFavourite": "Dodaj omiljene", + "@addFavourite": {}, + "queueReplaced": "Red čekanja zamijenjen.", + "@queueReplaced": {}, + "addToMix": "Dodaj u miks", + "@addToMix": {}, + "redownloadedItems": "{count,plural, =0{Nema potrebnih ponovnih preuzimanja.} =1{Ponovno preuzeta {count} stvar} other{Ponovno preuzeto {count} stvari}}", + "@redownloadedItems": { + "placeholders": { + "count": { + "type": "int" + } + } } } From eda6763667b41f68ee2cc0314d26a5d32323e2cc Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Mon, 12 Jun 2023 14:48:38 +0000 Subject: [PATCH 100/172] Translated using Weblate (Spanish) Currently translated at 100.0% (173 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/es/ --- lib/l10n/app_es.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 008c97d3e..7aa623d47 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -481,5 +481,9 @@ "removedFromPlaylist": "Eliminado de la lista de reproducción.", "@removedFromPlaylist": {}, "language": "Idioma", - "@language": {} + "@language": {}, + "confirm": "Confirmar", + "@confirm": {}, + "showUncensoredLogMessage": "Este registro contiene tu información de acceso. ¿Mostrar?", + "@showUncensoredLogMessage": {} } From 5e657d5129cc81d8ef1ec5058766496cca209095 Mon Sep 17 00:00:00 2001 From: Ivan Date: Fri, 16 Jun 2023 06:51:37 +0000 Subject: [PATCH 101/172] Translated using Weblate (Russian) Currently translated at 99.4% (172 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ru/ --- lib/l10n/app_ru.arb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index 2b3d4b685..eb424b1d6 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -13,7 +13,7 @@ "@urlTrailingSlash": { "description": "Error message that shows when the user submits a server URL that ends with a trailing slash (for example, http://0.0.0.0/)" }, - "selectMusicLibraries": "Выбрать библеотеку", + "selectMusicLibraries": "Выбрать библиотеку", "@selectMusicLibraries": { "description": "App bar title for library select screen" }, @@ -481,5 +481,9 @@ "removeFromPlaylistTitle": "Удалить из Плейлиста", "@removeFromPlaylistTitle": {}, "language": "Язык", - "@language": {} + "@language": {}, + "confirm": "Подтвердить", + "@confirm": {}, + "showUncensoredLogMessage": "Этот журнал содержит вашу регистрационную информацию. Показать?", + "@showUncensoredLogMessage": {} } From bef9e976af21159d7907be1b60f6c483c80e1ea3 Mon Sep 17 00:00:00 2001 From: FaboThePlayer Date: Sat, 17 Jun 2023 10:13:09 +0000 Subject: [PATCH 102/172] Translated using Weblate (Polish) Currently translated at 100.0% (173 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/pl/ --- lib/l10n/app_pl.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 528356db4..51edba1ba 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -481,5 +481,9 @@ "removedFromPlaylist": "Usunięto z listy odtwarzania.", "@removedFromPlaylist": {}, "language": "Język", - "@language": {} + "@language": {}, + "confirm": "Potwierdź", + "@confirm": {}, + "showUncensoredLogMessage": "Ten log zawiera twoje dane logowania. Pokazać?", + "@showUncensoredLogMessage": {} } From 479c122047b1da5152b0db7990495578fbc233dc Mon Sep 17 00:00:00 2001 From: Yonggan Date: Sun, 18 Jun 2023 13:14:34 +0200 Subject: [PATCH 103/172] Add to playlist in playlist --- .flutter | 2 +- lib/components/AlbumScreen/song_list_tile.dart | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.flutter b/.flutter index 4d9e56e69..4b1264501 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf +Subproject commit 4b12645012342076800eb701bcdfe18f87da21cf diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index 610fdf7f6..2cb7880d6 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -229,6 +229,16 @@ class _SongListTileState extends State { title: Text(AppLocalizations.of(context)!.replaceQueue), ), ), + if (widget.isInPlaylist) + PopupMenuItem( + enabled: !isOffline, + value: SongListTileMenuItems.addToPlaylist, + child: ListTile( + leading: const Icon(Icons.playlist_add), + title: Text(AppLocalizations.of(context)!.addToPlaylistTitle), + enabled: !isOffline, + ), + ), widget.isInPlaylist ? PopupMenuItem( enabled: !isOffline, From 7ccbf6a3d26a6eecc8b3217e3745c505a21f2448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Mon, 19 Jun 2023 16:46:50 +0000 Subject: [PATCH 104/172] Translated using Weblate (Turkish) Currently translated at 100.0% (173 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/tr/ --- lib/l10n/app_tr.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index f8b7e7a60..c0d255e65 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -481,5 +481,9 @@ } }, "language": "Dil", - "@language": {} + "@language": {}, + "confirm": "Onayla", + "@confirm": {}, + "showUncensoredLogMessage": "Bu günlük oturum açma bilgilerinizi içerir. Gösterilsin mi?", + "@showUncensoredLogMessage": {} } From 2a8a793d68c227fa225337ec445fafadd7cc82b5 Mon Sep 17 00:00:00 2001 From: CakesTwix Date: Tue, 20 Jun 2023 13:34:55 +0000 Subject: [PATCH 105/172] Translated using Weblate (Ukrainian) Currently translated at 100.0% (173 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/uk/ --- lib/l10n/app_uk.arb | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_uk.arb b/lib/l10n/app_uk.arb index 70ac79a6f..4a7481892 100644 --- a/lib/l10n/app_uk.arb +++ b/lib/l10n/app_uk.arb @@ -467,5 +467,23 @@ "message": "Повідомлення", "@message": {}, "stackTrace": "Трасування стека", - "@stackTrace": {} + "@stackTrace": {}, + "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", + "@downloadedItemsImagesCount": { + "description": "This is for merging downloadedItemsCount and downloadedImagesCount as Flutter's intl stuff doesn't support multiple plurals in one string. https://github.com/flutter/flutter/issues/86906", + "placeholders": { + "downloadedItems": { + "type": "String", + "example": "12 downloads" + }, + "downloadedImages": { + "type": "String", + "example": "1 image" + } + } + }, + "confirm": "Підтвердити", + "@confirm": {}, + "showUncensoredLogMessage": "Цей журнал містить ваші дані для входу. Показати?", + "@showUncensoredLogMessage": {} } From 7289eb0468ec9616b16f118c4c30e1b42158daed Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 28 Jun 2023 08:43:18 +0100 Subject: [PATCH 106/172] Add reorderable tabs --- .../TabsSettingsScreen/hide_tab_toggle.dart | 1 + lib/l10n/app_en.arb | 3 +- lib/models/finamp_models.dart | 5 ++ lib/models/finamp_models.g.dart | 15 +++++- lib/screens/music_screen.dart | 8 +-- lib/screens/tabs_settings_screen.dart | 51 +++++++++++++++++-- lib/services/finamp_settings_helper.dart | 17 +++++++ 7 files changed, 90 insertions(+), 10 deletions(-) diff --git a/lib/components/TabsSettingsScreen/hide_tab_toggle.dart b/lib/components/TabsSettingsScreen/hide_tab_toggle.dart index a4fd94f97..ddae160d1 100644 --- a/lib/components/TabsSettingsScreen/hide_tab_toggle.dart +++ b/lib/components/TabsSettingsScreen/hide_tab_toggle.dart @@ -19,6 +19,7 @@ class HideTabToggle extends StatelessWidget { builder: (_, box, __) { return SwitchListTile.adaptive( title: Text(tabContentType.toLocalisedString(context)), + // secondary: const Icon(Icons.drag_handle), // This should never be null, but it gets set to true if it is. value: FinampSettingsHelper.finampSettings.showTabs[tabContentType] ?? true, diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 40fe18a5e..2f65ccc86 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -482,5 +482,6 @@ "@bufferDurationSubtitle": {}, "language": "Language", "confirm": "Confirm", - "showUncensoredLogMessage": "This log contains your login information. Show?" + "showUncensoredLogMessage": "This log contains your login information. Show?", + "resetTabs": "Reset tabs" } \ No newline at end of file diff --git a/lib/models/finamp_models.dart b/lib/models/finamp_models.dart index 178f40624..c3d087872 100644 --- a/lib/models/finamp_models.dart +++ b/lib/models/finamp_models.dart @@ -53,6 +53,7 @@ const _showCoverAsPlayerBackground = true; const _hideSongArtistsIfSameAsAlbumArtists = true; const _disableGesture = false; const _bufferDurationSeconds = 50; +const _tabOrder = TabContentType.values; @HiveType(typeId: 28) class FinampSettings { @@ -84,6 +85,7 @@ class FinampSettings { this.bufferDurationSeconds = _bufferDurationSeconds, required this.tabSortBy, required this.tabSortOrder, + this.tabOrder = _tabOrder, }); @HiveField(0) @@ -167,6 +169,9 @@ class FinampSettings { @HiveField(21, defaultValue: {}) Map tabSortOrder; + @HiveField(22, defaultValue: _tabOrder) + List tabOrder; + static Future create() async { final internalSongDir = await getInternalSongDir(); final downloadLocation = DownloadLocation.create( diff --git a/lib/models/finamp_models.g.dart b/lib/models/finamp_models.g.dart index a9935509e..3c3b0c64f 100644 --- a/lib/models/finamp_models.g.dart +++ b/lib/models/finamp_models.g.dart @@ -99,13 +99,22 @@ class FinampSettingsAdapter extends TypeAdapter { tabSortOrder: fields[21] == null ? {} : (fields[21] as Map).cast(), + tabOrder: fields[22] == null + ? [ + TabContentType.albums, + TabContentType.artists, + TabContentType.playlists, + TabContentType.genres, + TabContentType.songs + ] + : (fields[22] as List).cast(), )..disableGesture = fields[19] == null ? false : fields[19] as bool; } @override void write(BinaryWriter writer, FinampSettings obj) { writer - ..writeByte(22) + ..writeByte(23) ..writeByte(0) ..write(obj.isOffline) ..writeByte(1) @@ -149,7 +158,9 @@ class FinampSettingsAdapter extends TypeAdapter { ..writeByte(20) ..write(obj.tabSortBy) ..writeByte(21) - ..write(obj.tabSortOrder); + ..write(obj.tabSortOrder) + ..writeByte(22) + ..write(obj.tabOrder); } @override diff --git a/lib/screens/music_screen.dart b/lib/screens/music_screen.dart index 0743558c4..5b89eed94 100644 --- a/lib/screens/music_screen.dart +++ b/lib/screens/music_screen.dart @@ -165,9 +165,11 @@ class _MusicScreenState extends State valueListenable: FinampSettingsHelper.finampSettingsListener, builder: (context, value, _) { final finampSettings = value.get("FinampSettings"); - final tabs = finampSettings!.showTabs.entries - .where((e) => e.value) - .map((e) => e.key); + + // Get the tabs from the user's tab order, and filter them to only + // include enabled tabs + final tabs = finampSettings!.tabOrder.where((e) => + FinampSettingsHelper.finampSettings.showTabs[e] ?? false); if (tabs.length != _tabController?.length) { _musicScreenLogger.info( diff --git a/lib/screens/tabs_settings_screen.dart b/lib/screens/tabs_settings_screen.dart index 8f9754df4..8db98240f 100644 --- a/lib/screens/tabs_settings_screen.dart +++ b/lib/screens/tabs_settings_screen.dart @@ -1,25 +1,68 @@ +import 'package:finamp/services/finamp_settings_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import '../models/finamp_models.dart'; import '../components/TabsSettingsScreen/hide_tab_toggle.dart'; -class TabsSettingsScreen extends StatelessWidget { +class TabsSettingsScreen extends StatefulWidget { const TabsSettingsScreen({Key? key}) : super(key: key); static const routeName = "/settings/tabs"; + @override + State createState() => _TabsSettingsScreenState(); +} + +class _TabsSettingsScreenState extends State { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: Text(AppLocalizations.of(context)!.tabs), + actions: [ + IconButton( + onPressed: () { + setState(() { + FinampSettingsHelper.resetTabs(); + }); + }, + icon: const Icon(Icons.refresh), + tooltip: AppLocalizations.of(context)!.resetTabs, + ) + ], ), body: Scrollbar( - child: ListView.builder( - itemCount: TabContentType.values.length, + child: ReorderableListView.builder( + // buildDefaultDragHandles: false, + itemCount: FinampSettingsHelper.finampSettings.tabOrder.length, itemBuilder: (context, index) { - return HideTabToggle(tabContentType: TabContentType.values[index]); + return HideTabToggle( + tabContentType: + FinampSettingsHelper.finampSettings.tabOrder[index], + key: + ValueKey(FinampSettingsHelper.finampSettings.tabOrder[index]), + ); + }, + onReorder: (oldIndex, newIndex) { + // It's a bit of a hack to call setState with no actual widget + // state, but it saves us from using listeners + setState(() { + // For some weird reason newIndex is one above what it should be + // when oldIndex is lower. This if statement is in Flutter's + // ReorderableListView documentation. + if (oldIndex < newIndex) { + newIndex -= 1; + } + + final oldValue = + FinampSettingsHelper.finampSettings.tabOrder[oldIndex]; + final newValue = + FinampSettingsHelper.finampSettings.tabOrder[newIndex]; + + FinampSettingsHelper.setTabOrder(oldIndex, newValue); + FinampSettingsHelper.setTabOrder(newIndex, oldValue); + }); }, ), ), diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index 696a70dbc..816610390 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -189,4 +189,21 @@ class FinampSettingsHelper { Hive.box("FinampSettings") .put("FinampSettings", finampSettingsTemp); } + + static void setTabOrder(int index, TabContentType tabContentType) { + FinampSettings finampSettingsTemp = finampSettings; + finampSettingsTemp.tabOrder[index] = tabContentType; + Hive.box("FinampSettings") + .put("FinampSettings", finampSettingsTemp); + } + + static void resetTabs() { + FinampSettings finampSettingsTemp = finampSettings; + finampSettingsTemp.tabOrder = TabContentType.values; + finampSettingsTemp.showTabs = Map.fromEntries( + TabContentType.values.map( + (e) => MapEntry(e, true), + ), + ); + } } From 24438a89497069ff1d5ff5ae623cf1f9ee446b26 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 17 Jul 2023 00:10:40 +0100 Subject: [PATCH 107/172] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index f5983829a..6f892db96 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.15+33 +version: 0.6.15+35 environment: sdk: ">=2.19.4 <3.0.0" From c1e6a16d21641558ff71bd45c17f78a22cb0365d Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 17 Jul 2023 02:25:48 +0100 Subject: [PATCH 108/172] Index image downloads by blurhash instead of ID This stops Finamp from downloading identical images. Blurhashes are used to test this as it doesn't require downloading the images to check if they're identical. Note that this commit does not include a migration from the old ID-based system, so weird things will happen if you update Finamp to this commit. --- lib/models/jellyfin_models.dart | 3 ++ lib/services/downloads_helper.dart | 53 +++++++++++++++++------------- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/lib/models/jellyfin_models.dart b/lib/models/jellyfin_models.dart index 4d8a7d195..ee292f75f 100644 --- a/lib/models/jellyfin_models.dart +++ b/lib/models/jellyfin_models.dart @@ -2198,6 +2198,9 @@ class BaseItemDto { return null; } + /// The first primary blurhash of this item. + String? get blurHash => imageBlurHashes?.primary?.values.first; + factory BaseItemDto.fromJson(Map json) => _$BaseItemDtoFromJson(json); Map toJson() => _$BaseItemDtoToJson(this); diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 9db97d713..da67f3316 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -83,8 +83,8 @@ class DownloadsHelper { item: parent, downloadedChildren: {}, viewId: viewId)); } - if (parent.imageId != null && - !_downloadedImagesBox.containsKey(parent.imageId) && + if (parent.blurHash != null && + !_downloadedImagesBox.containsKey(parent.blurHash) && parent.hasOwnImage) { _downloadsLogger .info("Downloading parent image for ${parent.name} (${parent.id}"); @@ -186,14 +186,14 @@ class DownloadsHelper { _downloadIdsBox.put(songDownloadId, songInfo); - // If the item has an image ID, handle getting/noting the downloaded + // If the item has an blurhash, handle getting/noting the downloaded // image. - if (item.imageId != null) { - if (_downloadedImagesBox.containsKey(item.imageId)) { + if (item.blurHash != null) { + if (_downloadedImagesBox.containsKey(item.blurHash)) { _downloadsLogger.info( - "Image ${item.imageId} already exists in downloadedImagesBox, adding requiredBy to DownloadedImage."); + "Image ${item.blurHash} already exists in downloadedImagesBox, adding requiredBy to DownloadedImage."); - final downloadedImage = _downloadedImagesBox.get(item.imageId)!; + final downloadedImage = _downloadedImagesBox.get(item.blurHash)!; downloadedImage.requiredBy.add(item.id); @@ -667,8 +667,8 @@ class DownloadsHelper { } DownloadedImage? getDownloadedImage(BaseItemDto item) { - if (item.imageId != null) { - return _downloadedImagesBox.get(item.imageId); + if (item.blurHash != null) { + return _downloadedImagesBox.get(item.blurHash); } else { return null; } @@ -685,8 +685,8 @@ class DownloadsHelper { // Get an iterable of downloaded items where the download has an image but // that image isn't downloaded Iterable missingItems = downloadedItems.where((element) => - element.song.imageId != null && - !_downloadedImagesBox.containsKey(element.song.imageId)); + element.song.blurHash != null && + !_downloadedImagesBox.containsKey(element.song.blurHash)); List> verifyFutures = []; @@ -703,8 +703,8 @@ class DownloadsHelper { // If any downloads were invalid, regenerate the iterable if (verifyResults.contains(false)) { missingItems = downloadedItems.where((element) => - element.song.imageId != null && - !_downloadedImagesBox.containsKey(element.song.imageId)); + element.song.blurHash != null && + !_downloadedImagesBox.containsKey(element.song.blurHash)); } final List> downloadFutures = []; @@ -719,8 +719,8 @@ class DownloadsHelper { Iterable missingParents = downloadedParents.where( (element) => - element.item.imageId != null && - !_downloadedImagesBox.containsKey(element.item.imageId)); + element.item.blurHash != null && + !_downloadedImagesBox.containsKey(element.item.blurHash)); verifyFutures = []; @@ -743,8 +743,8 @@ class DownloadsHelper { if (verifyResults.contains(false)) { missingParents = downloadedParents.where((element) => - element.item.imageId != null && - !_downloadedImagesBox.containsKey(element.item.imageId)); + element.item.blurHash != null && + !_downloadedImagesBox.containsKey(element.item.blurHash)); } for (final missingParent in missingParents) { @@ -777,7 +777,8 @@ class DownloadsHelper { await getDownloadsWithStatus(DownloadTaskStatus.failed); if (failedDownloadTasks?.isEmpty ?? true) { - _downloadsLogger.info("Failed downloads list is empty -> not redownloading anything"); + _downloadsLogger + .info("Failed downloads list is empty -> not redownloading anything"); return 0; } @@ -813,7 +814,6 @@ class DownloadsHelper { parentItems[downloadedSong.song.id]! .add(await _jellyfinApiData.getItemById(parent)); - } } @@ -892,14 +892,18 @@ class DownloadsHelper { /// given item has an image. If the item does not have an image, the function /// will throw an assert error. The function will return immediately if an /// image with the same ID is already downloaded. + /// + /// As of 0.6.15, images are indexed by blurhash to ensure that duplicate + /// images are not downloaded (many albums will have an identical image + /// per-song). Future _downloadImage({ required BaseItemDto item, required Directory downloadDir, required DownloadLocation downloadLocation, }) async { - assert(item.imageId != null); + assert(item.blurHash != null); - if (_downloadedImagesBox.containsKey(item.imageId)) return; + if (_downloadedImagesBox.containsKey(item.blurHash)) return; final imageUrl = _jellyfinApiData.getImageUrl( item: item, @@ -910,6 +914,9 @@ class DownloadsHelper { final tokenHeader = _jellyfinApiData.getTokenHeader(); final relativePath = path_helper.relative(downloadDir.path, from: downloadLocation.path); + + // We still use imageIds for filenames despite switching to blurhashes as + // blurhashes can include characters that filesystems don't support final fileName = item.imageId; final imageDownloadId = await FlutterDownloader.enqueue( @@ -925,11 +932,11 @@ class DownloadsHelper { if (imageDownloadId == null) { _downloadsLogger.severe( - "Adding image download for ${item.imageId} failed! downloadId is null. This only really happens if something goes horribly wrong with flutter_downloader's platform interface. This should never happen..."); + "Adding image download for ${item.blurHash} failed! downloadId is null. This only really happens if something goes horribly wrong with flutter_downloader's platform interface. This should never happen..."); } final imageInfo = DownloadedImage.create( - id: item.imageId!, + id: item.blurHash!, downloadId: imageDownloadId!, path: path_helper.join(relativePath, fileName), requiredBy: [item.id], From 64e1f11f87675c533f6d36d3f740dadbf53a19d0 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 19 Jul 2023 16:35:06 +0100 Subject: [PATCH 109/172] Revert "Fix in Music Screen Tab" This reverts commit b1209325b70f48d926464c0f938fcc8144fe6b64. I'm not sure what this commit does, but the music screen tab works fine in the current master (my fault for leaving this PR for so long lol) --- .../MusicScreen/music_screen_tab_view.dart | 614 ++++++++---------- 1 file changed, 285 insertions(+), 329 deletions(-) diff --git a/lib/components/MusicScreen/music_screen_tab_view.dart b/lib/components/MusicScreen/music_screen_tab_view.dart index b0c21d439..cb7e429c2 100644 --- a/lib/components/MusicScreen/music_screen_tab_view.dart +++ b/lib/components/MusicScreen/music_screen_tab_view.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; import 'package:hive/hive.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; -import '../../services/media_state_stream.dart'; + import '../../models/jellyfin_models.dart'; import '../../models/finamp_models.dart'; import '../../services/finamp_user_helper.dart'; @@ -134,349 +134,305 @@ class _MusicScreenTabViewState extends State Widget build(BuildContext context) { super.build(context); - return StreamBuilder( - stream: mediaStateStream, - builder: (context, snapshot) { - BaseItemDto? currentlyPlayingItem; - if (snapshot.hasData) { - // If we have a media item and the player hasn't finished, show - // the now playing bar. - if (snapshot.data!.mediaItem != null) { - final item = BaseItemDto.fromJson( - snapshot.data!.mediaItem!.extras!["itemJson"]); - currentlyPlayingItem = item; - } - } - return ValueListenableBuilder>( - valueListenable: FinampSettingsHelper.finampSettingsListener, - builder: (context, box, _) { - final isOffline = box.get("FinampSettings")?.isOffline ?? false; + return ValueListenableBuilder>( + valueListenable: FinampSettingsHelper.finampSettingsListener, + builder: (context, box, _) { + final isOffline = box.get("FinampSettings")?.isOffline ?? false; - if (isOffline) { - // We do the same checks we do when online to ensure that the list is - // not resorted when it doesn't have to be. - if (widget.searchTerm != _lastSearch || - offlineSortedItems == null || - widget.isFavourite != _oldIsFavourite || - widget.sortBy != _oldSortBy || - widget.sortOrder != _oldSortOrder || - widget.view != _oldView) { - _lastSearch = widget.searchTerm; - _oldIsFavourite = widget.isFavourite; - _oldSortBy = widget.sortBy; - _oldSortOrder = widget.sortOrder; - _oldView = widget.view; + if (isOffline) { + // We do the same checks we do when online to ensure that the list is + // not resorted when it doesn't have to be. + if (widget.searchTerm != _lastSearch || + offlineSortedItems == null || + widget.isFavourite != _oldIsFavourite || + widget.sortBy != _oldSortBy || + widget.sortOrder != _oldSortOrder || + widget.view != _oldView) { + _lastSearch = widget.searchTerm; + _oldIsFavourite = widget.isFavourite; + _oldSortBy = widget.sortBy; + _oldSortOrder = widget.sortOrder; + _oldView = widget.view; - DownloadsHelper downloadsHelper = - GetIt.instance(); + DownloadsHelper downloadsHelper = GetIt.instance(); - if (widget.tabContentType == TabContentType.artists) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.cloud_off, - size: 64, - color: Colors.white.withOpacity(0.5), - ), - const Padding(padding: EdgeInsets.all(8.0)), - const Text( - "Offline artists view hasn't been implemented") - ], - ), - ); - } + if (widget.tabContentType == TabContentType.artists) { + return Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.cloud_off, + size: 64, + color: Colors.white.withOpacity(0.5), + ), + const Padding(padding: EdgeInsets.all(8.0)), + const Text("Offline artists view hasn't been implemented") + ], + ), + ); + } - if (widget.searchTerm == null) { - if (widget.tabContentType == TabContentType.songs) { - // If we're on the songs tab, just get all of the downloaded items - offlineSortedItems = downloadsHelper.downloadedItems - .where((element) => - element.viewId == - _finampUserHelper.currentUser!.currentViewId) - .map((e) => e.song) - .toList(); + if (widget.searchTerm == null) { + if (widget.tabContentType == TabContentType.songs) { + // If we're on the songs tab, just get all of the downloaded items + offlineSortedItems = downloadsHelper.downloadedItems + .where((element) => + element.viewId == + _finampUserHelper.currentUser!.currentViewId) + .map((e) => e.song) + .toList(); + } else { + offlineSortedItems = downloadsHelper.downloadedParents + .where((element) => + element.item.type == + _includeItemTypes(widget.tabContentType) && + element.viewId == + _finampUserHelper.currentUser!.currentViewId) + .map((e) => e.item) + .toList(); + } + } else { + if (widget.tabContentType == TabContentType.songs) { + offlineSortedItems = downloadsHelper.downloadedItems + .where( + (element) { + return _offlineSearch( + item: element.song, + searchTerm: widget.searchTerm!, + tabContentType: widget.tabContentType); + }, + ) + .map((e) => e.song) + .toList(); + } else { + offlineSortedItems = downloadsHelper.downloadedParents + .where( + (element) { + return _offlineSearch( + item: element.item, + searchTerm: widget.searchTerm!, + tabContentType: widget.tabContentType); + }, + ) + .map((e) => e.item) + .toList(); + } + } + + offlineSortedItems!.sort((a, b) { + // if (a.name == null || b.name == null) { + // // Returning 0 is the same as both being the same + // return 0; + // } else { + // return a.name!.compareTo(b.name!); + // } + if (a.name == null || b.name == null) { + // Returning 0 is the same as both being the same + return 0; + } else { + switch (widget.sortBy) { + case SortBy.sortName: + if (a.name == null || b.name == null) { + // Returning 0 is the same as both being the same + return 0; } else { - offlineSortedItems = downloadsHelper.downloadedParents - .where((element) => - element.item.type == - _includeItemTypes(widget.tabContentType) && - element.viewId == - _finampUserHelper.currentUser!.currentViewId) - .map((e) => e.item) - .toList(); + return a.name!.compareTo(b.name!); } - } else { - if (widget.tabContentType == TabContentType.songs) { - offlineSortedItems = downloadsHelper.downloadedItems - .where( - (element) { - return _offlineSearch( - item: element.song, - searchTerm: widget.searchTerm!, - tabContentType: widget.tabContentType); - }, - ) - .map((e) => e.song) - .toList(); + case SortBy.albumArtist: + if (a.albumArtist == null || b.albumArtist == null) { + return 0; } else { - offlineSortedItems = downloadsHelper.downloadedParents - .where( - (element) { - return _offlineSearch( - item: element.item, - searchTerm: widget.searchTerm!, - tabContentType: widget.tabContentType); - }, - ) - .map((e) => e.item) - .toList(); + return a.albumArtist!.compareTo(b.albumArtist!); } - } - - offlineSortedItems!.sort((a, b) { - // if (a.name == null || b.name == null) { - // // Returning 0 is the same as both being the same - // return 0; - // } else { - // return a.name!.compareTo(b.name!); - // } - if (a.name == null || b.name == null) { - // Returning 0 is the same as both being the same + case SortBy.communityRating: + if (a.communityRating == null || + b.communityRating == null) { return 0; } else { - switch (widget.sortBy) { - case SortBy.sortName: - if (a.name == null || b.name == null) { - // Returning 0 is the same as both being the same - return 0; - } else { - return a.name!.compareTo(b.name!); - } - case SortBy.albumArtist: - if (a.albumArtist == null || b.albumArtist == null) { - return 0; - } else { - return a.albumArtist!.compareTo(b.albumArtist!); - } - case SortBy.communityRating: - if (a.communityRating == null || - b.communityRating == null) { - return 0; - } else { - return a.communityRating! - .compareTo(b.communityRating!); - } - case SortBy.criticRating: - if (a.criticRating == null || - b.criticRating == null) { - return 0; - } else { - return a.criticRating!.compareTo(b.criticRating!); - } - case SortBy.dateCreated: - if (a.dateCreated == null || b.dateCreated == null) { - return 0; - } else { - return a.dateCreated!.compareTo(b.dateCreated!); - } - case SortBy.premiereDate: - if (a.premiereDate == null || - b.premiereDate == null) { - return 0; - } else { - return a.premiereDate!.compareTo(b.premiereDate!); - } - case SortBy.random: - // We subtract the result by one so that we can get -1 values - // (see comareTo documentation) - return Random().nextInt(2) - 1; - default: - throw UnimplementedError( - "Unimplemented offline sort mode ${widget.sortBy}"); - } + return a.communityRating!.compareTo(b.communityRating!); } - }); - - if (widget.sortOrder == SortOrder.descending) { - // The above sort functions sort in ascending order, so we swap them - // when sorting in descending order. - offlineSortedItems = offlineSortedItems!.reversed.toList(); - } + case SortBy.criticRating: + if (a.criticRating == null || b.criticRating == null) { + return 0; + } else { + return a.criticRating!.compareTo(b.criticRating!); + } + case SortBy.dateCreated: + if (a.dateCreated == null || b.dateCreated == null) { + return 0; + } else { + return a.dateCreated!.compareTo(b.dateCreated!); + } + case SortBy.premiereDate: + if (a.premiereDate == null || b.premiereDate == null) { + return 0; + } else { + return a.premiereDate!.compareTo(b.premiereDate!); + } + case SortBy.random: + // We subtract the result by one so that we can get -1 values + // (see comareTo documentation) + return Random().nextInt(2) - 1; + default: + throw UnimplementedError( + "Unimplemented offline sort mode ${widget.sortBy}"); } + } + }); - return Scrollbar( - child: box.get("FinampSettings")!.contentViewType == - ContentViewType.list - ? ListView.builder( - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - itemCount: offlineSortedItems!.length, - key: UniqueKey(), - itemBuilder: (context, index) { - if (widget.tabContentType == - TabContentType.songs) { - return SongListTile( - item: offlineSortedItems![index], - isSong: true, - currentlyPlaying: currentlyPlayingItem != null - ? currentlyPlayingItem.id == offlineSortedItems![index].id - : false, - ); - } else { - return AlbumItem( - album: offlineSortedItems![index], - parentType: _getParentType(), - ); - } - }, - ) - : GridView.builder( - itemCount: offlineSortedItems!.length, - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context) - .size - .width > - MediaQuery.of(context).size.height - ? box - .get("FinampSettings")! - .contentGridViewCrossAxisCountLandscape - : box - .get("FinampSettings")! - .contentGridViewCrossAxisCountPortrait, - ), - itemBuilder: (context, index) { - if (widget.tabContentType == - TabContentType.songs) { - return SongListTile( - item: offlineSortedItems![index], - isSong: true, - currentlyPlaying: currentlyPlayingItem != null - ? currentlyPlayingItem.id == offlineSortedItems![index].id - : false - ); - } else { - return AlbumItem( - album: offlineSortedItems![index], - parentType: _getParentType(), - isGrid: true, - gridAddSettingsListener: false, - ); - } - }, - )); - } else { - // If the searchTerm argument is different to lastSearch, the user has changed their search input. - // This makes albumViewFuture search again so that results with the search are shown. - // This also means we don't redo a search unless we actaully need to. - if (widget.searchTerm != _lastSearch || - _pagingController.itemList == null || - widget.isFavourite != _oldIsFavourite || - widget.sortBy != _oldSortBy || - widget.sortOrder != _oldSortOrder || - widget.view != _oldView) { - _lastSearch = widget.searchTerm; - _oldIsFavourite = widget.isFavourite; - _oldSortBy = widget.sortBy; - _oldSortOrder = widget.sortOrder; - _oldView = widget.view; - _pagingController.refresh(); - } + if (widget.sortOrder == SortOrder.descending) { + // The above sort functions sort in ascending order, so we swap them + // when sorting in descending order. + offlineSortedItems = offlineSortedItems!.reversed.toList(); + } + } - return RefreshIndicator( - // RefreshIndicator wants an async function, so we use Future.sync() - // to run refresh() inside an async function - onRefresh: () => - Future.sync(() => _pagingController.refresh()), - child: Scrollbar( - child: box.get("FinampSettings")!.contentViewType == - ContentViewType.list - ? PagedListView( - pagingController: _pagingController, - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - builderDelegate: - PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - if (widget.tabContentType == - TabContentType.songs) { - return SongListTile( - item: item, - isSong: true, - currentlyPlaying: currentlyPlayingItem != null - ? currentlyPlayingItem.id == item.id - : false - ); - } else if (widget.tabContentType == - TabContentType.artists) { - return ArtistListTile(item: item); - } else { - return AlbumItem( - album: item, - parentType: _getParentType(), - ); - } - }, - firstPageProgressIndicatorBuilder: (_) => - const FirstPageProgressIndicator(), - newPageProgressIndicatorBuilder: (_) => - const NewPageProgressIndicator(), - ), - ) - : PagedGridView( - pagingController: _pagingController, - keyboardDismissBehavior: - ScrollViewKeyboardDismissBehavior.onDrag, - builderDelegate: - PagedChildBuilderDelegate( - itemBuilder: (context, item, index) { - if (widget.tabContentType == - TabContentType.songs) { - return SongListTile( - item: item, - isSong: true, - currentlyPlaying: currentlyPlayingItem != null - ? currentlyPlayingItem.id == item.id - : false, - ); - } else { - return AlbumItem( - album: item, - parentType: _getParentType(), - isGrid: true, - gridAddSettingsListener: false, - ); - } - }, - firstPageProgressIndicatorBuilder: (_) => - const FirstPageProgressIndicator(), - newPageProgressIndicatorBuilder: (_) => - const NewPageProgressIndicator(), - ), - gridDelegate: - SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: MediaQuery.of(context) - .size - .width > - MediaQuery.of(context).size.height - ? box - .get("FinampSettings")! - .contentGridViewCrossAxisCountLandscape - : box - .get("FinampSettings")! - .contentGridViewCrossAxisCountPortrait, - ), - ), - ), - ); - } - }, + return Scrollbar( + child: box.get("FinampSettings")!.contentViewType == + ContentViewType.list + ? ListView.builder( + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + itemCount: offlineSortedItems!.length, + key: UniqueKey(), + itemBuilder: (context, index) { + if (widget.tabContentType == TabContentType.songs) { + return SongListTile( + item: offlineSortedItems![index], + isSong: true, + ); + } else { + return AlbumItem( + album: offlineSortedItems![index], + parentType: _getParentType(), + ); + } + }, + ) + : GridView.builder( + itemCount: offlineSortedItems!.length, + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context).size.width > + MediaQuery.of(context).size.height + ? box + .get("FinampSettings")! + .contentGridViewCrossAxisCountLandscape + : box + .get("FinampSettings")! + .contentGridViewCrossAxisCountPortrait, + ), + itemBuilder: (context, index) { + if (widget.tabContentType == TabContentType.songs) { + return SongListTile( + item: offlineSortedItems![index], + isSong: true, + ); + } else { + return AlbumItem( + album: offlineSortedItems![index], + parentType: _getParentType(), + isGrid: true, + gridAddSettingsListener: false, + ); + } + }, + )); + } else { + // If the searchTerm argument is different to lastSearch, the user has changed their search input. + // This makes albumViewFuture search again so that results with the search are shown. + // This also means we don't redo a search unless we actaully need to. + if (widget.searchTerm != _lastSearch || + _pagingController.itemList == null || + widget.isFavourite != _oldIsFavourite || + widget.sortBy != _oldSortBy || + widget.sortOrder != _oldSortOrder || + widget.view != _oldView) { + _lastSearch = widget.searchTerm; + _oldIsFavourite = widget.isFavourite; + _oldSortBy = widget.sortBy; + _oldSortOrder = widget.sortOrder; + _oldView = widget.view; + _pagingController.refresh(); + } + + return RefreshIndicator( + // RefreshIndicator wants an async function, so we use Future.sync() + // to run refresh() inside an async function + onRefresh: () => Future.sync(() => _pagingController.refresh()), + child: Scrollbar( + child: box.get("FinampSettings")!.contentViewType == + ContentViewType.list + ? PagedListView( + pagingController: _pagingController, + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + if (widget.tabContentType == TabContentType.songs) { + return SongListTile( + item: item, + isSong: true, + ); + } else if (widget.tabContentType == + TabContentType.artists) { + return ArtistListTile(item: item); + } else { + return AlbumItem( + album: item, + parentType: _getParentType(), + ); + } + }, + firstPageProgressIndicatorBuilder: (_) => + const FirstPageProgressIndicator(), + newPageProgressIndicatorBuilder: (_) => + const NewPageProgressIndicator(), + ), + ) + : PagedGridView( + pagingController: _pagingController, + keyboardDismissBehavior: + ScrollViewKeyboardDismissBehavior.onDrag, + builderDelegate: PagedChildBuilderDelegate( + itemBuilder: (context, item, index) { + if (widget.tabContentType == TabContentType.songs) { + return SongListTile( + item: item, + isSong: true, + ); + } else { + return AlbumItem( + album: item, + parentType: _getParentType(), + isGrid: true, + gridAddSettingsListener: false, + ); + } + }, + firstPageProgressIndicatorBuilder: (_) => + const FirstPageProgressIndicator(), + newPageProgressIndicatorBuilder: (_) => + const NewPageProgressIndicator(), + ), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: MediaQuery.of(context).size.width > + MediaQuery.of(context).size.height + ? box + .get("FinampSettings")! + .contentGridViewCrossAxisCountLandscape + : box + .get("FinampSettings")! + .contentGridViewCrossAxisCountPortrait, + ), + ), + ), ); - }); + } + }, + ); } } From df834778a6a89eade649b83594deffc1e810d99a Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 19 Jul 2023 17:14:49 +0100 Subject: [PATCH 110/172] Wrap whole SongListTile in StreamBuilder This allows us to update the visualiser within SongListTile --- .../AlbumScreen/song_list_tile.dart | 211 +++++++++--------- 1 file changed, 106 insertions(+), 105 deletions(-) diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index a6d3459bf..35e12f33d 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -30,24 +30,24 @@ enum SongListTileMenuItems { } class SongListTile extends StatefulWidget { - const SongListTile( - {Key? key, - required this.item, - - /// Children that are related to this list tile, such as the other songs in - /// the album. This is used to give the audio service all the songs for the - /// item. If null, only this song will be given to the audio service. - this.children, - - /// Index of the song in whatever parent this widget is in. Used to start - /// the audio service at a certain index, such as when selecting the middle - /// song in an album. - this.index, - this.parentId, - this.isSong = false, - this.showArtists = true, - this.currentlyPlaying = false}) - : super(key: key); + const SongListTile({ + Key? key, + required this.item, + + /// Children that are related to this list tile, such as the other songs in + /// the album. This is used to give the audio service all the songs for the + /// item. If null, only this song will be given to the audio service. + this.children, + + /// Index of the song in whatever parent this widget is in. Used to start + /// the audio service at a certain index, such as when selecting the middle + /// song in an album. + this.index, + this.parentId, + this.isSong = false, + this.showArtists = true, + this.currentlyPlaying = false, + }) : super(key: key); final BaseItemDto item; final List? children; @@ -74,101 +74,102 @@ class _SongListTileState extends State { Widget build(BuildContext context) { final screenSize = MediaQuery.of(context).size; - final listTile = ListTile( - leading: AlbumImage(item: mutableItem), - title: StreamBuilder( + final listTile = StreamBuilder( stream: _audioHandler.mediaItem, builder: (context, snapshot) { - return RichText( - text: TextSpan( - children: [ - // third condition checks if the item is viewed from its album (instead of e.g. a playlist) - // same horrible check as in canGoToAlbum in GestureDetector below - if (mutableItem.indexNumber != null && - !widget.isSong && - mutableItem.albumId == widget.parentId) + return ListTile( + leading: AlbumImage(item: mutableItem), + title: RichText( + text: TextSpan( + children: [ + // third condition checks if the item is viewed from its album (instead of e.g. a playlist) + // same horrible check as in canGoToAlbum in GestureDetector below + if (mutableItem.indexNumber != null && + !widget.isSong && + mutableItem.albumId == widget.parentId) + TextSpan( + text: "${mutableItem.indexNumber}. ", + style: + TextStyle(color: Theme.of(context).disabledColor)), TextSpan( - text: "${mutableItem.indexNumber}. ", - style: TextStyle(color: Theme.of(context).disabledColor)), - TextSpan( - text: mutableItem.name ?? - AppLocalizations.of(context)!.unknownName, - style: TextStyle( - color: snapshot.data?.extras?["itemJson"]["Id"] == - mutableItem.id && - snapshot.data?.extras?["itemJson"]["AlbumId"] == - widget.parentId - ? Theme.of(context).colorScheme.secondary - : null, + text: mutableItem.name ?? + AppLocalizations.of(context)!.unknownName, + style: TextStyle( + color: snapshot.data?.extras?["itemJson"]["Id"] == + mutableItem.id && + snapshot.data?.extras?["itemJson"]["AlbumId"] == + widget.parentId + ? Theme.of(context).colorScheme.secondary + : null, + ), ), - ), - ], - style: Theme.of(context).textTheme.subtitle1, - ), - ); - }, - ), - subtitle: RichText( - text: TextSpan( - children: [ - WidgetSpan( - child: Transform.translate( - offset: const Offset(-3, 0), - child: DownloadedIndicator( - item: mutableItem, - size: Theme.of(context).textTheme.bodyText2!.fontSize! + 3, - ), + ], + style: Theme.of(context).textTheme.subtitle1, ), - alignment: PlaceholderAlignment.top, ), - TextSpan( - text: printDuration(Duration( - microseconds: (mutableItem.runTimeTicks == null - ? 0 - : mutableItem.runTimeTicks! ~/ 10))), - style: TextStyle( - color: Theme.of(context) - .textTheme - .bodyText2 - ?.color - ?.withOpacity(0.7)), + subtitle: RichText( + text: TextSpan( + children: [ + WidgetSpan( + child: Transform.translate( + offset: const Offset(-3, 0), + child: DownloadedIndicator( + item: mutableItem, + size: Theme.of(context).textTheme.bodyText2!.fontSize! + + 3, + ), + ), + alignment: PlaceholderAlignment.top, + ), + TextSpan( + text: printDuration(Duration( + microseconds: (mutableItem.runTimeTicks == null + ? 0 + : mutableItem.runTimeTicks! ~/ 10))), + style: TextStyle( + color: Theme.of(context) + .textTheme + .bodyText2 + ?.color + ?.withOpacity(0.7)), + ), + if (widget.showArtists) + TextSpan( + text: + " · ${processArtist(mutableItem.artists?.join(", ") ?? mutableItem.albumArtist, context)}", + style: TextStyle(color: Theme.of(context).disabledColor), + ) + ], + ), + overflow: TextOverflow.ellipsis, ), - if (widget.showArtists) - TextSpan( - text: - " · ${processArtist(mutableItem.artists?.join(", ") ?? mutableItem.albumArtist, context)}", - style: TextStyle(color: Theme.of(context).disabledColor), - ) - ], - ), - overflow: TextOverflow.ellipsis, - ), - trailing: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Visibility( - visible: widget.currentlyPlaying, - child: const Padding( - padding: EdgeInsets.fromLTRB(0, 0, 8, 0), - child: MiniMusicVisualizer( - color: Colors.blue, - width: 4, - height: 15, + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Visibility( + visible: widget.currentlyPlaying, + child: const Padding( + padding: EdgeInsets.fromLTRB(0, 0, 8, 0), + child: MiniMusicVisualizer( + color: Colors.blue, + width: 4, + height: 15, + ), + )), + FavoriteButton( + item: mutableItem, + onlyIfFav: true, + ), + ], ), - )), - FavoriteButton( - item: mutableItem, - onlyIfFav: true, - ), - ], - ), - onTap: () { - _audioServiceHelper.replaceQueueWithItem( - itemList: widget.children ?? [mutableItem], - initialIndex: widget.index ?? 0, - ); - }, - ); + onTap: () { + _audioServiceHelper.replaceQueueWithItem( + itemList: widget.children ?? [mutableItem], + initialIndex: widget.index ?? 0, + ); + }, + ); + }); return GestureDetector( onLongPressStart: (details) async { From 10cf2853fec3b307cf11f9da0cd0b380de42f6aa Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 19 Jul 2023 17:20:28 +0100 Subject: [PATCH 111/172] Remove StreamBuilder from SongsSliverList --- .../AlbumScreen/album_screen_content.dart | 66 +++++++------------ .../AlbumScreen/song_list_tile.dart | 33 +++++----- 2 files changed, 40 insertions(+), 59 deletions(-) diff --git a/lib/components/AlbumScreen/album_screen_content.dart b/lib/components/AlbumScreen/album_screen_content.dart index 0b35afb63..63f1947db 100644 --- a/lib/components/AlbumScreen/album_screen_content.dart +++ b/lib/components/AlbumScreen/album_screen_content.dart @@ -10,7 +10,6 @@ import 'album_screen_content_flexible_space_bar.dart'; import 'download_button.dart'; import 'song_list_tile.dart'; import 'playlist_name_edit_button.dart'; -import '../../services/media_state_stream.dart'; class AlbumScreenContent extends StatelessWidget { const AlbumScreenContent({ @@ -113,47 +112,28 @@ class _SongsSliverList extends StatelessWidget { // Adding this offset ensures playback starts for nth song on correct disc. int indexOffset = childrenForQueue.indexOf(childrenForList[0]); - return StreamBuilder( - stream: mediaStateStream, - builder: (context, snapshot) { - BaseItemDto? currentlyPlayingItem; - if (snapshot.hasData) { - // If we have a media item and the player hasn't finished, show - // the now playing bar. - if (snapshot.data!.mediaItem != null) { - final item = BaseItemDto.fromJson( - snapshot.data!.mediaItem!.extras!["itemJson"]); - currentlyPlayingItem = item; - } - } - return SliverList( - delegate: - SliverChildBuilderDelegate((BuildContext context, int index) { - final BaseItemDto item = childrenForList[index]; - return SongListTile( - item: item, - children: childrenForQueue, - index: index + indexOffset, - parentId: parent.id, - // show artists except for this one scenario - showArtists: !( - // we're on album screen - parent.type == "MusicAlbum" - // "hide song artists if they're the same as album artists" == true - && - FinampSettingsHelper - .finampSettings.hideSongArtistsIfSameAsAlbumArtists - // song artists == album artists - && - setEquals( - parent.albumArtists?.map((e) => e.name).toSet(), - item.artists?.toSet())), - currentlyPlaying: currentlyPlayingItem != null - ? currentlyPlayingItem.id == item.id - : false, - ); - }, childCount: childrenForList.length), - ); - }); + return SliverList( + delegate: SliverChildBuilderDelegate((BuildContext context, int index) { + final BaseItemDto item = childrenForList[index]; + return SongListTile( + item: item, + children: childrenForQueue, + index: index + indexOffset, + parentId: parent.id, + // show artists except for this one scenario + showArtists: !( + // we're on album screen + parent.type == "MusicAlbum" + // "hide song artists if they're the same as album artists" == true + && + FinampSettingsHelper + .finampSettings.hideSongArtistsIfSameAsAlbumArtists + // song artists == album artists + && + setEquals(parent.albumArtists?.map((e) => e.name).toSet(), + item.artists?.toSet())), + ); + }, childCount: childrenForList.length), + ); } } diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index 35e12f33d..4ce14ad64 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -46,7 +46,6 @@ class SongListTile extends StatefulWidget { this.parentId, this.isSong = false, this.showArtists = true, - this.currentlyPlaying = false, }) : super(key: key); final BaseItemDto item; @@ -55,7 +54,6 @@ class SongListTile extends StatefulWidget { final bool isSong; final String? parentId; final bool showArtists; - final bool currentlyPlaying; @override State createState() => _SongListTileState(); @@ -77,6 +75,13 @@ class _SongListTileState extends State { final listTile = StreamBuilder( stream: _audioHandler.mediaItem, builder: (context, snapshot) { + // I think past me did this check directly from the JSON for + // performance. It works for now, apologies if you're debugging it + // years in the future. + final isCurrentlyPlaying = snapshot.data?.extras?["itemJson"]["Id"] == + mutableItem.id && + snapshot.data?.extras?["itemJson"]["AlbumId"] == widget.parentId; + return ListTile( leading: AlbumImage(item: mutableItem), title: RichText( @@ -95,10 +100,7 @@ class _SongListTileState extends State { text: mutableItem.name ?? AppLocalizations.of(context)!.unknownName, style: TextStyle( - color: snapshot.data?.extras?["itemJson"]["Id"] == - mutableItem.id && - snapshot.data?.extras?["itemJson"]["AlbumId"] == - widget.parentId + color: isCurrentlyPlaying ? Theme.of(context).colorScheme.secondary : null, ), @@ -146,16 +148,15 @@ class _SongListTileState extends State { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - Visibility( - visible: widget.currentlyPlaying, - child: const Padding( - padding: EdgeInsets.fromLTRB(0, 0, 8, 0), - child: MiniMusicVisualizer( - color: Colors.blue, - width: 4, - height: 15, - ), - )), + if (isCurrentlyPlaying) + const Padding( + padding: EdgeInsets.fromLTRB(0, 0, 8, 0), + child: MiniMusicVisualizer( + color: Colors.blue, + width: 4, + height: 15, + ), + ), FavoriteButton( item: mutableItem, onlyIfFav: true, From c4e508063d985ff1b3f56b46ded5dc264cfb260d Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Wed, 19 Jul 2023 18:30:08 +0100 Subject: [PATCH 112/172] Add visualiser back again The merge conflict for SongListTile was horrible, easier to do it this way --- .../AlbumScreen/song_list_tile.dart | 164 ++++++++++-------- lib/components/favourite_button.dart | 16 +- pubspec.lock | 8 + pubspec.yaml | 1 + 4 files changed, 111 insertions(+), 78 deletions(-) diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index bb04444a1..68df8a753 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -105,86 +105,106 @@ class _SongListTileState extends State { } } - final listTile = ListTile( - leading: AlbumImage(item: widget.item), - title: StreamBuilder( + final listTile = StreamBuilder( stream: _audioHandler.mediaItem, builder: (context, snapshot) { - return RichText( - text: TextSpan( - children: [ - // third condition checks if the item is viewed from its album (instead of e.g. a playlist) - // same horrible check as in canGoToAlbum in GestureDetector below - if (widget.item.indexNumber != null && - !widget.isSong && - widget.item.albumId == widget.parentId) + // I think past me did this check directly from the JSON for + // performance. It works for now, apologies if you're debugging it + // years in the future. + final isCurrentlyPlaying = snapshot.data?.extras?["itemJson"]["Id"] == + widget.item.id && + snapshot.data?.extras?["itemJson"]["AlbumId"] == widget.parentId; + + return ListTile( + leading: AlbumImage(item: widget.item), + title: RichText( + text: TextSpan( + children: [ + // third condition checks if the item is viewed from its album (instead of e.g. a playlist) + // same horrible check as in canGoToAlbum in GestureDetector below + if (widget.item.indexNumber != null && + !widget.isSong && + widget.item.albumId == widget.parentId) + TextSpan( + text: "${widget.item.indexNumber}. ", + style: + TextStyle(color: Theme.of(context).disabledColor)), TextSpan( - text: "${widget.item.indexNumber}. ", - style: TextStyle(color: Theme.of(context).disabledColor)), - TextSpan( - text: widget.item.name ?? - AppLocalizations.of(context)!.unknownName, - style: TextStyle( - color: snapshot.data?.extras?["itemJson"]["Id"] == - widget.item.id && - snapshot.data?.extras?["itemJson"]["AlbumId"] == - widget.parentId - ? Theme.of(context).colorScheme.secondary - : null, + text: widget.item.name ?? + AppLocalizations.of(context)!.unknownName, + style: TextStyle( + color: isCurrentlyPlaying + ? Theme.of(context).colorScheme.secondary + : null, + ), ), - ), - ], - style: Theme.of(context).textTheme.titleMedium, + ], + style: Theme.of(context).textTheme.titleMedium, + ), ), - ); - }, - ), - subtitle: RichText( - text: TextSpan( - children: [ - WidgetSpan( - child: Transform.translate( - offset: const Offset(-3, 0), - child: DownloadedIndicator( - item: widget.item, - size: Theme.of(context).textTheme.bodyMedium!.fontSize! + 3, - ), + subtitle: RichText( + text: TextSpan( + children: [ + WidgetSpan( + child: Transform.translate( + offset: const Offset(-3, 0), + child: DownloadedIndicator( + item: widget.item, + size: + Theme.of(context).textTheme.bodyMedium!.fontSize! + + 3, + ), + ), + alignment: PlaceholderAlignment.top, + ), + TextSpan( + text: printDuration(Duration( + microseconds: (widget.item.runTimeTicks == null + ? 0 + : widget.item.runTimeTicks! ~/ 10))), + style: TextStyle( + color: Theme.of(context) + .textTheme + .bodyMedium + ?.color + ?.withOpacity(0.7)), + ), + if (widget.showArtists) + TextSpan( + text: + " · ${processArtist(widget.item.artists?.join(", ") ?? widget.item.albumArtist, context)}", + style: TextStyle(color: Theme.of(context).disabledColor), + ) + ], ), - alignment: PlaceholderAlignment.top, + overflow: TextOverflow.ellipsis, ), - TextSpan( - text: printDuration(Duration( - microseconds: (widget.item.runTimeTicks == null - ? 0 - : widget.item.runTimeTicks! ~/ 10))), - style: TextStyle( - color: Theme.of(context) - .textTheme - .bodyMedium - ?.color - ?.withOpacity(0.7)), + trailing: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (isCurrentlyPlaying) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: MiniMusicVisualizer( + color: Theme.of(context).colorScheme.secondary, + width: 4, + height: 15, + ), + ), + FavoriteButton( + item: widget.item, + onlyIfFav: true, + ), + ], ), - if (widget.showArtists) - TextSpan( - text: - " · ${processArtist(widget.item.artists?.join(", ") ?? widget.item.albumArtist, context)}", - style: TextStyle(color: Theme.of(context).disabledColor), - ) - ], - ), - overflow: TextOverflow.ellipsis, - ), - trailing: FavoriteButton( - item: widget.item, - onlyIfFav: true, - ), - onTap: () { - _audioServiceHelper.replaceQueueWithItem( - itemList: widget.children ?? [widget.item], - initialIndex: widget.index ?? 0, - ); - }, - ); + onTap: () { + _audioServiceHelper.replaceQueueWithItem( + itemList: widget.children ?? [widget.item], + initialIndex: widget.index ?? 0, + ); + }, + ); + }); return GestureDetector( onLongPressStart: (details) async { diff --git a/lib/components/favourite_button.dart b/lib/components/favourite_button.dart index 194b68384..57cf7f0b3 100644 --- a/lib/components/favourite_button.dart +++ b/lib/components/favourite_button.dart @@ -33,12 +33,16 @@ class _FavoriteButtonState extends State { bool isFav = widget.item!.userData!.isFavorite; if (widget.onlyIfFav) { - return Icon( - isFav ? Icons.favorite : null, - color: Colors.red, - size: 24.0, - semanticLabel: AppLocalizations.of(context)!.favourite, - ); + if (isFav) { + return Icon( + Icons.favorite, + color: Colors.red, + size: 24.0, + semanticLabel: AppLocalizations.of(context)!.favourite, + ); + } else { + return const SizedBox.shrink(); + } } else { return IconButton( icon: Icon( diff --git a/pubspec.lock b/pubspec.lock index d815bc5e3..f2955dc41 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -647,6 +647,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + mini_music_visualizer: + dependency: "direct main" + description: + name: mini_music_visualizer + sha256: "7f42d95663449595457edff06ff397de796166c95f2a1ab7fd87405200571802" + url: "https://pub.dev" + source: hosted + version: "1.0.2" nested: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f5983829a..3bf79dff4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -71,6 +71,7 @@ dependencies: git: url: https://github.com/lamarios/locale_names.git ref: cea057c220f4ee7e09e8f1fc7036110245770948 + mini_music_visualizer: ^1.0.2 dev_dependencies: flutter_test: From 355ce515731ce365d0928bbba369153e3f28a35c Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:50:58 +0100 Subject: [PATCH 113/172] Only show visualiser when playing --- .../AlbumScreen/song_list_tile.dart | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index 68df8a753..cc8b40f6c 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -1,4 +1,3 @@ -import 'package:audio_service/audio_service.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; @@ -9,8 +8,8 @@ import '../../services/audio_service_helper.dart'; import '../../services/jellyfin_api_helper.dart'; import '../../services/finamp_settings_helper.dart'; import '../../services/downloads_helper.dart'; +import '../../services/media_state_stream.dart'; import '../../services/process_artist.dart'; -import '../../services/music_player_background_task.dart'; import '../../screens/album_screen.dart'; import '../../screens/add_to_playlist_screen.dart'; import '../favourite_button.dart'; @@ -69,7 +68,6 @@ class SongListTile extends StatefulWidget { class _SongListTileState extends State { final _audioServiceHelper = GetIt.instance(); - final _audioHandler = GetIt.instance(); final _jellyfinApiHelper = GetIt.instance(); @override @@ -105,15 +103,17 @@ class _SongListTileState extends State { } } - final listTile = StreamBuilder( - stream: _audioHandler.mediaItem, + final listTile = StreamBuilder( + stream: mediaStateStream, builder: (context, snapshot) { // I think past me did this check directly from the JSON for // performance. It works for now, apologies if you're debugging it // years in the future. - final isCurrentlyPlaying = snapshot.data?.extras?["itemJson"]["Id"] == - widget.item.id && - snapshot.data?.extras?["itemJson"]["AlbumId"] == widget.parentId; + final isCurrentlyPlaying = + snapshot.data?.mediaItem?.extras?["itemJson"]["Id"] == + widget.item.id && + snapshot.data?.mediaItem?.extras?["itemJson"]["AlbumId"] == + widget.parentId; return ListTile( leading: AlbumImage(item: widget.item), @@ -182,7 +182,8 @@ class _SongListTileState extends State { trailing: Row( mainAxisSize: MainAxisSize.min, children: [ - if (isCurrentlyPlaying) + if (isCurrentlyPlaying && + (snapshot.data?.playbackState.playing ?? false)) Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: MiniMusicVisualizer( From fdb07dc61f2b48098cae9cf9854ed5739e60d8d4 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 20 Jul 2023 15:51:23 +0100 Subject: [PATCH 114/172] Dart format --- lib/components/AlbumScreen/song_list_tile.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index cc8b40f6c..6efcf5adf 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -183,7 +183,8 @@ class _SongListTileState extends State { mainAxisSize: MainAxisSize.min, children: [ if (isCurrentlyPlaying && - (snapshot.data?.playbackState.playing ?? false)) + (snapshot.data?.playbackState.playing ?? false + )) Padding( padding: const EdgeInsets.symmetric(horizontal: 8), child: MiniMusicVisualizer( From 32cbef607c1ad6ddd335a1877cee829596166d06 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 17 Jul 2023 00:10:40 +0100 Subject: [PATCH 115/172] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 3bf79dff4..990da90e9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.15+33 +version: 0.6.15+35 environment: sdk: ">=2.19.4 <3.0.0" From 0d63cca7ce662b72355a5504e69775f449d3ae93 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Jul 2023 21:47:34 +0100 Subject: [PATCH 116/172] Add migration for blurhashes --- .../MusicScreen/music_screen_drawer.dart | 6 ++ lib/models/finamp_models.dart | 4 + lib/services/downloads_helper.dart | 75 ++++++++++++++++--- 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/lib/components/MusicScreen/music_screen_drawer.dart b/lib/components/MusicScreen/music_screen_drawer.dart index 7e24022b8..9fd8bf75f 100644 --- a/lib/components/MusicScreen/music_screen_drawer.dart +++ b/lib/components/MusicScreen/music_screen_drawer.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; +import '../../services/downloads_helper.dart'; import '../../services/finamp_user_helper.dart'; import '../../screens/downloads_screen.dart'; import '../../screens/logs_screen.dart'; @@ -51,6 +52,11 @@ class MusicScreenDrawer extends StatelessWidget { .pushNamed(DownloadsScreen.routeName), ), const OfflineModeSwitchListTile(), + ListTile( + title: Text("Migrate"), + onTap: () => GetIt.instance() + .migrateBlurhashImages(), + ), const Divider(), ], ), diff --git a/lib/models/finamp_models.dart b/lib/models/finamp_models.dart index c3d087872..ef1596632 100644 --- a/lib/models/finamp_models.dart +++ b/lib/models/finamp_models.dart @@ -86,6 +86,7 @@ class FinampSettings { required this.tabSortBy, required this.tabSortOrder, this.tabOrder = _tabOrder, + this.hasCompletedBlurhashImageMigration = true, }); @HiveField(0) @@ -172,6 +173,9 @@ class FinampSettings { @HiveField(22, defaultValue: _tabOrder) List tabOrder; + @HiveField(23, defaultValue: false) + bool hasCompletedBlurhashImageMigration; + static Future create() async { final internalSongDir = await getInternalSongDir(); final downloadLocation = DownloadLocation.create( diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index da67f3316..0b259f99f 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -355,22 +355,28 @@ class DownloadsHelper { } } + /// Deletes an image if it no longer has any dependents (requiredBy is empty) Future _handleDeleteImage(DownloadedImage downloadedImage) async { if (downloadedImage.requiredBy.isEmpty) { _downloadsLogger .info("Image ${downloadedImage.id} has no dependencies, deleting."); - _downloadsLogger.info( - "Deleting ${downloadedImage.downloadId} from flutter_downloader"); + await _deleteImage(downloadedImage); + } + } - _downloadedImagesBox.delete(downloadedImage.id); - _downloadedImageIdsBox.delete(downloadedImage.downloadId); + /// Deletes an image, without checking if anything else depends on it first. + Future _deleteImage(DownloadedImage downloadedImage) async { + _downloadsLogger + .info("Deleting ${downloadedImage.downloadId} from flutter_downloader"); - await FlutterDownloader.remove( - taskId: downloadedImage.downloadId, - shouldDeleteContent: true, - ); - } + _downloadedImagesBox.delete(downloadedImage.id); + _downloadedImageIdsBox.delete(downloadedImage.downloadId); + + await FlutterDownloader.remove( + taskId: downloadedImage.downloadId, + shouldDeleteContent: true, + ); } /// Calculates the total file size of the given directory. @@ -845,6 +851,57 @@ class DownloadsHelper { return redownloadCount; } + /// Migrates id-based images to blurhash-based images (for 0.6.15). Should + /// only be run if a migration has not been performed. + Future migrateBlurhashImages() async { + final Map map = {}; + + // Get a map to link blurhashes to images. This will be the list of images + // we keep. + for (final item in downloadedItems) { + final image = _downloadedImagesBox.get(item.song.id); + + if (image != null && item.song.blurHash != null) { + map[item.song.blurHash!] = image; + } + } + + // Do above, but for parents. + for (final parent in downloadedParents) { + final image = _downloadedImagesBox.get(parent.item.id); + + if (image != null && parent.item.blurHash != null) { + map[parent.item.blurHash!] = image; + } + } + + final imagesToKeep = map.values.toSet(); + + final imagesToDelete = downloadedImages + .where((element) => !imagesToKeep.contains(element)) + .toList(); + + // Sanity check to make sure we haven't double counted/missed an image. + final imagesCount = imagesToKeep.length + imagesToDelete.length; + if (imagesCount != downloadedImages.length) { + final err = + "Unexpected number of items in images to keep/delete! Expected ${downloadedImages.length}, got $imagesCount"; + _downloadsLogger.severe(err); + throw err; + } + + for (final image in imagesToDelete) { + await _deleteImage(image); + } + + await _downloadedImagesBox.clear(); + await _downloadedImagesBox.putAll(map); + + await _downloadedImageIdsBox.clear(); + await _downloadedImageIdsBox + .putAll(map.map((key, value) => MapEntry(value.downloadId, value.id))); + } + Iterable get downloadedItems => _downloadedItemsBox.values; Iterable get downloadedImages => _downloadedImagesBox.values; From dc17fc58348375c1ab50b51c13b6a6cf6db8155a Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:33:00 +0100 Subject: [PATCH 117/172] Automatically perform blurhash migration --- lib/main.dart | 5 +++++ lib/services/downloads_helper.dart | 8 +++++--- lib/services/finamp_settings_helper.dart | 9 +++++++++ 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 0655b8ae1..90840aab0 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -93,6 +93,11 @@ void _setupJellyfinApiData() { Future _setupDownloadsHelper() async { GetIt.instance.registerSingleton(DownloadsHelper()); + + if (!FinampSettingsHelper.finampSettings.hasCompletedBlurhashImageMigration) { + await GetIt.instance().migrateBlurhashImages(); + FinampSettingsHelper.setHasCompletedBlurhashImageMigration(true); + } } Future _setupDownloader() async { diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 0b259f99f..05f05336d 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -877,6 +877,7 @@ class DownloadsHelper { final imagesToKeep = map.values.toSet(); + // Get a list of all images not in the keep set final imagesToDelete = downloadedImages .where((element) => !imagesToKeep.contains(element)) .toList(); @@ -890,13 +891,14 @@ class DownloadsHelper { throw err; } - for (final image in imagesToDelete) { - await _deleteImage(image); - } + // Delete all images. + await Future.wait(imagesToDelete.map((e) => _deleteImage(e))); + // Clear out the images box and put the kept images back in await _downloadedImagesBox.clear(); await _downloadedImagesBox.putAll(map); + // Do the same, but with the downloadId mapping await _downloadedImageIdsBox.clear(); await _downloadedImageIdsBox .putAll(map.map((key, value) => MapEntry(value.downloadId, value.id))); diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index 816610390..6e73e298f 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -190,6 +190,15 @@ class FinampSettingsHelper { .put("FinampSettings", finampSettingsTemp); } + static void setHasCompletedBlurhashImageMigration( + bool hasCompletedBlurhashImageMigration) { + FinampSettings finampSettingsTemp = finampSettings; + finampSettingsTemp.hasCompletedBlurhashImageMigration = + hasCompletedBlurhashImageMigration; + Hive.box("FinampSettings") + .put("FinampSettings", finampSettingsTemp); + } + static void setTabOrder(int index, TabContentType tabContentType) { FinampSettings finampSettingsTemp = finampSettings; finampSettingsTemp.tabOrder[index] = tabContentType; From 509362ea33f4bbeb0f831e8b654a54b608676c8d Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:33:37 +0100 Subject: [PATCH 118/172] Remove migrate button --- lib/components/MusicScreen/music_screen_drawer.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/components/MusicScreen/music_screen_drawer.dart b/lib/components/MusicScreen/music_screen_drawer.dart index 9fd8bf75f..7e24022b8 100644 --- a/lib/components/MusicScreen/music_screen_drawer.dart +++ b/lib/components/MusicScreen/music_screen_drawer.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; -import '../../services/downloads_helper.dart'; import '../../services/finamp_user_helper.dart'; import '../../screens/downloads_screen.dart'; import '../../screens/logs_screen.dart'; @@ -52,11 +51,6 @@ class MusicScreenDrawer extends StatelessWidget { .pushNamed(DownloadsScreen.routeName), ), const OfflineModeSwitchListTile(), - ListTile( - title: Text("Migrate"), - onTap: () => GetIt.instance() - .migrateBlurhashImages(), - ), const Divider(), ], ), From 24ed45a75dbaeb6e20bb8c46282a02c75638da14 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:35:33 +0100 Subject: [PATCH 119/172] Add some log messages to blurhash migration --- lib/services/downloads_helper.dart | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 05f05336d..160c9d85a 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -854,7 +854,9 @@ class DownloadsHelper { /// Migrates id-based images to blurhash-based images (for 0.6.15). Should /// only be run if a migration has not been performed. Future migrateBlurhashImages() async { - final Map map = {}; + final Map imageMap = {}; + + _downloadsLogger.info("Performing image blurhash migration"); // Get a map to link blurhashes to images. This will be the list of images // we keep. @@ -862,7 +864,7 @@ class DownloadsHelper { final image = _downloadedImagesBox.get(item.song.id); if (image != null && item.song.blurHash != null) { - map[item.song.blurHash!] = image; + imageMap[item.song.blurHash!] = image; } } @@ -871,11 +873,11 @@ class DownloadsHelper { final image = _downloadedImagesBox.get(parent.item.id); if (image != null && parent.item.blurHash != null) { - map[parent.item.blurHash!] = image; + imageMap[parent.item.blurHash!] = image; } } - final imagesToKeep = map.values.toSet(); + final imagesToKeep = imageMap.values.toSet(); // Get a list of all images not in the keep set final imagesToDelete = downloadedImages @@ -896,12 +898,15 @@ class DownloadsHelper { // Clear out the images box and put the kept images back in await _downloadedImagesBox.clear(); - await _downloadedImagesBox.putAll(map); + await _downloadedImagesBox.putAll(imageMap); // Do the same, but with the downloadId mapping await _downloadedImageIdsBox.clear(); - await _downloadedImageIdsBox - .putAll(map.map((key, value) => MapEntry(value.downloadId, value.id))); + await _downloadedImageIdsBox.putAll( + imageMap.map((key, value) => MapEntry(value.downloadId, value.id))); + + _downloadsLogger.info("Image blurhash migration complete."); + _downloadsLogger.info("${imagesToDelete.length} duplicate images deleted."); } Iterable get downloadedItems => _downloadedItemsBox.values; From f63e96db530955fe7e818e7a4444e5e42cf88d0d Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:49:28 +0100 Subject: [PATCH 120/172] Generate Hive models --- lib/models/finamp_models.g.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/models/finamp_models.g.dart b/lib/models/finamp_models.g.dart index 3c3b0c64f..c7a16ea6b 100644 --- a/lib/models/finamp_models.g.dart +++ b/lib/models/finamp_models.g.dart @@ -108,13 +108,15 @@ class FinampSettingsAdapter extends TypeAdapter { TabContentType.songs ] : (fields[22] as List).cast(), + hasCompletedBlurhashImageMigration: + fields[23] == null ? false : fields[23] as bool, )..disableGesture = fields[19] == null ? false : fields[19] as bool; } @override void write(BinaryWriter writer, FinampSettings obj) { writer - ..writeByte(23) + ..writeByte(24) ..writeByte(0) ..write(obj.isOffline) ..writeByte(1) @@ -160,7 +162,9 @@ class FinampSettingsAdapter extends TypeAdapter { ..writeByte(21) ..write(obj.tabSortOrder) ..writeByte(22) - ..write(obj.tabOrder); + ..write(obj.tabOrder) + ..writeByte(23) + ..write(obj.hasCompletedBlurhashImageMigration); } @override From d7d85b3b2c5bae88805fd54cf0a2d4f4968aacdd Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:50:55 +0100 Subject: [PATCH 121/172] Remove obsolete TODO --- lib/main.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/main.dart b/lib/main.dart index 90840aab0..6d7c8a8d5 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -114,7 +114,6 @@ Future _setupDownloader() async { FlutterDownloader.registerCallback(_DummyCallback.callback); } -// TODO: move this function somewhere else since it's also run in MusicPlayerBackgroundTask.dart Future setupHive() async { await Hive.initFlutter(); Hive.registerAdapter(BaseItemDtoAdapter()); From a99ca2ef0adc462c94bb534c2468d098a0dc287b Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Thu, 27 Jul 2023 22:57:23 +0100 Subject: [PATCH 122/172] Set artist sort to ProductionYear,PremiereDate Albums without a PremiereDate weren't sorted correctly. This change will sort by year, and then use PremiereDate when available --- lib/components/MusicScreen/music_screen_tab_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/components/MusicScreen/music_screen_tab_view.dart b/lib/components/MusicScreen/music_screen_tab_view.dart index c8231bed5..77cc87ddd 100644 --- a/lib/components/MusicScreen/music_screen_tab_view.dart +++ b/lib/components/MusicScreen/music_screen_tab_view.dart @@ -90,7 +90,7 @@ class _MusicScreenTabViewState extends State : widget.parentItem == null ? "SortName" : widget.parentItem?.type == "MusicArtist" - ? "PremiereDate" + ? "ProductionYear,PremiereDate" : "SortName"), sortOrder: widget.sortOrder?.toString() ?? SortOrder.ascending.toString(), From db9bbcf9825c80ed47c80a5c4cbd9f94ed7561c5 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:01:39 +0100 Subject: [PATCH 123/172] Run dart fix --- lib/screens/tabs_settings_screen.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/screens/tabs_settings_screen.dart b/lib/screens/tabs_settings_screen.dart index 8db98240f..0e47bb495 100644 --- a/lib/screens/tabs_settings_screen.dart +++ b/lib/screens/tabs_settings_screen.dart @@ -2,7 +2,6 @@ import 'package:finamp/services/finamp_settings_helper.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import '../models/finamp_models.dart'; import '../components/TabsSettingsScreen/hide_tab_toggle.dart'; class TabsSettingsScreen extends StatefulWidget { From da5f4b22dffda58caf3b9079c1150530d41ba0db Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 11:56:24 +0100 Subject: [PATCH 124/172] Merge requiredBy when migrating blurhashes --- lib/services/downloads_helper.dart | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 160c9d85a..3e059d274 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -884,6 +884,26 @@ class DownloadsHelper { .where((element) => !imagesToKeep.contains(element)) .toList(); + for (final image in imagesToDelete) { + final song = getDownloadedSong(image.requiredBy.first); + + if (song != null) { + final blurHash = song.song.blurHash; + + imageMap[blurHash]?.requiredBy.addAll(image.requiredBy); + } + } + + // Go through each requiredBy and remove duplicates + for (final imageEntry in imageMap.entries) { + final image = imageEntry.value; + + image.requiredBy = image.requiredBy.toSet().toList(); + _downloadsLogger.warning(image.requiredBy); + + imageMap[imageEntry.key] = image; + } + // Sanity check to make sure we haven't double counted/missed an image. final imagesCount = imagesToKeep.length + imagesToDelete.length; if (imagesCount != downloadedImages.length) { From 8e7e01d69baf6acee4254bc9c0cac2623e1d9666 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:11:59 +0100 Subject: [PATCH 125/172] Fix deleting parent image --- lib/services/downloads_helper.dart | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 3e059d274..9373dcf85 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -327,12 +327,16 @@ class DownloadsHelper { } if (deletedFor != null) { - final downloadedImage = _downloadedImagesBox.get(deletedFor); + final parentItem = getDownloadedParent(deletedFor)?.item; - downloadedImage?.requiredBy.remove(deletedFor); + if (parentItem != null) { + final downloadedImage = getDownloadedImage(parentItem); - if (downloadedImage != null) { - deleteDownloadFutures.add(_handleDeleteImage(downloadedImage)); + downloadedImage?.requiredBy.remove(deletedFor); + + if (downloadedImage != null) { + deleteDownloadFutures.add(_handleDeleteImage(downloadedImage)); + } } } From f1bb2704b778aca1b3a9c3aeac2b37463f0e75a8 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:13:28 +0100 Subject: [PATCH 126/172] Bump version, add F-Droid metadata --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 990da90e9..42f4295b8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.15+35 +version: 0.6.15+36 environment: sdk: ">=2.19.4 <3.0.0" From 2253767ea3b3c0b3b6e949dbcb47560d3b598f35 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:22:39 +0100 Subject: [PATCH 127/172] Update flutterw version --- .flutter | 2 +- pubspec.lock | 282 ++++++++++++++++++++++++--------------------------- 2 files changed, 134 insertions(+), 150 deletions(-) diff --git a/.flutter b/.flutter index 4b1264501..f468f3366 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit 4b12645012342076800eb701bcdfe18f87da21cf +Subproject commit f468f3366c26a5092eb964a230ce7892fda8f2f8 diff --git a/pubspec.lock b/pubspec.lock index f2955dc41..97266b61e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: a36ec4843dc30ea6bf652bf25e3448db6c5e8bcf4aa55f063a5d1dad216d8214 + sha256: ae92f5d747aee634b87f89d9946000c2de774be1d6ac3e58268224348cd0101a url: "https://pub.dev" source: hosted - version: "58.0.0" + version: "61.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: cc4242565347e98424ce9945c819c192ec0838cb9d1f6aa4a97cc96becbc5b27 + sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted - version: "5.10.0" + version: "5.13.0" android_id: dependency: "direct main" description: @@ -37,10 +37,10 @@ packages: dependency: transitive description: name: args - sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.2" async: dependency: transitive description: @@ -53,10 +53,10 @@ packages: dependency: "direct main" description: name: audio_service - sha256: "7e86d7ce23caad605199f7b25e548fe7b618fb0c150fa0585f47a910fe7e7a67" + sha256: "8b719ac7982fdea1a54a528f19e345907295489c53709ba4cdee65a2955c0f4d" url: "https://pub.dev" source: hosted - version: "0.18.9" + version: "0.18.10" audio_service_platform_interface: dependency: transitive description: @@ -77,10 +77,10 @@ packages: dependency: "direct main" description: name: audio_session - sha256: e4acc4e9eaa32436dfc5d7aed7f0a370f2d7bb27ee27de30d6c4f220c2a05c73 + sha256: "8a2bc5e30520e18f3fb0e366793d78057fb64cd5287862c76af0c8771f2a52ad" url: "https://pub.dev" source: hosted - version: "0.1.13" + version: "0.1.16" auto_size_text: dependency: "direct main" description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: build - sha256: "3fbda25365741f8251b39f3917fb3c8e286a96fd068a5a242e11c2012d495777" + sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.4.1" build_config: dependency: transitive description: @@ -117,34 +117,34 @@ packages: dependency: transitive description: name: build_daemon - sha256: "757153e5d9cd88253cb13f28c2fb55a537dc31fefd98137549895b5beb7c6169" + sha256: "5f02d73eb2ba16483e693f80bee4f088563a820e47d1027d4cdfe62b5bb43e65" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "4.0.0" build_resolvers: dependency: transitive description: name: build_resolvers - sha256: db49b8609ef8c81cca2b310618c3017c00f03a92af44c04d310b907b2d692d95 + sha256: "6c4dd11d05d056e76320b828a1db0fc01ccd376922526f8e9d6c796a5adbac20" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.2.1" build_runner: dependency: "direct dev" description: name: build_runner - sha256: b0a8a7b8a76c493e85f1b84bffa0588859a06197863dba8c9036b15581fd9727 + sha256: "10c6bcdbf9d049a0b666702cf1cee4ddfdc38f02a19d35ae392863b47519848b" url: "https://pub.dev" source: hosted - version: "2.3.3" + version: "2.4.6" build_runner_core: dependency: transitive description: name: build_runner_core - sha256: "14febe0f5bac5ae474117a36099b4de6f1dbc52df6c5e55534b3da9591bf4292" + sha256: "6d6ee4276b1c5f34f21fdf39425202712d2be82019983d52f351c94aafbc2c41" url: "https://pub.dev" source: hosted - version: "7.2.7" + version: "7.2.10" built_collection: dependency: transitive description: @@ -157,10 +157,10 @@ packages: dependency: transitive description: name: built_value - sha256: "31b7c748fd4b9adf8d25d72a4c4a59ef119f12876cf414f94f8af5131d5fa2b0" + sha256: "598a2a682e2a7a90f08ba39c0aaa9374c5112340f0a2e275f61b59389543d166" url: "https://pub.dev" source: hosted - version: "8.4.4" + version: "8.6.1" characters: dependency: transitive description: @@ -173,26 +173,26 @@ packages: dependency: transitive description: name: checked_yaml - sha256: "3d1505d91afa809d177efd4eed5bb0eb65805097a1463abdd2add076effae311" + sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.0.3" chopper: dependency: "direct main" description: name: chopper - sha256: b2645618fa760df06d7609c96b092d7b3e7a8f23639d34269f62f45a5edabc7d + sha256: "23aac2db54f6a7854ed8984fba4b33222e53123c71a0ccf01d2b1087a1b5aab7" url: "https://pub.dev" source: hosted - version: "6.1.1" + version: "6.1.4" chopper_generator: dependency: "direct dev" description: name: chopper_generator - sha256: "62360a1a3536645aaee030dd7719089838a478682867d46dbcf30a3c0e5305b4" + sha256: "09d512f1acb086cf4101bef02bdb23ee7a8eb55a9935f53af866736606c57523" url: "https://pub.dev" source: hosted - version: "6.0.0" + version: "6.0.3" cli_util: dependency: transitive description: @@ -221,10 +221,10 @@ packages: dependency: transitive description: name: code_builder - sha256: "0d43dd1288fd145de1ecc9a3948ad4a6d5a82f0a14c4fdd0892260787d975cbe" + sha256: "4ad01d6e56db961d29661561effde45e519939fdaeb46c351275b182eac70189" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.0" collection: dependency: transitive description: @@ -253,10 +253,10 @@ packages: dependency: transitive description: name: crypto - sha256: aa274aa7774f8964e4f4f38cc994db7b6158dd36e9187aaceaddc994b35c6c67 + sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" cupertino_icons: dependency: "direct main" description: @@ -269,18 +269,18 @@ packages: dependency: transitive description: name: dart_style - sha256: "6d691edde054969f0e0f26abb1b30834b5138b963793e56f69d3a9a4435e6352" + sha256: "1efa911ca7086affd35f463ca2fc1799584fb6aa89883cf0af8e3664d6a02d55" url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.2" device_info_plus: dependency: "direct main" description: name: device_info_plus - sha256: "435383ca05f212760b0a70426b5a90354fe6bd65992b3a5e27ab6ede74c02f5c" + sha256: f52ab3b76b36ede4d135aab80194df8925b553686f0fa12226b4e2d658e45903 url: "https://pub.dev" source: hosted - version: "8.2.0" + version: "8.2.2" device_info_plus_platform_interface: dependency: transitive description: @@ -309,10 +309,10 @@ packages: dependency: transitive description: name: ffi - sha256: a38574032c5f1dd06c4aee541789906c12ccaab8ba01446e800d9c5b79c4a978 + sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" file: dependency: transitive description: @@ -362,10 +362,10 @@ packages: dependency: transitive description: name: flutter_cache_manager - sha256: "32cd900555219333326a2d0653aaaf8671264c29befa65bbd9856d204a4c9fb3" + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.3.1" flutter_downloader: dependency: "direct main" description: @@ -387,10 +387,10 @@ packages: dependency: "direct dev" description: name: flutter_lints - sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + sha256: "2118df84ef0c3ca93f96123a616ae8540879991b8b57af2f81b76a7ada49b2a4" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.0.2" flutter_localizations: dependency: "direct main" description: flutter @@ -400,10 +400,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: c224ac897bed083dabf11f238dd11a239809b446740be0c2044608c50029ffdf + sha256: "950e77c2bbe1692bc0874fc7fb491b96a4dc340457f4ea1641443d0a6c1ea360" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.15" flutter_riverpod: dependency: "direct main" description: @@ -442,26 +442,26 @@ packages: dependency: "direct main" description: name: get_it - sha256: f9982979e3d2f286a957c04d2c3a98f55b0f0a06ffd6c5c4abbb96f06937f463 + sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468" url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.6.0" glob: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: name: graphs - sha256: f9e130f3259f52d26f0cfc0e964513796dafed572fa52e45d2f8d6ca14db39b2 + sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19 url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.1" hive: dependency: "direct main" description: @@ -490,10 +490,10 @@ packages: dependency: transitive description: name: http - sha256: "6aa2946395183537c8b880962d935877325d6a09a2867c3970c05c0fed6ac482" + sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" url: "https://pub.dev" source: hosted - version: "0.13.5" + version: "0.13.6" http_multi_server: dependency: transitive description: @@ -514,10 +514,10 @@ packages: dependency: transitive description: name: image - sha256: "483a389d6ccb292b570c31b3a193779b1b0178e7eb571986d9a49904b6861227" + sha256: a72242c9a0ffb65d03de1b7113bc4e189686fc07c7147b8b41811d0dd0e0d9bf url: "https://pub.dev" source: hosted - version: "4.0.15" + version: "4.0.17" infinite_scroll_pagination: dependency: "direct main" description: @@ -554,50 +554,50 @@ packages: dependency: "direct main" description: name: json_annotation - sha256: c33da08e136c3df0190bd5bbe51ae1df4a7d96e7954d1d7249fea2968a72d317 + sha256: b10a7b2ff83d83c777edba3c6a0f97045ddadd56c944e1a23a3fdf43a1bf4467 url: "https://pub.dev" source: hosted - version: "4.8.0" + version: "4.8.1" json_serializable: dependency: "direct dev" description: name: json_serializable - sha256: dadc08bd61f72559f938dd08ec20dbfec6c709bba83515085ea943d2078d187a + sha256: aa1f5a8912615733e0fdc7a02af03308933c93235bdc8d50d0b0c8a8ccb0b969 url: "https://pub.dev" source: hosted - version: "6.6.1" + version: "6.7.1" just_audio: dependency: "direct main" description: name: just_audio - sha256: "7e6d31508dacd01a066e3889caf6282e5f1eb60707c230203b21a83af5c55586" + sha256: "890cd0fc41a1a4530c171e375a2a3fb6a09d84e9d508c5195f40bcff54330327" url: "https://pub.dev" source: hosted - version: "0.9.32" + version: "0.9.34" just_audio_platform_interface: dependency: transitive description: name: just_audio_platform_interface - sha256: eff112d5138bea3ba544b6338b1e0537a32b5e1425e4d0dc38f732771cda7c84 + sha256: d8409da198bbc59426cd45d4c92fca522a2ec269b576ce29459d6d6fcaeb44df url: "https://pub.dev" source: hosted - version: "4.2.0" + version: "4.2.1" just_audio_web: dependency: transitive description: name: just_audio_web - sha256: "89d8db6f19f3821bb6bf908c4bfb846079afb2ab575b783d781a6bf119e3abaf" + sha256: ff62f733f437b25a0ff590f0e295fa5441dcb465f1edbdb33b3dea264705bc13 url: "https://pub.dev" source: hosted - version: "0.4.7" + version: "0.4.8" lints: dependency: transitive description: name: lints - sha256: "5e4a9cd06d447758280a8ac2405101e0e2094d2a1dbdd3756aec3fe7775ba593" + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "2.1.1" locale_names: dependency: "direct main" description: @@ -611,10 +611,10 @@ packages: dependency: "direct main" description: name: logging - sha256: "04094f2eb032cbb06c6f6e8d3607edcfcb0455e2bb6cbc010cb01171dcb64e6d" + sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" matcher: dependency: transitive description: @@ -683,10 +683,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: cbff87676c352d97116af6dbea05aa28c4d65eb0f6d5677a520c11a69ca9a24d + sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.1.2" package_info_plus_platform_interface: dependency: transitive description: @@ -707,34 +707,34 @@ packages: dependency: "direct main" description: name: path_provider - sha256: c7edf82217d4b2952b2129a61d3ad60f1075b9299e629e149a8d2e39c2e6aad4 + sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" url: "https://pub.dev" source: hosted - version: "2.0.14" + version: "2.0.15" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: da97262be945a72270513700a92b39dd2f4a54dad55d061687e2e37a6390366a + sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" url: "https://pub.dev" source: hosted - version: "2.0.25" + version: "2.0.27" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: ad4c4d011830462633f03eb34445a45345673dfd4faf1ab0b4735fbd93b19183 + sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: "2ae08f2216225427e64ad224a24354221c2c7907e448e6e0e8b57b1eb9f10ad1" + sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 url: "https://pub.dev" source: hosted - version: "2.1.10" + version: "2.1.11" path_provider_platform_interface: dependency: transitive description: @@ -747,66 +747,58 @@ packages: dependency: transitive description: name: path_provider_windows - sha256: f53720498d5a543f9607db4b0e997c4b5438884de25b0f73098cc2671a51b130 + sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" url: "https://pub.dev" source: hosted - version: "2.1.5" - pedantic: - dependency: transitive - description: - name: pedantic - sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" - url: "https://pub.dev" - source: hosted - version: "1.11.1" + version: "2.1.7" permission_handler: dependency: "direct main" description: name: permission_handler - sha256: "33c6a1253d1f95fd06fa74b65b7ba907ae9811f9d5c1d3150e51417d04b8d6a8" + sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.4.3" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "8028362b40c4a45298f1cbfccd227c8dd6caf0e27088a69f2ba2ab15464159e2" + sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" url: "https://pub.dev" source: hosted - version: "10.2.0" + version: "10.3.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: ee96ac32f5a8e6f80756e25b25b9f8e535816c8e6665a96b6d70681f8c4f7e85 + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.0.8" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "68abbc472002b5e6dfce47fe9898c6b7d8328d58b5d2524f75e277c07a97eb84" + sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" url: "https://pub.dev" source: hosted - version: "3.9.0" + version: "3.11.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" petitparser: dependency: transitive description: name: petitparser - sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + sha256: cb3798bef7fc021ac45b308f4b51208a152792445cce0448c9a4ba5879dd8750 url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "5.4.0" platform: dependency: transitive description: @@ -819,10 +811,10 @@ packages: dependency: transitive description: name: plugin_platform_interface - sha256: "6a2128648c854906c53fa8e33986fc0247a1116122f9534dd20e3ab9e16a32bc" + sha256: "43798d895c929056255600343db8f049921cbec94d31ec87f1dc5c16c01935dd" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.1.5" pointycastle: dependency: transitive description: @@ -839,14 +831,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" provider: dependency: "direct main" description: @@ -859,18 +843,18 @@ packages: dependency: transitive description: name: pub_semver - sha256: "307de764d305289ff24ad257ad5c5793ce56d04947599ad68b3baa124105fc17" + sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" pubspec_parse: dependency: transitive description: name: pubspec_parse - sha256: ec85d7d55339d85f44ec2b682a82fea340071e8978257e5a43e69f79e98ef50c + sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" riverpod: dependency: transitive description: @@ -891,10 +875,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: "692261968a494e47323dcc8bc66d8d52e81bc27cb4b808e4e8d7e8079d4cc01a" + sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1 url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.4" share_plus_platform_interface: dependency: transitive description: @@ -907,18 +891,18 @@ packages: dependency: transitive description: name: shelf - sha256: c24a96135a2ccd62c64b69315a14adc5c3419df63b4d7c05832a346fdb73682c + sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4 url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" shelf_web_socket: dependency: transitive description: name: shelf_web_socket - sha256: a988c0e8d8ffbdb8a28aa7ec8e449c260f3deb808781fe1284d22c5bba7156e8 + sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1" url: "https://pub.dev" source: hosted - version: "1.0.3" + version: "1.0.4" simple_gesture_detector: dependency: "direct main" description: @@ -936,26 +920,26 @@ packages: dependency: transitive description: name: sliver_tools - sha256: ccdc502098a8bfa07b3ec582c282620031481300035584e1bb3aca296a505e8c + sha256: eae28220badfb9d0559207badcbbc9ad5331aac829a88cb0964d330d2a4636a6 url: "https://pub.dev" source: hosted - version: "0.2.10" + version: "0.2.12" source_gen: dependency: transitive description: name: source_gen - sha256: c2bea18c95cfa0276a366270afaa2850b09b4a76db95d546f3d003dcc7011298 + sha256: fc0da689e5302edb6177fdd964efcb7f58912f43c28c2047a808f5bfff643d16 url: "https://pub.dev" source: hosted - version: "1.2.7" + version: "1.4.0" source_helper: dependency: transitive description: name: source_helper - sha256: "3b67aade1d52416149c633ba1bb36df44d97c6b51830c2198e934e3fca87ca1f" + sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.4" source_span: dependency: transitive description: @@ -968,18 +952,18 @@ packages: dependency: transitive description: name: sqflite - sha256: e7dfb6482d5d02b661d0b2399efa72b98909e5aa7b8336e1fb37e226264ade00 + sha256: "591f1602816e9c31377d5f008c2d9ef7b8aca8941c3f89cc5fd9d84da0c38a9a" url: "https://pub.dev" source: hosted - version: "2.2.7" + version: "2.3.0" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "220831bf0bd5333ff2445eee35ec131553b804e6b5d47a4a37ca6f5eb66e282c" + sha256: "1b92f368f44b0dee2425bb861cfa17b6f6cf3961f762ff6f941d20b33355660a" url: "https://pub.dev" source: hosted - version: "2.4.4" + version: "2.5.0" stack_trace: dependency: transitive description: @@ -1056,42 +1040,42 @@ packages: dependency: transitive description: name: typed_data - sha256: "26f87ade979c47a150c9eaab93ccd2bebe70a27dc0b4b29517f2904f04eb11a5" + sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.2" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "206fb8334a700ef7754d6a9ed119e7349bc830448098f21a69bf1b4ed038cabc" + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.0.5" url_launcher_platform_interface: dependency: transitive description: name: url_launcher_platform_interface - sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + sha256: bfdfa402f1f3298637d71ca8ecfe840b4696698213d5346e9d12d4ab647ee2ea url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" url_launcher_web: dependency: transitive description: name: url_launcher_web - sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + sha256: cc26720eefe98c1b71d85f9dc7ef0cada5132617046369d9dc296b3ecaa5cbb4 url: "https://pub.dev" source: hosted - version: "2.0.16" + version: "2.0.18" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: a83ba3607a507758669cfafb03f9de09bf6e6280c14d9b9cb18f013e406dcacd + sha256: "7967065dd2b5fccc18c653b97958fdf839c5478c28e767c61ee879f4e7882422" url: "https://pub.dev" source: hosted - version: "3.0.5" + version: "3.0.7" uuid: dependency: "direct main" description: @@ -1120,10 +1104,10 @@ packages: dependency: transitive description: name: watcher - sha256: "6a7f46926b01ce81bfc339da6a7f20afbe7733eff9846f6d6a5466aa4c6667c0" + sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.0" web_socket_channel: dependency: transitive description: @@ -1144,26 +1128,26 @@ packages: dependency: transitive description: name: xdg_directories - sha256: ee1505df1426458f7f60aac270645098d318a8b4766d85fde75f76f2e21807d1 + sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff url: "https://pub.dev" source: hosted - version: "1.0.0" + version: "1.0.1" xml: dependency: transitive description: name: xml - sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + sha256: "5bc72e1e45e941d825fd7468b9b4cc3b9327942649aeb6fc5cdbf135f0a86e84" url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.3.0" yaml: dependency: transitive description: name: yaml - sha256: "23812a9b125b48d4007117254bca50abb6c712352927eece9e155207b1db2370" + sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5" url: "https://pub.dev" source: hosted - version: "3.1.1" + version: "3.1.2" sdks: - dart: ">=3.0.0-0 <4.0.0" + dart: ">=3.0.0 <4.0.0" flutter: ">=3.3.0" From 3113045ee6c06108edc2ef26fd174e9ae49ce298 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:22:52 +0100 Subject: [PATCH 128/172] Actually add fastlane metadata --- fastlane/metadata/android/en-US/changelogs/36.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/36.txt diff --git a/fastlane/metadata/android/en-US/changelogs/36.txt b/fastlane/metadata/android/en-US/changelogs/36.txt new file mode 100644 index 000000000..d03a0dd9a --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/36.txt @@ -0,0 +1 @@ +New features and bug fixes. Full changelog at https://github.com/jmshrv/finamp/releases/tag/0.6.16 \ No newline at end of file From b9ccf9123d58ea6b65b649fdde1eed1869f38b90 Mon Sep 17 00:00:00 2001 From: Stathis Kefalas Date: Thu, 22 Jun 2023 13:21:10 +0000 Subject: [PATCH 129/172] Translated using Weblate (Greek) Currently translated at 78.0% (135 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/el/ --- lib/l10n/app_el.arb | 40 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_el.arb b/lib/l10n/app_el.arb index 7cef69ebb..1c7be67f0 100644 --- a/lib/l10n/app_el.arb +++ b/lib/l10n/app_el.arb @@ -343,5 +343,43 @@ "example": "landscape" } } - } + }, + "theme": "Θέμα", + "@theme": {}, + "system": "Σύστημα", + "@system": {}, + "light": "Φωτεινό", + "@light": {}, + "dark": "Σκοτεινό", + "@dark": {}, + "tabs": "Καρτέλες", + "@tabs": {}, + "cancelSleepTimer": "Ακύρωση χρονοδιακόπτη ύπνου;", + "@cancelSleepTimer": {}, + "setSleepTimer": "Ρύθμιση Χρονοδιακόπτη Ύπνου", + "@setSleepTimer": {}, + "minutes": "Λεπτά", + "@minutes": {}, + "sleepTimerTooltip": "Χρονοδιακόπτης Ύπνου", + "@sleepTimerTooltip": {}, + "addToPlaylistTooltip": "Προσθήκη στην λίστα αναπαραγωγής", + "@addToPlaylistTooltip": {}, + "newPlaylist": "Νέα Λίστα Αναπαραγωγής", + "@newPlaylist": {}, + "addToPlaylistTitle": "Προσθήκη στην Λίστα Αναπαραγωγής", + "@addToPlaylistTitle": {}, + "playlistCreated": "Η Λίστα Αναπαραγωγής δημιουργήθηκε.", + "@playlistCreated": {}, + "removeFromPlaylistTooltip": "Αφαίρεση από την λίστα αναπαραγωγής", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "Αφαίρεση από την Λίστα Αναπαραγωγής", + "@removeFromPlaylistTitle": {}, + "createButtonLabel": "ΔΗΜΙΟΥΡΓΙΑ", + "@createButtonLabel": {}, + "invalidNumber": "Μη Έγκυρος Αριθμός", + "@invalidNumber": {}, + "yesButtonLabel": "ΝΑΙ", + "@yesButtonLabel": {}, + "noButtonLabel": "ΟΧΙ", + "@noButtonLabel": {} } From ff0caff08c202db00900aa836b7e0df5e5a63acb Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Sat, 24 Jun 2023 13:26:11 +0000 Subject: [PATCH 130/172] Translated using Weblate (Croatian) Currently translated at 100.0% (173 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/hr/ --- lib/l10n/app_hr.arb | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index b28b73aec..72ea49391 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -114,7 +114,7 @@ "@removeFromMix": {}, "bufferDuration": "Trajanje međuspremnika", "@bufferDuration": {}, - "bufferDurationSubtitle": "Koliko playera treba spremiti u međuspremnik, u sekundama. Potreban je reset.", + "bufferDurationSubtitle": "Koliko igrač treba spremiti u međuspremnik, u sekundama. Zahtijeva ponovno pokretanje.", "@bufferDurationSubtitle": {}, "language": "Jezik", "@language": {}, @@ -481,5 +481,9 @@ "type": "int" } } - } + }, + "confirm": "Potvrdi", + "@confirm": {}, + "showUncensoredLogMessage": "Ovaj zapis sadrži tvoje podatke za prijavu. Pokazati?", + "@showUncensoredLogMessage": {} } From 1c44767080760243fb66de579159917c62008251 Mon Sep 17 00:00:00 2001 From: Ettore Atalan Date: Mon, 26 Jun 2023 16:48:08 +0000 Subject: [PATCH 131/172] Translated using Weblate (German) Currently translated at 100.0% (173 of 173 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/de/ --- lib/l10n/app_de.arb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index f73d5f9fc..d102691a0 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -481,5 +481,9 @@ "bufferDurationSubtitle": "Wie viele Sekunden der Player vorladen soll. Benötigt einen Neustart.", "@bufferDurationSubtitle": {}, "language": "Sprache", - "@language": {} + "@language": {}, + "confirm": "Bestätigen", + "@confirm": {}, + "showUncensoredLogMessage": "Dieses Protokoll enthält Ihre Anmeldeinformationen. Anzeigen?", + "@showUncensoredLogMessage": {} } From 64c23d66435d80cf78e4fdc09020be6059f962a4 Mon Sep 17 00:00:00 2001 From: gallegonovato Date: Thu, 29 Jun 2023 16:08:04 +0000 Subject: [PATCH 132/172] Translated using Weblate (Spanish) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/es/ --- lib/l10n/app_es.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 7aa623d47..d41d61dba 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -485,5 +485,7 @@ "confirm": "Confirmar", "@confirm": {}, "showUncensoredLogMessage": "Este registro contiene tu información de acceso. ¿Mostrar?", - "@showUncensoredLogMessage": {} + "@showUncensoredLogMessage": {}, + "resetTabs": "Restablecer las pestañas", + "@resetTabs": {} } From 84476753bf6ce991a19d37463962f5051f7c5543 Mon Sep 17 00:00:00 2001 From: Ettore Atalan Date: Thu, 29 Jun 2023 17:09:05 +0000 Subject: [PATCH 133/172] Translated using Weblate (German) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/de/ --- lib/l10n/app_de.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index d102691a0..94bc4f48a 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -485,5 +485,7 @@ "confirm": "Bestätigen", "@confirm": {}, "showUncensoredLogMessage": "Dieses Protokoll enthält Ihre Anmeldeinformationen. Anzeigen?", - "@showUncensoredLogMessage": {} + "@showUncensoredLogMessage": {}, + "resetTabs": "Tabs zurücksetzen", + "@resetTabs": {} } From 741414fa1fdfaef20ec48c82bc0b354482cae043 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Thu, 13 Jul 2023 13:39:07 +0000 Subject: [PATCH 134/172] Translated using Weblate (Croatian) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/hr/ --- lib/l10n/app_hr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index 72ea49391..e75d7fabe 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -485,5 +485,7 @@ "confirm": "Potvrdi", "@confirm": {}, "showUncensoredLogMessage": "Ovaj zapis sadrži tvoje podatke za prijavu. Pokazati?", - "@showUncensoredLogMessage": {} + "@showUncensoredLogMessage": {}, + "resetTabs": "Resetiraj kartice", + "@resetTabs": {} } From 6fe4bf8bcfecda42e12d6e0e95f9b7a722edc0a4 Mon Sep 17 00:00:00 2001 From: Frits van Bommel Date: Wed, 19 Jul 2023 08:21:21 +0000 Subject: [PATCH 135/172] Translated using Weblate (Dutch) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/nl/ --- lib/l10n/app_nl.arb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_nl.arb b/lib/l10n/app_nl.arb index 0d48bce20..21552ed6a 100644 --- a/lib/l10n/app_nl.arb +++ b/lib/l10n/app_nl.arb @@ -16,7 +16,7 @@ "@addDownloads": {}, "addButtonLabel": "Toevoegen", "@addButtonLabel": {}, - "artists": "Artiest", + "artists": "Artiesten", "@artists": {}, "albumArtist": "Album Artiest", "@albumArtist": {}, @@ -456,7 +456,7 @@ "@system": {}, "transcode": "CONVERTEREN", "@transcode": {}, - "startupError": "Er ging iets mis bij het starten van de app. Dit was de foutmelding: {error}\n\nCreër een issue op github.com/UnicornsOnLSD/finamp met een schermafbeelding van deze pagina. Wanneer het probleem zich vaker voordoet, kan je de app-data verwijderen om de app te resetten.", + "startupError": "Er ging iets mis bij het starten van de app. Dit was de foutmelding: {error}\n\nCreëer een issue op github.com/UnicornsOnLSD/finamp met een schermafbeelding van deze pagina. Wanneer het probleem zich vaker voordoet, kan je de app-data verwijderen om de app te resetten.", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -479,5 +479,13 @@ "removeFromPlaylistTitle": "Verwijder van afspeellijst", "@removeFromPlaylistTitle": {}, "removedFromPlaylist": "Verwijderd van afspeellijst.", - "@removedFromPlaylist": {} + "@removedFromPlaylist": {}, + "resetTabs": "Tabbladen resetten", + "@resetTabs": {}, + "language": "Taal", + "@language": {}, + "confirm": "Bevestigen", + "@confirm": {}, + "showUncensoredLogMessage": "Dit logboek bevat uw inloggegevens. Tonen?", + "@showUncensoredLogMessage": {} } From b6a9f2341b203234db5446caf2119950e5a8b8e0 Mon Sep 17 00:00:00 2001 From: Eryk Michalak Date: Sat, 22 Jul 2023 13:39:35 +0000 Subject: [PATCH 136/172] Translated using Weblate (Polish) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/pl/ --- lib/l10n/app_pl.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_pl.arb b/lib/l10n/app_pl.arb index 51edba1ba..bb2d5420a 100644 --- a/lib/l10n/app_pl.arb +++ b/lib/l10n/app_pl.arb @@ -485,5 +485,7 @@ "confirm": "Potwierdź", "@confirm": {}, "showUncensoredLogMessage": "Ten log zawiera twoje dane logowania. Pokazać?", - "@showUncensoredLogMessage": {} + "@showUncensoredLogMessage": {}, + "resetTabs": "Resetowanie zakładek", + "@resetTabs": {} } From 8cc3ac0f7ae3c6e4e1250d30ce5d663368fa3cf2 Mon Sep 17 00:00:00 2001 From: James Harvey Date: Tue, 25 Jul 2023 00:15:18 +0000 Subject: [PATCH 137/172] Translated using Weblate (Japanese) Currently translated at 90.2% (157 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ja/ --- lib/l10n/app_ja.arb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 7dfc4aaa9..fd29ceade 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -207,7 +207,7 @@ "@logOut": {}, "downloadedSongsWillNotBeDeleted": "ダウロード済みの曲は削除されません", "@downloadedSongsWillNotBeDeleted": {}, - "jellyfinUsesAACForTranscoding": "Jelllyfin はトランスコードに AAC を利用します", + "jellyfinUsesAACForTranscoding": "Jellyfin はトランスコードに AAC を利用します", "@jellyfinUsesAACForTranscoding": {}, "enableTranscoding": "トランスコード有効", "@enableTranscoding": {}, From 1462018dbd3a900248d3d2fd268ec90fcd68bfed Mon Sep 17 00:00:00 2001 From: MrPotatoBobx Date: Thu, 27 Jul 2023 09:46:55 +0000 Subject: [PATCH 138/172] Translated using Weblate (Arabic) Currently translated at 89.6% (156 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ar/ --- lib/l10n/app_ar.arb | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_ar.arb b/lib/l10n/app_ar.arb index ca39987c5..e3eeb9578 100644 --- a/lib/l10n/app_ar.arb +++ b/lib/l10n/app_ar.arb @@ -49,7 +49,7 @@ "@clear": {}, "shuffleAll": "استماع عشوائي للكل", "@shuffleAll": {}, - "startMixNoSongsAlbum": "إكبس طويل على البوم لتضيف أو تزيل هم من بناء الخلطة قبل ان تبدأ الخلطة", + "startMixNoSongsAlbum": "إكبس طويل على البوم لتضيف أو تزيل ها من بناء الخلطة قبل ان تبدأ الخلطة", "@startMixNoSongsAlbum": { "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" }, @@ -455,5 +455,15 @@ "addToMix": "إضافة إالى الخلطة", "@addToMix": {}, "removeFromMix": "إزالة من الخلطة", - "@removeFromMix": {} + "@removeFromMix": {}, + "minutes": "دقائق", + "@minutes": {}, + "removeFromPlaylistTooltip": "إزالة من قائمة الأغاني", + "@removeFromPlaylistTooltip": {}, + "removeFromPlaylistTitle": "إزالة من قائمة الأغاني", + "@removeFromPlaylistTitle": {}, + "removedFromPlaylist": "تمت الإزالة من قائمة الأغاني.", + "@removedFromPlaylist": {}, + "language": "اللغة", + "@language": {} } From 4df2ed0c9b4ae64ba93ed4f9203ec886d6c89b53 Mon Sep 17 00:00:00 2001 From: Axus Wizix Date: Wed, 26 Jul 2023 11:09:10 +0000 Subject: [PATCH 139/172] Translated using Weblate (Russian) Currently translated at 99.4% (173 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/ru/ --- lib/l10n/app_ru.arb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb index eb424b1d6..cbf9a7144 100644 --- a/lib/l10n/app_ru.arb +++ b/lib/l10n/app_ru.arb @@ -29,7 +29,7 @@ "@music": {}, "finamp": "Finamp", "@finamp": {}, - "albumArtist": "Исполнитель", + "albumArtist": "Исполнитель альбома", "@albumArtist": {}, "artist": "Исполнитель", "@artist": {}, @@ -485,5 +485,7 @@ "confirm": "Подтвердить", "@confirm": {}, "showUncensoredLogMessage": "Этот журнал содержит вашу регистрационную информацию. Показать?", - "@showUncensoredLogMessage": {} + "@showUncensoredLogMessage": {}, + "resetTabs": "Сбросить вкладки", + "@resetTabs": {} } From 47f03a8fea0896db6cec2f2daf0fb5a5e09b1955 Mon Sep 17 00:00:00 2001 From: Alper Date: Wed, 26 Jul 2023 11:12:25 +0000 Subject: [PATCH 140/172] Translated using Weblate (Turkish) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/tr/ --- lib/l10n/app_tr.arb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_tr.arb b/lib/l10n/app_tr.arb index c0d255e65..1a69e0366 100644 --- a/lib/l10n/app_tr.arb +++ b/lib/l10n/app_tr.arb @@ -485,5 +485,7 @@ "confirm": "Onayla", "@confirm": {}, "showUncensoredLogMessage": "Bu günlük oturum açma bilgilerinizi içerir. Gösterilsin mi?", - "@showUncensoredLogMessage": {} + "@showUncensoredLogMessage": {}, + "resetTabs": "Sekmeleri sıfırla", + "@resetTabs": {} } From 717eaff2a99d648b3b7840e0cc669d740faff76c Mon Sep 17 00:00:00 2001 From: SuperDumbTM Date: Wed, 26 Jul 2023 04:03:18 +0000 Subject: [PATCH 141/172] Translated using Weblate (Chinese (Traditional, Hong Kong)) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hant_HK/ --- lib/l10n/app_zh_Hant_HK.arb | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_zh_Hant_HK.arb b/lib/l10n/app_zh_Hant_HK.arb index 31849d329..219d38f32 100644 --- a/lib/l10n/app_zh_Hant_HK.arb +++ b/lib/l10n/app_zh_Hant_HK.arb @@ -125,7 +125,7 @@ "@downloadErrorsTitle": {}, "noErrors": "沒有錯誤!", "@noErrors": {}, - "errorScreenError": "在讀取錯誤資訊時出現錯誤!建議在 GitHub 上回報此問題及重設應用程式。", + "errorScreenError": "讀取錯誤紀錄時出現錯誤!建議在 GitHub 上回報此問題及重設應用程式。", "@errorScreenError": {}, "failedToGetSongFromDownloadId": "無法從下載 ID 中取得歌曲", "@failedToGetSongFromDownloadId": {}, @@ -340,7 +340,7 @@ "@bufferDuration": {}, "bufferDurationSubtitle": "播放器可以預先載入多少的音訊數據(秒)。重啟以套用設定。", "@bufferDurationSubtitle": {}, - "startupError": "應用程式啟動時出錯({error})\n\n您可以在 github.com/UnicornsOnLSD/finamp 回報有關問題並附上截圖。如果問題持續,您可以嘗試清除應用程式的資料。", + "startupError": "應用程式啟動時出錯({error})\n\n您可以在 github.com/UnicornsOnLSD/finamp 回報有關問題並附上截圖。如果問題持續,您可以嘗試重設應用程式。", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -481,5 +481,11 @@ } }, "language": "語言", - "@language": {} + "@language": {}, + "showUncensoredLogMessage": "紀錄檔內包含你的登入資訊。顯示?", + "@showUncensoredLogMessage": {}, + "confirm": "確定", + "@confirm": {}, + "resetTabs": "重設分頁", + "@resetTabs": {} } From c57bc1293cf422f2f1e29ab9efaef05ec744d8c7 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 14:45:09 +0100 Subject: [PATCH 142/172] Update Xcode languages --- ios/Runner.xcodeproj/project.pbxproj | 25 +++++++++++++++++++ ios/Runner/el.lproj/LaunchScreen.strings | 1 + ios/Runner/el.lproj/Main.strings | 1 + ios/Runner/hr.lproj/LaunchScreen.strings | 1 + ios/Runner/hr.lproj/Main.strings | 1 + ios/Runner/th.lproj/LaunchScreen.strings | 1 + ios/Runner/th.lproj/Main.strings | 1 + ios/Runner/uk.lproj/LaunchScreen.strings | 1 + ios/Runner/uk.lproj/Main.strings | 1 + .../zh-Hant-HK.lproj/LaunchScreen.strings | 1 + ios/Runner/zh-Hant-HK.lproj/Main.strings | 1 + 11 files changed, 35 insertions(+) create mode 100644 ios/Runner/el.lproj/LaunchScreen.strings create mode 100644 ios/Runner/el.lproj/Main.strings create mode 100644 ios/Runner/hr.lproj/LaunchScreen.strings create mode 100644 ios/Runner/hr.lproj/Main.strings create mode 100644 ios/Runner/th.lproj/LaunchScreen.strings create mode 100644 ios/Runner/th.lproj/Main.strings create mode 100644 ios/Runner/uk.lproj/LaunchScreen.strings create mode 100644 ios/Runner/uk.lproj/Main.strings create mode 100644 ios/Runner/zh-Hant-HK.lproj/LaunchScreen.strings create mode 100644 ios/Runner/zh-Hant-HK.lproj/Main.strings diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index ec56283fb..258641ef5 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -76,6 +76,16 @@ 1AB3434728AEB3BA00B8C792 /* Info-Debug.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Debug.plist"; sourceTree = ""; }; 1AB3434828AEB3BA00B8C792 /* Info-Profile.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Profile.plist"; sourceTree = ""; }; 1AB3434928AEB3BA00B8C792 /* Info-Release.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "Info-Release.plist"; sourceTree = ""; }; + 1AC8B9DA2A73FD5E00200E3C /* zh-Hant-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-HK"; path = "zh-Hant-HK.lproj/Main.strings"; sourceTree = ""; }; + 1AC8B9DB2A73FD5E00200E3C /* zh-Hant-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hant-HK"; path = "zh-Hant-HK.lproj/LaunchScreen.strings"; sourceTree = ""; }; + 1AC8B9DC2A73FD7C00200E3C /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Main.strings; sourceTree = ""; }; + 1AC8B9DD2A73FD7C00200E3C /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1AC8B9DE2A73FD9000200E3C /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/Main.strings; sourceTree = ""; }; + 1AC8B9DF2A73FD9000200E3C /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1AC8B9E02A73FDB500200E3C /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Main.strings; sourceTree = ""; }; + 1AC8B9E12A73FDB500200E3C /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/LaunchScreen.strings; sourceTree = ""; }; + 1AC8B9E22A73FDC000200E3C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/Main.strings; sourceTree = ""; }; + 1AC8B9E32A73FDC000200E3C /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/LaunchScreen.strings; sourceTree = ""; }; 1AD2DF3A2921A4A6006B24E3 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/Main.strings; sourceTree = ""; }; 1AD2DF3B2921A4A6006B24E3 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/LaunchScreen.strings; sourceTree = ""; }; 1AD2DF3C2921A4AF006B24E3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Main.strings; sourceTree = ""; }; @@ -254,6 +264,11 @@ tr, vi, ja, + "zh-Hant-HK", + hr, + el, + th, + uk, ); mainGroup = 97C146E51CF9000F007C117D; productRefGroup = 97C146EF1CF9000F007C117D /* Products */; @@ -396,6 +411,11 @@ 1A3D4FC129E71B4700C17E1B /* tr */, 1A3D4FC329E71B4B00C17E1B /* vi */, 1A5F1B1929F734E500E6F504 /* ja */, + 1AC8B9DA2A73FD5E00200E3C /* zh-Hant-HK */, + 1AC8B9DC2A73FD7C00200E3C /* hr */, + 1AC8B9DE2A73FD9000200E3C /* el */, + 1AC8B9E02A73FDB500200E3C /* th */, + 1AC8B9E22A73FDC000200E3C /* uk */, ); name = Main.storyboard; sourceTree = ""; @@ -428,6 +448,11 @@ 1A3D4FC229E71B4700C17E1B /* tr */, 1A3D4FC429E71B4B00C17E1B /* vi */, 1A5F1B1A29F734E500E6F504 /* ja */, + 1AC8B9DB2A73FD5E00200E3C /* zh-Hant-HK */, + 1AC8B9DD2A73FD7C00200E3C /* hr */, + 1AC8B9DF2A73FD9000200E3C /* el */, + 1AC8B9E12A73FDB500200E3C /* th */, + 1AC8B9E32A73FDC000200E3C /* uk */, ); name = LaunchScreen.storyboard; sourceTree = ""; diff --git a/ios/Runner/el.lproj/LaunchScreen.strings b/ios/Runner/el.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/el.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/el.lproj/Main.strings b/ios/Runner/el.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/el.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/hr.lproj/LaunchScreen.strings b/ios/Runner/hr.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/hr.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/hr.lproj/Main.strings b/ios/Runner/hr.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/hr.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/th.lproj/LaunchScreen.strings b/ios/Runner/th.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/th.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/th.lproj/Main.strings b/ios/Runner/th.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/th.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/uk.lproj/LaunchScreen.strings b/ios/Runner/uk.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/uk.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/uk.lproj/Main.strings b/ios/Runner/uk.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/uk.lproj/Main.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/zh-Hant-HK.lproj/LaunchScreen.strings b/ios/Runner/zh-Hant-HK.lproj/LaunchScreen.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/zh-Hant-HK.lproj/LaunchScreen.strings @@ -0,0 +1 @@ + diff --git a/ios/Runner/zh-Hant-HK.lproj/Main.strings b/ios/Runner/zh-Hant-HK.lproj/Main.strings new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/ios/Runner/zh-Hant-HK.lproj/Main.strings @@ -0,0 +1 @@ + From 7e6c232050c388cec202d1e260b8b1f6300fee8b Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 15:16:47 +0100 Subject: [PATCH 143/172] Podfile moment --- ios/Podfile.lock | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 075acad49..77ccf7644 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -71,14 +71,14 @@ PODS: - path_provider_foundation (0.0.1): - Flutter - FlutterMacOS - - permission_handler_apple (9.0.4): + - permission_handler_apple (9.1.1): - Flutter - SDWebImage (5.13.4): - SDWebImage/Core (= 5.13.4) - SDWebImage/Core (5.13.4) - share_plus (0.0.1): - Flutter - - sqflite (0.0.2): + - sqflite (0.0.3): - Flutter - FMDB (>= 2.7.5) - SwiftyGif (5.4.3) @@ -148,11 +148,11 @@ SPEC CHECKSUMS: FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e - path_provider_foundation: c68054786f1b4f3343858c1e1d0caaded73f0be9 - permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce + path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 + permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 - sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 + sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 PODFILE CHECKSUM: 063e508f261fd49adc2e97f5d1ac1a2bc0e8e8ac From dd72a3573d3cb55f47f3cc0327b2a41779c0c9c7 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 28 Jul 2023 15:17:26 +0100 Subject: [PATCH 144/172] Fix typo in fastlane metadata (although F-Droid will still show the typo :( ) --- fastlane/metadata/android/en-US/changelogs/36.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fastlane/metadata/android/en-US/changelogs/36.txt b/fastlane/metadata/android/en-US/changelogs/36.txt index d03a0dd9a..792b37ee4 100644 --- a/fastlane/metadata/android/en-US/changelogs/36.txt +++ b/fastlane/metadata/android/en-US/changelogs/36.txt @@ -1 +1 @@ -New features and bug fixes. Full changelog at https://github.com/jmshrv/finamp/releases/tag/0.6.16 \ No newline at end of file +New features and bug fixes. Full changelog at https://github.com/jmshrv/finamp/releases/tag/0.6.15 From e86c5d7816d338ecb3e761dee47e58d232e488df Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sun, 30 Jul 2023 15:21:41 +0100 Subject: [PATCH 145/172] Set CADisableMinimumFrameDurationOnPhone to true Enables 120Hz display on ProMotion iPhones --- ios/Runner/Info-Debug.plist | 2 ++ ios/Runner/Info-Profile.plist | 2 ++ ios/Runner/Info-Release.plist | 2 ++ 3 files changed, 6 insertions(+) diff --git a/ios/Runner/Info-Debug.plist b/ios/Runner/Info-Debug.plist index fd9e60bd9..02fe7cef1 100644 --- a/ios/Runner/Info-Debug.plist +++ b/ios/Runner/Info-Debug.plist @@ -68,5 +68,7 @@ NSLocalNetworkUsageDescription Required for debugging + CADisableMinimumFrameDurationOnPhone + diff --git a/ios/Runner/Info-Profile.plist b/ios/Runner/Info-Profile.plist index fd9e60bd9..02fe7cef1 100644 --- a/ios/Runner/Info-Profile.plist +++ b/ios/Runner/Info-Profile.plist @@ -68,5 +68,7 @@ NSLocalNetworkUsageDescription Required for debugging + CADisableMinimumFrameDurationOnPhone + diff --git a/ios/Runner/Info-Release.plist b/ios/Runner/Info-Release.plist index 992c314f5..4b9a8f969 100644 --- a/ios/Runner/Info-Release.plist +++ b/ios/Runner/Info-Release.plist @@ -64,6 +64,8 @@ UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight + CADisableMinimumFrameDurationOnPhone + UIViewControllerBasedStatusBarAppearance From 87e1eec133a3e9417fe088207a0a6e7a87f52b48 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Tue, 1 Aug 2023 23:47:52 +0200 Subject: [PATCH 146/172] :recycle: Simplify generatePlaybackProgressInfo --- .../music_player_background_task.dart | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 1e9c6f79b..363bf59e2 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -376,25 +376,27 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { required bool includeNowPlayingQueue, bool isStopEvent = false, }) { - if (_queueAudioSource.length == 0 && item == null) { - // This function relies on _queue having items, so we return null if it's - // empty to avoid more errors. - return null; + if (item == null) { + final currentIndex = _player.currentIndex; + if (_queueAudioSource.length == 0 || currentIndex == 0) { + // This function relies on _queue having items, + // so we return null if it's empty or no index is played + // and no custom item was passed to avoid more errors. + return null; + } + item = _getQueueItem(currentIndex!); } try { return PlaybackProgressInfo( - itemId: item?.extras?["itemJson"]["Id"] ?? - _getQueueItem(_player.currentIndex ?? 0).extras!["itemJson"]["Id"], + itemId: item.extras!["itemJson"]["Id"], isPaused: !_player.playing, isMuted: _player.volume == 0, positionTicks: isStopEvent - ? (item?.duration?.inMicroseconds ?? 0) * 10 + ? (item.duration?.inMicroseconds ?? 0) * 10 : _player.position.inMicroseconds * 10, repeatMode: _jellyfinRepeatMode(_player.loopMode), - playMethod: item?.extras!["shouldTranscode"] ?? - _getQueueItem(_player.currentIndex ?? 0) - .extras!["shouldTranscode"] + playMethod: item.extras!["shouldTranscode"] ?? false ? "Transcode" : "DirectPlay", // We don't send the queue since it seems useless and it can cause @@ -628,4 +630,4 @@ AudioServiceRepeatMode _audioServiceRepeatMode(LoopMode loopMode) { case LoopMode.all: return AudioServiceRepeatMode.all; } -} +} \ No newline at end of file From 3d72f677da44f77a83fef3aaa3252f85e9b988af Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 2 Aug 2023 01:08:16 +0200 Subject: [PATCH 147/172] :recycle: Rewrite playback state reporting --- .../music_player_background_task.dart | 208 +++++++++++------- 1 file changed, 128 insertions(+), 80 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 363bf59e2..af04a4b99 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -44,9 +44,6 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { /// new queue. int? nextInitialIndex; - /// The item that was previously played. Used for reporting playback status. - MediaItem? _previousItem; - /// Set to true when we're stopping the audio service. Used to avoid playback /// progress reporting. bool _isStopping = false; @@ -67,7 +64,24 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { // Propagate all events from the audio player to AudioService clients. _player.playbackEventStream.listen((event) async { - playbackState.add(_transformEvent(event)); + final prevState = playbackState.valueOrNull; + final prevIndex = prevState?.queueIndex; + final prevItem = mediaItem.valueOrNull; + final currentState = _transformEvent(event); + final currentIndex = currentState.queueIndex; + + playbackState.add(currentState); + + if (currentIndex != null) { + final currentItem = _getQueueItem(currentIndex); + + // Differences in queue index or item id are considered track changes + if (prevIndex != currentIndex || prevItem?.id != currentItem.id) { + mediaItem.add(currentItem); + + onTrackChanged(currentItem, currentState, prevItem, prevState); + } + } if (playbackState.valueOrNull != null && playbackState.valueOrNull?.processingState != @@ -87,41 +101,6 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { } }); - _player.currentIndexStream.listen((event) async { - if (event == null) return; - - final currentItem = _getQueueItem(event); - mediaItem.add(currentItem); - - if (!FinampSettingsHelper.finampSettings.isOffline) { - final jellyfinApiHelper = GetIt.instance(); - - if (_previousItem != null) { - final playbackData = generatePlaybackProgressInfo( - item: _previousItem, - includeNowPlayingQueue: true, - isStopEvent: true, - ); - - if (playbackData != null) { - await jellyfinApiHelper.stopPlaybackProgress(playbackData); - } - } - - final playbackData = generatePlaybackProgressInfo( - item: currentItem, - includeNowPlayingQueue: true, - ); - - if (playbackData != null) { - await jellyfinApiHelper.reportPlaybackStart(playbackData); - } - - // Set item for next index update - _previousItem = currentItem; - } - }); - // PlaybackEvent doesn't include shuffle/loops so we listen for changes here _player.shuffleModeEnabledStream.listen( (_) => playbackState.add(_transformEvent(_player.playbackEvent))); @@ -155,13 +134,9 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { _isStopping = true; - // Clear the previous item. - _previousItem = null; - // Tell Jellyfin we're no longer playing audio if we're online if (!FinampSettingsHelper.finampSettings.isOffline) { - final playbackInfo = - generatePlaybackProgressInfo(includeNowPlayingQueue: false); + final playbackInfo = generateCurrentPlaybackProgressInfo(); if (playbackInfo != null) { await _jellyfinApiHelper.stopPlaybackProgress(playbackInfo); } @@ -349,7 +324,8 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { break; default: return Future.error( - "Unsupported AudioServiceRepeatMode! Recieved ${repeatMode.toString()}, requires all, none, or one."); + "Unsupported AudioServiceRepeatMode! Received ${repeatMode.toString()}, requires all, none, or one.", + ); } } catch (e) { _audioServiceBackgroundTaskLogger.severe(e); @@ -368,34 +344,56 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { } } - /// Generates PlaybackProgressInfo from current player info. Returns null if - /// _queue is empty. If an item is not supplied, the current queue index will - /// be used. - PlaybackProgressInfo? generatePlaybackProgressInfo({ - MediaItem? item, - required bool includeNowPlayingQueue, - bool isStopEvent = false, - }) { - if (item == null) { - final currentIndex = _player.currentIndex; - if (_queueAudioSource.length == 0 || currentIndex == 0) { - // This function relies on _queue having items, - // so we return null if it's empty or no index is played - // and no custom item was passed to avoid more errors. - return null; + /// Report track changes to the Jellyfin Server if the user is not offline. + onTrackChanged( + MediaItem currentItem, + PlaybackState currentState, + MediaItem? previousItem, + PlaybackState? previousState, + ) async { + if (FinampSettingsHelper.finampSettings.isOffline) { + return; + } + + final jellyfinApiHelper = GetIt.instance(); + + if (previousItem != null && previousState != null) { + final playbackData = generatePlaybackProgressInfoFromState( + previousItem, + previousState, + ); + + if (playbackData != null) { + await jellyfinApiHelper.stopPlaybackProgress(playbackData); } - item = _getQueueItem(currentIndex!); } + final playbackData = generatePlaybackProgressInfoFromState( + currentItem, + currentState, + ); + + if (playbackData != null) { + await jellyfinApiHelper.reportPlaybackStart(playbackData); + } + } + + /// Generates PlaybackProgressInfo for the supplied item and player info. + PlaybackProgressInfo? generatePlaybackProgressInfo( + MediaItem item, { + required bool isPaused, + required bool isMuted, + required Duration playerPosition, + required String repeatMode, + required bool includeNowPlayingQueue, + }) { try { return PlaybackProgressInfo( itemId: item.extras!["itemJson"]["Id"], - isPaused: !_player.playing, - isMuted: _player.volume == 0, - positionTicks: isStopEvent - ? (item.duration?.inMicroseconds ?? 0) * 10 - : _player.position.inMicroseconds * 10, - repeatMode: _jellyfinRepeatMode(_player.loopMode), + isPaused: isPaused, + isMuted: isMuted, + positionTicks: playerPosition.inMicroseconds * 10, + repeatMode: repeatMode, playMethod: item.extras!["shouldTranscode"] ?? false ? "Transcode" : "DirectPlay", @@ -418,6 +416,45 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { } } + /// Generates PlaybackProgressInfo from current player info. + /// Returns null if _queue is empty. + /// If an item is not supplied, the current queue index will be used. + PlaybackProgressInfo? generateCurrentPlaybackProgressInfo() { + final currentIndex = _player.currentIndex; + if (_queueAudioSource.length == 0 || currentIndex == null) { + // This function relies on _queue having items, + // so we return null if it's empty or no index is played + // and no custom item was passed to avoid more errors. + return null; + } + final item = _getQueueItem(currentIndex); + + return generatePlaybackProgressInfo( + item, + isPaused: !_player.playing, + isMuted: _player.volume == 0, + playerPosition: _player.position, + repeatMode: _jellyfinRepeatModeFromLoopMode(_player.loopMode), + includeNowPlayingQueue: false, + ); + } + + /// Generates PlaybackProgressInfo for the supplied item and playback state. + PlaybackProgressInfo? generatePlaybackProgressInfoFromState( + MediaItem item, + PlaybackState state, + ) { + return generatePlaybackProgressInfo( + item, + isPaused: !state.playing, + // TODO: get volume from state? + isMuted: false, + playerPosition: state.position, + repeatMode: _jellyfinRepeatModeFromRepeatMode(state.repeatMode), + includeNowPlayingQueue: true, + ); + } + void setNextInitialIndex(int index) { nextInitialIndex = index; } @@ -496,8 +533,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { try { JellyfinApiHelper jellyfinApiHelper = GetIt.instance(); - final playbackInfo = - generatePlaybackProgressInfo(includeNowPlayingQueue: false); + final playbackInfo = generateCurrentPlaybackProgressInfo(); if (playbackInfo != null) { await jellyfinApiHelper.updatePlaybackProgress(playbackInfo); } @@ -536,7 +572,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { } } } else { - // We have to deserialise this because Dart is stupid and can't handle + // We have to deserialize this because Dart is stupid and can't handle // sending classes through isolates. final downloadedSong = DownloadedSong.fromJson(mediaItem.extras!["downloadedSongJson"]); @@ -610,24 +646,36 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { } } -String _jellyfinRepeatMode(LoopMode loopMode) { +AudioServiceRepeatMode _audioServiceRepeatMode(LoopMode loopMode) { switch (loopMode) { - case LoopMode.all: - return "RepeatAll"; - case LoopMode.one: - return "RepeatOne"; case LoopMode.off: - return "RepeatNone"; + return AudioServiceRepeatMode.none; + case LoopMode.one: + return AudioServiceRepeatMode.one; + case LoopMode.all: + return AudioServiceRepeatMode.all; } } -AudioServiceRepeatMode _audioServiceRepeatMode(LoopMode loopMode) { +String _jellyfinRepeatModeFromLoopMode(LoopMode loopMode) { switch (loopMode) { case LoopMode.off: - return AudioServiceRepeatMode.none; + return "RepeatNone"; case LoopMode.one: - return AudioServiceRepeatMode.one; + return "RepeatOne"; case LoopMode.all: - return AudioServiceRepeatMode.all; + return "RepeatAll"; + } +} + +String _jellyfinRepeatModeFromRepeatMode(AudioServiceRepeatMode repeatMode) { + switch (repeatMode) { + case AudioServiceRepeatMode.none: + return "RepeatNone"; + case AudioServiceRepeatMode.one: + return "RepeatOne"; + case AudioServiceRepeatMode.all: + case AudioServiceRepeatMode.group: + return "RepeatAll"; } } \ No newline at end of file From 6812d0fa0b3c6ec2ef07e1c2b07176ee1c272a34 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 2 Aug 2023 03:42:46 +0200 Subject: [PATCH 148/172] :bug: Don't report previously stopped tracks again --- .../music_player_background_task.dart | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index af04a4b99..95fcc3a75 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -358,13 +358,20 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { final jellyfinApiHelper = GetIt.instance(); if (previousItem != null && previousState != null) { - final playbackData = generatePlaybackProgressInfoFromState( - previousItem, - previousState, - ); + const maxStateReportAge = Duration(seconds: 15); + final stateAge = DateTime.now().difference(previousState.updateTime); + + // Only report stop events if the respective playback state isn't out of date. + // This catches duplicate events when the user switches from a stopped track. + if (stateAge < maxStateReportAge) { + final playbackData = generatePlaybackProgressInfoFromState( + previousItem, + previousState, + ); - if (playbackData != null) { - await jellyfinApiHelper.stopPlaybackProgress(playbackData); + if (playbackData != null) { + await jellyfinApiHelper.stopPlaybackProgress(playbackData); + } } } From f240762a632fef3764ddcab9937ee5ec975eeb91 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Thu, 3 Aug 2023 03:39:15 +0200 Subject: [PATCH 149/172] Resolve TODO --- lib/services/music_player_background_task.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 95fcc3a75..3ffee0a71 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -454,7 +454,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { return generatePlaybackProgressInfo( item, isPaused: !state.playing, - // TODO: get volume from state? + // always consider as unmuted isMuted: false, playerPosition: state.position, repeatMode: _jellyfinRepeatModeFromRepeatMode(state.repeatMode), From 063a50ad7a0181b7222f85afbdf635b31b6c0d95 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Fri, 4 Aug 2023 13:52:00 +0200 Subject: [PATCH 150/172] :bug: Remove the playback state age check again for now The playbackEventStream unfortunately isn't frequent enough, causing the previous event to sometimes be older than the specified maximum age. This causes stop events to be dropped sometimes. Thus, we revert this, but also ensure that the position, that is automatically extrapolated based on the current time, doesn't exceed the duration of the track. --- .../music_player_background_task.dart | 27 +++++++++---------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 3ffee0a71..4413b8b90 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -76,7 +76,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { final currentItem = _getQueueItem(currentIndex); // Differences in queue index or item id are considered track changes - if (prevIndex != currentIndex || prevItem?.id != currentItem.id) { + if (currentIndex != prevIndex || currentItem.id != prevItem?.id) { mediaItem.add(currentItem); onTrackChanged(currentItem, currentState, prevItem, prevState); @@ -358,20 +358,13 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { final jellyfinApiHelper = GetIt.instance(); if (previousItem != null && previousState != null) { - const maxStateReportAge = Duration(seconds: 15); - final stateAge = DateTime.now().difference(previousState.updateTime); - - // Only report stop events if the respective playback state isn't out of date. - // This catches duplicate events when the user switches from a stopped track. - if (stateAge < maxStateReportAge) { - final playbackData = generatePlaybackProgressInfoFromState( - previousItem, - previousState, - ); + final playbackData = generatePlaybackProgressInfoFromState( + previousItem, + previousState, + ); - if (playbackData != null) { - await jellyfinApiHelper.stopPlaybackProgress(playbackData); - } + if (playbackData != null) { + await jellyfinApiHelper.stopPlaybackProgress(playbackData); } } @@ -451,12 +444,16 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { MediaItem item, PlaybackState state, ) { + final duration = item.duration; return generatePlaybackProgressInfo( item, isPaused: !state.playing, // always consider as unmuted isMuted: false, - playerPosition: state.position, + // ensure the (extrapolated) position doesn't exceed the duration + playerPosition: duration != null && state.position > duration + ? duration + : state.position, repeatMode: _jellyfinRepeatModeFromRepeatMode(state.repeatMode), includeNowPlayingQueue: true, ); From 0a054e00cb566edd1bef84dac2bd5cc97ba2d5cd Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Fri, 4 Aug 2023 15:23:00 +0200 Subject: [PATCH 151/172] Don't send stop events for paused tracks at position 0 --- lib/services/music_player_background_task.dart | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 4413b8b90..e52c57140 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -345,7 +345,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { } /// Report track changes to the Jellyfin Server if the user is not offline. - onTrackChanged( + Future onTrackChanged( MediaItem currentItem, PlaybackState currentState, MediaItem? previousItem, @@ -357,7 +357,11 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { final jellyfinApiHelper = GetIt.instance(); - if (previousItem != null && previousState != null) { + if (previousItem != null && + previousState != null && + // don't submit stop events for idle tracks (at position 0 and not playing) + (previousState.playing || + previousState.updatePosition != Duration.zero)) { final playbackData = generatePlaybackProgressInfoFromState( previousItem, previousState, From 32791517c74bf1ce76a389f9dbfdc8b2624f202d Mon Sep 17 00:00:00 2001 From: raykast Date: Sun, 6 Aug 2023 23:34:08 -0700 Subject: [PATCH 152/172] Add Play Next & Add to Queue for songs and albums. --- .../AlbumScreen/song_list_tile.dart | 22 +++++++++- lib/components/MusicScreen/album_item.dart | 43 +++++++++++++++++++ lib/l10n/app_en.arb | 16 ++++++- lib/services/audio_service_helper.dart | 29 +++++++++++-- .../music_player_background_task.dart | 29 ++++++++++++- 5 files changed, 131 insertions(+), 8 deletions(-) diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index bef409cc8..77058a47b 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -20,6 +20,7 @@ import 'downloaded_indicator.dart'; enum SongListTileMenuItems { addToQueue, + playNext, replaceQueueWithItem, addToPlaylist, removeFromPlaylist, @@ -245,6 +246,13 @@ class _SongListTileState extends State { title: Text(AppLocalizations.of(context)!.addToQueue), ), ), + PopupMenuItem( + value: SongListTileMenuItems.playNext, + child: ListTile( + leading: const Icon(Icons.queue_music), + title: Text(AppLocalizations.of(context)!.playNext), + ), + ), PopupMenuItem( value: SongListTileMenuItems.replaceQueueWithItem, child: ListTile( @@ -324,7 +332,7 @@ class _SongListTileState extends State { switch (selection) { case SongListTileMenuItems.addToQueue: - await _audioServiceHelper.addQueueItem(widget.item); + await _audioServiceHelper.addQueueItems([widget.item]); if (!mounted) return; @@ -333,6 +341,16 @@ class _SongListTileState extends State { )); break; + case SongListTileMenuItems.playNext: + await _audioServiceHelper.insertQueueItemsNext([widget.item]); + + if (!mounted) return; + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.insertedIntoQueue), + )); + break; + case SongListTileMenuItems.replaceQueueWithItem: await _audioServiceHelper .replaceQueueWithItem(itemList: [widget.item]); @@ -451,7 +469,7 @@ class _SongListTileState extends State { ), ), confirmDismiss: (direction) async { - await _audioServiceHelper.addQueueItem(widget.item); + await _audioServiceHelper.addQueueItems([widget.item]); if (!mounted) return false; diff --git a/lib/components/MusicScreen/album_item.dart b/lib/components/MusicScreen/album_item.dart index b98843017..f93d726a0 100644 --- a/lib/components/MusicScreen/album_item.dart +++ b/lib/components/MusicScreen/album_item.dart @@ -5,6 +5,7 @@ import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:get_it/get_it.dart'; import '../../models/jellyfin_models.dart'; +import '../../services/audio_service_helper.dart'; import '../../services/jellyfin_api_helper.dart'; import '../../screens/artist_screen.dart'; import '../../screens/album_screen.dart'; @@ -12,6 +13,8 @@ import '../error_snackbar.dart'; import 'album_item_card.dart'; enum _AlbumListTileMenuItems { + addToQueue, + playNext, addFavourite, removeFavourite, addToMixList, @@ -58,6 +61,8 @@ class AlbumItem extends StatefulWidget { } class _AlbumItemState extends State { + final _audioServiceHelper = GetIt.instance(); + late BaseItemDto mutableAlbum; late Function() onTap; @@ -110,6 +115,20 @@ class _AlbumItemState extends State { screenSize.height - details.globalPosition.dy, ), items: [ + PopupMenuItem<_AlbumListTileMenuItems>( + value: _AlbumListTileMenuItems.addToQueue, + child: ListTile( + leading: const Icon(Icons.queue_music), + title: Text(AppLocalizations.of(context)!.addToQueue), + ), + ), + PopupMenuItem<_AlbumListTileMenuItems>( + value: _AlbumListTileMenuItems.playNext, + child: ListTile( + leading: const Icon(Icons.queue_music), + title: Text(AppLocalizations.of(context)!.playNext), + ), + ), mutableAlbum.userData!.isFavorite ? PopupMenuItem<_AlbumListTileMenuItems>( value: _AlbumListTileMenuItems.removeFavourite, @@ -148,6 +167,30 @@ class _AlbumItemState extends State { if (!mounted) return; switch (selection) { + case _AlbumListTileMenuItems.addToQueue: + final children = await jellyfinApiHelper.getItems( + parentItem: widget.album, isGenres: false); + await _audioServiceHelper.addQueueItems(children!); + + if (!mounted) return; + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.addedToQueue), + )); + break; + + case _AlbumListTileMenuItems.playNext: + final children = await jellyfinApiHelper.getItems( + parentItem: widget.album, isGenres: false); + await _audioServiceHelper.insertQueueItemsNext(children!); + + if (!mounted) return; + + ScaffoldMessenger.of(context).showSnackBar(SnackBar( + content: Text(AppLocalizations.of(context)!.insertedIntoQueue), + )); + break; + case _AlbumListTileMenuItems.addFavourite: try { final newUserData = diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 2f65ccc86..b2f3ee0e7 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -417,7 +417,13 @@ "queue": "Queue", "@queue": {}, "addToQueue": "Add to Queue", - "@addToQueue": {}, + "@addToQueue": { + "description": "Popup menu item title for adding an item to the end of the play queue." + }, + "playNext": "Play Next", + "@playNext": { + "description": "Popup menu item title for inserting an item into the play queue after the currently-playing item." + }, "replaceQueue": "Replace Queue", "@replaceQueue": {}, "instantMix": "Instant Mix", @@ -429,7 +435,13 @@ "addFavourite": "Add Favourite", "@addFavourite": {}, "addedToQueue": "Added to queue.", - "@addedToQueue": {}, + "@addedToQueue": { + "description": "Snackbar message that shows when the user successfully adds items to the end of the play queue." + }, + "insertedIntoQueue": "Inserted into queue.", + "@insertedIntoQueue": { + "description": "Snackbar message that shows when the user successfully inserts items into the play queue at a location that is not necessarily the end." + }, "queueReplaced": "Queue replaced.", "@queueReplaced": {}, "removedFromPlaylist": "Removed from playlist.", diff --git a/lib/services/audio_service_helper.dart b/lib/services/audio_service_helper.dart index 989ee97a3..7439b55d1 100644 --- a/lib/services/audio_service_helper.dart +++ b/lib/services/audio_service_helper.dart @@ -64,17 +64,40 @@ class AudioServiceHelper { } } + @Deprecated("Use addQueueItems instead") Future addQueueItem(BaseItemDto item) async { + await addQueueItems([item]); + } + + Future addQueueItems(List items) async { try { // If the queue is empty (like when the app is first launched), run the // replace queue function instead so that the song gets played if ((_audioHandler.queue.valueOrNull?.length ?? 0) == 0) { - await replaceQueueWithItem(itemList: [item]); + await replaceQueueWithItem(itemList: items); + return; + } + + final mediaItems = + await Future.wait(items.map((i) => _generateMediaItem(i))); + await _audioHandler.addQueueItems(mediaItems); + } catch (e) { + audioServiceHelperLogger.severe(e); + return Future.error(e); + } + } + + Future insertQueueItemsNext(List items) async { + try { + // See above comment in addQueueItem + if ((_audioHandler.queue.valueOrNull?.length ?? 0) == 0) { + await replaceQueueWithItem(itemList: items); return; } - final itemMediaItem = await _generateMediaItem(item); - await _audioHandler.addQueueItem(itemMediaItem); + final mediaItems = + await Future.wait(items.map((i) => _generateMediaItem(i))); + await _audioHandler.insertQueueItemsNext(mediaItems); } catch (e) { audioServiceHelperLogger.severe(e); return Future.error(e); diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 1e9c6f79b..a4ba51297 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -198,9 +198,36 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { } @override + @Deprecated("Use addQueueItems instead") Future addQueueItem(MediaItem mediaItem) async { + addQueueItems([mediaItem]); + } + + @override + Future addQueueItems(List mediaItems) async { try { - await _queueAudioSource.add(await _mediaItemToAudioSource(mediaItem)); + final sources = + await Future.wait(mediaItems.map((i) => _mediaItemToAudioSource(i))); + await _queueAudioSource.addAll(sources); + queue.add(_queueFromSource()); + } catch (e) { + _audioServiceBackgroundTaskLogger.severe(e); + return Future.error(e); + } + } + + Future insertQueueItemsNext(List mediaItems) async { + try { + int? idx = _player.nextIndex; + if (idx == null) { + idx = _player.currentIndex; + if (idx != null) ++idx; + } + idx ??= 0; + + final sources = + await Future.wait(mediaItems.map((i) => _mediaItemToAudioSource(i))); + await _queueAudioSource.insertAll(idx, sources); queue.add(_queueFromSource()); } catch (e) { _audioServiceBackgroundTaskLogger.severe(e); From e324fd33d6c0900f890ffc52e65833563d693008 Mon Sep 17 00:00:00 2001 From: raykast Date: Mon, 7 Aug 2023 00:37:55 -0700 Subject: [PATCH 153/172] nextIndex causes bug with repeat-one looping. --- lib/services/music_player_background_task.dart | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index a4ba51297..68cda95cd 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -218,16 +218,10 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { Future insertQueueItemsNext(List mediaItems) async { try { - int? idx = _player.nextIndex; - if (idx == null) { - idx = _player.currentIndex; - if (idx != null) ++idx; - } - idx ??= 0; - final sources = await Future.wait(mediaItems.map((i) => _mediaItemToAudioSource(i))); - await _queueAudioSource.insertAll(idx, sources); + await _queueAudioSource.insertAll( + (_player.currentIndex ?? -1) + 1, sources); queue.add(_queueFromSource()); } catch (e) { _audioServiceBackgroundTaskLogger.severe(e); From e69b201e91e5e468fd8301311268c10ec0668eed Mon Sep 17 00:00:00 2001 From: raykast Date: Mon, 7 Aug 2023 00:43:39 -0700 Subject: [PATCH 154/172] Copy album queue sort and limit from album screen. --- lib/components/MusicScreen/album_item.dart | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/components/MusicScreen/album_item.dart b/lib/components/MusicScreen/album_item.dart index f93d726a0..93cd8fb62 100644 --- a/lib/components/MusicScreen/album_item.dart +++ b/lib/components/MusicScreen/album_item.dart @@ -169,7 +169,11 @@ class _AlbumItemState extends State { switch (selection) { case _AlbumListTileMenuItems.addToQueue: final children = await jellyfinApiHelper.getItems( - parentItem: widget.album, isGenres: false); + parentItem: widget.album, + sortBy: "ParentIndexNumber,IndexNumber,SortName", + includeItemTypes: "Audio", + isGenres: false, + ); await _audioServiceHelper.addQueueItems(children!); if (!mounted) return; @@ -181,7 +185,11 @@ class _AlbumItemState extends State { case _AlbumListTileMenuItems.playNext: final children = await jellyfinApiHelper.getItems( - parentItem: widget.album, isGenres: false); + parentItem: widget.album, + sortBy: "ParentIndexNumber,IndexNumber,SortName", + includeItemTypes: "Audio", + isGenres: false, + ); await _audioServiceHelper.insertQueueItemsNext(children!); if (!mounted) return; From 6ae1af948ebbab7c1fa80cadf27d25cef2f51d2e Mon Sep 17 00:00:00 2001 From: raykast Date: Mon, 7 Aug 2023 03:40:10 -0700 Subject: [PATCH 155/172] Change shuffle order to make queue edits intuitive --- .../music_player_background_task.dart | 82 ++++++++++++++++++- 1 file changed, 78 insertions(+), 4 deletions(-) diff --git a/lib/services/music_player_background_task.dart b/lib/services/music_player_background_task.dart index 68cda95cd..45afe3069 100644 --- a/lib/services/music_player_background_task.dart +++ b/lib/services/music_player_background_task.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:io'; +import 'dart:math'; import 'package:android_id/android_id.dart'; import 'package:audio_service/audio_service.dart'; @@ -15,6 +16,66 @@ import 'finamp_settings_helper.dart'; import 'finamp_user_helper.dart'; import 'jellyfin_api_helper.dart'; +// Largely copied from just_audio's DefaultShuffleOrder, but with a mildly +// stupid hack to insert() to make Play Next work +class FinampShuffleOrder extends ShuffleOrder { + final Random _random; + @override + final indices = []; + + FinampShuffleOrder({Random? random}) : _random = random ?? Random(); + + @override + void shuffle({int? initialIndex}) { + assert(initialIndex == null || indices.contains(initialIndex)); + if (indices.length <= 1) return; + indices.shuffle(_random); + if (initialIndex == null) return; + + const initialPos = 0; + final swapPos = indices.indexOf(initialIndex); + // Swap the indices at initialPos and swapPos. + final swapIndex = indices[initialPos]; + indices[initialPos] = initialIndex; + indices[swapPos] = swapIndex; + } + + @override + void insert(int index, int count) { + // Offset indices after insertion point. + for (var i = 0; i < indices.length; i++) { + if (indices[i] >= index) { + indices[i] += count; + } + } + + final newIndices = List.generate(count, (i) => index + i); + // This is the only modification from DefaultShuffleOrder: Only shuffle + // inserted indices amongst themselves, but keep them contiguous + newIndices.shuffle(_random); + indices.insertAll(index, newIndices); + } + + @override + void removeRange(int start, int end) { + final count = end - start; + // Remove old indices. + final oldIndices = List.generate(count, (i) => start + i).toSet(); + indices.removeWhere(oldIndices.contains); + // Offset indices after deletion point. + for (var i = 0; i < indices.length; i++) { + if (indices[i] >= end) { + indices[i] -= count; + } + } + } + + @override + void clear() { + indices.clear(); + } +} + /// This provider handles the currently playing music so that multiple widgets /// can control music. class MusicPlayerBackgroundTask extends BaseAudioHandler { @@ -30,8 +91,10 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { FinampSettingsHelper.finampSettings.bufferDuration, )), ); - ConcatenatingAudioSource _queueAudioSource = - ConcatenatingAudioSource(children: []); + ConcatenatingAudioSource _queueAudioSource = ConcatenatingAudioSource( + children: [], + shuffleOrder: FinampShuffleOrder(), + ); final _audioServiceBackgroundTaskLogger = Logger("MusicPlayerBackgroundTask"); final _jellyfinApiHelper = GetIt.instance(); final _finampUserHelper = GetIt.instance(); @@ -218,10 +281,20 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { Future insertQueueItemsNext(List mediaItems) async { try { + var idx = _player.currentIndex; + if (idx != null) { + if (_player.shuffleModeEnabled) { + var next = _player.shuffleIndices?.indexOf(idx); + idx = next == -1 || next == null ? null : next + 1; + } else { + ++idx; + } + } + idx ??= 0; + final sources = await Future.wait(mediaItems.map((i) => _mediaItemToAudioSource(i))); - await _queueAudioSource.insertAll( - (_player.currentIndex ?? -1) + 1, sources); + await _queueAudioSource.insertAll(idx, sources); queue.add(_queueFromSource()); } catch (e) { _audioServiceBackgroundTaskLogger.severe(e); @@ -241,6 +314,7 @@ class MusicPlayerBackgroundTask extends BaseAudioHandler { // Create a new ConcatenatingAudioSource with the new queue. _queueAudioSource = ConcatenatingAudioSource( children: audioSources, + shuffleOrder: FinampShuffleOrder(), ); try { From 7b0f169b66fee9d449626a0efd377a3a78eeec46 Mon Sep 17 00:00:00 2001 From: raykast Date: Mon, 7 Aug 2023 12:31:33 -0700 Subject: [PATCH 156/172] Fix inconsistent popup menu items. --- .../AlbumScreen/song_list_tile.dart | 26 ++++++------ lib/components/MusicScreen/album_item.dart | 40 +++++++++++-------- lib/services/audio_service_helper.dart | 4 ++ 3 files changed, 41 insertions(+), 29 deletions(-) diff --git a/lib/components/AlbumScreen/song_list_tile.dart b/lib/components/AlbumScreen/song_list_tile.dart index 77058a47b..385d4ec45 100644 --- a/lib/components/AlbumScreen/song_list_tile.dart +++ b/lib/components/AlbumScreen/song_list_tile.dart @@ -239,20 +239,22 @@ class _SongListTileState extends State { screenSize.height - details.globalPosition.dy, ), items: [ - PopupMenuItem( - value: SongListTileMenuItems.addToQueue, - child: ListTile( - leading: const Icon(Icons.queue_music), - title: Text(AppLocalizations.of(context)!.addToQueue), + if (_audioServiceHelper.hasQueueItems()) ...[ + PopupMenuItem( + value: SongListTileMenuItems.addToQueue, + child: ListTile( + leading: const Icon(Icons.queue_music), + title: Text(AppLocalizations.of(context)!.addToQueue), + ), ), - ), - PopupMenuItem( - value: SongListTileMenuItems.playNext, - child: ListTile( - leading: const Icon(Icons.queue_music), - title: Text(AppLocalizations.of(context)!.playNext), + PopupMenuItem( + value: SongListTileMenuItems.playNext, + child: ListTile( + leading: const Icon(Icons.queue_music), + title: Text(AppLocalizations.of(context)!.playNext), + ), ), - ), + ], PopupMenuItem( value: SongListTileMenuItems.replaceQueueWithItem, child: ListTile( diff --git a/lib/components/MusicScreen/album_item.dart b/lib/components/MusicScreen/album_item.dart index 93cd8fb62..db339f37c 100644 --- a/lib/components/MusicScreen/album_item.dart +++ b/lib/components/MusicScreen/album_item.dart @@ -98,11 +98,7 @@ class _AlbumItemState extends State { onLongPressStart: (details) async { Feedback.forLongPress(context); - if (FinampSettingsHelper.finampSettings.isOffline) { - // If offline, don't show the context menu since the only options here - // are for online. - return; - } + final isOffline = FinampSettingsHelper.finampSettings.isOffline; final jellyfinApiHelper = GetIt.instance(); @@ -115,48 +111,58 @@ class _AlbumItemState extends State { screenSize.height - details.globalPosition.dy, ), items: [ - PopupMenuItem<_AlbumListTileMenuItems>( - value: _AlbumListTileMenuItems.addToQueue, - child: ListTile( - leading: const Icon(Icons.queue_music), - title: Text(AppLocalizations.of(context)!.addToQueue), + if (_audioServiceHelper.hasQueueItems()) ...[ + PopupMenuItem<_AlbumListTileMenuItems>( + value: _AlbumListTileMenuItems.addToQueue, + child: ListTile( + leading: const Icon(Icons.queue_music), + title: Text(AppLocalizations.of(context)!.addToQueue), + ), ), - ), - PopupMenuItem<_AlbumListTileMenuItems>( - value: _AlbumListTileMenuItems.playNext, - child: ListTile( - leading: const Icon(Icons.queue_music), - title: Text(AppLocalizations.of(context)!.playNext), + PopupMenuItem<_AlbumListTileMenuItems>( + value: _AlbumListTileMenuItems.playNext, + child: ListTile( + leading: const Icon(Icons.queue_music), + title: Text(AppLocalizations.of(context)!.playNext), + ), ), - ), + ], mutableAlbum.userData!.isFavorite ? PopupMenuItem<_AlbumListTileMenuItems>( + enabled: !isOffline, value: _AlbumListTileMenuItems.removeFavourite, child: ListTile( + enabled: !isOffline, leading: const Icon(Icons.favorite_border), title: Text(AppLocalizations.of(context)!.removeFavourite), ), ) : PopupMenuItem<_AlbumListTileMenuItems>( + enabled: !isOffline, value: _AlbumListTileMenuItems.addFavourite, child: ListTile( + enabled: !isOffline, leading: const Icon(Icons.favorite), title: Text(AppLocalizations.of(context)!.addFavourite), ), ), jellyfinApiHelper.selectedMixAlbumIds.contains(mutableAlbum.id) ? PopupMenuItem<_AlbumListTileMenuItems>( + enabled: !isOffline, value: _AlbumListTileMenuItems.removeFromMixList, child: ListTile( + enabled: !isOffline, leading: const Icon(Icons.explore_off), title: Text(AppLocalizations.of(context)!.removeFromMix), ), ) : PopupMenuItem<_AlbumListTileMenuItems>( + enabled: !isOffline, value: _AlbumListTileMenuItems.addToMixList, child: ListTile( + enabled: !isOffline, leading: const Icon(Icons.explore), title: Text(AppLocalizations.of(context)!.addToMix), ), diff --git a/lib/services/audio_service_helper.dart b/lib/services/audio_service_helper.dart index 7439b55d1..925bb2111 100644 --- a/lib/services/audio_service_helper.dart +++ b/lib/services/audio_service_helper.dart @@ -64,6 +64,10 @@ class AudioServiceHelper { } } + bool hasQueueItems() { + return (_audioHandler.queue.valueOrNull?.length ?? 0) != 0; + } + @Deprecated("Use addQueueItems instead") Future addQueueItem(BaseItemDto item) async { await addQueueItems([item]); From 73d35c1e1e5072203f54d27ca4e58424fcf3f963 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 11 Aug 2023 16:33:54 +0100 Subject: [PATCH 157/172] Set DownloadedImage ids to blurhash when migrating --- lib/services/downloads_helper.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 9373dcf85..7ee67d4ef 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -898,13 +898,16 @@ class DownloadsHelper { } } - // Go through each requiredBy and remove duplicates + // Go through each requiredBy and remove duplicates. We also set the image's + // id to the blurhash. for (final imageEntry in imageMap.entries) { final image = imageEntry.value; image.requiredBy = image.requiredBy.toSet().toList(); _downloadsLogger.warning(image.requiredBy); + image.id = imageEntry.key; + imageMap[imageEntry.key] = image; } From 423c5b7d842f39f985a2d89e202a74e891539cf3 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 11 Aug 2023 20:46:49 +0100 Subject: [PATCH 158/172] Fix incorrect IDs in blurhash migration --- .flutter | 2 +- lib/main.dart | 13 ++++++++- lib/models/finamp_models.dart | 8 +++++ lib/models/finamp_models.g.dart | 8 +++-- lib/services/downloads_helper.dart | 37 ++++++++++++++++++++++++ lib/services/finamp_settings_helper.dart | 9 ++++++ 6 files changed, 73 insertions(+), 4 deletions(-) diff --git a/.flutter b/.flutter index f468f3366..4d9e56e69 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit f468f3366c26a5092eb964a230ce7892fda8f2f8 +Subproject commit 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf diff --git a/lib/main.dart b/lib/main.dart index 6d7c8a8d5..33aa5c349 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -93,11 +93,22 @@ void _setupJellyfinApiData() { Future _setupDownloadsHelper() async { GetIt.instance.registerSingleton(DownloadsHelper()); + final downloadsHelper = GetIt.instance(); + + // We awkwardly cache this value since going from 0.6.14 -> 0.6.16 will switch + // hasCompletedBlurhashImageMigration despite doing a fixed migration + final shouldRunBlurhashImageMigrationIdFix = + FinampSettingsHelper.finampSettings.shouldRunBlurhashImageMigrationIdFix; if (!FinampSettingsHelper.finampSettings.hasCompletedBlurhashImageMigration) { - await GetIt.instance().migrateBlurhashImages(); + await downloadsHelper.migrateBlurhashImages(); FinampSettingsHelper.setHasCompletedBlurhashImageMigration(true); } + + if (shouldRunBlurhashImageMigrationIdFix) { + await downloadsHelper.fixBlurhashMigrationIds(); + FinampSettingsHelper.setHasCompletedBlurhashImageMigrationIdFix(true); + } } Future _setupDownloader() async { diff --git a/lib/models/finamp_models.dart b/lib/models/finamp_models.dart index ef1596632..e4d037a28 100644 --- a/lib/models/finamp_models.dart +++ b/lib/models/finamp_models.dart @@ -87,6 +87,7 @@ class FinampSettings { required this.tabSortOrder, this.tabOrder = _tabOrder, this.hasCompletedBlurhashImageMigration = true, + this.hasCompletedBlurhashImageMigrationIdFix = true, }); @HiveField(0) @@ -176,6 +177,9 @@ class FinampSettings { @HiveField(23, defaultValue: false) bool hasCompletedBlurhashImageMigration; + @HiveField(24, defaultValue: false) + bool hasCompletedBlurhashImageMigrationIdFix; + static Future create() async { final internalSongDir = await getInternalSongDir(); final downloadLocation = DownloadLocation.create( @@ -216,6 +220,10 @@ class FinampSettings { SortOrder getSortOrder(TabContentType tabType) { return tabSortOrder[tabType] ?? SortOrder.ascending; } + + bool get shouldRunBlurhashImageMigrationIdFix => + hasCompletedBlurhashImageMigration && + !hasCompletedBlurhashImageMigrationIdFix; } /// Custom storage locations for storing music. diff --git a/lib/models/finamp_models.g.dart b/lib/models/finamp_models.g.dart index c7a16ea6b..e8e2fe943 100644 --- a/lib/models/finamp_models.g.dart +++ b/lib/models/finamp_models.g.dart @@ -110,13 +110,15 @@ class FinampSettingsAdapter extends TypeAdapter { : (fields[22] as List).cast(), hasCompletedBlurhashImageMigration: fields[23] == null ? false : fields[23] as bool, + hasCompletedBlurhashImageMigrationIdFix: + fields[24] == null ? false : fields[24] as bool, )..disableGesture = fields[19] == null ? false : fields[19] as bool; } @override void write(BinaryWriter writer, FinampSettings obj) { writer - ..writeByte(24) + ..writeByte(25) ..writeByte(0) ..write(obj.isOffline) ..writeByte(1) @@ -164,7 +166,9 @@ class FinampSettingsAdapter extends TypeAdapter { ..writeByte(22) ..write(obj.tabOrder) ..writeByte(23) - ..write(obj.hasCompletedBlurhashImageMigration); + ..write(obj.hasCompletedBlurhashImageMigration) + ..writeByte(24) + ..write(obj.hasCompletedBlurhashImageMigrationIdFix); } @override diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index 7ee67d4ef..cdb0edc7e 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -936,6 +936,43 @@ class DownloadsHelper { _downloadsLogger.info("${imagesToDelete.length} duplicate images deleted."); } + Future fixBlurhashMigrationIds() async { + _downloadsLogger.info("Fixing blurhash migration IDs from 0.6.15"); + + final List images = []; + + for (final image in downloadedImages) { + final item = getDownloadedSong(image.requiredBy.first) ?? + getDownloadedParent(image.requiredBy.first); + + if (item == null) { + // I should really use error enums when I rip this whole system out + throw "Failed to get item from image during blurhash migration fix!"; + } + + switch (item.runtimeType) { + case DownloadedSong: + image.id = (item as DownloadedSong).song.blurHash!; + break; + case DownloadedParent: + image.id = (item as DownloadedParent).item.blurHash!; + break; + default: + throw "Item was unexpected type! got ${item.runtimeType}. This really shouldn't happen..."; + } + + images.add(image); + } + + await _downloadedImagesBox.clear(); + await _downloadedImagesBox + .putAll(Map.fromEntries(images.map((e) => MapEntry(e.id, e)))); + + await _downloadedImageIdsBox.clear(); + await _downloadedImageIdsBox.putAll( + Map.fromEntries(images.map((e) => MapEntry(e.downloadId, e.id)))); + } + Iterable get downloadedItems => _downloadedItemsBox.values; Iterable get downloadedImages => _downloadedImagesBox.values; diff --git a/lib/services/finamp_settings_helper.dart b/lib/services/finamp_settings_helper.dart index 6e73e298f..b8722c6f3 100644 --- a/lib/services/finamp_settings_helper.dart +++ b/lib/services/finamp_settings_helper.dart @@ -199,6 +199,15 @@ class FinampSettingsHelper { .put("FinampSettings", finampSettingsTemp); } + static void setHasCompletedBlurhashImageMigrationIdFix( + bool hasCompletedBlurhashImageMigrationIdFix) { + FinampSettings finampSettingsTemp = finampSettings; + finampSettingsTemp.hasCompletedBlurhashImageMigrationIdFix = + hasCompletedBlurhashImageMigrationIdFix; + Hive.box("FinampSettings") + .put("FinampSettings", finampSettingsTemp); + } + static void setTabOrder(int index, TabContentType tabContentType) { FinampSettings finampSettingsTemp = finampSettings; finampSettingsTemp.tabOrder[index] = tabContentType; From 8eb4d1364ac468b8d1c49cee6e2ed223564892a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=84=A1=E6=83=85=E5=A4=A9?= Date: Fri, 28 Jul 2023 14:38:04 +0000 Subject: [PATCH 159/172] Translated using Weblate (Chinese (Simplified)) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/zh_Hans/ --- lib/l10n/app_zh.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 3efe8899a..9fa7093df 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -481,5 +481,11 @@ "removeFromPlaylistTooltip": "从播放列表删除", "@removeFromPlaylistTooltip": {}, "language": "语言", - "@language": {} + "@language": {}, + "confirm": "确认", + "@confirm": {}, + "showUncensoredLogMessage": "该日志包含您的登录信息。显示?", + "@showUncensoredLogMessage": {}, + "resetTabs": "重置选项卡", + "@resetTabs": {} } From 325c78147b474ad8528a4d71346d2b41013c5949 Mon Sep 17 00:00:00 2001 From: Felipe Silva Date: Fri, 28 Jul 2023 20:01:41 +0000 Subject: [PATCH 160/172] Translated using Weblate (Portuguese (Brazil)) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/pt_BR/ --- lib/l10n/app_pt_BR.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_pt_BR.arb b/lib/l10n/app_pt_BR.arb index 05efcd773..1c35a727b 100644 --- a/lib/l10n/app_pt_BR.arb +++ b/lib/l10n/app_pt_BR.arb @@ -481,5 +481,11 @@ } }, "language": "Idioma", - "@language": {} + "@language": {}, + "resetTabs": "Redefinir abas", + "@resetTabs": {}, + "confirm": "Confirmar", + "@confirm": {}, + "showUncensoredLogMessage": "Este registro contém suas credenciais de acesso. Revelar?", + "@showUncensoredLogMessage": {} } From d1d4843f77ac36f555ea6ed6d80013dde0859c84 Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Fri, 28 Jul 2023 15:44:26 +0000 Subject: [PATCH 161/172] Translated using Weblate (Croatian) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/hr/ --- lib/l10n/app_hr.arb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index e75d7fabe..c7c1bf645 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -241,7 +241,7 @@ } } }, - "downloadErrors": "Preuzmi greške", + "downloadErrors": "Greške preuzimanja", "@downloadErrors": {}, "downloadedItemsCount": "{count,plural,=1{{count} stvar} other{{count} stvari}}", "@downloadedItemsCount": { @@ -275,7 +275,7 @@ } } }, - "downloadErrorsTitle": "Preuzmi greške", + "downloadErrorsTitle": "Greške preuzimanja", "@downloadErrorsTitle": {}, "editPlaylistNameTooltip": "Uredi ime popisa za reprodukciju", "@editPlaylistNameTooltip": {}, From 2aff87b816cbafb2fdbee4dd32906f0a1689265f Mon Sep 17 00:00:00 2001 From: Ekapol Tassaneeyasin Date: Sat, 29 Jul 2023 03:56:58 +0000 Subject: [PATCH 162/172] Translated using Weblate (Thai) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/th/ --- lib/l10n/app_th.arb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb index 13a2fe118..dedb98721 100644 --- a/lib/l10n/app_th.arb +++ b/lib/l10n/app_th.arb @@ -481,5 +481,11 @@ "shuffleAll": "สุ่มทั้งหมด", "@shuffleAll": {}, "showTextOnGridViewSubtitle": "แสดงหรือไม่แสดงข้อความ (ชื่อเพลง, ศิลปิน, อื่น ๆ) บนตารางแสดงเพลง", - "@showTextOnGridViewSubtitle": {} + "@showTextOnGridViewSubtitle": {}, + "resetTabs": "รีเซ็ตการตั้งค่าแท็ป", + "@resetTabs": {}, + "confirm": "ยืนยัน", + "@confirm": {}, + "showUncensoredLogMessage": "ล็อกนี้มีข้อมูลการล็อกอินของคุณอยู่ ต้องการให้แสดงใช่ไหม?", + "@showUncensoredLogMessage": {} } From 95920bc4732c23c434e8f49fe7aa648bf3e3e4df Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Fri, 28 Jul 2023 23:03:42 +0000 Subject: [PATCH 163/172] Translated using Weblate (Croatian) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/hr/ --- lib/l10n/app_hr.arb | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index c7c1bf645..295c48ec2 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -1,7 +1,7 @@ { - "serverUrl": "URL Servera", + "serverUrl": "URL servera", "@serverUrl": {}, - "startupError": "Dogodila se greška prilikom pokretanja aplikacije. Greška: {error}\n\nMolimo napravite issue na github.com/UnicornsOnLSD/finamp sa slikom zaslona ove stranice. Ako problem i dalje nastavi, pokušajte očistiti podatke aplikacije i resetirati ju.", + "startupError": "Dogodila se greška prilikom pokretanja aplikacije. Greška: {error}\n\nPrijavi problem na github.com/UnicornsOnLSD/finamp sa snimkom ekrana ove stranice. Ako ovaj problem ustraje, izbriši podatke svoje aplikacije za resetiranje aplikacije.", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -21,9 +21,9 @@ "@username": {}, "password": "Lozinka", "@password": {}, - "logs": "Dnevnik logova", + "logs": "Zapisi", "@logs": {}, - "next": "Sljedeće", + "next": "Dalje", "@next": {}, "genres": "Žanrovi", "@genres": {}, @@ -97,7 +97,7 @@ "@startingInstantMix": {}, "anErrorHasOccured": "Dogodila se greška.", "@anErrorHasOccured": {}, - "responseError401": "{error} Status kod {statusCode}. Ovo vjerojatno znači da ste koristili pokrešno korisničko ime/lozinku, ili vaš kljent nije više prijavljen.", + "responseError401": "{error} Kod stanja {statusCode}. Ovo vjerojatno znači da si koristio/la pokrešno korisničko ime/lozinku ili da tvoj klijent više nije prijavljen.", "@responseError401": { "placeholders": { "error": { @@ -126,7 +126,7 @@ "@startMixNoSongsArtist": { "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" }, - "urlStartWithHttps": "URL mora početi sa http:// ili https://", + "urlStartWithHttps": "URL mora početi s http:// ili https://", "@urlStartWithHttps": { "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" }, @@ -136,7 +136,7 @@ "@internalExternalIpExplanation": { "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." }, - "emptyServerUrl": "URL Servera je smije biti prazan", + "emptyServerUrl": "URL servera ne smije biti prazan", "@emptyServerUrl": { "description": "Error message that shows when the user submits a login without a server URL" }, @@ -321,9 +321,9 @@ "@downloadsDeleted": {}, "addButtonLabel": "DODAJ", "@addButtonLabel": {}, - "shareLogs": "Podijeli zapise logova", + "shareLogs": "Dijeli zapise", "@shareLogs": {}, - "logsCopied": "Zapisi logova kopirani.", + "logsCopied": "Zapisi su kopirani.", "@logsCopied": {}, "message": "Poruka", "@message": {}, From 4e5671230caba9c22381d1510761e1b17c617b9d Mon Sep 17 00:00:00 2001 From: Milo Ivir Date: Sun, 30 Jul 2023 13:07:36 +0000 Subject: [PATCH 164/172] Translated using Weblate (Croatian) Currently translated at 100.0% (174 of 174 strings) Translation: Finamp/Finamp Translate-URL: https://hosted.weblate.org/projects/finamp/finamp/hr/ --- lib/l10n/app_hr.arb | 144 ++++++++++++++++++++++---------------------- 1 file changed, 72 insertions(+), 72 deletions(-) diff --git a/lib/l10n/app_hr.arb b/lib/l10n/app_hr.arb index 295c48ec2..4174fa151 100644 --- a/lib/l10n/app_hr.arb +++ b/lib/l10n/app_hr.arb @@ -1,7 +1,7 @@ { "serverUrl": "URL servera", "@serverUrl": {}, - "startupError": "Dogodila se greška prilikom pokretanja aplikacije. Greška: {error}\n\nPrijavi problem na github.com/UnicornsOnLSD/finamp sa snimkom ekrana ove stranice. Ako ovaj problem ustraje, izbriši podatke svoje aplikacije za resetiranje aplikacije.", + "startupError": "Dogodila se greška prilikom pokretanja aplikacije. Greška: {error}\n\nPrijavi problem na github.com/UnicornsOnLSD/finamp sa snimkom ekrana ove stranice. Ako ovaj problem ustraje, izbriši svoje podatke aplikacije i resetiraj aplikaciju.", "@startupError": { "description": "The error message that shows when startup fails.", "placeholders": { @@ -11,9 +11,9 @@ } } }, - "sortOrder": "Redoslijed sortiranja", + "sortOrder": "Redoslijed razvrstavanja", "@sortOrder": {}, - "selectMusicLibraries": "Odaberi glazbenu knjižnicu", + "selectMusicLibraries": "Odaberi fonoteke", "@selectMusicLibraries": { "description": "App bar title for library select screen" }, @@ -31,7 +31,7 @@ "@music": {}, "name": "Ime", "@name": {}, - "random": "Nasumično", + "random": "Slučajno", "@random": {}, "revenue": "Prihod", "@revenue": {}, @@ -41,7 +41,7 @@ "@downloadMissingImages": {}, "noErrors": "Nema grešaka!", "@noErrors": {}, - "failedToGetSongFromDownloadId": "Neuspjelo dohvaćanje pjesme s ID preuzimanja", + "failedToGetSongFromDownloadId": "Neuspjelo dohvaćanje pjesme s ID-a preuzimanja", "@failedToGetSongFromDownloadId": {}, "error": "Greška", "@error": {}, @@ -51,7 +51,7 @@ "@favourite": {}, "location": "Lokacija", "@location": {}, - "applicationLegalese": "Licencirano sa Mozilla Public License 2.0. Izvorni kod dostupan na:\n\ngithub.com/jmshrv/finamp", + "applicationLegalese": "Licenca: Mozilla Public License 2.0. Izvorni kod je dostupan na:\n\ngithub.com/jmshrv/finamp", "@applicationLegalese": {}, "transcoding": "Transkodiranje", "@transcoding": {}, @@ -59,15 +59,15 @@ "@logOut": {}, "unknownError": "Nepoznata greška", "@unknownError": {}, - "pathReturnSlashErrorMessage": "Putovi koji vraćaju \"/\" ne mogu se koristiti", + "pathReturnSlashErrorMessage": "Staze koje vraćaju \"/\" se ne mogu koristiti", "@pathReturnSlashErrorMessage": {}, "shuffleAllSongCount": "Izmiješaj sve pjesme", "@shuffleAllSongCount": {}, - "list": "Lista", + "list": "Popis", "@list": {}, - "portrait": "Portret", + "portrait": "Uspravni", "@portrait": {}, - "landscape": "Pejzaž", + "landscape": "Ležeći", "@landscape": {}, "gridCrossAxisCount": "{value} broj rešetkastih linija", "@gridCrossAxisCount": { @@ -79,25 +79,25 @@ } } }, - "showTextOnGridView": "Prikaži tekst u rešetkastom pregledu", + "showTextOnGridView": "Prikaži tekst u rešetkastom prikazu", "@showTextOnGridView": {}, - "showTextOnGridViewSubtitle": "Da li prikazati tekst (naslov, umjetnik itd..) na rešetkastom glazbenom ekranu.", + "showTextOnGridViewSubtitle": "Da li prikazati tekst (naslov, izvođač itd.) u rešetkastom ekranu glazbe.", "@showTextOnGridViewSubtitle": {}, - "showCoverAsPlayerBackground": "Prikaži mutan cover kao pozadinu playera", + "showCoverAsPlayerBackground": "Prikaži mutnu sliku omota kao pozadinu playera", "@showCoverAsPlayerBackground": {}, - "tabs": "Tabovi", + "tabs": "Kartice", "@tabs": {}, - "streaming": "STRUJANJE", + "streaming": "PRIJENOS", "@streaming": {}, - "statusError": "STATUS GREŠKE", + "statusError": "GREŠKA STANJA", "@statusError": {}, - "removedFromPlaylist": "Uklonjeno iz popisa za reprodukciju.", + "removedFromPlaylist": "Uklonjeno iz popisa pjesama.", "@removedFromPlaylist": {}, "startingInstantMix": "Pokretanje instant miksa.", "@startingInstantMix": {}, "anErrorHasOccured": "Dogodila se greška.", "@anErrorHasOccured": {}, - "responseError401": "{error} Kod stanja {statusCode}. Ovo vjerojatno znači da si koristio/la pokrešno korisničko ime/lozinku ili da tvoj klijent više nije prijavljen.", + "responseError401": "{error} Kȏd stanja {statusCode}. Ovo vjerojatno znači da si koristio/la pokrešno korisničko ime/lozinku ili da tvoj klijent više nije prijavljen.", "@responseError401": { "placeholders": { "error": { @@ -114,7 +114,7 @@ "@removeFromMix": {}, "bufferDuration": "Trajanje međuspremnika", "@bufferDuration": {}, - "bufferDurationSubtitle": "Koliko igrač treba spremiti u međuspremnik, u sekundama. Zahtijeva ponovno pokretanje.", + "bufferDurationSubtitle": "Koliko player treba spremiti u međuspremnik, u sekundama. Zahtijeva ponovno pokretanje.", "@bufferDurationSubtitle": {}, "language": "Jezik", "@language": {}, @@ -122,7 +122,7 @@ "@unknownName": {}, "songs": "Pjesme", "@songs": {}, - "startMixNoSongsArtist": "Pritisni i drži na umjetnika da dodate ili uklonite iz graditelja mixa prije početka mixa", + "startMixNoSongsArtist": "Pritisni dugo na izvođača za dodavanje ili uklanjanje izvođača iz miksera prije pokretanja miksa", "@startMixNoSongsArtist": { "description": "Snackbar message that shows when the user presses the instant mix button with no artists selected" }, @@ -130,9 +130,9 @@ "@urlStartWithHttps": { "description": "Error message that shows when the user submits a server URL that doesn't start with http:// or https:// (for example, ftp://0.0.0.0" }, - "artists": "Umjetnici", + "artists": "Izvođači", "@artists": {}, - "internalExternalIpExplanation": "Ako želite pristupiti Jellyfin serveru udaljeno (remotely), morate korisititi externu IP adresu.\n\nAko je Vaš server na HTTP portu (80/443), ne morate navesti port. Ovo je vrlo vjerojatno slučaj ako je vaš server iza obrnute proxy.", + "internalExternalIpExplanation": "Ako želiš pristupiti Jellyfin serveru na daljinski način moraš korisititi tvoju externu IP adresu.\n\nAko je tvoj server na HTTP priključku (80/443), ne moraš navesti priključak. To će vjerojatno slučaj ako se tvoj server nalazi iza obrnutog proxija.", "@internalExternalIpExplanation": { "description": "Extra info for which IP to use for remote access, and info on whether or not the user needs to specify a port." }, @@ -146,19 +146,19 @@ }, "albums": "Albumi", "@albums": {}, - "couldNotFindLibraries": "Nemoguće pronaći knjižnicu.", + "couldNotFindLibraries": "Nije bilo moguće pronaći niti jednu fonoteku.", "@couldNotFindLibraries": { "description": "Error message when the user does not have any libraries" }, - "playlists": "Popis za reprodukciju", + "playlists": "Popisi pjesama", "@playlists": {}, - "startMix": "Pokreni Mix", + "startMix": "Pokreni miks", "@startMix": {}, "playCount": "Broj reprodukcija", "@playCount": {}, "datePlayed": "Datum reprodukcije", "@datePlayed": {}, - "startMixNoSongsAlbum": "Pritisni i drži na album da dodate ili uklonite iz graditelja mixa prije početka mixa", + "startMixNoSongsAlbum": "Pritisni dugo na album za dodavanje ili uklanjanje albuma iz miksera prije pokretanja miksa", "@startMixNoSongsAlbum": { "description": "Snackbar message that shows when the user presses the instant mix button with no albums selected" }, @@ -168,7 +168,7 @@ "@album": {}, "communityRating": "Ocjena zajednice", "@communityRating": {}, - "clear": "Očisti", + "clear": "Izbriši", "@clear": {}, "finamp": "Finamp", "@finamp": {}, @@ -178,17 +178,17 @@ "@downloads": {}, "settings": "Postavke", "@settings": {}, - "offlineMode": "Način izvan mreže", + "offlineMode": "Neumrežen modus", "@offlineMode": {}, - "sortBy": "Sortiraj po", + "sortBy": "Razvrstaj po", "@sortBy": {}, "productionYear": "Godina produkcije", "@productionYear": {}, - "albumArtist": "Album Umjetnik", + "albumArtist": "Izvođač albuma", "@albumArtist": {}, "dateAdded": "Datum dodavanja", "@dateAdded": {}, - "artist": "Umjetnik", + "artist": "Izvođač", "@artist": {}, "budget": "Budžet", "@budget": {}, @@ -196,7 +196,7 @@ "@premiereDate": {}, "criticRating": "Ocjena kritičara", "@criticRating": {}, - "downloadedMissingImages": "{count,plural, =0{Nema nedostajućih fotografija} =1{Preuzeta {count} nedostajuća fotografija} other{Preuzeto {count} nedostajućih fotografija}}", + "downloadedMissingImages": "{count,plural, =0{Nema nedostajućih fotografija} =1{Preuzeta je {count} nedostajuća fotografija} few{Preuzete su {count} nedostajuće fotografije} other{Preuzeto je {count} nedostajućih fotografija}}", "@downloadedMissingImages": { "description": "Message that shows when the user downloads missing images", "placeholders": { @@ -205,11 +205,11 @@ } } }, - "editPlaylistNameTitle": "Uredi ime popisa za reprodukciju", + "editPlaylistNameTitle": "Uredi ime popisa pjesama", "@editPlaylistNameTitle": {}, "customLocation": "Prilagođena lokacija", "@customLocation": {}, - "viewTypeSubtitle": "Vidi tip za glazbeni ekran", + "viewTypeSubtitle": "Vrsta prikaza za ekran glazbe", "@viewTypeSubtitle": {}, "downloadedItemsImagesCount": "{downloadedItems}, {downloadedImages}", "@downloadedItemsImagesCount": { @@ -233,7 +233,7 @@ } } }, - "downloadCount": "{count,plural, =1{{count} preuzimanje} other{{count} preuzimanja}}", + "downloadCount": "{count,plural, =1{{count} preuzimanje} few{{count} preuzimanja} other{{count} preuzimanja}}", "@downloadCount": { "placeholders": { "count": { @@ -243,7 +243,7 @@ }, "downloadErrors": "Greške preuzimanja", "@downloadErrors": {}, - "downloadedItemsCount": "{count,plural,=1{{count} stvar} other{{count} stvari}}", + "downloadedItemsCount": "{count,plural,=1{{count} stavka} few{{count} stavke} other{{count} stavki}}", "@downloadedItemsCount": { "placeholders": { "count": { @@ -251,7 +251,7 @@ } } }, - "downloadedImagesCount": "{count,plural,=1{{count} fotografija} other{{count} fotografija}}", + "downloadedImagesCount": "{count,plural,=1{{count} fotografija} few{{count} fotografije} other{{count} fotografija}}", "@downloadedImagesCount": { "placeholders": { "count": { @@ -277,9 +277,9 @@ }, "downloadErrorsTitle": "Greške preuzimanja", "@downloadErrorsTitle": {}, - "editPlaylistNameTooltip": "Uredi ime popisa za reprodukciju", + "editPlaylistNameTooltip": "Uredi ime popisa pjesama", "@editPlaylistNameTooltip": {}, - "playlistNameUpdated": "Ime popisa za reprodukciju ažurirano.", + "playlistNameUpdated": "Ime popisa pjesama je ažurirano.", "@playlistNameUpdated": {}, "dlEnqueued": "{count} u redu čekanja", "@dlEnqueued": { @@ -297,13 +297,13 @@ } } }, - "errorScreenError": "Dogodila se greška prilikom dohvaćanja liste grešaka! Jedino što preostaje je napraviti issue na GitHub-u i izbrisati podatke aplikacije", + "errorScreenError": "Dogodila se greška prilikom dohvaćanja popisa grešaka! Prijavi problem na GitHubu i izbriši podatke aplikacije", "@errorScreenError": {}, "shuffleButtonLabel": "IZMIJEŠAJ", "@shuffleButtonLabel": {}, "addDownloads": "Dodaj preuzimanja", "@addDownloads": {}, - "songCount": "{count,plural,=1{{count} Pjesma} other{{count} Pjesama}}", + "songCount": "{count,plural,=1{{count} pjesma} few{{count} pjesme} other{{count} pjesama}}", "@songCount": { "placeholders": { "count": { @@ -333,19 +333,19 @@ "@layoutAndTheme": {}, "customLocationsBuggy": "Prilagođene lokacije su izrazito pune grešaka zbog problema oko dozvola. Razmišljam o načinima da ovo ispravim, ali za sada ne bih preporučio korištenje.", "@customLocationsBuggy": {}, - "shuffleAllSongCountSubtitle": "Količina pjesama da se učita kada koristite izmiješaj sve pjesme tipku.", + "shuffleAllSongCountSubtitle": "Količina pjesama koja se učitava kada se koristi gumb „Izmiješaj sve pjesme”.", "@shuffleAllSongCountSubtitle": {}, - "noArtist": "Nema umjetnika", + "noArtist": "Nema izvođača", "@noArtist": {}, "downloadLocations": "Lokacija preuzimanja", "@downloadLocations": {}, - "audioService": "Audio Servis", + "audioService": "Usluga audioreprodukcije", "@audioService": {}, "areYouSure": "Jeste li sigurni?", "@areYouSure": {}, "jellyfinUsesAACForTranscoding": "Jellyfin koristi AAC za transkodiranje", "@jellyfinUsesAACForTranscoding": {}, - "notAvailableInOfflineMode": "Nije dostupno u načinu izvan mreže", + "notAvailableInOfflineMode": "Nije dostupno u neumreženom modusu", "@notAvailableInOfflineMode": {}, "addDownloadLocation": "Dodaj lokaciju preuzimanja", "@addDownloadLocation": {}, @@ -355,21 +355,21 @@ "@enterLowPriorityStateOnPauseSubtitle": {}, "grid": "Rešetka", "@grid": {}, - "showCoverAsPlayerBackgroundSubtitle": "Da li koristiti mutni cover kao pozadinu na ekranu playera.", + "showCoverAsPlayerBackgroundSubtitle": "Da li koristiti mutnu sliku omota kao pozadinu na ekranu playera.", "@showCoverAsPlayerBackgroundSubtitle": {}, - "disableGesture": "Ugasi geste", + "disableGesture": "Deaktiviraj geste", "@disableGesture": {}, "theme": "Tema", "@theme": {}, - "dark": "Tamno", + "dark": "Tamna", "@dark": {}, "downloadedSongsWillNotBeDeleted": "Preuzete pjesme neće biti izbrisane", "@downloadedSongsWillNotBeDeleted": {}, - "bitrate": "Bitrate", + "bitrate": "Brzina prijenosa", "@bitrate": {}, - "bitrateSubtitle": "Veći bitrate daje veću kvalitetu zvuka s troškom većeg prometa.", + "bitrateSubtitle": "Veća brzina prijenosa daje veću kvalitetu zvuka, ali troši veću količinu prometa.", "@bitrateSubtitle": {}, - "setSleepTimer": "Postavi vrijeme spavanja", + "setSleepTimer": "Postavi odbrojavanje", "@setSleepTimer": {}, "downloaded": "PREUZETO", "@downloaded": {}, @@ -379,7 +379,7 @@ "@enableTranscoding": {}, "enableTranscodingSubtitle": "Transkodira stream glazbe na server strani.", "@enableTranscodingSubtitle": {}, - "addToPlaylistTooltip": "Dodaj u popis za reprodukciju", + "addToPlaylistTooltip": "Dodaj u popis pjesama", "@addToPlaylistTooltip": {}, "goToAlbum": "Idi na album", "@goToAlbum": {}, @@ -397,49 +397,49 @@ } } }, - "hideSongArtistsIfSameAsAlbumArtists": "Sakrij umjetnike pjesme ako je isti kao umjetnik albuma", + "hideSongArtistsIfSameAsAlbumArtists": "Sakrij izvođače pjesama ako su isti kao izvođači albuma", "@hideSongArtistsIfSameAsAlbumArtists": {}, - "minutes": "Minuta", + "minutes": "Minute", "@minutes": {}, - "viewType": "Vidi tip", + "viewType": "Vrsta prikaza", "@viewType": {}, - "createButtonLabel": "KREIRAJ", + "createButtonLabel": "STVORI", "@createButtonLabel": {}, - "sleepTimerTooltip": "Vrijeme spavanja", + "sleepTimerTooltip": "Odbrojavanje", "@sleepTimerTooltip": {}, - "addToPlaylistTitle": "Dodaj u reprodukciju", + "addToPlaylistTitle": "Dodaj u popis pjesama", "@addToPlaylistTitle": {}, - "removeFromPlaylistTitle": "Ukloni iz popisa za reprodukciju", + "removeFromPlaylistTitle": "Ukloni iz popisa pjesama", "@removeFromPlaylistTitle": {}, - "newPlaylist": "Novi popis za reprodukciju", + "newPlaylist": "Novi popis pjesama", "@newPlaylist": {}, - "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Da li prikazati umjetnike pjesme na ekranu albuma ako se ne razlikuje od umjetnika albuma.", + "hideSongArtistsIfSameAsAlbumArtistsSubtitle": "Da li prikazati izvođače pjesama na ekranu albuma ako se ne razlikuju od izvođača albuma.", "@hideSongArtistsIfSameAsAlbumArtistsSubtitle": {}, - "disableGestureSubtitle": "Da li ugasiti geste.", + "disableGestureSubtitle": "Da li deaktivirati geste.", "@disableGestureSubtitle": {}, "system": "Sistem", "@system": {}, - "light": "Svijetlo", + "light": "Svijetla", "@light": {}, - "cancelSleepTimer": "Ugasi vrijeme spavanja?", + "cancelSleepTimer": "Prekinuti odbrojavanje?", "@cancelSleepTimer": {}, "yesButtonLabel": "DA", "@yesButtonLabel": {}, "noButtonLabel": "NE", "@noButtonLabel": {}, - "invalidNumber": "Nemoguć broj", + "invalidNumber": "Neispravan broj", "@invalidNumber": {}, - "removeFromPlaylistTooltip": "Ukloni iz popisa za reprodukciju", + "removeFromPlaylistTooltip": "Ukloni iz popisa pjesama", "@removeFromPlaylistTooltip": {}, - "unknownArtist": "Nepoznat umjetnik", + "unknownArtist": "Nepoznat izvođač", "@unknownArtist": {}, "transcode": "TRANSKODIRAJ", "@transcode": {}, - "playlistCreated": "Popis za reprodukciju kreiran.", + "playlistCreated": "Popis pjesama je stvoren.", "@playlistCreated": {}, "noAlbum": "Nema albuma", "@noAlbum": {}, - "noItem": "Nema stvari", + "noItem": "Nema stavki", "@noItem": {}, "queue": "Red čekanja", "@queue": {}, @@ -449,7 +449,7 @@ "@addToQueue": {}, "instantMix": "Instant miks", "@instantMix": {}, - "responseError": "{error} status kod {statusCode}.", + "responseError": "{error} Kȏd stanja {statusCode}.", "@responseError": { "placeholders": { "error": { @@ -470,11 +470,11 @@ "@removeFavourite": {}, "addFavourite": "Dodaj omiljene", "@addFavourite": {}, - "queueReplaced": "Red čekanja zamijenjen.", + "queueReplaced": "Red čekanja je zamijenjen.", "@queueReplaced": {}, "addToMix": "Dodaj u miks", "@addToMix": {}, - "redownloadedItems": "{count,plural, =0{Nema potrebnih ponovnih preuzimanja.} =1{Ponovno preuzeta {count} stvar} other{Ponovno preuzeto {count} stvari}}", + "redownloadedItems": "{count,plural, =0{Ponovna preuzimanja nisu potrebna.} =1{Ponovo je preuzeta {count} stavka} few{Ponovo su preuzete {count} stavke} other{Ponovo je preuzeto {count} stavki}}", "@redownloadedItems": { "placeholders": { "count": { From 4f9cedfebe0c080b765a98337e8dd8be092cfbfb Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 11 Aug 2023 20:55:42 +0100 Subject: [PATCH 165/172] Bump version --- fastlane/metadata/android/en-US/changelogs/37.txt | 1 + pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/37.txt diff --git a/fastlane/metadata/android/en-US/changelogs/37.txt b/fastlane/metadata/android/en-US/changelogs/37.txt new file mode 100644 index 000000000..2ad6c7eff --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/37.txt @@ -0,0 +1 @@ +Fixed a bug created in 0.6.15. Full changelog at https://github.com/jmshrv/finamp/releases/tag/0.6.16 \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index 42f4295b8..8c795f272 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.15+36 +version: 0.6.16+37 environment: sdk: ">=2.19.4 <3.0.0" From f62591adb92d76413c7e10ca9881fc38dc2c0f70 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Fri, 11 Aug 2023 21:40:16 +0100 Subject: [PATCH 166/172] Add comment to fixBlurhashMigrationIds --- lib/services/downloads_helper.dart | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/services/downloads_helper.dart b/lib/services/downloads_helper.dart index cdb0edc7e..4f1e96646 100644 --- a/lib/services/downloads_helper.dart +++ b/lib/services/downloads_helper.dart @@ -936,6 +936,12 @@ class DownloadsHelper { _downloadsLogger.info("${imagesToDelete.length} duplicate images deleted."); } + /// Fixes DownloadedImage IDs created by the migration in 0.6.15. In it, + /// migrated images did not have their IDs set to the blurhash. This function + /// sets every image's ID to its blurhash. This function should only be run + /// once, only when required (i.e., upgrading from 0.6.15). In theory, running + /// it on an unaffected database should do nothing, but there's no point doing + /// redundant migrations. Future fixBlurhashMigrationIds() async { _downloadsLogger.info("Fixing blurhash migration IDs from 0.6.15"); From dfd512890b06f5a3ecf7215a620239166a955521 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sun, 13 Aug 2023 15:18:20 +0100 Subject: [PATCH 167/172] Update Flutter wrapper --- .flutter | 2 +- pubspec.lock | 36 ++++++++++++++++++------------------ 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/.flutter b/.flutter index 4d9e56e69..f468f3366 160000 --- a/.flutter +++ b/.flutter @@ -1 +1 @@ -Subproject commit 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf +Subproject commit f468f3366c26a5092eb964a230ce7892fda8f2f8 diff --git a/pubspec.lock b/pubspec.lock index 97266b61e..2f4fdb690 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -309,10 +309,10 @@ packages: dependency: transitive description: name: ffi - sha256: ed5337a5660c506388a9f012be0288fb38b49020ce2b45fe1f8b8323fe429f99 + sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" url: "https://pub.dev" source: hosted - version: "2.0.2" + version: "2.1.0" file: dependency: transitive description: @@ -707,50 +707,50 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: "909b84830485dbcd0308edf6f7368bc8fd76afa26a270420f34cabea2a6467a0" url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.0" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.1.0" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "916731ccbdce44d545414dd9961f26ba5fbaa74bcbb55237d8e65a623a8c7297" + sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5" url: "https://pub.dev" source: hosted - version: "2.2.4" + version: "2.3.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3 url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.2.0" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.0" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.0" permission_handler: dependency: "direct main" description: @@ -883,10 +883,10 @@ packages: dependency: transitive description: name: share_plus_platform_interface - sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" + sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.3.0" shelf: dependency: transitive description: @@ -1128,10 +1128,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: e0b1147eec179d3911f1f19b59206448f78195ca1d20514134e10641b7d7fbff + sha256: f0c26453a2d47aa4c2570c6a033246a3fc62da2fe23c7ffdd0a7495086dc0247 url: "https://pub.dev" source: hosted - version: "1.0.1" + version: "1.0.2" xml: dependency: transitive description: From 3db283bf273ab98c0aec93b46ed5f83951e6c7ff Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sun, 13 Aug 2023 15:20:24 +0100 Subject: [PATCH 168/172] Bump version --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 8c795f272..5948bdbba 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 0.6.16+37 +version: 0.6.17+38 environment: sdk: ">=2.19.4 <3.0.0" From 4e9ede8a609cbcbd3a048c613b9d5302beeae250 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Sun, 13 Aug 2023 15:21:38 +0100 Subject: [PATCH 169/172] Add F-Droid metadata --- fastlane/metadata/android/en-US/changelogs/38.txt | 1 + 1 file changed, 1 insertion(+) create mode 100644 fastlane/metadata/android/en-US/changelogs/38.txt diff --git a/fastlane/metadata/android/en-US/changelogs/38.txt b/fastlane/metadata/android/en-US/changelogs/38.txt new file mode 100644 index 000000000..8a19668e7 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/38.txt @@ -0,0 +1 @@ +Fix F-Droid build. Changes from 0.6.16 (which was missed) can be seen at https://github.com/jmshrv/finamp/releases/tag/0.6.16 From 95c9dcf7603fa30fbe1d336ba87093f7e6dc4c44 Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Sun, 13 Aug 2023 20:44:19 +0200 Subject: [PATCH 170/172] :sparkles: Upgrade chopper and aggregate chopper logs to single entry --- android/build.gradle | 2 +- lib/services/chopper_aggregate_logger.dart | 118 ++++++++++++++++++ .../http_aggregate_logging_interceptor.dart | 29 +++++ lib/services/jellyfin_api.dart | 3 +- pubspec.lock | 28 ++--- pubspec.yaml | 8 +- 6 files changed, 168 insertions(+), 20 deletions(-) create mode 100644 lib/services/chopper_aggregate_logger.dart create mode 100644 lib/services/http_aggregate_logging_interceptor.dart diff --git a/android/build.gradle b/android/build.gradle index 713d7f6e6..cb313cafb 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,5 +1,5 @@ buildscript { - ext.kotlin_version = '1.7.10' + ext.kotlin_version = '1.8.10' repositories { google() mavenCentral() diff --git a/lib/services/chopper_aggregate_logger.dart b/lib/services/chopper_aggregate_logger.dart new file mode 100644 index 000000000..54467f4a6 --- /dev/null +++ b/lib/services/chopper_aggregate_logger.dart @@ -0,0 +1,118 @@ +import 'dart:async'; +import 'dart:collection'; + +import 'package:chopper/chopper.dart' as chopper; +import 'package:chopper/src/chopper_log_record.dart'; +import 'package:logging/logging.dart'; + +/// A logger that aggregates the request and response logs from Chopper. +/// Once a request (or response) is completed, +/// the whole request log is flushed to the delegated chopperLogger. +/// +/// All other operations are delegated by default. +class ChopperAggregateLogger implements Logger { + final Logger _delegate = chopper.chopperLogger; + + final Map _requests = HashMap(); + + final Map _responses = HashMap(); + + @override + String get name => _delegate.name; + + @override + String get fullName => _delegate.fullName; + + @override + Logger? get parent => _delegate.parent; + + @override + Level get level => _delegate.level; + + @override + set level(Level? value) { + _delegate.level = value; + } + + @override + Map get children => _delegate.children; + + @override + void log(Level logLevel, Object? message, + [Object? error, StackTrace? stackTrace, Zone? zone]) { + if (message is ChopperLogRecord) { + if (message.request != null) { + _requests[message.request]?.writeln(message.message); + return; + } else if (message.response != null) { + _responses[message.response]?.writeln(message.message); + return; + } + } + _delegate.log(logLevel, message, error, stackTrace, zone); + } + + void onStartRequest(chopper.Request request) { + _requests[request] = StringBuffer(); + } + + void onEndRequest(chopper.Request request) { + info(_requests.remove(request)?.toString().trim()); + } + + void onStartResponse(chopper.Response response) { + _responses[response] = StringBuffer(); + } + + void onEndResponse(chopper.Response response) { + info(_responses.remove(response)?.toString().trim()); + } + + @override + Stream get onLevelChanged => _delegate.onLevelChanged; + + @override + Stream get onRecord => _delegate.onRecord; + + @override + void clearListeners() { + _delegate.clearListeners(); + } + + @override + bool isLoggable(Level value) { + return _delegate.isLoggable(value); + } + + @override + void finest(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.FINEST, message, error, stackTrace); + + @override + void finer(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.FINER, message, error, stackTrace); + + @override + void fine(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.FINE, message, error, stackTrace); + + @override + void config(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.CONFIG, message, error, stackTrace); + + @override + void info(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.INFO, message, error, stackTrace); + + @override + void warning(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.WARNING, message, error, stackTrace); + + @override + void severe(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.SEVERE, message, error, stackTrace); + + @override + void shout(Object? message, [Object? error, StackTrace? stackTrace]) => + log(Level.SHOUT, message, error, stackTrace); +} diff --git a/lib/services/http_aggregate_logging_interceptor.dart b/lib/services/http_aggregate_logging_interceptor.dart new file mode 100644 index 000000000..061ab4d9c --- /dev/null +++ b/lib/services/http_aggregate_logging_interceptor.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:chopper/chopper.dart'; +import 'package:finamp/services/chopper_aggregate_logger.dart'; + +final aggregateLogger = ChopperAggregateLogger(); + +/// A HttpLoggingInterceptor that aggregates the request and +/// response logs from Chopper, using the [ChopperAggregateLogger]. +class HttpAggregateLoggingInterceptor extends HttpLoggingInterceptor { + HttpAggregateLoggingInterceptor({level = Level.body}) + : super(level: level, logger: aggregateLogger); + + @override + FutureOr onRequest(Request request) async { + aggregateLogger.onStartRequest(request); + final result = await super.onRequest(request); + aggregateLogger.onEndRequest(request); + return result; + } + + @override + FutureOr onResponse(Response response) { + aggregateLogger.onStartResponse(response); + final result = super.onResponse(response); + aggregateLogger.onEndResponse(response); + return result; + } +} diff --git a/lib/services/jellyfin_api.dart b/lib/services/jellyfin_api.dart index fca6fee19..d546c54d8 100644 --- a/lib/services/jellyfin_api.dart +++ b/lib/services/jellyfin_api.dart @@ -3,6 +3,7 @@ import 'dart:io' show Platform; import 'package:android_id/android_id.dart'; import 'package:chopper/chopper.dart'; import 'package:device_info_plus/device_info_plus.dart'; +import 'package:finamp/services/http_aggregate_logging_interceptor.dart'; import 'package:get_it/get_it.dart'; import 'package:package_info_plus/package_info_plus.dart'; @@ -413,7 +414,7 @@ abstract class JellyfinApi extends ChopperService { // return request.copyWith( // headers: {"X-Emby-Authentication": await getAuthHeader()}); // }, - HttpLoggingInterceptor(), + HttpAggregateLoggingInterceptor(), ], ); diff --git a/pubspec.lock b/pubspec.lock index 2f4fdb690..ddf9eb963 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -181,18 +181,18 @@ packages: dependency: "direct main" description: name: chopper - sha256: "23aac2db54f6a7854ed8984fba4b33222e53123c71a0ccf01d2b1087a1b5aab7" + sha256: "8cc23242759fb2d8a325e3e2371094f73e14fec02f01ebaba51e03db5e9eabab" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.3" chopper_generator: dependency: "direct dev" description: name: chopper_generator - sha256: "09d512f1acb086cf4101bef02bdb23ee7a8eb55a9935f53af866736606c57523" + sha256: "3e55981c7c208269263263d61d23afec9f08ddb022e728bd3ab2cb344096b1a2" url: "https://pub.dev" source: hosted - version: "6.0.3" + version: "7.0.1" cli_util: dependency: transitive description: @@ -325,10 +325,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: b85eb92b175767fdaa0c543bf3b0d1f610fe966412ea72845fe5ba7801e763ff + sha256: "9d6e95ec73abbd31ec54d0e0df8a961017e165aba1395e462e5b31ea0c165daf" url: "https://pub.dev" source: hosted - version: "5.2.10" + version: "5.3.1" file_sizes: dependency: "direct main" description: @@ -490,10 +490,10 @@ packages: dependency: transitive description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "759d1a329847dd0f39226c688d3e06a6b8679668e350e2891a6474f8b4bb8525" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.1.0" http_multi_server: dependency: transitive description: @@ -683,10 +683,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: "10259b111176fba5c505b102e3a5b022b51dd97e30522e906d6922c745584745" + sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "4.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -875,10 +875,10 @@ packages: dependency: "direct main" description: name: share_plus - sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1 + sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11" url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "7.1.0" share_plus_platform_interface: dependency: transitive description: @@ -1120,10 +1120,10 @@ packages: dependency: transitive description: name: win32 - sha256: a6f0236dbda0f63aa9a25ad1ff9a9d8a4eaaa5012da0dc59d21afdb1dc361ca4 + sha256: "5a751eddf9db89b3e5f9d50c20ab8612296e4e8db69009788d6c8b060a84191c" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "4.1.4" xdg_directories: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 5948bdbba..3c7c1ae1f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,7 +27,7 @@ dependencies: sdk: flutter json_annotation: ^4.8.0 - chopper: ^6.1.1 + chopper: ^7.0.3 get_it: ^7.2.0 just_audio: ^0.9.32 audio_service: ^0.18.9 @@ -51,9 +51,9 @@ dependencies: infinite_scroll_pagination: ^3.2.0 flutter_sticky_header: ^0.6.5 device_info_plus: ^8.2.0 - package_info_plus: ^3.1.0 + package_info_plus: ^4.1.0 octo_image: ^1.0.2 - share_plus: ^6.3.2 + share_plus: ^7.1.0 # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. @@ -77,7 +77,7 @@ dev_dependencies: flutter_test: sdk: flutter build_runner: ^2.3.3 - chopper_generator: ^6.0.0 + chopper_generator: ^7.0.1 hive_generator: ^2.0.0 json_serializable: ^6.6.1 flutter_launcher_icons: ^0.13.0 From 13eba9f458e8636c39a69ed46f4da9906087bdd9 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Mon, 11 Sep 2023 18:08:18 +0100 Subject: [PATCH 171/172] Set initialIndex to 0 on Android Hopefully fixes #482 --- ...bum_screen_content_flexible_space_bar.dart | 6 +++- pubspec.lock | 34 ++++++++++++------- 2 files changed, 26 insertions(+), 14 deletions(-) diff --git a/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart b/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart index e5e4cf012..ab5f85279 100644 --- a/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart +++ b/lib/components/AlbumScreen/album_screen_content_flexible_space_bar.dart @@ -1,3 +1,4 @@ +import 'dart:io'; import 'dart:math'; import 'package:flutter/material.dart'; @@ -72,7 +73,10 @@ class AlbumScreenContentFlexibleSpaceBar extends StatelessWidget { audioServiceHelper.replaceQueueWithItem( itemList: items, shuffle: true, - initialIndex: Random().nextInt(items.length), + // Android doesn't like #442 + initialIndex: Platform.isAndroid + ? 0 + : Random().nextInt(items.length), ), icon: const Icon(Icons.shuffle), label: Text( diff --git a/pubspec.lock b/pubspec.lock index ddf9eb963..e92f4c2db 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -229,10 +229,10 @@ packages: dependency: transitive description: name: collection - sha256: "4a07be6cb69c84d677a6c3096fcf960cc3285a8330b4603e0d463d15d9bd934c" + sha256: f092b211a4319e98e5ff58223576de6c2803db36221657b46c82574721240687 url: "https://pub.dev" source: hosted - version: "1.17.1" + version: "1.17.2" convert: dependency: transitive description: @@ -530,10 +530,10 @@ packages: dependency: "direct main" description: name: intl - sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 + sha256: "3bc132a9dbce73a7e4a21a17d06e1878839ffbf975568bc875c60537824b0c4d" url: "https://pub.dev" source: hosted - version: "0.18.0" + version: "0.18.1" io: dependency: transitive description: @@ -619,18 +619,18 @@ packages: dependency: transitive description: name: matcher - sha256: "6501fbd55da300384b768785b83e5ce66991266cec21af89ab9ae7f5ce1c4cbb" + sha256: "1803e76e6653768d64ed8ff2e1e67bea3ad4b923eb5c56a295c3e634bad5960e" url: "https://pub.dev" source: hosted - version: "0.12.15" + version: "0.12.16" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: d92141dc6fe1dad30722f9aa826c7fbc896d021d792f80678280601aff8cf724 + sha256: "9528f2f296073ff54cb9fee677df673ace1218163c3bc7628093e7eed5203d41" url: "https://pub.dev" source: hosted - version: "0.2.0" + version: "0.5.0" meta: dependency: transitive description: @@ -944,10 +944,10 @@ packages: dependency: transitive description: name: source_span - sha256: dd904f795d4b4f3b870833847c461801f6750a9fa8e61ea5ac53f9422b31f250 + sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c" url: "https://pub.dev" source: hosted - version: "1.9.1" + version: "1.10.0" sqflite: dependency: transitive description: @@ -1024,10 +1024,10 @@ packages: dependency: transitive description: name: test_api - sha256: eb6ac1540b26de412b3403a163d919ba86f6a973fe6cc50ae3541b80092fdcfb + sha256: "75760ffd7786fffdfb9597c35c5b27eaeec82be8edfb6d71d32651128ed7aab8" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "0.6.0" timing: dependency: transitive description: @@ -1108,6 +1108,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.0" + web: + dependency: transitive + description: + name: web + sha256: dc8ccd225a2005c1be616fe02951e2e342092edf968cf0844220383757ef8f10 + url: "https://pub.dev" + source: hosted + version: "0.1.4-beta" web_socket_channel: dependency: transitive description: @@ -1149,5 +1157,5 @@ packages: source: hosted version: "3.1.2" sdks: - dart: ">=3.0.0 <4.0.0" + dart: ">=3.1.0-185.0.dev <4.0.0" flutter: ">=3.3.0" From 6dbde0cd3838ea4b4d37628b6497310fec7ea566 Mon Sep 17 00:00:00 2001 From: James Harvey <44349936+jmshrv@users.noreply.github.com> Date: Tue, 19 Sep 2023 13:15:17 +0100 Subject: [PATCH 172/172] Update packages When building for Android, we get some weird warnings but it seems to work fine --- ios/Podfile.lock | 6 +- ios/Runner.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- .../new_page_progress_indicator.dart | 4 +- pubspec.lock | 184 ++++++++++-------- pubspec.yaml | 8 +- 6 files changed, 111 insertions(+), 95 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 77ccf7644..51b411965 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -138,7 +138,7 @@ SPEC CHECKSUMS: audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_session: 4f3e461722055d21515cf3261b64c973c062f345 CropViewController: 58fb440f30dac788b129d2a1f24cffdcb102669c - device_info_plus: e5c5da33f982a436e103237c0c85f9031142abed + device_info_plus: 7545d84d8d1b896cb16a4ff98c19f07ec4b298ea DKCamera: a902b66921fca14b7a75266feb8c7568aa7caa71 DKImagePickerController: b512c28220a2b8ac7419f21c491fc8534b7601ac DKPhotoGallery: fdfad5125a9fdda9cc57df834d49df790dbb4179 @@ -147,11 +147,11 @@ SPEC CHECKSUMS: flutter_downloader: b7301ae057deadd4b1650dc7c05375f10ff12c39 FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa - package_info_plus: 6c92f08e1f853dc01228d6f553146438dafcd14e + package_info_plus: fd030dabf36271f146f1f3beacd48f564b0f17f7 path_provider_foundation: 29f094ae23ebbca9d3d0cec13889cd9060c0e943 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 SDWebImage: e5cc87bf736e60f49592f307bdf9e157189298a3 - share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 + share_plus: 599aa54e4ea31d4b4c0e9c911bcc26c55e791028 sqflite: 31f7eba61e3074736dff8807a9b41581e4f7f15a SwiftyGif: 6c3eafd0ce693cad58bb63d2b2fb9bacb8552780 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 258641ef5..5ac1c8fee 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -224,7 +224,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1430; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a33..a6b826db2 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ =3.1.0-185.0.dev <4.0.0" - flutter: ">=3.3.0" + dart: ">=3.1.0 <4.0.0" + flutter: ">=3.13.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3c7c1ae1f..e4643c58f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -45,12 +45,12 @@ dependencies: logging: ^1.1.1 clipboard: ^0.1.3 file_picker: ^5.2.8 - permission_handler: ^10.2.0 + permission_handler: ^11.0.0 provider: ^6.0.5 uuid: ^3.0.7 - infinite_scroll_pagination: ^3.2.0 + infinite_scroll_pagination: ^4.0.0 flutter_sticky_header: ^0.6.5 - device_info_plus: ^8.2.0 + device_info_plus: ^9.0.3 package_info_plus: ^4.1.0 octo_image: ^1.0.2 share_plus: ^7.1.0 @@ -59,7 +59,7 @@ dependencies: # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.5 path: ^1.8.2 - android_id: ^0.2.0 + android_id: ^0.3.5 intl: ^0.18.0 auto_size_text: ^3.0.0 flutter_riverpod: ^2.3.4