From ef4f6c429f21b639f736e13d48965314f1497c14 Mon Sep 17 00:00:00 2001 From: RaptaG Date: Thu, 6 Jul 2023 18:04:09 +0300 Subject: [PATCH 01/23] Remove random space Signed-off-by: RaptaG --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index f965af850..0bce62d71 100644 --- a/LICENSE +++ b/LICENSE @@ -9,4 +9,4 @@ Redistribution and use in source and binary forms, with or without modification, 3. All advertising materials mentioning features or use of this software must display the following acknowledgement: This product includes software developed by Kingkor Roy Tirtho. 4. Neither the name of the Software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -THIS SOFTWARE IS PROVIDED BY KINGKOR ROY TIRTHO AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KINGKOR ROY TIRTHO AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file +THIS SOFTWARE IS PROVIDED BY KINGKOR ROY TIRTHO AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL KINGKOR ROY TIRTHO AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 704fcca298ffb191cc73de312b22ac3121f7ebb3 Mon Sep 17 00:00:00 2001 From: Sir RaptaG <77157639+RaptaG@users.noreply.github.com> Date: Thu, 6 Jul 2023 22:47:17 +0300 Subject: [PATCH 02/23] Improve install.sh (#521) - Refactor a big portion of the code - Use the least amount of dependencies - Many tweaks and improvements --- README.md | 4 +- scripts/install.sh | 268 +++++++++++++++++++++------------------------ 2 files changed, 128 insertions(+), 144 deletions(-) diff --git a/README.md b/README.md index 177e4c211..088ae801a 100644 --- a/README.md +++ b/README.md @@ -161,15 +161,15 @@ You can compile Spotube's source code by [following these instructions](CONTRIBU - [Kingkor Roy Tirtho](https://github.com/KRTirtho) - The Founder, Maintainer and Lead Developer - [Owen Connor](https://github.com/owencz1998) - The Cool Discord Moderator -- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer - [RaptaG](https://github.com/RaptaG) - The GitHub Moderator and Community Manager +- [Piotr Rogowski](https://github.com/karniv00l) - The MacOS Developer - [Rusty Apple](https://github.com/RustyApple) - The Mysterious Unknown Guy ## 💼 License Spotube is open source and licensed under the [BSD-4-Clause](/LICENSE) License. -If you are concerned, feel free to [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p). +If you are concerned, you can [read the reason of choosing this license](https://dev.to/krtirtho/choosing-open-source-license-wisely-1m3p).
diff --git a/scripts/install.sh b/scripts/install.sh index 52526f95c..4d54c0690 100755 --- a/scripts/install.sh +++ b/scripts/install.sh @@ -1,182 +1,166 @@ -#!/bin/bash - - -INSTLLATION_DIR=/usr/share/spotube -DESKTOP_FILE_PATH=/usr/share/applications/spotube.desktop -APP_DATA_PATH=/usr/share/appdata/spotube.appdata.xml -ICON_PATH=/usr/share/icons/spotube/spotube-logo.png -BIN_SYMLINK_PATH=/usr/bin/spotube - -TEMP_DIR=/tmp/spotube-installer - -# get latest version from github api -VERSION=$(curl --silent "https://api.github.com/repos/KRTirtho/spotube/releases/latest" \ - | grep -Po '"tag_name": "\K.*?(?=")') +#!/usr/bin/env bash + +# Varibles +fname="$(basename $0)" +installDir='/usr/share/spotube' +desktopFile='/usr/share/applications/spotube.desktop' +appdata='/usr/share/appdata/spotube.appdata.xml' +icon='/usr/share/icons/spotube/spotube-logo.png' +symlink='/usr/bin/spotube' +temp='/tmp/spotube-installer' +latestVer="$(wget -qO- "https://api.github.com/repos/KRTirtho/spotube/releases/latest" \ | grep -Po '"tag_name": "\K.*?(?=")')" + +# Root check - From CAAIS (https://codeberg.org/RaptaG/CAAIS), under GPL-3.0 +function rootCheck() { + if [ "${EUID}" -ne 0 ]; then + echo "Error: Root permissions are required for ${fname} to work." + echo "Please run './${fname}' for more information." + exit 1 + fi +} -function spotube_help(){ - # available flags are -v or --version to specify what version to download - echo "Usage: ./install.sh [flags]" - echo "Flags:" - echo " -v, --version Specify what version to download. Default: $VERSION" - echo " -h, --help Show this help message" - echo " -r, --remove Remove spotube from your system" +# Flags +function help(){ + echo "Usage: sudo ./${fname} [flags]" + echo 'Flags:' + echo ' -i, --install Install any Spotube version (if not specified, the latest is installed).' + echo ' -h, --help This help menu' + echo ' -r, --remove Removes Spotube from your system' exit 0 } -# a function to check if a given command exists or not and returns bool +# Checks whether a given command exists or not and returns bool function command_exists() { command -v "$@" >/dev/null 2>&1 } function install_deps(){ - local DEBIAN_DEPS="curl tar mpv libappindicator3-1 gir1.2-appindicator3-0.1 libsecret-1-0 libnotify-bin libjsoncpp25" - local RPM_DEPS="curl tar mpv libappindicator jsoncpp libsecret libnotify" - local ARCH_DEPS="curl tar mpv libappindicator-gtk3 libsecret jsoncpp libnotify" + local debianDeps='mpv libappindicator3-1 gir1.2-appindicator3-0.1 libsecret-1-0 libnotify-bin libjsoncpp25' + local rpmDeps='mpv libappindicator jsoncpp libsecret libnotify' + local archDeps='mpv libappindicator-gtk3 libsecret jsoncpp libnotify' if command_exists apt; then - sudo apt install -y $DEBIAN_DEPS + apt install -y ${debianDeps} elif command_exists dnf; then - sudo dnf install -y $RPM_DEPS + dnf install -y ${debianDeps} elif command_exists yum; then - sudo yum install -y $RPM_DEPS + yum install -y ${rpmDeps} elif command_exists zypper; then - sudo zypper install -y $RPM_DEPS + zypper install -y ${rpmDeps} elif command_exists pacman; then - sudo pacman -Sy $ARCH_DEPS - else - echo "Your package manager is not supported by this script. Please install the dependencies manually." - echo "The dependencies are: curl, tar, mpv, appindicator, libsecret, jsoncpp, libnotify" -fi + pacman -Sy ${archDeps} + else + # Maybe one day + # # Deps + # # JsonCpp + # wget https://github.com/open-source-parsers/jsoncpp/tarball/master -O jsoncpp.tar.gz + # tar -xf jsoncpp.tar.gz && cd open-source-parsers-jsoncpp-* + echo 'You have to install some dependancies manually in order for Spotube to work.' + echo "The deps are the following: ${rpmDeps}" + fi } function download_extract_spotube(){ - local TAR_PATH=/tmp/spotube-$VERSION.tar.xz - local DOWNLOAD_URL=https://github.com/KRTirtho/spotube/releases/download/v$VERSION/spotube-linux-$VERSION-x86_64.tar.xz + local tarPath="/tmp/spotube-${ver}.tar.xz" + local donwloadURL="https://github.com/KRTirtho/spotube/releases/download/v${ver}/spotube-linux-${ver}-x86_64.tar.xz" - # check if version is nightly - - if [ "$VERSION" = "nightly" ]; then - DOWNLOAD_URL=https://github.com/KRTirtho/spotube/releases/download/nightly/spotube-linux-nightly-x86_64.tar.xz + if [ "${ver}" = "nightly" ]; then + downloadURL"=https://github.com/KRTirtho/spotube/releases/download/nightly/spotube-linux-nightly-x86_64.tar.xz" fi - - rm -rf $TEMP_DIR - mkdir -p $TEMP_DIR - + rm -rf ${temp} + mkdir -p ${temp} - # check if already exists downloaded file - if [ -f $TAR_PATH ]; then - echo "Found spotube-$VERSION.tar.xz in /tmp. Skipping download..." + # Check if already exists downloaded file + if [ -f ${tarPath} ]; then + echo "Installation file detected. Skipping download..." else - echo "Downloading spotube-$VERSION.tar.xz..." - curl -L $DOWNLOAD_URL -o $TAR_PATH + echo "Downloading spotube-${ver}.tar.xz..." + wget -q ${downloadURL} -P ${tarPath} fi - # Extract the tarball - tar -xf $TAR_PATH -C $TEMP_DIR - - # check if $TEMP_DIR empty or not + tar -xf ${tarPath} -C ${temp} - if [ ! "$(ls -A $TEMP_DIR)" ]; then - echo "Failed to extract the tarball. Redownloading..." - rm -f $TAR_PATH - curl -L $DOWNLOAD_URL -o $TAR_PATH - tar -xf $TAR_PATH -C $TEMP_DIR + # Is $temp empty or not + if [ ! "$(ls -A ${temp})" ]; then + echo 'Failed to extract the tarball. Redownloading...' + rm -f ${tarPath} + wget -q ${downloadURL} -P ${tarPath} + tar -xf ${tarPath} -C ${temp} fi - # checking one last time - if [ ! "$(ls -A $TEMP_DIR)" ]; then - echo "Failed to extract the tarball. Aborting installation..." + # Once again + if [ ! "$(ls -A ${temp})" ]; then + echo 'Failed to extract the tarball. Installation aborted.' exit 1 fi } function install_spotube(){ - # check if exists and uninstall if user allows - - if [ -d $INSTLLATION_DIR ]; then - echo "Spotube is already installed. Do you want to uninstall it and then install? [y/N]" - read -r uninstall - if [ "$uninstall" = "y" ] || [ "$uninstall" = "Y" ]; then - uninstall_spotube - else - echo "Aborting installation..." - exit 1 - fi + if [ -d ${installDir} ]; then + echo -n "Spotube is already installed. Do you want to reinstall it? [y/N] " + read reinstall + + case "${reinstall}" in + [yY]*) + uninstall_spotube ;; + *) + echo 'Aborting installation...' + exit 1 ;; + esac fi - - # Move the files to /usr/share/spotube - - sudo mkdir -p $INSTLLATION_DIR - - sudo mv $TEMP_DIR/data $INSTLLATION_DIR - sudo mv $TEMP_DIR/lib $INSTLLATION_DIR - sudo mv $TEMP_DIR/spotube $INSTLLATION_DIR - - # Move the desktop file to /usr/share/applications - - sudo mv $TEMP_DIR/spotube.desktop $DESKTOP_FILE_PATH - - # Move the appdata file to /usr/share/appdata - - sudo mv $TEMP_DIR/com.github.KRTirtho.Spotube.appdata.xml $APP_DATA_PATH - # Move the logo to /usr/share/icons/spotube - - sudo mkdir -p /usr/share/icons/spotube - - sudo mv $TEMP_DIR/spotube-logo.png $ICON_PATH - - # Create a symlink to /usr/bin - - sudo ln -s /usr/share/spotube/spotube $BIN_SYMLINK_PATH - - # Clean up - - rm -rf $TEMP_DIR - - echo "Spotube $VERSION has been installed successfully!" + # Install Spotube from temp dir + mkdir -p ${installDir} + mv ${temp}/data ${installDir} + mv ${temp}/lib ${installDir} + mv ${temp}/spotube ${installDir} + mv ${temp}/spotube.desktop ${desktopDir} + mv ${temp}/com.github.KRTirtho.Spotube.appdata.xml ${appdata} + mkdir -p /usr/share/icons/spotube + mv ${temp}/spotube-logo.png ${icon} + ln -s /usr/share/spotube/spotube ${symlink} + + rm -rf ${temp} # Remove temp dir + echo "Spotube ${ver} has been installed successfully!" } function uninstall_spotube(){ - # confirm - - echo "Are you sure you want to uninstall Spotube?" - echo - echo "This will remove following files and directories:" - echo " $INSTLLATION_DIR" - echo " $DESKTOP_FILE_PATH" - echo " $APP_DATA_PATH" - echo " $ICON_PATH" - echo " $BIN_SYMLINK_PATH" - echo - echo "[y/N]" - - read -r CONFIRMATION - - if [[ "$CONFIRMATION" != "y" ]]; then - echo "Aborting uninstallation..." - exit 0 - fi - - # remove the files - - - sudo rm -rf $INSTLLATION_DIR $DESKTOP_FILE_PATH $APP_DATA_PATH $ICON_PATH $BIN_SYMLINK_PATH -} - -# parse arguments -v, --version, -r, --remove, -h, --help - -while [[ "$#" -gt 0 ]]; do - case $1 in - -v|--version) VERSION="$2"; shift ;; - -r|--remove) uninstall_spotube; exit 0 ;; - -h|--help) spotube_help; exit 0 ;; - *) echo "Unknown parameter passed: $1"; spotube_help; exit 1 ;; + echo -n "Are you sure you want to uninstall Spotube? [y/N] " + read confirm + + case "${confirm}" in + [yY]*) + echo 'Unstalling Spotube..' + rm -rf ${installDir} ${desktopDir} ${appdata} ${icon} ${symlink} ;; + *) + echo 'Aborting...' + exit 0 ;; esac - shift -done +} -install_deps -download_extract_spotube -install_spotube \ No newline at end of file +case "$1" in +-i | --install) + if [ "$2" != "" ]; then + ver="$2" + else + ver="${latestVer}" + fi + + rootCheck + install_deps + download_extract_spotube + install_spotube + exit 0 ;; +-r | --remove) + rootCheck + uninstall_spotube + exit 0 ;; +-h | --help | "") + help + exit 0 ;; +*) + echo "Invalid flag '$1'" + echo "Please run ./${fname} for more information." + exit 1 ;; +esac From 0089d471ae6d595e058061e3ac44caecdba12f61 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 8 Jul 2023 21:44:43 +0600 Subject: [PATCH 03/23] fix: negative index exception in update palette (#561) --- lib/provider/proxy_playlist/proxy_playlist_provider.dart | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index 6dab7b745..31e8bb1f8 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -488,14 +488,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier return; } return Future.microtask(() async { - final activeTrack = state.tracks.elementAtOrNull(state.active ?? 0); - - if (activeTrack == null) return; + if (state.activeTrack == null) return; final palette = await PaletteGenerator.fromImageProvider( UniversalImage.imageProvider( TypeConversionUtils.image_X_UrlString( - activeTrack.album?.images, + state.activeTrack?.album?.images, placeholder: ImagePlaceholder.albumArt, ), height: 50, From dc76634a6e4ccdca0f09d63a2db82cce53d950d7 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 8 Jul 2023 21:58:57 +0600 Subject: [PATCH 04/23] fix: shuffle not working (#562) --- lib/services/audio_player/mk_state_player.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/services/audio_player/mk_state_player.dart b/lib/services/audio_player/mk_state_player.dart index 053cd89fc..5eb16e01d 100644 --- a/lib/services/audio_player/mk_state_player.dart +++ b/lib/services/audio_player/mk_state_player.dart @@ -128,6 +128,7 @@ class MkPlayerWithState extends Player { _playlist = null; _tempMedias = null; _playerStateStream.add(AudioPlaybackState.stopped); + _shuffleStream.add(false); } @override @@ -242,6 +243,12 @@ class MkPlayerWithState extends Player { play: true, ); } + + // replace in the _tempMedias if it's not null + if (shuffled && _tempMedias != null) { + final tempIndex = _tempMedias!.indexOf(media); + _tempMedias![tempIndex] = Media(newUrl, extras: media.extras); + } break; } } From e4cbdd37479a572198c1ca27fcbbba0232275513 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 11 Jul 2023 22:13:22 +0600 Subject: [PATCH 05/23] fix: remove adaptive widgets (#520) --- .../playlist_generate/recommendation_attribute_dials.dart | 6 +++--- lib/components/player/player_actions.dart | 2 +- lib/components/player/player_controls.dart | 2 +- lib/components/player/volume_slider.dart | 2 +- lib/components/shared/track_table/track_tile.dart | 2 +- lib/components/shared/track_table/tracks_table_view.dart | 2 +- lib/pages/library/playlist_generate/playlist_generate.dart | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/components/library/playlist_generate/recommendation_attribute_dials.dart b/lib/components/library/playlist_generate/recommendation_attribute_dials.dart index 5af0b870f..87f7cb1ba 100644 --- a/lib/components/library/playlist_generate/recommendation_attribute_dials.dart +++ b/lib/components/library/playlist_generate/recommendation_attribute_dials.dart @@ -40,7 +40,7 @@ class RecommendationAttributeDials extends HookWidget { children: [ Text(context.l10n.min, style: labelStyle), Expanded( - child: Slider.adaptive( + child: Slider( value: values.min / base, min: 0, max: 1, @@ -58,7 +58,7 @@ class RecommendationAttributeDials extends HookWidget { children: [ Text(context.l10n.target, style: labelStyle), Expanded( - child: Slider.adaptive( + child: Slider( value: values.target / base, min: 0, max: 1, @@ -76,7 +76,7 @@ class RecommendationAttributeDials extends HookWidget { children: [ Text(context.l10n.max, style: labelStyle), Expanded( - child: Slider.adaptive( + child: Slider( value: values.max / base, min: 0, max: 1, diff --git a/lib/components/player/player_actions.dart b/lib/components/player/player_actions.dart index 0d17c263c..78d90ff1b 100644 --- a/lib/components/player/player_actions.dart +++ b/lib/components/player/player_actions.dart @@ -128,7 +128,7 @@ class PlayerActions extends HookConsumerWidget { const SizedBox( height: 20, width: 20, - child: CircularProgressIndicator.adaptive( + child: CircularProgressIndicator( strokeWidth: 2, ), ) diff --git a/lib/components/player/player_controls.dart b/lib/components/player/player_controls.dart index 11f697403..b4984c513 100644 --- a/lib/components/player/player_controls.dart +++ b/lib/components/player/player_controls.dart @@ -141,7 +141,7 @@ class PlayerControls extends HookConsumerWidget { children: [ Tooltip( message: context.l10n.slide_to_seek, - child: Slider.adaptive( + child: Slider( // cannot divide by zero // there's an edge case for value being bigger // than total duration. Keeping it resolved diff --git a/lib/components/player/volume_slider.dart b/lib/components/player/volume_slider.dart index 55f8520dd..754451259 100644 --- a/lib/components/player/volume_slider.dart +++ b/lib/components/player/volume_slider.dart @@ -29,7 +29,7 @@ class VolumeSlider extends HookConsumerWidget { } } }, - child: Slider.adaptive( + child: Slider( min: 0, max: 1, value: volume, diff --git a/lib/components/shared/track_table/track_tile.dart b/lib/components/shared/track_table/track_tile.dart index 4953a6abe..7926f55aa 100644 --- a/lib/components/shared/track_table/track_tile.dart +++ b/lib/components/shared/track_table/track_tile.dart @@ -92,7 +92,7 @@ class TrackTile extends HookConsumerWidget { else if (constrains.smAndDown) const SizedBox(width: 16), if (onChanged != null) - Checkbox.adaptive( + Checkbox( value: selected, onChanged: onChanged, ), diff --git a/lib/components/shared/track_table/tracks_table_view.dart b/lib/components/shared/track_table/tracks_table_view.dart index d41d47381..6172f5011 100644 --- a/lib/components/shared/track_table/tracks_table_view.dart +++ b/lib/components/shared/track_table/tracks_table_view.dart @@ -115,7 +115,7 @@ class TracksTableView extends HookConsumerWidget { ); }, child: showCheck.value - ? Checkbox.adaptive( + ? Checkbox( value: selected.value.length == sortedTracks.length, onChanged: (checked) { if (!showCheck.value) showCheck.value = true; diff --git a/lib/pages/library/playlist_generate/playlist_generate.dart b/lib/pages/library/playlist_generate/playlist_generate.dart index c6f65fb69..786568739 100644 --- a/lib/pages/library/playlist_generate/playlist_generate.dart +++ b/lib/pages/library/playlist_generate/playlist_generate.dart @@ -285,7 +285,7 @@ class PlaylistGeneratorPage extends HookConsumerWidget { ), ), Expanded( - child: Slider.adaptive( + child: Slider( value: value.toDouble(), min: 10, max: 100, From 26dbd523737d868114a47e82acd412cdae622b7c Mon Sep 17 00:00:00 2001 From: Ice Year Date: Wed, 12 Jul 2023 00:15:58 +0800 Subject: [PATCH 06/23] feat(translations): add Simplified Chinese translation. (#556) --- lib/collections/language_codes.dart | 10 +- lib/l10n/app_zh-cn.arb | 253 ++++++++++++++++++++++++++++ lib/l10n/l10n.dart | 1 + 3 files changed, 259 insertions(+), 5 deletions(-) create mode 100644 lib/l10n/app_zh-cn.arb diff --git a/lib/collections/language_codes.dart b/lib/collections/language_codes.dart index 8cd08c840..54b1b5aa6 100644 --- a/lib/collections/language_codes.dart +++ b/lib/collections/language_codes.dart @@ -9,7 +9,7 @@ class ISOLanguageName { } // Uncomment the languages as we add support for them -// Currently supported: bn,en,fr,hi +// Currently supported: bn,en,fr,hi,zh-cn abstract class LanguageLocals { static final Map isoLangs = { // "ab": const ISOLanguageName( @@ -128,10 +128,10 @@ abstract class LanguageLocals { // name: "Chichewa", // nativeName: "chiCheŵa", // ), - // "zh": const ISOLanguageName( - // name: "Chinese", - // nativeName: "汉语", - // ), + "zh-cn": const ISOLanguageName( + name: "Simplified Chinese", + nativeName: "简体中文", + ), // "cv": const ISOLanguageName( // name: "Chuvash", // nativeName: "чӑваш чӗлхи", diff --git a/lib/l10n/app_zh-cn.arb b/lib/l10n/app_zh-cn.arb new file mode 100644 index 000000000..6d083b842 --- /dev/null +++ b/lib/l10n/app_zh-cn.arb @@ -0,0 +1,253 @@ +{ + "guest": "访客", + "browse": "浏览", + "search": "搜索", + "library": "音乐库", + "lyrics": "歌词", + "settings": "设置", + "genre_categories_filter": "筛选类别...", + "genre": "探索歌单", + "personalized": "为你打造", + "featured": "推荐", + "new_releases": "新歌热播", + "songs": "歌曲", + "playing_track": "播放 {track}", + "queue_clear_alert": "这将清空当前的播放队列。{track_length} 首歌曲将被移除\n你确定要继续吗?", + "load_more": "加载更多", + "playlists": "歌单", + "artists": "艺人", + "albums": "专辑", + "tracks": "歌曲", + "downloads": "下载", + "filter_playlists": "筛选歌单...", + "liked_tracks": "已点赞的歌曲", + "liked_tracks_description": "你点赞过的所有歌曲", + "create_playlist": "创建歌单", + "create_a_playlist": "创建一个歌单", + "create": "创建", + "cancel": "取消", + "playlist_name": "歌单名称", + "name_of_playlist": "歌单的名称", + "description": "描述", + "public": "公开", + "collaborative": "共享协作", + "search_local_tracks": "搜索本地歌曲...", + "play": "播放", + "delete": "删除", + "none": "无", + "sort_a_z": "按字母正序", + "sort_z_a": "按字母倒序", + "sort_artist": "按艺人", + "sort_album": "按专辑", + "sort_tracks": "排序方式", + "currently_downloading": "正在下载 ({tracks_length})", + "cancel_all": "取消全部", + "filter_artist": "筛选艺人...", + "followers": "{followers} 名关注者", + "add_artist_to_blacklist": "屏蔽该艺人", + "top_tracks": "热门歌曲", + "fans_also_like": "粉丝也喜欢", + "loading": "加载中...", + "artist": "艺人", + "blacklisted": "已屏蔽", + "following": "关注中", + "follow": "关注", + "artist_url_copied": "艺人的分享链接已复制至剪贴板", + "added_to_queue": "已添加 {tracks} 首歌曲到播放队列", + "filter_albums": "筛选专辑...", + "synced": "同步", + "plain": "无同步", + "shuffle": "随机播放", + "search_tracks": "搜索歌曲...", + "released": "发行时间", + "error": "错误 {error}", + "title": "标题", + "time": "时长", + "more_actions": "更多操作", + "download_count": "下载 ({count}) 首歌曲", + "add_count_to_playlist": "添加 ({count}) 首歌曲到歌单中", + "add_count_to_queue": "添加 ({count}) 首歌曲到播放队列中", + "play_count_next": "接下来播放 ({count}) 首歌曲", + "album": "专辑", + "copied_to_clipboard": "已将 {data} 复制至剪贴板", + "add_to_following_playlists": "添加 {track} 到以下播放列表", + "add": "添加", + "added_track_to_queue": "添加 {track} 到播放队列", + "add_to_queue": "添加到播放队列", + "track_will_play_next": "{track} 将在下一首播放", + "play_next": "下一首播放", + "removed_track_from_queue": "将 {track} 从播放队列中移除", + "remove_from_queue": "从播放队列移除", + "remove_from_favorites": "取消点赞", + "save_as_favorite": 点赞", + "add_to_playlist": "添加到歌单", + "remove_from_playlist": "从歌单中移除", + "add_to_blacklist": "添加到屏蔽列表", + "remove_from_blacklist": "从屏蔽列表中移除", + "share": "分享", + "mini_player": "小窗模式", + "slide_to_seek": "滑动以前进或后退", + "shuffle_playlist": "随机播放歌单", + "unshuffle_playlist": "取消随机播放歌单", + "previous_track": "上一首歌曲", + "next_track": "下一首歌曲", + "pause_playback": "暂停播放", + "resume_playback": "恢复播放", + "loop_track": "单曲循环", + "repeat_playlist": "歌单循环", + "queue": "播放队列", + "alternative_track_sources": "其它音源", + "download_track": "下载歌曲", + "tracks_in_queue": "{tracks} 首歌曲在播放队列中", + "clear_all": "清除全部", + "show_hide_ui_on_hover": "悬停时显示/隐藏控制栏", + "always_on_top": "置顶", + "exit_mini_player": "退出小窗模式", + "download_location": "下载路径", + "account": "账户", + "login_with_spotify": "使用 Spotify 登录", + "connect_with_spotify": "与 Spotify 账户连接", + "logout": "退出", + "logout_of_this_account": "退出该账户", + "language_region": "语言和地区", + "language": "语言", + "system_default": "系统默认", + "market_place_region": "市场地区", + "recommendation_country": "选择国家与地区以获取对应推荐", + "appearance": "外观", + "layout_mode": "布局类型", + "override_layout_settings": "将覆盖响应式布局设置", + "adaptive": "自适应", + "compact": "紧凑", + "extended": "宽广", + "theme": "主题", + "dark": "深色", + "light": "浅色", + "system": "系统", + "accent_color": "主色调", + "sync_album_color": "匹配封面颜色", + "sync_album_color_description": "选取专辑封面主题色作为主色调", + "playback": "播放", + "audio_quality": "音质", + "high": "高", + "low": "低", + "pre_download_play": "先下后播", + "pre_download_play_description": "先下载歌曲后再播放而非流式播放(推荐带宽较高用户使用)", + "skip_non_music": "跳过非音乐片段(屏蔽赞助商)", + "blacklist_description": "已屏蔽的歌曲与艺人", + "wait_for_download_to_finish": "请等待当前下载任务完成", + "download_lyrics": "下载歌曲时同时下载歌词", + "desktop": "桌面端设置", + "close_behavior": "点击关闭按钮行为", + "close": "关闭", + "minimize_to_tray": "最小化到托盘", + "show_tray_icon": "显示托盘图标", + "about": "关于", + "u_love_spotube": "我们明白你喜欢 Spotube", + "check_for_updates": "检查更新", + "about_spotube": "关于 Spotube", + "blacklist": "屏蔽列表", + "please_sponsor": "请赞助/捐赠", + "spotube_description": "Spotube,一个轻量、跨平台且完全免费的 Spotify 客户端。", + "version": "版本", + "build_number": "构建代码", + "founder": "发起人", + "repository": "源码", + "bug_issues": "缺陷和问题报告", + "made_with": "于孟加拉🇧🇩用 ❤️ 发电", + "kingkor_roy_tirtho": "Kingkor Roy Tirtho", + "copyright": "© 2021-{current_year} Kingkor Roy Tirtho", + "license": "许可证", + "add_spotify_credentials": "添加你的 Spotify 登录信息以开始使用", + "credentials_will_not_be_shared_disclaimer": "不用担心,软件不会收集或分享任何个人数据给第三方", + "know_how_to_login": "不知道该怎么做?", + "follow_step_by_step_guide": "请按照以下指南进行", + "spotify_cookie": "Spotify {name} Cookie", + "cookie_name_cookie": "{name} Cookie", + "fill_in_all_fields": "请填写所有栏目", + "submit": "提交", + "exit": "退出", + "previous": "上一步", + "next": "下一步", + "done": "完成", + "step_1": "步骤 1", + "first_go_to": "首先,前往", + "login_if_not_logged_in": "如果尚未登录,请登录或者注册一个账户", + "step_2": "步骤 2", + "step_2_steps": "1. 一旦你已经完成登录, 按 F12 键或者鼠标右击网页空白区域 > 选择“检查”以打开浏览器开发者工具(DevTools)\n2. 然后选择 \"应用(Application)\" 标签页(Chrome, Edge, Brave 等基于 Chromium 的浏览器) 或 \"存储(Storage)\" 标签页 (Firefox, Palemoon 等基于 Firefox 的浏览器))\n3. 选择 \"Cookies\" 栏目然后选择 \"https://accounts.spotify.com\" 子栏目", + "step_3": "步骤 3", + "step_3_steps": "复制名称为 \"sp_dc\" 和 \"sp_key\" 的值(Cookie Value)", + "success_emoji": "成功🥳", + "success_message": "你已经成功使用 Spotify 登录。干得漂亮!", + "step_4": "步骤 4", + "step_4_steps": "将 \"sp_dc\" 与 \"sp_key\" 的值分别复制后粘贴到对应的区域", + "something_went_wrong": "某些地方出现了问题", + "piped_instance": "管道服务器实例", + "piped_description": "管道服务器实例用于匹配歌曲\n它们中的一部分可能并不能正常工作。使用时请自行承担风险", + "generate_playlist": "生成歌单", + "track_exists": "歌曲 {track} 已存在", + "replace_downloaded_tracks": "替换已下载的歌曲", + "skip_download_tracks": "下载时跳过已下载的歌曲", + "do_you_want_to_replace": "你确定要替换已下载的歌曲吗??", + "replace": "替换", + "skip": "跳过", + "select_up_to_count_type": "选择多达 {count} 种的类型 {type}", + "select_genres": "选择曲风", + "add_genres": "添加曲风", + "country": "国家和地区", + "number_of_tracks_generate": "生成歌曲的数目", + "acousticness": "原声程度", + "danceability": "律动感", + "energy": "冲击感", + "instrumentalness": "歌唱部分占比", + "liveness": "现场感", + "loudness": "响度", + "speechiness": "朗诵比例", + "valence": "心理感受", + "popularity": "流行度", + "key": "曲调", + "duration": "歌曲时长 (s)", + "tempo": "分钟节拍数 (BPM)", + "mode": "旋律重复度", + "time_signature": "音符时值", + "short": "短", + "medium": "中", + "long": "长", + "min": "最低", + "max": "最高", + "target": "目标", + "moderate": "中", + "deselect_all": "取消全选", + "select_all": "全选", + "are_you_sure": "你确定吗?", + "generating_playlist": "正在生成你的自定义歌单...", + "selected_count_tracks": "已选择 {count} 首歌曲", + "download_warning": "如果你大量下载这些歌曲,你显然在侵犯音乐的版权并对音乐创作社区造成了伤害。我希望你能意识到这一点。永远要尊重并支持艺术家们的辛勤工作", + "download_ip_ban_warning": "小心,如果出现超出正常的下载请求那你的 IP 可能会被 YouTube 封禁,这意味着你的设备将在长达 2-3 个月的时间内无法使用该 IP 访问 YouTube(即使你没登录)。Spotube 对此不承担任何责任", + "by_clicking_accept_terms": "点击 '同意' 代表着你同意以下的条款", + "download_agreement_1": "我明白侵犯音乐版权是一件不好的事情", + "download_agreement_2": "我将尽可能支持艺术家的工作。我现在之所以做不到是因为缺乏资金来购买正版", + "download_agreement_3": "我完全了解我的 IP 存在被 YouTube的风险。我同意 Spotube 的所有者与贡献者们无须对我目前的行为所导致的任何后果负责", + "decline": "拒绝", + "accept": "同意", + "details": "详情", + "youtube": "YouTube", + "channel": "频道", + "likes": "赞", + "dislikes": "踩", + "views": "浏览次数", + "streamUrl": "播放流 URL", + "stop": "停止", + "sort_newest": "按添加日期正序", + "sort_oldest": "按添加日期倒序", + "sleep_timer": "睡眠定时器", + "mins": "{minutes} 分", + "hours": "{hours} 时", + "hour": "{hours} 时", + "custom_hours": "自定义时间", + "logs": "日志", + "developers": "开发者", + "not_logged_in": "你尚未登录", + "search_mode": "搜索模式", + "youtube_api_type": "YouTube API 类型" +} diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index eb6b0c42e..62a390bc5 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -13,5 +13,6 @@ class L10n { const Locale('hi', 'IN'), const Locale('de', 'GE'), const Locale('ja', 'JP'), + const Locale('zh-cn', 'CN'), ]; } From e5839b6980fa918fe44ff43008ee3f91ece3d598 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Tue, 11 Jul 2023 22:23:51 +0600 Subject: [PATCH 07/23] chore: fix simplified chinese --- lib/collections/language_codes.dart | 4 ++-- lib/l10n/{app_zh-cn.arb => app_zh.arb} | 4 ++-- lib/l10n/l10n.dart | 3 ++- 3 files changed, 6 insertions(+), 5 deletions(-) rename lib/l10n/{app_zh-cn.arb => app_zh.arb} (99%) diff --git a/lib/collections/language_codes.dart b/lib/collections/language_codes.dart index 54b1b5aa6..c56247b07 100644 --- a/lib/collections/language_codes.dart +++ b/lib/collections/language_codes.dart @@ -9,7 +9,7 @@ class ISOLanguageName { } // Uncomment the languages as we add support for them -// Currently supported: bn,en,fr,hi,zh-cn +// Currently supported: bn,en,fr,hi,zh abstract class LanguageLocals { static final Map isoLangs = { // "ab": const ISOLanguageName( @@ -128,7 +128,7 @@ abstract class LanguageLocals { // name: "Chichewa", // nativeName: "chiCheŵa", // ), - "zh-cn": const ISOLanguageName( + "zh": const ISOLanguageName( name: "Simplified Chinese", nativeName: "简体中文", ), diff --git a/lib/l10n/app_zh-cn.arb b/lib/l10n/app_zh.arb similarity index 99% rename from lib/l10n/app_zh-cn.arb rename to lib/l10n/app_zh.arb index 6d083b842..9212ebff3 100644 --- a/lib/l10n/app_zh-cn.arb +++ b/lib/l10n/app_zh.arb @@ -79,7 +79,7 @@ "removed_track_from_queue": "将 {track} 从播放队列中移除", "remove_from_queue": "从播放队列移除", "remove_from_favorites": "取消点赞", - "save_as_favorite": 点赞", + "save_as_favorite": "点赞", "add_to_playlist": "添加到歌单", "remove_from_playlist": "从歌单中移除", "add_to_blacklist": "添加到屏蔽列表", @@ -249,5 +249,5 @@ "developers": "开发者", "not_logged_in": "你尚未登录", "search_mode": "搜索模式", - "youtube_api_type": "YouTube API 类型" + "youtube_api_type": "API 类型" } diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 62a390bc5..d575444e8 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -3,6 +3,7 @@ /// Kingkor Roy Tirtho => English, Bengali /// ChatGPT (GPT 3.5) XD => Hindi, French /// maboroshin@github => Japanese +/// iceyear@github => Simplified Chinese import 'package:flutter/material.dart'; class L10n { @@ -13,6 +14,6 @@ class L10n { const Locale('hi', 'IN'), const Locale('de', 'GE'), const Locale('ja', 'JP'), - const Locale('zh-cn', 'CN'), + const Locale('zh', 'CN'), ]; } From affdb57ecd1c34c863ef018e2407a3b406a85415 Mon Sep 17 00:00:00 2001 From: meenbeese Date: Tue, 11 Jul 2023 14:04:46 -0400 Subject: [PATCH 08/23] Use official Play Store badge (#570) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 753377443..0a7849c67 100644 --- a/README.md +++ b/README.md @@ -69,7 +69,7 @@ This handy table lists all methods you can use to install Spotube: Android - Download from Play store + Get it on Google Play
From 3e0834f83c666e0f1ae5cb0070e88a13793374ec Mon Sep 17 00:00:00 2001 From: Piotr Rogowski Date: Tue, 1 Aug 2023 15:44:00 +0200 Subject: [PATCH 09/23] Fix player position performance issue (#606) --- .vscode/settings.json | 1 + lib/components/player/player_controls.dart | 392 ++++++++++----------- lib/components/player/player_overlay.dart | 170 +++++---- lib/hooks/use_progress.dart | 27 +- 4 files changed, 293 insertions(+), 297 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index fa4f1f51e..c4917255c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "danceability", "instrumentalness", "Mpris", + "riverpod", "speechiness", "Spotube", "winget" diff --git a/lib/components/player/player_controls.dart b/lib/components/player/player_controls.dart index b4984c513..7ae4fa82f 100644 --- a/lib/components/player/player_controls.dart +++ b/lib/components/player/player_controls.dart @@ -48,7 +48,6 @@ class PlayerControls extends HookConsumerWidget { final playing = useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; - final buffering = useStream(audioPlayer.bufferingStream).data ?? true; final theme = Theme.of(context); final isDominantColorDark = ThemeData.estimateBrightnessForColor( @@ -89,215 +88,208 @@ class PlayerControls extends HookConsumerWidget { iconSize: compact ? 18 : 24, ); - return RepaintBoundary( - child: GestureDetector( - behavior: HitTestBehavior.translucent, - onTap: () { - if (focusNode.canRequestFocus) { - focusNode.requestFocus(); - } - }, - child: FocusableActionDetector( - focusNode: focusNode, - shortcuts: shortcuts, - actions: actions, - child: Container( - constraints: const BoxConstraints(maxWidth: 600), - child: Column( - children: [ - if (!compact) - HookBuilder( - builder: (context) { - final ( - :bufferProgress, - :duration, - :position, - :progressStatic - ) = useProgress(ref); + return GestureDetector( + behavior: HitTestBehavior.translucent, + onTap: () { + if (focusNode.canRequestFocus) { + focusNode.requestFocus(); + } + }, + child: FocusableActionDetector( + focusNode: focusNode, + shortcuts: shortcuts, + actions: actions, + child: Container( + constraints: const BoxConstraints(maxWidth: 600), + child: Column( + children: [ + if (!compact) + HookBuilder( + builder: (context) { + final ( + :bufferProgress, + :duration, + :position, + :progressStatic + ) = useProgress(ref); - final totalMinutes = PrimitiveUtils.zeroPadNumStr( - duration.inMinutes.remainder(60), - ); - final totalSeconds = PrimitiveUtils.zeroPadNumStr( - duration.inSeconds.remainder(60), - ); - final currentMinutes = PrimitiveUtils.zeroPadNumStr( - position.inMinutes.remainder(60), - ); - final currentSeconds = PrimitiveUtils.zeroPadNumStr( - position.inSeconds.remainder(60), - ); + final totalMinutes = PrimitiveUtils.zeroPadNumStr( + duration.inMinutes.remainder(60), + ); + final totalSeconds = PrimitiveUtils.zeroPadNumStr( + duration.inSeconds.remainder(60), + ); + final currentMinutes = PrimitiveUtils.zeroPadNumStr( + position.inMinutes.remainder(60), + ); + final currentSeconds = PrimitiveUtils.zeroPadNumStr( + position.inSeconds.remainder(60), + ); - final progress = useState( - useMemoized(() => progressStatic, []), - ); + final progress = useState( + useMemoized(() => progressStatic, []), + ); - useEffect(() { - progress.value = progressStatic; - return null; - }, [progressStatic]); + useEffect(() { + progress.value = progressStatic; + return null; + }, [progressStatic]); - return Column( - children: [ - Tooltip( - message: context.l10n.slide_to_seek, - child: Slider( - // cannot divide by zero - // there's an edge case for value being bigger - // than total duration. Keeping it resolved - value: progress.value.toDouble(), - secondaryTrackValue: bufferProgress, - onChanged: - playlist.isFetching == true || buffering - ? null - : (v) { - progress.value = v; - }, - onChangeEnd: (value) async { - await audioPlayer.seek( - Duration( - seconds: - (value * duration.inSeconds).toInt(), - ), - ); - }, - activeColor: sliderColor, - secondaryActiveColor: - sliderColor.withOpacity(0.2), - inactiveColor: sliderColor.withOpacity(0.15), - ), + return Column( + children: [ + Tooltip( + message: context.l10n.slide_to_seek, + child: Slider( + // cannot divide by zero + // there's an edge case for value being bigger + // than total duration. Keeping it resolved + value: progress.value.toDouble(), + secondaryTrackValue: bufferProgress, + onChanged: playlist.isFetching == true + ? null + : (v) { + progress.value = v; + }, + onChangeEnd: (value) async { + await audioPlayer.seek( + Duration( + seconds: (value * duration.inSeconds).toInt(), + ), + ); + }, + activeColor: sliderColor, + secondaryActiveColor: sliderColor.withOpacity(0.2), + inactiveColor: sliderColor.withOpacity(0.15), ), - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 8.0, + ), + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8.0, + ), + child: DefaultTextStyle( + style: theme.textTheme.bodySmall!.copyWith( + color: palette?.dominantColor?.bodyTextColor, ), - child: DefaultTextStyle( - style: theme.textTheme.bodySmall!.copyWith( - color: palette?.dominantColor?.bodyTextColor, - ), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text("$currentMinutes:$currentSeconds"), - Text("$totalMinutes:$totalSeconds"), - ], - ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("$currentMinutes:$currentSeconds"), + Text("$totalMinutes:$totalSeconds"), + ], ), ), - ], - ); - }, + ), + ], + ); + }, + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + StreamBuilder( + stream: audioPlayer.shuffledStream, + builder: (context, snapshot) { + final shuffled = snapshot.data ?? false; + return IconButton( + tooltip: shuffled + ? context.l10n.unshuffle_playlist + : context.l10n.shuffle_playlist, + icon: const Icon(SpotubeIcons.shuffle), + style: shuffled ? activeButtonStyle : buttonStyle, + onPressed: playlist.isFetching == true + ? null + : () { + if (shuffled) { + audioPlayer.setShuffle(false); + } else { + audioPlayer.setShuffle(true); + } + }, + ); + }), + IconButton( + tooltip: context.l10n.previous_track, + icon: const Icon(SpotubeIcons.skipBack), + style: buttonStyle, + onPressed: playlist.isFetching == true + ? null + : playlistNotifier.previous, ), - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - StreamBuilder( - stream: audioPlayer.shuffledStream, - builder: (context, snapshot) { - final shuffled = snapshot.data ?? false; - return IconButton( - tooltip: shuffled - ? context.l10n.unshuffle_playlist - : context.l10n.shuffle_playlist, - icon: const Icon(SpotubeIcons.shuffle), - style: shuffled ? activeButtonStyle : buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : () { - if (shuffled) { - audioPlayer.setShuffle(false); - } else { - audioPlayer.setShuffle(true); - } - }, - ); - }), - IconButton( - tooltip: context.l10n.previous_track, - icon: const Icon(SpotubeIcons.skipBack), - style: buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : playlistNotifier.previous, - ), - IconButton( - tooltip: playing - ? context.l10n.pause_playback - : context.l10n.resume_playback, - icon: playlist.isFetching == true - ? SizedBox( - height: 20, - width: 20, - child: CircularProgressIndicator( - color: accentColor?.titleTextColor ?? - theme.colorScheme.onPrimary, - ), - ) - : Icon( - playing ? SpotubeIcons.pause : SpotubeIcons.play, - ), - style: resumePauseStyle, - onPressed: playlist.isFetching == true - ? null - : Actions.handler( - context, - PlayPauseIntent(ref), - ), - ), - IconButton( - tooltip: context.l10n.next_track, - icon: const Icon(SpotubeIcons.skipForward), - style: buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : playlistNotifier.next, - ), - StreamBuilder( - stream: audioPlayer.loopModeStream, - builder: (context, snapshot) { - final loopMode = - snapshot.data ?? PlaybackLoopMode.none; - return IconButton( - tooltip: loopMode == PlaybackLoopMode.one - ? context.l10n.loop_track - : loopMode == PlaybackLoopMode.all - ? context.l10n.repeat_playlist - : null, - icon: Icon( - loopMode == PlaybackLoopMode.one - ? SpotubeIcons.repeatOne - : SpotubeIcons.repeat, + IconButton( + tooltip: playing + ? context.l10n.pause_playback + : context.l10n.resume_playback, + icon: playlist.isFetching == true + ? SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator( + color: accentColor?.titleTextColor ?? + theme.colorScheme.onPrimary, ), - style: loopMode == PlaybackLoopMode.one || - loopMode == PlaybackLoopMode.all - ? activeButtonStyle - : buttonStyle, - onPressed: playlist.isFetching == true || buffering - ? null - : () async { - switch (await audioPlayer.loopMode) { - case PlaybackLoopMode.all: - audioPlayer - .setLoopMode(PlaybackLoopMode.one); - break; - case PlaybackLoopMode.one: - audioPlayer - .setLoopMode(PlaybackLoopMode.none); - break; - case PlaybackLoopMode.none: - audioPlayer - .setLoopMode(PlaybackLoopMode.all); - break; - } - }, - ); - }), - ], - ), - const SizedBox(height: 5) - ], - ), + ) + : Icon( + playing ? SpotubeIcons.pause : SpotubeIcons.play, + ), + style: resumePauseStyle, + onPressed: playlist.isFetching == true + ? null + : Actions.handler( + context, + PlayPauseIntent(ref), + ), + ), + IconButton( + tooltip: context.l10n.next_track, + icon: const Icon(SpotubeIcons.skipForward), + style: buttonStyle, + onPressed: playlist.isFetching == true + ? null + : playlistNotifier.next, + ), + StreamBuilder( + stream: audioPlayer.loopModeStream, + builder: (context, snapshot) { + final loopMode = snapshot.data ?? PlaybackLoopMode.none; + return IconButton( + tooltip: loopMode == PlaybackLoopMode.one + ? context.l10n.loop_track + : loopMode == PlaybackLoopMode.all + ? context.l10n.repeat_playlist + : null, + icon: Icon( + loopMode == PlaybackLoopMode.one + ? SpotubeIcons.repeatOne + : SpotubeIcons.repeat, + ), + style: loopMode == PlaybackLoopMode.one || + loopMode == PlaybackLoopMode.all + ? activeButtonStyle + : buttonStyle, + onPressed: playlist.isFetching == true + ? null + : () async { + switch (await audioPlayer.loopMode) { + case PlaybackLoopMode.all: + audioPlayer + .setLoopMode(PlaybackLoopMode.one); + break; + case PlaybackLoopMode.one: + audioPlayer + .setLoopMode(PlaybackLoopMode.none); + break; + case PlaybackLoopMode.none: + audioPlayer + .setLoopMode(PlaybackLoopMode.all); + break; + } + }, + ); + }), + ], + ), + const SizedBox(height: 5) + ], ), ), ), diff --git a/lib/components/player/player_overlay.dart b/lib/components/player/player_overlay.dart index e51d11efa..f4984ad26 100644 --- a/lib/components/player/player_overlay.dart +++ b/lib/components/player/player_overlay.dart @@ -62,99 +62,95 @@ class PlayerOverlay extends HookConsumerWidget { child: AnimatedOpacity( duration: const Duration(milliseconds: 250), opacity: canShow ? 1 : 0, - child: RepaintBoundary( - child: Material( - type: MaterialType.transparency, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - HookBuilder( - builder: (context) { - final progress = useProgress(ref); - // animated - return TweenAnimationBuilder( - duration: const Duration(milliseconds: 250), - tween: Tween( - begin: 0, - end: progress.progressStatic, - ), - builder: (context, value, child) { - return LinearProgressIndicator( - value: value, - minHeight: 2, - backgroundColor: Colors.transparent, - valueColor: AlwaysStoppedAnimation( - theme.colorScheme.primary, - ), - ); - }, - ); - }, - ), - Expanded( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () => - GoRouter.of(context).push("/player"), - child: PlayerTrackDetails( - albumArt: albumArt, - color: textColor, - ), + child: Material( + type: MaterialType.transparency, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + HookBuilder( + builder: (context) { + final progress = useProgress(ref); + // animated + return TweenAnimationBuilder( + duration: const Duration(milliseconds: 250), + tween: Tween( + begin: 0, + end: progress.progressStatic, + ), + builder: (context, value, child) { + return LinearProgressIndicator( + value: value, + minHeight: 2, + backgroundColor: Colors.transparent, + valueColor: AlwaysStoppedAnimation( + theme.colorScheme.primary, + ), + ); + }, + ); + }, + ), + Expanded( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => + GoRouter.of(context).push("/player"), + child: PlayerTrackDetails( + albumArt: albumArt, + color: textColor, ), ), ), - Row( - children: [ - IconButton( - icon: Icon( - SpotubeIcons.skipBack, - color: textColor, - ), - onPressed: playlistNotifier.previous, - ), - Consumer( - builder: (context, ref, _) { - return IconButton( - icon: playlist.isFetching - ? const SizedBox( - height: 20, - width: 20, - child: - CircularProgressIndicator(), - ) - : Icon( - playing - ? SpotubeIcons.pause - : SpotubeIcons.play, - color: textColor, - ), - onPressed: - Actions.handler( - context, - PlayPauseIntent(ref), - ), - ); - }, + ), + Row( + children: [ + IconButton( + icon: Icon( + SpotubeIcons.skipBack, + color: textColor, ), - IconButton( - icon: Icon( - SpotubeIcons.skipForward, - color: textColor, - ), - onPressed: playlistNotifier.next, + onPressed: playlistNotifier.previous, + ), + Consumer( + builder: (context, ref, _) { + return IconButton( + icon: playlist.isFetching + ? const SizedBox( + height: 20, + width: 20, + child: CircularProgressIndicator(), + ) + : Icon( + playing + ? SpotubeIcons.pause + : SpotubeIcons.play, + color: textColor, + ), + onPressed: Actions.handler( + context, + PlayPauseIntent(ref), + ), + ); + }, + ), + IconButton( + icon: Icon( + SpotubeIcons.skipForward, + color: textColor, ), - ], - ), - ], - ), + onPressed: playlistNotifier.next, + ), + ], + ), + ], ), - ], - ), + ), + ], ), ), ), diff --git a/lib/hooks/use_progress.dart b/lib/hooks/use_progress.dart index 8ba2f336a..404291906 100644 --- a/lib/hooks/use_progress.dart +++ b/lib/hooks/use_progress.dart @@ -1,6 +1,5 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; ({ @@ -9,21 +8,29 @@ import 'package:spotube/services/audio_player/audio_player.dart'; Duration duration, double bufferProgress }) useProgress(WidgetRef ref) { - final playlist = ref.watch(ProxyPlaylistNotifier.provider); - final bufferProgress = useStream(audioPlayer.bufferedPositionStream).data?.inSeconds ?? 0; + Duration audioPlayerDuration = Duration.zero; + Duration audioPlayerPosition = Duration.zero; + // Duration future is needed for getting the duration of the song // as stream can be null when no event occurs (Mostly needed for android) - final durationFuture = useFuture(audioPlayer.duration); - final duration = useStream(audioPlayer.durationStream).data ?? - durationFuture.data ?? - Duration.zero; + audioPlayer.duration.then((value) { + if (value != null) { + audioPlayerDuration = value; + } + }); - final positionFuture = useFuture(audioPlayer.position); - final position = useState(positionFuture.data ?? Duration.zero); + audioPlayer.position.then((value) { + if (value != null) { + audioPlayerPosition = value; + } + }); + final position = useState(audioPlayerPosition); + final duration = + useStream(audioPlayer.durationStream).data ?? audioPlayerDuration; final sliderMax = duration.inSeconds; final sliderValue = position.value.inSeconds; @@ -38,7 +45,7 @@ import 'package:spotube/services/audio_player/audio_player.dart'; lastPosition = event; position.value = event; }).cancel; - }, []); + }, [audioPlayerPosition, audioPlayerDuration]); return ( progressStatic: From 042d7a4a10c78dd93a56a2f32d18a0fb74dbe697 Mon Sep 17 00:00:00 2001 From: Souda <52483168+soudabot@users.noreply.github.com> Date: Tue, 1 Aug 2023 13:46:04 +0000 Subject: [PATCH 10/23] feat: add spanish translations (#585) * Update bug_report.yml * Create app_es.arb * Fix the missing lines and improve the translation --------- Co-authored-by: Kingkor Roy Tirtho --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + lib/l10n/app_es.arb | 253 ++++++++++++++++++++++++++ 2 files changed, 254 insertions(+) create mode 100644 lib/l10n/app_es.arb diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 816c7b897..9a2f44fee 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -56,6 +56,7 @@ body: - "Website (spotube.netlify.app)" - "GitHub Releases (Binary)" - "GitHub Actions (Nightly Binary)" + - "Play Store (Android)" - "F-Droid (Android)" - "Arch User Repository (AUR)" - "Flathub (Flatpak)" diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb new file mode 100644 index 000000000..eb6a714a6 --- /dev/null +++ b/lib/l10n/app_es.arb @@ -0,0 +1,253 @@ +{ + "guest": "Invitado", + "browse": "Explorar", + "search": "Buscar", + "library": "Biblioteca", + "lyrics": "Letras", + "settings": "Configuración", + "genre_categories_filter": "Filtrar categorías o géneros...", + "genre": "Género", + "personalized": "Personalizado", + "featured": "Destacado", + "new_releases": "Nuevos Lanzamientos", + "songs": "Canciones", + "playing_track": "Reproduciendo {track}", + "queue_clear_alert": "Esto eliminará la lista actual. Se eliminarán {track_length} canciones.\n¿Deseas continuar?", + "load_more": "Cargar más", + "playlists": "Listas de reproducción", + "artists": "Artistas", + "albums": "Álbumes", + "tracks": "Canciones", + "downloads": "Descargas", + "filter_playlists": "Filtrar tus listas de reproducción...", + "liked_tracks": "Canciones Favoritas", + "liked_tracks_description": "Todas tus canciones favoritas", + "create_playlist": "Crear Lista de reproducción", + "create_a_playlist": "Crear una lista de reproducción", + "create": "Crear", + "cancel": "Cancelar", + "playlist_name": "Nombre de la lista", + "name_of_playlist": "Nombre de la lista", + "description": "Descripción", + "public": "Pública", + "collaborative": "Colaborativa", + "search_local_tracks": "Buscar canciones locales...", + "play": "Reproducir", + "delete": "Eliminar", + "none": "Ninguno", + "sort_a_z": "Ordenar de la A a la Z", + "sort_z_a": "Ordenar de la Z a la A", + "sort_artist": "Ordenar por Artista", + "sort_album": "Ordenar por Álbum", + "sort_tracks": "Ordenar Canciones", + "currently_downloading": "Descargando en curso ({tracks_length})", + "cancel_all": "Cancelar todo", + "filter_artist": "Filtrar artistas...", + "followers": "{followers} Seguidores", + "add_artist_to_blacklist": "Agregar artista a la lista negra", + "top_tracks": "Mejores Canciones", + "fans_also_like": "A los fans también les gusta", + "loading": "Cargando...", + "artist": "Artista", + "blacklisted": "En la lista negra", + "following": "Siguiendo", + "follow": "Seguir", + "artist_url_copied": "URL del artista copiada al portapapeles", + "added_to_queue": "Agregadas {tracks} canciones a la lista", + "filter_albums": "Filtrar álbumes...", + "synced": "Sincronizado", + "plain": "Normal", + "shuffle": "Aleatorio", + "search_tracks": "Buscar canciones...", + "released": "Lanzado", + "error": "Error {error}", + "title": "Título", + "time": "Duración", + "more_actions": "Más acciones", + "download_count": "Descargas ({count})", + "add_count_to_playlist": "Agregar ({count}) a la lista", + "add_count_to_queue": "Agregar ({count}) a la lista", + "play_count_next": "Reproducir ({count}) a continuación", + "album": "Álbum", + "copied_to_clipboard": "{data} copiado al portapapeles", + "add_to_following_playlists": "Agregar {track} a las listas de reproducción siguientes", + "add": "Agregar", + "added_track_to_queue": "{track} agregada a la lista", + "add_to_queue": "Agregar a la lista", + "track_will_play_next": "{track} se reproducirá a continuación", + "play_next": "Reproducir a continuación", + "removed_track_from_queue": "{track} eliminada de la lista", + "remove_from_queue": "Eliminar de la lista", + "remove_from_favorites": "Eliminar de favoritos", + "save_as_favorite": "Guardar como favorito", + "add_to_playlist": "Agregar a la lista", + "remove_from_playlist": "Eliminar de la lista", + "add_to_blacklist": "Agregar a la lista negra", + "remove_from_blacklist": "Eliminar de la lista negra", + "share": "Compartir", + "mini_player": "Reproductor Mini", + "slide_to_seek": "Desliza para buscar adelante o atrás", + "shuffle_playlist": "Reproducir lista en orden aleatorio", + "unshuffle_playlist": "Desactivar reproducción aleatoria", + "previous_track": "Pista anterior", + "next_track": "Pista siguiente", + "pause_playback": "Pausar reproducción", + "resume_playback": "Reanudar reproducción", + "loop_track": "Repetir pista", + "repeat_playlist": "Repetir lista", + "queue": "Lista", + "alternative_track_sources": "Fuentes alternativas de canciones", + "download_track": "Descargar canción", + "tracks_in_queue": "{tracks} canciones en la lista", + "clear_all": "Limpiar todo", + "show_hide_ui_on_hover": "Mostrar/Ocultar interfaz al pasar el cursor", + "always_on_top": "Siempre visible", + "exit_mini_player": "Salir del reproductor mini", + "download_location": "Ubicación de descargas", + "account": "Cuenta", + "login_with_spotify": "Iniciar sesión con tu cuenta de Spotify", + "connect_with_spotify": "Conectar con Spotify", + "logout": "Cerrar sesión", + "logout_of_this_account": "Cerrar sesión de esta cuenta", + "language_region": "Idioma y Región", + "language": "Idioma", + "system_default": "Predeterminado del sistema", + "market_place_region": "Región de la tienda", + "recommendation_country": "País de recomendación", + "appearance": "Apariencia", + "layout_mode": "Modo de diseño", + "override_layout_settings": "Anular la configuración del modo de diseño responsive", + "adaptive": "Adaptable", + "compact": "Compacto", + "extended": "Extendido", + "theme": "Tema", + "dark": "Oscuro", + "light": "Claro", + "system": "Sistema", + "accent_color": "Color de acento", + "sync_album_color": "Sincronizar color del álbum", + "sync_album_color_description": "Usa el color dominante del arte del álbum como color de acento", + "playback": "Reproducción", + "audio_quality": "Calidad de audio", + "high": "Alta", + "low": "Baja", + "pre_download_play": "Pre-descargar y reproducir", + "pre_download_play_description": "En lugar de transmitir audio, descarga bytes y reproduce en su lugar (recomendado para usuarios con mayor ancho de banda)", + "skip_non_music": "Omitir segmentos que no son música (SponsorBlock)", + "blacklist_description": "Canciones y artistas en la lista negra", + "wait_for_download_to_finish": "Por favor, espera a que termine la descarga actual", + "download_lyrics": "Descargar letras junto con las canciones", + "desktop": "Escritorio", + "close_behavior": "Comportamiento al cerrar", + "close": "Cerrar", + "minimize_to_tray": "Minimizar en la bandeja del sistema", + "show_tray_icon": "Mostrar icono en la bandeja del sistema", + "about": "Acerca de", + "u_love_spotube": "Sabemos que te encanta Spotube", + "check_for_updates": "Buscar actualizaciones", + "about_spotube": "Acerca de Spotube", + "blacklist": "Lista negra", + "please_sponsor": "Por favor, apoya/dona", + "spotube_description": "Spotube, un cliente ligero, multiplataforma y gratuito de Spotify", + "version": "Versión", + "build_number": "Número de compilación", + "founder": "Fundador", + "repository": "Repositorio", + "bug_issues": "Errores y problemas", + "made_with": "Hecho con ❤️ en Bangladesh🇧🇩", + "kingkor_roy_tirtho": "Kingkor Roy Tirtho", + "copyright": "© 2021-{current_year} Kingkor Roy Tirtho", + "license": "Licencia", + "add_spotify_credentials": "Agrega tus credenciales de Spotify para comenzar", + "credentials_will_not_be_shared_disclaimer": "No te preocupes, tus credenciales no serán recopiladas ni compartidas con nadie", + "know_how_to_login": "¿No sabes cómo hacerlo?", + "follow_step_by_step_guide": "Sigue la guía paso a paso", + "spotify_cookie": "Cookie de Spotify {name}", + "cookie_name_cookie": "Cookie {name}", + "fill_in_all_fields": "Por favor, completa todos los campos", + "submit": "Enviar", + "exit": "Salir", + "previous": "Anterior", + "next": "Siguiente", + "done": "Listo", + "step_1": "Paso 1", + "first_go_to": "Primero, ve a", + "login_if_not_logged_in": "e inicia sesión/registra tu cuenta si no lo has hecho aún", + "step_2": "Paso 2", + "step_2_steps": "1. Una vez que hayas iniciado sesión, presiona F12 o haz clic derecho con el ratón > Inspeccionar para abrir las herramientas de desarrollo del navegador.\n2. Luego ve a la pestaña \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Ve a la sección \"Cookies\" y luego la subsección \"https://accounts.spotify.com\"", + "step_3": "Paso 3", + "step_3_steps": "Copia los valores de las Cookies \"sp_dc\" y \"sp_key\"", + "success_emoji": "¡Éxito! 🥳", + "success_message": "Ahora has iniciado sesión con éxito en tu cuenta de Spotify. ¡Buen trabajo!", + "step_4": "Paso 4", + "step_4_steps": "Pega los valores copiados de \"sp_dc\" y \"sp_key\" en los campos respectivos", + "something_went_wrong": "Algo salió mal", + "piped_instance": "Instancia del servidor Piped", + "piped_description": "La instancia del servidor Piped a utilizar para la coincidencia de pistas\nAlgunas pueden no funcionar bien, úsalas bajo tu propio riesgo", + "generate_playlist": "Generar Lista de reproducción", + "track_exists": "La canción {track} ya existe", + "replace_downloaded_tracks": "Reemplazar todas las canciones descargadas", + "skip_download_tracks": "Omitir la descarga de todas las canciones descargadas", + "do_you_want_to_replace": "¿Deseas reemplazar la canción existente?", + "replace": "Reemplazar", + "skip": "Omitir", + "select_up_to_count_type": "Seleccionar hasta {count} {type}", + "select_genres": "Seleccionar Géneros", + "add_genres": "Agregar Géneros", + "country": "País", + "number_of_tracks_generate": "Número de canciones a generar", + "acousticness": "Acousticness", + "danceability": "Danceability", + "energy": "Energía", + "instrumentalness": "Instrumentalidad", + "liveness": "En vivo", + "loudness": "Volumen", + "speechiness": "Habla", + "valence": "Valencia", + "popularity": "Popularidad", + "key": "Tono", + "duration": "Duración (s)", + "tempo": "Tempo (BPM)", + "mode": "Modo", + "time_signature": "Compás", + "short": "Corto", + "medium": "Medio", + "long": "Largo", + "min": "Mín.", + "max": "Máx.", + "target": "Objetivo", + "moderate": "Moderado", + "deselect_all": "Deseleccionar todo", + "select_all": "Seleccionar todo", + "are_you_sure": "¿Estás seguro?", + "generating_playlist": "Generando tu lista de reproducción personalizada...", + "selected_count_tracks": "Seleccionadas {count} canciones", + "download_warning": "Si descargas todas las canciones de golpe, estás claramente pirateando música y causando daño a la sociedad creativa de la música. Espero que seas consciente de esto y siempre intentes respetar y apoyar el arduo trabajo de los artistas", + "download_ip_ban_warning": "Por cierto, tu IP puede ser bloqueada en YouTube debido a solicitudes de descarga excesivas. El bloqueo de IP significa que no podrás usar YouTube (incluso si has iniciado sesión) durante al menos 2-3 meses desde esa dirección IP. Y Spotube no se hace responsable si esto ocurre alguna vez", + "by_clicking_accept_terms": "Al hacer clic en 'Aceptar', aceptas los siguientes términos:", + "download_agreement_1": "Sé que estoy pirateando música. Soy malo", + "download_agreement_2": "Apoyaré al artista donde pueda y solo lo hago porque no tengo dinero para comprar su arte", + "download_agreement_3": "Soy completamente consciente de que mi IP puede ser bloqueada en YouTube y no responsabilizo a Spotube ni a sus dueños/contribuyentes por cualquier incidente causado por mi acción actual", + "decline": "Rechazar", + "accept": "Aceptar", + "details": "Detalles", + "youtube": "YouTube", + "channel": "Canal", + "likes": "Me gusta", + "dislikes": "No me gusta", + "views": "Vistas", + "streamUrl": "URL del streaming", + "stop": "Detener", + "sort_newest": "Ordenar por más recientes", + "sort_oldest": "Ordenar por más antiguos", + "sleep_timer": "Temporizador de apagado", + "mins": "{minutes} minutos", + "hours": "{hours} horas", + "hour": "{hours} hora", + "custom_hours": "Horas personalizadas", + "logs": "Registros", + "developers": "Desarrolladores", + "not_logged_in": "No has iniciado sesión", + "search_mode": "Modo de búsqueda", + "youtube_api_type": "Tipo de API de YouTube" +} From 6dff0996bdfee603acf242b1316f8793d625267c Mon Sep 17 00:00:00 2001 From: Piotr Rogowski Date: Wed, 2 Aug 2023 06:44:06 +0200 Subject: [PATCH 11/23] feat: Force High Refresh Rate on some Android devices (#607) * Force High Refresh Rate on some Android devices * fix: check android platform for high refresh-rate --------- Co-authored-by: Kingkor Roy Tirtho --- lib/main.dart | 6 ++++++ pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + 3 files changed, 15 insertions(+) diff --git a/lib/main.dart b/lib/main.dart index 3e9348ff9..eae240ada 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -36,6 +36,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:spotube/hooks/use_init_sys_tray.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_native_splash/flutter_native_splash.dart'; +import 'package:flutter_displaymode/flutter_displaymode.dart'; Future main(List rawArgs) async { final parser = ArgParser(); @@ -85,6 +86,11 @@ Future main(List rawArgs) async { MediaKit.ensureInitialized(); + // force High Refresh Rate on some Android devices (like One Plus) +if (DesktopTools.platform.isAndroid) { + await FlutterDisplayMode.setHighRefreshRate(); +} + await DesktopTools.ensureInitialized( DesktopWindowOptions( hideTitleBar: true, diff --git a/pubspec.lock b/pubspec.lock index e830bebaa..18afa0fe1 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -632,6 +632,14 @@ packages: url: "https://github.com/KRTirtho/flutter_desktop_tools.git" source: git version: "0.0.1" + flutter_displaymode: + dependency: "direct main" + description: + name: flutter_displaymode + sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" + url: "https://pub.dev" + source: hosted + version: "0.6.0" flutter_distributor: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index 20df062ce..c03a58c79 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -97,6 +97,7 @@ dependencies: duration: ^3.0.12 disable_battery_optimization: ^1.1.0+1 youtube_explode_dart: ^1.12.4 + flutter_displaymode: ^0.6.0 dev_dependencies: build_runner: ^2.3.2 From 4885dca04f06658391d1063e6c5a009547391a6f Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 2 Aug 2023 11:12:22 +0600 Subject: [PATCH 12/23] fix: duration is always zero in PlayerView --- lib/hooks/use_progress.dart | 64 ++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/lib/hooks/use_progress.dart b/lib/hooks/use_progress.dart index 404291906..62dccbced 100644 --- a/lib/hooks/use_progress.dart +++ b/lib/hooks/use_progress.dart @@ -1,3 +1,4 @@ +import 'package:async/async.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -11,47 +12,60 @@ import 'package:spotube/services/audio_player/audio_player.dart'; final bufferProgress = useStream(audioPlayer.bufferedPositionStream).data?.inSeconds ?? 0; - Duration audioPlayerDuration = Duration.zero; - Duration audioPlayerPosition = Duration.zero; - - // Duration future is needed for getting the duration of the song - // as stream can be null when no event occurs (Mostly needed for android) - audioPlayer.duration.then((value) { - if (value != null) { - audioPlayerDuration = value; - } - }); - - audioPlayer.position.then((value) { - if (value != null) { - audioPlayerPosition = value; - } - }); - - final position = useState(audioPlayerPosition); - final duration = - useStream(audioPlayer.durationStream).data ?? audioPlayerDuration; - final sliderMax = duration.inSeconds; + final duration = useState(Duration.zero); + final position = useState(Duration.zero); + + final sliderMax = duration.value.inSeconds; final sliderValue = position.value.inSeconds; useEffect(() { + final durationOperation = + CancelableOperation.fromFuture(audioPlayer.duration); + durationOperation.then((value) { + if (value != null) { + duration.value = value; + } + }); + + final durationSubscription = audioPlayer.durationStream.listen((event) { + duration.value = event; + }); + + final positionOperation = + CancelableOperation.fromFuture(audioPlayer.position); + + positionOperation.then((value) { + if (value != null) { + position.value = value; + } + }); + // audioPlayer.positionStream is fired every 200ms and only 1s delay is // enough. Thus only update the position if the difference is more than 1s // Reduces CPU usage var lastPosition = position.value; - return audioPlayer.positionStream.listen((event) { + + final positionSubscription = audioPlayer.positionStream.listen((event) { if (event.inMilliseconds > 1000 && event.inMilliseconds - lastPosition.inMilliseconds < 1000) return; + lastPosition = event; position.value = event; - }).cancel; - }, [audioPlayerPosition, audioPlayerDuration]); + }); + + return () { + positionOperation.cancel(); + positionSubscription.cancel(); + durationOperation.cancel(); + durationSubscription.cancel(); + }; + }, []); return ( progressStatic: sliderMax == 0 || sliderValue > sliderMax ? 0 : sliderValue / sliderMax, position: position.value, - duration: duration, + duration: duration.value, bufferProgress: sliderMax == 0 || bufferProgress > sliderMax ? 0 : bufferProgress / sliderMax, From 888a4b1162c25371d7f6e88fae3a2473cabf1434 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 2 Aug 2023 15:12:55 +0600 Subject: [PATCH 13/23] fix(linux): crash on no secret service (#608) --- .../shared/dialogs/prompt_dialog.dart | 16 +++++--- lib/l10n/app_bn.arb | 5 ++- lib/l10n/app_de.arb | 5 ++- lib/l10n/app_en.arb | 7 +++- lib/l10n/app_es.arb | 7 +++- lib/l10n/app_fr.arb | 5 ++- lib/l10n/app_hi.arb | 5 ++- lib/l10n/app_ja.arb | 5 ++- lib/l10n/app_zh.arb | 7 +++- lib/pages/root/root_app.dart | 13 ++++++ lib/utils/persisted_state_notifier.dart | 40 ++++++++++++++++++- 11 files changed, 96 insertions(+), 19 deletions(-) diff --git a/lib/components/shared/dialogs/prompt_dialog.dart b/lib/components/shared/dialogs/prompt_dialog.dart index 6fd54a986..30a63bcfd 100644 --- a/lib/components/shared/dialogs/prompt_dialog.dart +++ b/lib/components/shared/dialogs/prompt_dialog.dart @@ -1,11 +1,12 @@ import 'package:flutter/material.dart'; +import 'package:spotube/extensions/context.dart'; Future showPromptDialog({ required BuildContext context, required String title, required String message, String okText = "Ok", - String cancelText = "Cancel", + String? cancelText = "Cancel", }) async { return showDialog( context: context, @@ -14,12 +15,15 @@ Future showPromptDialog({ title: Text(title), content: Text(message), actions: [ - OutlinedButton( - onPressed: () => Navigator.of(context).pop(false), - child: Text(cancelText), - ), + if (cancelText != null) + OutlinedButton( + onPressed: () => Navigator.of(context).pop(false), + child: Text( + cancelText == "Cancel" ? context.l10n.cancel : cancelText, + ), + ), FilledButton( - child: Text(okText), + child: Text(okText == "Ok" ? context.l10n.ok : okText), onPressed: () => Navigator.of(context).pop(true), ), ], diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index 106190246..73ab308f2 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -249,5 +249,8 @@ "developers": "ডেভেলপার", "not_logged_in": "আপনি লগইন করা নেই", "search_mode": "অনুসন্ধান মোড", - "youtube_api_type": "API প্রকার" + "youtube_api_type": "API প্রকার", + "ok": "ঠিক আছে", + "failed_to_encrypt": "এনক্রিপ্ট করা ব্যর্থ হয়েছে", + "encryption_failed_warning": "Spotube আপনার তথ্যগুলি নিরাপদভাবে স্টোর করতে এনক্রিপশন ব্যবহার করে। কিন্তু এটি ব্যর্থ হয়েছে। তাই এটি অনিরাপদ স্টোরে ফলফল হবে\nযদি আপনি Linux ব্যবহার করেন, তবে দয়া করে নিশ্চিত হউন যে আপনার কোনও সিক্রেট-সার্ভিস gnome-keyring, kde-wallet, keepassxc ইত্যাদি ইনস্টল করা আছে" } \ No newline at end of file diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 9f6d67f53..4d005ea32 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -249,5 +249,8 @@ "developers": "Entwickler", "not_logged_in": "Sie sind nicht angemeldet", "search_mode": "Suchmodus", - "youtube_api_type": "API-Typ" + "youtube_api_type": "API-Typ", + "ok": "OK", + "failed_to_encrypt": "Verschlüsselung fehlgeschlagen", + "encryption_failed_warning": "Spotube verwendet Verschlüsselung, um Ihre Daten sicher zu speichern. Dies ist jedoch fehlgeschlagen. Daher wird es auf unsichere Speicherung zurückgreifen\nWenn Sie Linux verwenden, stellen Sie bitte sicher, dass Sie Secret-Services wie gnome-keyring, kde-wallet und keepassxc installiert haben" } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 1ded2ac6a..9e919acd9 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -249,5 +249,8 @@ "developers": "Developers", "not_logged_in": "You're not logged in", "search_mode": "Search Mode", - "youtube_api_type": "API Type" -} + "youtube_api_type": "API Type", + "ok": "Ok", + "failed_to_encrypt": "Failed to encrypt", + "encryption_failed_warning": "Spotube uses encryption to securely store your data. But failed to do so. So it'll fallback to insecure storage\nIf you're using linux, please make sure you've any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed" +} \ No newline at end of file diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index eb6a714a6..9a2285998 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -249,5 +249,8 @@ "developers": "Desarrolladores", "not_logged_in": "No has iniciado sesión", "search_mode": "Modo de búsqueda", - "youtube_api_type": "Tipo de API de YouTube" -} + "youtube_api_type": "Tipo de API de YouTube", + "ok": "OK", + "failed_to_encrypt": "Error al cifrar", + "encryption_failed_warning": "Spotube utiliza el cifrado para almacenar sus datos de forma segura. Pero ha fallado. Por lo tanto, volverá a un almacenamiento no seguro\nSi está utilizando Linux, asegúrese de tener instalados servicios secretos como gnome-keyring, kde-wallet y keepassxc" +} \ No newline at end of file diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 3036a9aae..62c62d7f1 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -249,5 +249,8 @@ "developers": "Développeurs", "not_logged_in": "Vous n'êtes pas connecté(e)", "search_mode": "Mode de recherche", - "youtube_api_type": "Type d'API" + "youtube_api_type": "Type d'API", + "ok": "OK", + "failed_to_encrypt": "Échec de la cryptage", + "encryption_failed_warning": "Spotube utilise le cryptage pour stocker vos données en toute sécurité. Mais cela a échoué. Il basculera donc vers un stockage non sécurisé\nSi vous utilisez Linux, assurez-vous d'avoir installé des services secrets tels que gnome-keyring, kde-wallet et keepassxc" } \ No newline at end of file diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index 675c97e68..e6963e4b8 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -249,5 +249,8 @@ "developers": "डेवलपर्स", "not_logged_in": "आप लॉग इन नहीं हैं", "search_mode": "खोज मोड", - "youtube_api_type": "API प्रकार" + "youtube_api_type": "API प्रकार", + "ok": "ठीक है", + "failed_to_encrypt": "एन्क्रिप्ट करने में विफल रहा", + "encryption_failed_warning": "Spotube आपके डेटा को सुरक्षित रूप से स्टोर करने के लिए एन्क्रिप्शन का उपयोग करता है। लेकिन इसमें विफल रहा। इसलिए, यह असुरक्षित स्टोरेज पर फॉलबैक करेगा\nयदि आप Linux का उपयोग कर रहे हैं, तो कृपया सुनिश्चित करें कि आपके पास gnome-keyring, kde-wallet, keepassxc आदि जैसी कोई सीक्रेट-सर्विस इंस्टॉल की गई है" } \ No newline at end of file diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 85172fd0d..121605267 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -249,5 +249,8 @@ "developers": "開発", "not_logged_in": "ログインしていません", "search_mode": "検索モード", - "youtube_api_type": "APIの種類" + "youtube_api_type": "APIの種類", + "ok": "分かりました", + "failed_to_encrypt": "暗号化に失敗しました", + "encryption_failed_warning": "Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください" } \ No newline at end of file diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 9212ebff3..4a4c47054 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -249,5 +249,8 @@ "developers": "开发者", "not_logged_in": "你尚未登录", "search_mode": "搜索模式", - "youtube_api_type": "API 类型" -} + "youtube_api_type": "API 类型", + "ok": "确定", + "failed_to_encrypt": "加密失败", + "encryption_failed_warning": "Spotube使用加密来安全地存储您的数据。但是失败了。因此,它将回退到不安全的存储\n如果您使用Linux,请确保已安装gnome-keyring、kde-wallet和keepassxc等秘密服务" +} \ No newline at end of file diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index 930c7f55f..a9ef43dab 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -5,12 +5,14 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/components/shared/dialogs/replace_downloaded_dialog.dart'; import 'package:spotube/components/root/bottom_player.dart'; import 'package:spotube/components/root/sidebar.dart'; import 'package:spotube/components/root/spotube_navigation_bar.dart'; import 'package:spotube/hooks/use_update_checker.dart'; import 'package:spotube/provider/download_manager_provider.dart'; +import 'package:spotube/utils/persisted_state_notifier.dart'; const rootPaths = { 0: "/", @@ -33,6 +35,17 @@ class RootApp extends HookConsumerWidget { final showingDialogCompleter = useRef(Completer()..complete()); final downloader = ref.watch(downloadManagerProvider.notifier); + useEffect(() { + WidgetsBinding.instance.addPostFrameCallback((_) async { + final sharedPreferences = await SharedPreferences.getInstance(); + + if (sharedPreferences.getBool(kIsUsingEncryption) == false && + context.mounted) { + await PersistedStateNotifier.showNoEncryptionDialog(context); + } + }); + }, []); + useEffect(() { downloader.onFileExists = (track) async { if (!isMounted()) return false; diff --git a/lib/utils/persisted_state_notifier.dart b/lib/utils/persisted_state_notifier.dart index 551d74380..c8deafab7 100644 --- a/lib/utils/persisted_state_notifier.dart +++ b/lib/utils/persisted_state_notifier.dart @@ -1,10 +1,14 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter/widgets.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:hive/hive.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/components/shared/dialogs/prompt_dialog.dart'; +import 'package:spotube/extensions/context.dart'; import 'package:spotube/utils/platform.dart'; import 'package:spotube/utils/primitive_utils.dart'; @@ -15,6 +19,8 @@ const secureStorage = FlutterSecureStorage( ); const kKeyBoxName = "spotube_box_name"; +const kNoEncryptionWarningShownKey = "showedNoEncryptionWarning"; +const kIsUsingEncryption = "isUsingEncryption"; String getBoxKey(String boxName) => "spotube_box_$boxName"; abstract class PersistedStateNotifier extends StateNotifier { @@ -34,12 +40,36 @@ abstract class PersistedStateNotifier extends StateNotifier { static late LazyBox _box; static late LazyBox _encryptedBox; + static Future showNoEncryptionDialog(BuildContext context) async { + final localStorage = await SharedPreferences.getInstance(); + final wasShownAlready = + localStorage.getBool(kNoEncryptionWarningShownKey) == true; + + if (wasShownAlready || !context.mounted) { + return; + } + + await showPromptDialog( + context: context, + title: context.l10n.failed_to_encrypt, + message: context.l10n.encryption_failed_warning, + cancelText: null, + ); + await localStorage.setBool(kNoEncryptionWarningShownKey, true); + } + static Future read(String key) async { final localStorage = await SharedPreferences.getInstance(); if (kIsMacOS || kIsIOS) { return localStorage.getString(key); } else { - return secureStorage.read(key: key); + try { + await localStorage.setBool(kIsUsingEncryption, true); + return await secureStorage.read(key: key); + } catch (e) { + await localStorage.setBool(kIsUsingEncryption, false); + return localStorage.getString(key); + } } } @@ -49,7 +79,13 @@ abstract class PersistedStateNotifier extends StateNotifier { await localStorage.setString(key, value); return; } else { - return secureStorage.write(key: key, value: value); + try { + await localStorage.setBool(kIsUsingEncryption, true); + await secureStorage.write(key: key, value: value); + } catch (e) { + await localStorage.setBool(kIsUsingEncryption, false); + await localStorage.setString(key, value); + } } } From 0e5d54639a62a8378d5b085df8d8be1f4d5bae77 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 2 Aug 2023 15:17:52 +0600 Subject: [PATCH 14/23] chore: enable spanish translation in UI --- lib/collections/language_codes.dart | 8 ++++---- lib/l10n/l10n.dart | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/collections/language_codes.dart b/lib/collections/language_codes.dart index c56247b07..4f1de031d 100644 --- a/lib/collections/language_codes.dart +++ b/lib/collections/language_codes.dart @@ -600,10 +600,10 @@ abstract class LanguageLocals { // name: "Southern Sotho", // nativeName: "Sesotho", // ), - // "es": const ISOLanguageName( - // name: "Spanish; Castilian", - // nativeName: "español, castellano", - // ), + "es": const ISOLanguageName( + name: "Spanish", + nativeName: "español", + ), // "su": const ISOLanguageName( // name: "Sundanese", // nativeName: "Basa Sunda", diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index d575444e8..46462c9f8 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -10,6 +10,7 @@ class L10n { static final all = [ const Locale('en'), const Locale('bn', 'BD'), + const Locale('es', 'ES'), const Locale('fr', 'FR'), const Locale('hi', 'IN'), const Locale('de', 'GE'), From 0b7affdc058c028982266d5c93215697301846bd Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 11:51:13 +0600 Subject: [PATCH 15/23] fix: track not skipping to next even when source is available --- lib/components/playlist/playlist_card.dart | 4 +- lib/main.dart | 2 +- lib/provider/dbus_provider.dart | 12 ------ .../proxy_playlist_provider.dart | 40 ++++++------------- .../audio_services/linux_audio_service.dart | 3 +- 5 files changed, 19 insertions(+), 42 deletions(-) delete mode 100644 lib/provider/dbus_provider.dart diff --git a/lib/components/playlist/playlist_card.dart b/lib/components/playlist/playlist_card.dart index 579fbf93c..be7abfb9e 100644 --- a/lib/components/playlist/playlist_card.dart +++ b/lib/components/playlist/playlist_card.dart @@ -72,7 +72,9 @@ class PlaylistCard extends HookConsumerWidget { playlistNotifier.addCollection(playlist.id!); tracks.value = fetchedTracks; } finally { - updating.value = false; + if (context.mounted) { + updating.value = false; + } } }, onAddToQueuePressed: () async { diff --git a/lib/main.dart b/lib/main.dart index eae240ada..e90642ec3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -156,7 +156,7 @@ if (DesktopTools.platform.isAndroid) { runApp( DevicePreview( availableLocales: L10n.all, - enabled: !kReleaseMode && DesktopTools.platform.isDesktop, + enabled: false, data: const DevicePreviewData( isEnabled: false, orientation: Orientation.portrait, diff --git a/lib/provider/dbus_provider.dart b/lib/provider/dbus_provider.dart deleted file mode 100644 index 938d3687f..000000000 --- a/lib/provider/dbus_provider.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:dbus/dbus.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/utils/platform.dart'; - -final Provider dbusClientProvider = Provider((ref) { - if (kIsLinux) { - return DBusClient.session(); - } - return null; -}); - -final dbus = DBusClient.session(); diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index 31e8bb1f8..c7dcfbc26 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; +import 'package:async/async.dart'; import 'package:catcher/catcher.dart'; import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -68,7 +69,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier () async { notificationService = await AudioServices.create(ref, this); - (String, List)? currentSegments; + ({String source, List segments})? currentSegments; bool isFetchingSegments = false; audioPlayer.activeSourceChangedStream.listen((newActiveSource) async { final newActiveTrack = @@ -110,14 +111,14 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier bool isPreSearching = false; - listenTo60Percent(percent) async { + listenTo2Percent(int percent) async { if (isPreSearching || audioPlayer.currentSource == null || audioPlayer.nextSource == null) return; + try { isPreSearching = true; - // TODO: Make repeat mode sensitive changes later final oldTrack = mapSourcesToTracks([audioPlayer.nextSource!]).firstOrNull; final track = await ensureSourcePlayable(audioPlayer.nextSource!); @@ -126,12 +127,12 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier state = state.copyWith(tracks: mergeTracks([track], state.tracks)); if (currentSegments == null || (oldTrack?.id != null && - currentSegments!.$1 != oldTrack!.id!) && + currentSegments!.source != oldTrack!.id!) && !isFetchingSegments) { isFetchingSegments = true; currentSegments = ( - audioPlayer.currentSource!, - await getAndCacheSkipSegments( + source: audioPlayer.currentSource!, + segments: await getAndCacheSkipSegments( track.ytTrack.id, ), ); @@ -147,47 +148,32 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier } } finally { isPreSearching = false; - - /// Sometimes fetching can take a lot of time, so we need to check - /// if next source is playable or not at 99% progress. If not, then - /// it'll be paused automatically - /// - /// After fetching the nextSource and replacing it, we need to check - /// if the player is paused or not. If it is paused, then we need to - /// resume it to skip to next track - if (audioPlayer.isPaused) { + if (percent > 98 && !audioPlayer.isPlaying) { await audioPlayer.resume(); } } } - audioPlayer.percentCompletedStream(60).listen(listenTo60Percent); - - // player stops at 99% if nextSource is still not playable - audioPlayer.percentCompletedStream(99).listen((_) async { - if (audioPlayer.nextSource == null || - isPlayable(audioPlayer.nextSource!)) return; - await audioPlayer.pause(); - }); + audioPlayer.percentCompletedStream(2).listen(listenTo2Percent); audioPlayer.positionStream.listen((position) async { if (preferences.searchMode == SearchMode.youtubeMusic || !preferences.skipNonMusic) return; if (currentSegments == null || - currentSegments!.$1 != state.activeTrack!.id! && + currentSegments!.source != state.activeTrack!.id! && !isFetchingSegments) { isFetchingSegments = true; currentSegments = ( - audioPlayer.currentSource!, - await getAndCacheSkipSegments( + source: audioPlayer.currentSource!, + segments: await getAndCacheSkipSegments( (state.activeTrack as SpotubeTrack).ytTrack.id, ), ); isFetchingSegments = false; } - final (_, segments) = currentSegments!; + final (source: _, :segments) = currentSegments!; if (segments.isEmpty) return; for (final segment in segments) { diff --git a/lib/services/audio_services/linux_audio_service.dart b/lib/services/audio_services/linux_audio_service.dart index 99d840a4a..28370c867 100644 --- a/lib/services/audio_services/linux_audio_service.dart +++ b/lib/services/audio_services/linux_audio_service.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:dbus/dbus.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/provider/dbus_provider.dart'; import 'package:spotube/models/spotube_track.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist.dart'; import 'package:spotube/provider/proxy_playlist/proxy_playlist_provider.dart'; @@ -12,6 +11,8 @@ import 'package:spotube/services/audio_player/loop_mode.dart'; import 'package:spotube/utils/type_conversion_utils.dart'; import 'package:window_manager/window_manager.dart'; +final dbus = DBusClient.session(); + class _MprisMediaPlayer2 extends DBusObject { /// Creates a new object to expose on [path]. _MprisMediaPlayer2() : super(DBusObjectPath('/org/mpris/MediaPlayer2')) { From dfd60bd4cc0fe8fe90e0cbfd26331df505cde2aa Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 13:10:56 +0600 Subject: [PATCH 16/23] fix: cache segments casting error --- lib/main.dart | 6 +- lib/models/skip_segment.dart | 3 +- .../proxy_playlist/next_fetcher_mixin.dart | 9 +-- .../proxy_playlist_provider.dart | 71 ++++++++++--------- .../audio_player/mk_state_player.dart | 3 +- 5 files changed, 47 insertions(+), 45 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index e90642ec3..e1161287e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -87,9 +87,9 @@ Future main(List rawArgs) async { MediaKit.ensureInitialized(); // force High Refresh Rate on some Android devices (like One Plus) -if (DesktopTools.platform.isAndroid) { + if (DesktopTools.platform.isAndroid) { await FlutterDisplayMode.setHighRefreshRate(); -} + } await DesktopTools.ensureInitialized( DesktopWindowOptions( @@ -122,7 +122,7 @@ if (DesktopTools.platform.isAndroid) { MatchedTrack.boxName, path: hiveCacheDir, ); - await Hive.openLazyBox>( + await Hive.openLazyBox( SkipSegment.boxName, path: hiveCacheDir, ); diff --git a/lib/models/skip_segment.dart b/lib/models/skip_segment.dart index ef4cb8896..90f20f5a9 100644 --- a/lib/models/skip_segment.dart +++ b/lib/models/skip_segment.dart @@ -12,8 +12,7 @@ class SkipSegment { static String version = 'v1'; static final boxName = "oss.krtirtho.spotube.skip_segments.$version"; - static LazyBox> get box => - Hive.lazyBox>(boxName); + static LazyBox get box => Hive.lazyBox(boxName); SkipSegment.fromJson(Map json) : start = json['start'], diff --git a/lib/provider/proxy_playlist/next_fetcher_mixin.dart b/lib/provider/proxy_playlist/next_fetcher_mixin.dart index ac8f5bbc4..0236ec589 100644 --- a/lib/provider/proxy_playlist/next_fetcher_mixin.dart +++ b/lib/provider/proxy_playlist/next_fetcher_mixin.dart @@ -94,12 +94,13 @@ mixin NextFetcher on StateNotifier { } List mapSourcesToTracks(List sources) { - final tracks = state.tracks; - return sources .map((source) { - final track = tracks.firstWhereOrNull( - (track) => makeAppropriateSource(track) == source, + final track = state.tracks.firstWhereOrNull( + (track) { + final newSource = makeAppropriateSource(track); + return newSource == source; + }, ); return track; }) diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index c7dcfbc26..32343b79e 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -70,7 +70,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier notificationService = await AudioServices.create(ref, this); ({String source, List segments})? currentSegments; - bool isFetchingSegments = false; + audioPlayer.activeSourceChangedStream.listen((newActiveSource) async { final newActiveTrack = mapSourcesToTracks([newActiveSource]).firstOrNull; @@ -87,10 +87,6 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier .indexWhere((element) => element.id == newActiveTrack.id), ); - isFetchingSegments = true; - - isFetchingSegments = false; - updatePalette(); }); @@ -114,7 +110,8 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier listenTo2Percent(int percent) async { if (isPreSearching || audioPlayer.currentSource == null || - audioPlayer.nextSource == null) return; + audioPlayer.nextSource == null || + isPlayable(audioPlayer.nextSource!)) return; try { isPreSearching = true; @@ -125,19 +122,6 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier if (track != null) { state = state.copyWith(tracks: mergeTracks([track], state.tracks)); - if (currentSegments == null || - (oldTrack?.id != null && - currentSegments!.source != oldTrack!.id!) && - !isFetchingSegments) { - isFetchingSegments = true; - currentSegments = ( - source: audioPlayer.currentSource!, - segments: await getAndCacheSkipSegments( - track.ytTrack.id, - ), - ); - isFetchingSegments = false; - } } if (oldTrack != null && track != null) { @@ -148,29 +132,34 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier } } finally { isPreSearching = false; - if (percent > 98 && !audioPlayer.isPlaying) { - await audioPlayer.resume(); - } } } audioPlayer.percentCompletedStream(2).listen(listenTo2Percent); + bool isFetchingSegments = false; + audioPlayer.positionStream.listen((position) async { - if (preferences.searchMode == SearchMode.youtubeMusic || + if ((preferences.youtubeApiType == YoutubeApiType.piped && + preferences.searchMode == SearchMode.youtubeMusic) || !preferences.skipNonMusic) return; + final notSameSegmentId = + currentSegments?.source != audioPlayer.currentSource; + if (currentSegments == null || - currentSegments!.source != state.activeTrack!.id! && - !isFetchingSegments) { + (notSameSegmentId && !isFetchingSegments)) { isFetchingSegments = true; - currentSegments = ( - source: audioPlayer.currentSource!, - segments: await getAndCacheSkipSegments( - (state.activeTrack as SpotubeTrack).ytTrack.id, - ), - ); - isFetchingSegments = false; + try { + currentSegments = ( + source: audioPlayer.currentSource!, + segments: await getAndCacheSkipSegments( + (state.activeTrack as SpotubeTrack).ytTrack.id, + ), + ); + } finally { + isFetchingSegments = false; + } } final (source: _, :segments) = currentSegments!; @@ -354,6 +343,10 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier } Future addTracksAtFirst(Iterable tracks) async { + if (state.tracks.length == 1) { + return addTracks(tracks); + } + tracks = blacklist.filter(tracks).toList() as List; final destIndex = state.active != null ? state.active! + 1 : 0; final newTracks = state.tracks.toList()..insertAll(destIndex, tracks); @@ -492,12 +485,15 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier Future> getAndCacheSkipSegments(String id) async { if (!preferences.skipNonMusic || - preferences.searchMode != SearchMode.youtube) return []; + (preferences.youtubeApiType == YoutubeApiType.piped && + preferences.searchMode == SearchMode.youtubeMusic)) return []; try { final cached = await SkipSegment.box.get(id); if (cached != null && cached.isNotEmpty) { - return List.castFrom(cached); + return List.castFrom( + (cached as List).map((json) => SkipSegment.fromJson(json)).toList(), + ); } final res = await get(Uri( @@ -519,6 +515,11 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier )); if (res.body == "Not Found") { + Catcher.reportCheckedError( + "[SponsorBlock] no skip segments found for $id\n" + "${res.request?.url}", + StackTrace.current, + ); return List.castFrom([]); } @@ -537,7 +538,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier await SkipSegment.box.put( id, - segments, + segments.map((e) => e.toJson()).toList(), ); return List.castFrom(segments); } catch (e, stack) { diff --git a/lib/services/audio_player/mk_state_player.dart b/lib/services/audio_player/mk_state_player.dart index 5eb16e01d..81e5e67e0 100644 --- a/lib/services/audio_player/mk_state_player.dart +++ b/lib/services/audio_player/mk_state_player.dart @@ -270,7 +270,8 @@ class MkPlayerWithState extends Player { FutureOr insert(int index, Media media) { if (_playlist == null || index < 0 || - index > _playlist!.medias.length - 1) { + (_playlist!.medias.length > 1 && + index > _playlist!.medias.length - 1)) { return null; } From 1431472f072c5496e395d94e98b2caaf4f87e8b3 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 13:19:56 +0600 Subject: [PATCH 17/23] fix: avoid sponsor block for first few seconds to not break the stream --- lib/provider/proxy_playlist/proxy_playlist_provider.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index 32343b79e..263c09391 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -140,6 +140,8 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier bool isFetchingSegments = false; audioPlayer.positionStream.listen((position) async { + // skipping in very first second breaks stream + if (position < const Duration(seconds: 3)) return; if ((preferences.youtubeApiType == YoutubeApiType.piped && preferences.searchMode == SearchMode.youtubeMusic) || !preferences.skipNonMusic) return; From d8cf2ae1315dc3848fe1ac12286faafe90fdbed7 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 13:23:36 +0600 Subject: [PATCH 18/23] fix: avoid sponsor block for first few seconds to not break the stream --- lib/provider/proxy_playlist/proxy_playlist_provider.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/provider/proxy_playlist/proxy_playlist_provider.dart b/lib/provider/proxy_playlist/proxy_playlist_provider.dart index 263c09391..151b04c24 100644 --- a/lib/provider/proxy_playlist/proxy_playlist_provider.dart +++ b/lib/provider/proxy_playlist/proxy_playlist_provider.dart @@ -141,7 +141,6 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier audioPlayer.positionStream.listen((position) async { // skipping in very first second breaks stream - if (position < const Duration(seconds: 3)) return; if ((preferences.youtubeApiType == YoutubeApiType.piped && preferences.searchMode == SearchMode.youtubeMusic) || !preferences.skipNonMusic) return; @@ -165,7 +164,7 @@ class ProxyPlaylistNotifier extends PersistedStateNotifier } final (source: _, :segments) = currentSegments!; - if (segments.isEmpty) return; + if (segments.isEmpty || position < const Duration(seconds: 3)) return; for (final segment in segments) { if ((position.inSeconds >= segment.start && From 5a563ef4289423ceb5c44ba13f3cfda34b2d16dd Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 13:47:26 +0600 Subject: [PATCH 19/23] fix: flags not showing up, html in descriptions --- lib/components/shared/playbutton_card.dart | 35 ++++++++++++------- .../track_collection_heading.dart | 7 ++-- lib/pages/settings/settings.dart | 27 +++++++++++--- lib/provider/user_preferences_provider.dart | 6 ++-- pubspec.lock | 8 +++++ pubspec.yaml | 1 + 6 files changed, 62 insertions(+), 22 deletions(-) diff --git a/lib/components/shared/playbutton_card.dart b/lib/components/shared/playbutton_card.dart index f381e7ced..86c3f0463 100644 --- a/lib/components/shared/playbutton_card.dart +++ b/lib/components/shared/playbutton_card.dart @@ -9,6 +9,15 @@ import 'package:spotube/hooks/use_breakpoint_value.dart'; import 'package:spotube/hooks/use_brightness_value.dart'; import 'package:spotube/utils/platform.dart'; +final htmlTagRegexp = RegExp(r"<[^>]*>", caseSensitive: true); + +String? useDescription(String? description) { + return useMemoized(() { + if (description == null) return null; + return description.replaceAll(htmlTagRegexp, ''); + }, [description]); +} + class PlaybuttonCard extends HookWidget { final void Function()? onTap; final void Function()? onPlaybuttonPressed; @@ -40,19 +49,17 @@ class PlaybuttonCard extends HookWidget { final radius = BorderRadius.circular(15); final double size = useBreakpointValue( - xs: 130, - sm: 130, - md: 150, - others: 170, - ) ?? - 170; + xs: 130, + sm: 130, + md: 150, + others: 170, + ); final end = useBreakpointValue( - xs: 15, - sm: 15, - others: 20, - ) ?? - 20; + xs: 15, + sm: 15, + others: 20, + ); final textsHeight = useState( (textsKey.currentContext?.findRenderObject() as RenderBox?) @@ -61,6 +68,8 @@ class PlaybuttonCard extends HookWidget { 110.00, ); + final cleanDescription = useDescription(description); + useEffect(() { WidgetsBinding.instance.addPostFrameCallback((_) { textsHeight.value = @@ -123,11 +132,11 @@ class PlaybuttonCard extends HookWidget { overflow: TextOverflow.ellipsis, ), ), - if (description != null) + if (cleanDescription != null) Padding( padding: const EdgeInsets.symmetric(horizontal: 12.0), child: AutoSizeText( - description!, + cleanDescription, maxLines: 2, style: theme.textTheme.bodySmall?.copyWith( color: diff --git a/lib/components/shared/track_table/track_collection_view/track_collection_heading.dart b/lib/components/shared/track_table/track_collection_view/track_collection_heading.dart index 1aeb9107e..1c6cb4d41 100644 --- a/lib/components/shared/track_table/track_collection_view/track_collection_heading.dart +++ b/lib/components/shared/track_table/track_collection_view/track_collection_heading.dart @@ -9,6 +9,7 @@ import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/album/album_card.dart'; import 'package:spotube/components/shared/image/universal_image.dart'; +import 'package:spotube/components/shared/playbutton_card.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; @@ -42,6 +43,8 @@ class TrackCollectionHeading extends HookConsumerWidget { Widget build(BuildContext context, ref) { final theme = Theme.of(context); + final cleanDescription = useDescription(description); + return LayoutBuilder( builder: (context, constrains) { return DecoratedBox( @@ -111,13 +114,13 @@ class TrackCollectionHeading extends HookConsumerWidget { fontWeight: FontWeight.normal, ), ), - if (description != null) + if (cleanDescription != null) ConstrainedBox( constraints: BoxConstraints( maxWidth: constrains.mdAndDown ? 400 : 300, ), child: Text( - description!, + cleanDescription, style: const TextStyle(color: Colors.white), maxLines: 2, overflow: TextOverflow.fade, diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 9e935e493..546e59ffc 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_desktop_tools/flutter_desktop_tools.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:go_router/go_router.dart'; +import 'package:google_fonts/google_fonts.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:piped_client/piped_client.dart'; import 'package:spotube/collections/env.dart'; @@ -323,7 +324,8 @@ class SettingsPage extends HookConsumerWidget { title: Text(context.l10n.piped_instance), subtitle: Text( - context.l10n.piped_description), + context.l10n.piped_description, + ), value: preferences.pipedInstance, showValueWhenUnfolded: false, options: data @@ -331,9 +333,26 @@ class SettingsPage extends HookConsumerWidget { .map( (e) => DropdownMenuItem( value: e.apiUrl, - child: Text( - "${e.name}\n" - "${e.locations.map(countryCodeToEmoji).join(" ")}", + child: RichText( + text: TextSpan( + children: [ + TextSpan( + text: + "${e.name.trim()}\n", + style: Theme.of(context) + .textTheme + .labelLarge, + ), + TextSpan( + text: e.locations + .map( + countryCodeToEmoji) + .join(""), + style: GoogleFonts + .notoColorEmoji(), + ), + ], + ), ), ), ) diff --git a/lib/provider/user_preferences_provider.dart b/lib/provider/user_preferences_provider.dart index fcd951098..cbaaa197d 100644 --- a/lib/provider/user_preferences_provider.dart +++ b/lib/provider/user_preferences_provider.dart @@ -77,13 +77,13 @@ class UserPreferences extends PersistedChangeNotifier { this.checkUpdate = true, this.audioQuality = AudioQuality.high, this.downloadLocation = "", - this.closeBehavior = CloseBehavior.minimizeToTray, + this.closeBehavior = CloseBehavior.close, this.showSystemTrayIcon = true, this.locale = const Locale("system", "system"), this.pipedInstance = "https://pipedapi.kavin.rocks", - this.searchMode = SearchMode.youtube, + this.searchMode = SearchMode.youtubeMusic, this.skipNonMusic = true, - this.youtubeApiType = YoutubeApiType.youtube, + this.youtubeApiType = YoutubeApiType.piped, }) : super() { if (downloadLocation.isEmpty) { _getDefaultDownloadDirectory().then( diff --git a/pubspec.lock b/pubspec.lock index 18afa0fe1..4d7f27e6e 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -881,6 +881,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.0.6" + google_fonts: + dependency: "direct main" + description: + name: google_fonts + sha256: "6b6f10f0ce3c42f6552d1c70d2c28d764cf22bb487f50f66cca31dcd5194f4d6" + url: "https://pub.dev" + source: hosted + version: "4.0.4" gotrue: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c03a58c79..ac2e022cb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,6 +98,7 @@ dependencies: disable_battery_optimization: ^1.1.0+1 youtube_explode_dart: ^1.12.4 flutter_displaymode: ^0.6.0 + google_fonts: ^4.0.4 dev_dependencies: build_runner: ^2.3.2 From b8c6d7eb6ae1c54bdc83a455850dfca0f27bd881 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 13:52:02 +0600 Subject: [PATCH 20/23] fix: alternative track source textfield safe area --- .../player/sibling_tracks_sheet.dart | 204 +++++++++--------- 1 file changed, 103 insertions(+), 101 deletions(-) diff --git a/lib/components/player/sibling_tracks_sheet.dart b/lib/components/player/sibling_tracks_sheet.dart index 774ad890d..b7d802f94 100644 --- a/lib/components/player/sibling_tracks_sheet.dart +++ b/lib/components/player/sibling_tracks_sheet.dart @@ -122,112 +122,114 @@ class SiblingTracksSheet extends HookConsumerWidget { ]); var mediaQuery = MediaQuery.of(context); - return BackdropFilter( - filter: ImageFilter.blur( - sigmaX: 12.0, - sigmaY: 12.0, - ), - child: AnimatedSize( - duration: const Duration(milliseconds: 300), - child: Container( - height: isSearching.value && mediaQuery.smAndDown - ? mediaQuery.size.height - : mediaQuery.size.height * .6, - margin: const EdgeInsets.all(8.0), - decoration: BoxDecoration( - borderRadius: borderRadius, - color: theme.scaffoldBackgroundColor.withOpacity(.3), - ), - child: Scaffold( - backgroundColor: Colors.transparent, - appBar: AppBar( - centerTitle: true, - title: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: !isSearching.value - ? Text( - context.l10n.alternative_track_sources, - style: theme.textTheme.headlineSmall, - ) - : TextField( - autofocus: true, - controller: searchController, - decoration: InputDecoration( - hintText: context.l10n.search, - hintStyle: theme.textTheme.headlineSmall, - border: InputBorder.none, + return SafeArea( + child: BackdropFilter( + filter: ImageFilter.blur( + sigmaX: 12.0, + sigmaY: 12.0, + ), + child: AnimatedSize( + duration: const Duration(milliseconds: 300), + child: Container( + height: isSearching.value && mediaQuery.smAndDown + ? mediaQuery.size.height + : mediaQuery.size.height * .6, + margin: const EdgeInsets.all(8.0), + decoration: BoxDecoration( + borderRadius: borderRadius, + color: theme.scaffoldBackgroundColor.withOpacity(.3), + ), + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + centerTitle: true, + title: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: !isSearching.value + ? Text( + context.l10n.alternative_track_sources, + style: theme.textTheme.headlineSmall, + ) + : TextField( + autofocus: true, + controller: searchController, + decoration: InputDecoration( + hintText: context.l10n.search, + hintStyle: theme.textTheme.headlineSmall, + border: InputBorder.none, + ), + style: theme.textTheme.headlineSmall, ), - style: theme.textTheme.headlineSmall, + ), + automaticallyImplyLeading: false, + backgroundColor: Colors.transparent, + actions: [ + if (!isSearching.value) + IconButton( + icon: const Icon(SpotubeIcons.search, size: 18), + onPressed: () { + isSearching.value = true; + }, + ) + else ...[ + if (preferences.youtubeApiType == YoutubeApiType.piped) + PopupMenuButton( + icon: const Icon(SpotubeIcons.filter, size: 18), + onSelected: (SearchMode mode) { + searchMode.value = mode; + }, + initialValue: searchMode.value, + itemBuilder: (context) => SearchMode.values + .map( + (e) => PopupMenuItem( + value: e, + child: Text(e.label), + ), + ) + .toList(), ), - ), - automaticallyImplyLeading: false, - backgroundColor: Colors.transparent, - actions: [ - if (!isSearching.value) - IconButton( - icon: const Icon(SpotubeIcons.search, size: 18), - onPressed: () { - isSearching.value = true; - }, - ) - else ...[ - if (preferences.youtubeApiType == YoutubeApiType.piped) - PopupMenuButton( - icon: const Icon(SpotubeIcons.filter, size: 18), - onSelected: (SearchMode mode) { - searchMode.value = mode; + IconButton( + icon: const Icon(SpotubeIcons.close, size: 18), + onPressed: () { + isSearching.value = false; }, - initialValue: searchMode.value, - itemBuilder: (context) => SearchMode.values - .map( - (e) => PopupMenuItem( - value: e, - child: Text(e.label), - ), - ) - .toList(), ), - IconButton( - icon: const Icon(SpotubeIcons.close, size: 18), - onPressed: () { - isSearching.value = false; - }, - ), - ] - ], - ), - body: Padding( - padding: const EdgeInsets.all(8.0), - child: AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - transitionBuilder: (child, animation) => - FadeTransition(opacity: animation, child: child), - child: switch (isSearching.value) { - false => ListView.builder( - itemCount: siblings.length, - itemBuilder: (context, index) => - itemBuilder(siblings[index]), - ), - true => FutureBuilder( - future: searchRequest, - builder: (context, snapshot) { - if (snapshot.hasError) { - return Center( - child: Text(snapshot.error.toString()), + ] + ], + ), + body: Padding( + padding: const EdgeInsets.all(8.0), + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + transitionBuilder: (child, animation) => + FadeTransition(opacity: animation, child: child), + child: switch (isSearching.value) { + false => ListView.builder( + itemCount: siblings.length, + itemBuilder: (context, index) => + itemBuilder(siblings[index]), + ), + true => FutureBuilder( + future: searchRequest, + builder: (context, snapshot) { + if (snapshot.hasError) { + return Center( + child: Text(snapshot.error.toString()), + ); + } else if (!snapshot.hasData) { + return const Center( + child: CircularProgressIndicator()); + } + + return ListView.builder( + itemCount: snapshot.data!.length, + itemBuilder: (context, index) => + itemBuilder(snapshot.data![index]), ); - } else if (!snapshot.hasData) { - return const Center( - child: CircularProgressIndicator()); - } - - return ListView.builder( - itemCount: snapshot.data!.length, - itemBuilder: (context, index) => - itemBuilder(snapshot.data![index]), - ); - }, - ), - }, + }, + ), + }, + ), ), ), ), From aee84a8167660870782e4f25ff3b57f3505422f7 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 20:49:32 +0600 Subject: [PATCH 21/23] chore: use new website domain --- .github/ISSUE_TEMPLATE/bug_report.yml | 2 +- lib/hooks/use_update_checker.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 9a2f44fee..e0031d172 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -53,7 +53,7 @@ body: description: Where did you install Spotube from? multiple: true options: - - "Website (spotube.netlify.app)" + - "Website (spotube.netlify.app) or (spotube.krtirtho.dev)" - "GitHub Releases (Binary)" - "GitHub Actions (Nightly Binary)" - "Play Store (Android)" diff --git a/lib/hooks/use_update_checker.dart b/lib/hooks/use_update_checker.dart index b348909ec..33df5397d 100644 --- a/lib/hooks/use_update_checker.dart +++ b/lib/hooks/use_update_checker.dart @@ -62,7 +62,7 @@ void useUpdateChecker(WidgetRef ref) { barrierColor: Colors.black26, builder: (context) { const url = - "https://spotube.netlify.app/other-downloads/stable-downloads"; + "https://spotube.krtirtho.dev/other-downloads/stable-downloads"; return AlertDialog( title: const Text("Spotube has an update"), actions: [ From b4928405122ae5e5d4d4560f316f2a546a2fabe4 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 21:25:01 +0600 Subject: [PATCH 22/23] fix: login dialog stays after login, mention sp_gaid in tutorial --- lib/components/desktop_login/login_form.dart | 4 +- lib/l10n/app_bn.arb | 7 +- lib/l10n/app_de.arb | 7 +- lib/l10n/app_en.arb | 7 +- lib/l10n/app_es.arb | 7 +- lib/l10n/app_fr.arb | 7 +- lib/l10n/app_hi.arb | 7 +- lib/l10n/app_ja.arb | 7 +- lib/l10n/app_zh.arb | 7 +- lib/l10n/l10n.dart | 2 +- lib/pages/settings/settings.dart | 83 +++++++++++++------- lib/provider/user_preferences_provider.dart | 2 +- 12 files changed, 90 insertions(+), 57 deletions(-) diff --git a/lib/components/desktop_login/login_form.dart b/lib/components/desktop_login/login_form.dart index 0d2662867..42b8fb56d 100644 --- a/lib/components/desktop_login/login_form.dart +++ b/lib/components/desktop_login/login_form.dart @@ -38,8 +38,8 @@ class TokenLoginForm extends HookConsumerWidget { TextField( controller: keyCodeController, decoration: InputDecoration( - hintText: context.l10n.spotify_cookie("\"sp_key\""), - labelText: context.l10n.cookie_name_cookie("sp_key"), + hintText: context.l10n.spotify_cookie("\"sp_key (or sp_gaid)\""), + labelText: context.l10n.cookie_name_cookie("sp_key (or sp_gaid)"), ), keyboardType: TextInputType.visiblePassword, ), diff --git a/lib/l10n/app_bn.arb b/lib/l10n/app_bn.arb index 73ab308f2..a69b40b47 100644 --- a/lib/l10n/app_bn.arb +++ b/lib/l10n/app_bn.arb @@ -176,14 +176,15 @@ "step_2": "ধাপ 2", "step_2_steps": "১. একবার আপনি লগ ইন করলে, ব্রাউজার ডেভটুল খুলতে F12 বা মাউসের রাইট ক্লিক > \"Inspect to open Browser DevTools\" টিপুন।\n২. তারপর \"Application\" ট্যাবে যান (Chrome, Edge, Brave etc..) অথবা \"Storage\" Tab (Firefox, Palemoon etc..)\n৩. \"Cookies \" বিভাগে যান তারপর \"https://accounts.spotify.com\" উপবিভাগে যান", "step_3": "ধাপ 3", - "step_3_steps": "\"sp_dc\" এবং \"sp_key\" কুকিজের মান কপি করুন", + "step_3_steps": "\"sp_dc\" এবং \"sp_key\" (অথবা sp_gaid) কুকিজের মান কপি করুন", "success_emoji": "আমরা সফল🥳", "success_message": "এখন আপনি সফলভাবে আপনার Spotify অ্যাকাউন্ট দিয়ে লগ ইন করেছেন। সাধুভাত আপনাকে", "step_4": "ধাপ 4", - "step_4_steps": "কপি করা \"sp_dc\" এবং \"sp_key\" এর মান সংশ্লিষ্ট ফিল্ডে পেস্ট করুন", + "step_4_steps": "কপি করা \"sp_dc\" এবং \"sp_key\" (অথবা sp_gaid) এর মান সংশ্লিষ্ট ফিল্ডে পেস্ট করুন", "something_went_wrong": "কিছু ভুল হয়েছে", "piped_instance": "Piped সার্ভার এড্রেস", - "piped_description": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার\n এগুলোর মধ্যে কিছু ভাল কাজ নাও করতে পারে৷ তাই নিজ দায়িত্বে ব্যবহার করুন", + "piped_description": "গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার", + "piped_warning": "এগুলোর মধ্যে কিছু ভাল কাজ নাও করতে পারে৷ তাই নিজ দায়িত্বে ব্যবহার করুন", "generate_playlist": "প্লেলিস্ট তৈরি করুন", "track_exists": "ট্র্যাক {track} ইতিমধ্যে বিদ্যমান", "replace_downloaded_tracks": "সমস্ত ডাউনলোড করা ট্র্যাক প্রতিস্থাপন করুন", diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index 4d005ea32..32ff1459d 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -176,14 +176,15 @@ "step_2": "Schritt 2", "step_2_steps": "1. Wenn du angemeldet bist, drücke F12 oder klicke mit der rechten Maustaste > Inspektion, um die Browser-Entwicklertools zu öffnen.\n2. Gehe dann zum \"Anwendungs\"-Tab (Chrome, Edge, Brave usw.) oder zum \"Storage\"-Tab (Firefox, Palemoon usw.)\n3. Gehe zum Abschnitt \"Cookies\" und dann zum Unterabschnitt \"https://accounts.spotify.com\"", "step_3": "Schritt 3", - "step_3_steps": "Kopiere die Werte der Cookies \"sp_dc\" und \"sp_key\"", + "step_3_steps": "Kopiere die Werte der Cookies \"sp_dc\" und \"sp_key\" (oder sp_gaid)", "success_emoji": "Erfolg🥳", "success_message": "Jetzt bist du erfolgreich mit deinem Spotify-Konto angemeldet. Gut gemacht, Kumpel!", "step_4": "Schritt 4", - "step_4_steps": "Füge die kopierten Werte von \"sp_dc\" und \"sp_key\" in die entsprechenden Felder ein", + "step_4_steps": "Füge die kopierten Werte von \"sp_dc\" und \"sp_key\" (oder sp_gaid) in die entsprechenden Felder ein", "something_went_wrong": "Etwas ist schiefgelaufen", "piped_instance": "Piped-Serverinstanz", - "piped_description": "Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll\nEinige von ihnen funktionieren möglicherweise nicht gut. Verwende sie also auf eigenes Risiko", + "piped_description": "Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll", + "piped_warning": "Einige von ihnen funktionieren möglicherweise nicht gut. Verwende sie also auf eigenes Risiko", "generate_playlist": "Playlist generieren", "track_exists": "Track {track} existiert bereits", "replace_downloaded_tracks": "Alle heruntergeladenen Titel ersetzen", diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 9e919acd9..5bc6df2ba 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -176,14 +176,15 @@ "step_2": "Step 2", "step_2_steps": "1. Once you're logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection", "step_3": "Step 3", - "step_3_steps": "Copy the values of \"sp_dc\" and \"sp_key\" Cookies", + "step_3_steps": "Copy the values of \"sp_dc\" and \"sp_key\" (or sp_gaid) Cookies", "success_emoji": "Success🥳", "success_message": "Now you're successfully Logged In with your Spotify account. Good Job, mate!", "step_4": "Step 4", - "step_4_steps": "Paste the copied \"sp_dc\" and \"sp_key\" values in the respective fields", + "step_4_steps": "Paste the copied \"sp_dc\" and \"sp_key\" (or sp_gaid) values in the respective fields", "something_went_wrong": "Something went wrong", "piped_instance": "Piped Server Instance", - "piped_description": "The Piped server instance to use for track matching\nSome of them might not work well. So use at your own risk", + "piped_description": "The Piped server instance to use for track matching", + "piped_warning": "Some of them might not work well. So use at your own risk", "generate_playlist": "Generate Playlist", "track_exists": "Track {track} already exists", "replace_downloaded_tracks": "Replace all downloaded tracks", diff --git a/lib/l10n/app_es.arb b/lib/l10n/app_es.arb index 9a2285998..f2f7ae772 100644 --- a/lib/l10n/app_es.arb +++ b/lib/l10n/app_es.arb @@ -176,14 +176,15 @@ "step_2": "Paso 2", "step_2_steps": "1. Una vez que hayas iniciado sesión, presiona F12 o haz clic derecho con el ratón > Inspeccionar para abrir las herramientas de desarrollo del navegador.\n2. Luego ve a la pestaña \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Ve a la sección \"Cookies\" y luego la subsección \"https://accounts.spotify.com\"", "step_3": "Paso 3", - "step_3_steps": "Copia los valores de las Cookies \"sp_dc\" y \"sp_key\"", + "step_3_steps": "Copia los valores de las Cookies \"sp_dc\" y \"sp_key\" (o sp_gaid)", "success_emoji": "¡Éxito! 🥳", "success_message": "Ahora has iniciado sesión con éxito en tu cuenta de Spotify. ¡Buen trabajo!", "step_4": "Paso 4", - "step_4_steps": "Pega los valores copiados de \"sp_dc\" y \"sp_key\" en los campos respectivos", + "step_4_steps": "Pega los valores copiados de \"sp_dc\" y \"sp_key\" (o sp_gaid) en los campos respectivos", "something_went_wrong": "Algo salió mal", "piped_instance": "Instancia del servidor Piped", - "piped_description": "La instancia del servidor Piped a utilizar para la coincidencia de pistas\nAlgunas pueden no funcionar bien, úsalas bajo tu propio riesgo", + "piped_description": "La instancia del servidor Piped a utilizar para la coincidencia de pistas", + "piped_warning": "Algunas pueden no funcionar bien, úsalas bajo tu propio riesgo", "generate_playlist": "Generar Lista de reproducción", "track_exists": "La canción {track} ya existe", "replace_downloaded_tracks": "Reemplazar todas las canciones descargadas", diff --git a/lib/l10n/app_fr.arb b/lib/l10n/app_fr.arb index 62c62d7f1..17aba9b40 100644 --- a/lib/l10n/app_fr.arb +++ b/lib/l10n/app_fr.arb @@ -176,14 +176,15 @@ "step_2": "Étape 2", "step_2_steps": "1. Une fois connecté, appuyez sur F12 ou clic droit de la souris > Inspecter pour ouvrir les outils de développement du navigateur.\n2. Ensuite, allez dans l'onglet \"Application\" (Chrome, Edge, Brave, etc.) ou l'onglet \"Stockage\" (Firefox, Palemoon, etc.)\n3. Allez dans la section \"Cookies\", puis dans la sous-section \"https://accounts.spotify.com\"", "step_3": "Étape 3", - "step_3_steps": "Copiez les valeurs des cookies \"sp_dc\" et \"sp_key\"", + "step_3_steps": "Copiez les valeurs des cookies \"sp_dc\" et \"sp_key\" (ou sp_gaid)", "success_emoji": "Succès🥳", "success_message": "Vous êtes maintenant connecté avec succès à votre compte Spotify. Bon travail, mon ami!", "step_4": "Étape 4", - "step_4_steps": "Collez les valeurs copiées de \"sp_dc\" et \"sp_key\" dans les champs respectifs", + "step_4_steps": "Collez les valeurs copiées de \"sp_dc\" et \"sp_key\" (ou sp_gaid) dans les champs respectifs", "something_went_wrong": "Quelque chose s'est mal passé", "piped_instance": "Instance pipée", - "piped_description": "L'instance de serveur Piped à utiliser pour la correspondance des pistes\nCertaines d'entre elles peuvent ne pas fonctionner correctement. Alors utilisez à vos risques et périls", + "piped_description": "L'instance de serveur Piped à utiliser pour la correspondance des pistes", + "piped_warning": "Certaines d'entre elles peuvent ne pas fonctionner correctement. Alors utilisez à vos risques et périls", "generate_playlist": "Générer une playlist", "track_exists": "La piste {track} existe déjà", "replace_downloaded_tracks": "Remplacer toutes les pistes téléchargées", diff --git a/lib/l10n/app_hi.arb b/lib/l10n/app_hi.arb index e6963e4b8..a3ff9a2ae 100644 --- a/lib/l10n/app_hi.arb +++ b/lib/l10n/app_hi.arb @@ -176,14 +176,15 @@ "step_2": "2 चरण", "step_2_steps": "1. जब आप लॉगिन हो जाएँ, तो F12 दबाएं या माउस राइट क्लिक> निरीक्षण करें ताकि ब्राउज़र डेवटूल्स खुलें।\n2. फिर ब्राउज़र के \"एप्लिकेशन\" टैब (Chrome, Edge, Brave आदि) या \"स्टोरेज\" टैब (Firefox, Palemoon आदि) में जाएं\n3. \"कुकीज़\" अनुभाग में जाएं फिर \"https: //accounts.spotify.com\" उप-अनुभाग में जाएं", "step_3": "स्टेप 3", - "step_3_steps": "\"sp_dc\" और \"sp_key\" कुकीज़ के मान कॉपी करें", + "step_3_steps": "\"sp_dc\" और \"sp_key\" (या sp_gaid) कुकीज़ के मान कॉपी करें", "success_emoji": "सफलता🥳", "success_message": "अब आप अपने स्पॉटिफाई अकाउंट से सफलतापूर्वक लॉगइन हो गए हैं। अच्छा काम किया!", "step_4": "स्टेप 4", - "step_4_steps": "कॉपी की गई \"sp_dc\" और \"sp_key\" मानों को संबंधित फील्ड में पेस्ट करें", + "step_4_steps": "कॉपी की गई \"sp_dc\" और \"sp_key\" (या sp_gaid) मानों को संबंधित फील्ड में पेस्ट करें", "something_went_wrong": "कुछ गलत हो गया", "piped_instance": "पाइप्ड सर्वर", - "piped_description": "पाइप किए गए सर्वर\n गानों का मिलान करने के लिए उपयोग किए जाते हैं, हो सकता है कि उनमें से कुछ के साथ ठीक से काम न करें इसलिए अपने जोखिम पर उपयोग करें", + "piped_description": "पाइप किए गए सर्वर", + "piped_warning": "गानों का मिलान करने के लिए उपयोग किए जाते हैं, हो सकता है कि उनमें से कुछ के साथ ठीक से काम न करें इसलिए अपने जोखिम पर उपयोग करें", "generate_playlist": "प्लेलिस्ट बनाएं", "track_exists": "ट्रैक {track} पहले से मौजूद है", "replace_downloaded_tracks": "सभी डाउनलोड किए गए ट्रैक्स को बदलें", diff --git a/lib/l10n/app_ja.arb b/lib/l10n/app_ja.arb index 121605267..3eb39ef1c 100644 --- a/lib/l10n/app_ja.arb +++ b/lib/l10n/app_ja.arb @@ -176,14 +176,15 @@ "step_2": "ステップ 2", "step_2_steps": "1. ログインしたら、F12を押すか、マウス右クリック > 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます", "step_3": "ステップ 3", - "step_3_steps": "sp_dc と sp_key の値 (Value) をコピーします", + "step_3_steps": "sp_dc と sp_key (または or sp_gaid) の値 (Value) をコピーします", "success_emoji": "成功🥳", "success_message": "アカウントへのログインに成功しました。よくできました!", "step_4": "ステップ 4", - "step_4_steps": "コピーした sp_dc と sp_keyの値をそれぞれの入力欄に貼り付けます", + "step_4_steps": "コピーした sp_dc と sp_key (または or sp_gaid) の値をそれぞれの入力欄に貼り付けます", "something_went_wrong": "何か誤りがあります", "piped_instance": "Piped サーバーのインスタンス", - "piped_description": "曲の一致に使う Piped サーバーのインスタンス\nそれらの一部ではうまく動作しないこともあります。自己責任で使用してください", + "piped_description": "曲の一致に使う Piped サーバーのインスタンス", + "piped_warning": "それらの一部ではうまく動作しないこともあります。自己責任で使用してください", "generate_playlist": "再生リストの生成", "track_exists": "曲 {track} は既に存在します", "replace_downloaded_tracks": "すべてのダウンロード済みの曲を置換", diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 4a4c47054..889c6d21b 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -176,14 +176,15 @@ "step_2": "步骤 2", "step_2_steps": "1. 一旦你已经完成登录, 按 F12 键或者鼠标右击网页空白区域 > 选择“检查”以打开浏览器开发者工具(DevTools)\n2. 然后选择 \"应用(Application)\" 标签页(Chrome, Edge, Brave 等基于 Chromium 的浏览器) 或 \"存储(Storage)\" 标签页 (Firefox, Palemoon 等基于 Firefox 的浏览器))\n3. 选择 \"Cookies\" 栏目然后选择 \"https://accounts.spotify.com\" 子栏目", "step_3": "步骤 3", - "step_3_steps": "复制名称为 \"sp_dc\" 和 \"sp_key\" 的值(Cookie Value)", + "step_3_steps": "复制名称为 \"sp_dc\" 和 \"sp_key\" (或 sp_gaid) 的值(Cookie Value)", "success_emoji": "成功🥳", "success_message": "你已经成功使用 Spotify 登录。干得漂亮!", "step_4": "步骤 4", - "step_4_steps": "将 \"sp_dc\" 与 \"sp_key\" 的值分别复制后粘贴到对应的区域", + "step_4_steps": "将 \"sp_dc\" 与 \"sp_key\" (或 sp_gaid) 的值分别复制后粘贴到对应的区域", "something_went_wrong": "某些地方出现了问题", "piped_instance": "管道服务器实例", - "piped_description": "管道服务器实例用于匹配歌曲\n它们中的一部分可能并不能正常工作。使用时请自行承担风险", + "piped_description": "管道服务器实例用于匹配歌曲", + "piped_warning": "它们中的一部分可能并不能正常工作。使用时请自行承担风险", "generate_playlist": "生成歌单", "track_exists": "歌曲 {track} 已存在", "replace_downloaded_tracks": "替换已下载的歌曲", diff --git a/lib/l10n/l10n.dart b/lib/l10n/l10n.dart index 46462c9f8..b7b3caf59 100644 --- a/lib/l10n/l10n.dart +++ b/lib/l10n/l10n.dart @@ -10,10 +10,10 @@ class L10n { static final all = [ const Locale('en'), const Locale('bn', 'BD'), + const Locale('de', 'GE'), const Locale('es', 'ES'), const Locale('fr', 'FR'), const Locale('hi', 'IN'), - const Locale('de', 'GE'), const Locale('ja', 'JP'), const Locale('zh', 'CN'), ]; diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 546e59ffc..28ec1e834 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -75,37 +75,48 @@ class SettingsPage extends HookConsumerWidget { heading: context.l10n.account, children: [ if (auth == null) - AdaptiveListTile( - leading: Icon( - SpotubeIcons.login, - color: theme.colorScheme.primary, - ), - title: Align( - alignment: Alignment.centerLeft, - child: AutoSizeText( - context.l10n.login_with_spotify, - maxLines: 1, - style: TextStyle( - color: theme.colorScheme.primary, - ), + LayoutBuilder(builder: (context, constrains) { + return ListTile( + leading: Icon( + SpotubeIcons.login, + color: theme.colorScheme.primary, ), - ), - trailing: (context, update) => FilledButton( - onPressed: () { - GoRouter.of(context).push("/login"); - }, - style: ButtonStyle( - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(25.0), + title: Align( + alignment: Alignment.centerLeft, + child: AutoSizeText( + context.l10n.login_with_spotify, + maxLines: 1, + style: TextStyle( + color: theme.colorScheme.primary, ), ), ), - child: Text( - context.l10n.connect_with_spotify.toUpperCase(), - ), - ), - ) + onTap: constrains.mdAndUp + ? null + : () { + GoRouter.of(context).push("/login"); + }, + trailing: constrains.smAndDown + ? null + : FilledButton( + onPressed: () { + GoRouter.of(context).push("/login"); + }, + style: ButtonStyle( + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(25.0), + ), + ), + ), + child: Text( + context.l10n.connect_with_spotify + .toUpperCase(), + ), + ), + ); + }) else Builder(builder: (context) { return ListTile( @@ -323,8 +334,22 @@ class SettingsPage extends HookConsumerWidget { const Icon(SpotubeIcons.piped), title: Text(context.l10n.piped_instance), - subtitle: Text( - context.l10n.piped_description, + subtitle: RichText( + text: TextSpan( + children: [ + TextSpan( + text: context + .l10n.piped_description), + const TextSpan(text: "\n"), + TextSpan( + text: + context.l10n.piped_warning, + style: Theme.of(context) + .textTheme + .labelMedium, + ) + ], + ), ), value: preferences.pipedInstance, showValueWhenUnfolded: false, diff --git a/lib/provider/user_preferences_provider.dart b/lib/provider/user_preferences_provider.dart index cbaaa197d..4f40d3364 100644 --- a/lib/provider/user_preferences_provider.dart +++ b/lib/provider/user_preferences_provider.dart @@ -251,7 +251,7 @@ class UserPreferences extends PersistedChangeNotifier { youtubeApiType = YoutubeApiType.values.firstWhere( (type) => type.name == map["youtubeApiType"], - orElse: () => YoutubeApiType.youtube, + orElse: () => YoutubeApiType.piped, ); } From 7053d8fd7ab5a2ebf9c1fa5afc09039f6a17f46b Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 4 Aug 2023 21:39:51 +0600 Subject: [PATCH 23/23] chore: bump version and generate CHANGELOGS --- CHANGELOG.md | 24 ++++++++++++++++++++++++ CONTRIBUTION.md | 2 +- aur-struct/.SRCINFO | 1 + aur-struct/PKGBUILD | 2 +- linux/packaging/deb/make_config.yaml | 1 + linux/packaging/rpm/make_config.yaml | 1 + pubspec.yaml | 5 ++++- 7 files changed, 33 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 036106111..c1ab3755f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,30 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +## [3.0.1](https://github.com/KRTirtho/spotube/compare/v3.0.0...v3.1.0) (2023-08-04) + + +### Features + +* Force High Refresh Rate on some Android devices ([#607](https://github.com/KRTirtho/spotube/issues/607)) ([6dff099](https://github.com/KRTirtho/spotube/commit/6dff0996bdfee603acf242b1316f8793d625267c)) +* **translations:** add spanish translations ([#585](https://github.com/KRTirtho/spotube/issues/585)) ([042d7a4](https://github.com/KRTirtho/spotube/commit/042d7a4a10c78dd93a56a2f32d18a0fb74dbe697)) +* **translations:** add Simplified Chinese translation. ([#556](https://github.com/KRTirtho/spotube/issues/556)) ([26dbd52](https://github.com/KRTirtho/spotube/commit/26dbd523737d868114a47e82acd412cdae622b7c)) + + +### Bug Fixes + +* alternative track source textfield safe area ([b8c6d7e](https://github.com/KRTirtho/spotube/commit/b8c6d7eb6ae1c54bdc83a455850dfca0f27bd881)) +* avoid sponsor block for first few seconds to not break the stream ([d8cf2ae](https://github.com/KRTirtho/spotube/commit/d8cf2ae1315dc3848fe1ac12286faafe90fdbed7)) +* cache segments casting error ([dfd60bd](https://github.com/KRTirtho/spotube/commit/dfd60bd4cc0fe8fe90e0cbfd26331df505cde2aa)) +* duration is always zero in PlayerView ([4885dca](https://github.com/KRTirtho/spotube/commit/4885dca04f06658391d1063e6c5a009547391a6f)) +* flags not showing up and html in descriptions ([5a563ef](https://github.com/KRTirtho/spotube/commit/5a563ef4289423ceb5c44ba13f3cfda34b2d16dd)) +* **linux:** crash when no secret service provider found ([#608](https://github.com/KRTirtho/spotube/issues/608)) ([888a4b1](https://github.com/KRTirtho/spotube/commit/888a4b1162c25371d7f6e88fae3a2473cabf1434)) +* login dialog stays after login, mention sp_gaid in tutorial ([b492840](https://github.com/KRTirtho/spotube/commit/b4928405122ae5e5d4d4560f316f2a546a2fabe4)) +* **album_sync**: negative index exception in update palette ([#561](https://github.com/KRTirtho/spotube/issues/561)) ([0089d47](https://github.com/KRTirtho/spotube/commit/0089d471ae6d595e058061e3ac44caecdba12f61)) +* remove adaptive widgets ([#520](https://github.com/KRTirtho/spotube/issues/520)) ([e4cbdd3](https://github.com/KRTirtho/spotube/commit/e4cbdd37479a572198c1ca27fcbbba0232275513)) +* shuffle not working ([#562](https://github.com/KRTirtho/spotube/issues/562)) ([dc76634](https://github.com/KRTirtho/spotube/commit/dc76634a6e4ccdca0f09d63a2db82cce53d950d7)) +* track not skipping to next even when source is available ([0b7affd](https://github.com/KRTirtho/spotube/commit/0b7affdc058c028982266d5c93215697301846bd)) + ## [3.0.0](https://github.com/KRTirtho/spotube/compare/v2.7.1...v3.0.0) (2023-07-02) diff --git a/CONTRIBUTION.md b/CONTRIBUTION.md index 954655de0..c9526640e 100644 --- a/CONTRIBUTION.md +++ b/CONTRIBUTION.md @@ -131,7 +131,7 @@ Do the following: ``` - Fedora ```bash - dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel + dnf install mpv mpv-devel libappindicator-gtk3 libappindicator-gtk3-devel libsecret libsecret-devel jsoncpp jsoncpp-devel libnotify libnotify-devel NetworkManager ``` - Clone the Repo - Create a `.env` in root of the project following the `.env.example` template diff --git a/aur-struct/.SRCINFO b/aur-struct/.SRCINFO index 4f50a9510..13c823820 100644 --- a/aur-struct/.SRCINFO +++ b/aur-struct/.SRCINFO @@ -10,6 +10,7 @@ pkgbase = spotube-bin depends = libsecret depends = jsoncpp depends = libnotify + depends = networkmanager source = https://github.com/KRTirtho/spotube/releases/download/v2.3.0/Spotube-linux-x86_64.tar.xz md5sums = 8cd6a7385c5c75d203dccd762f1d63ec diff --git a/aur-struct/PKGBUILD b/aur-struct/PKGBUILD index 313cd3081..9d2e093f5 100644 --- a/aur-struct/PKGBUILD +++ b/aur-struct/PKGBUILD @@ -8,7 +8,7 @@ arch=(x86_64) url="https://github.com/KRTirtho/spotube/" license=('BSD-4-Clause') groups=() -depends=('mpv' 'libappindicator-gtk3' 'libsecret' 'jsoncpp' 'libnotify') +depends=('mpv' 'libappindicator-gtk3' 'libsecret' 'jsoncpp' 'libnotify' 'networkmanager') makedepends=() checkdepends=() optdepends=() diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml index c120ce45c..b8c9ec048 100644 --- a/linux/packaging/deb/make_config.yaml +++ b/linux/packaging/deb/make_config.yaml @@ -16,6 +16,7 @@ dependencies: - libsecret-1-0 - libnotify-bin - libjsoncpp25 + - network-manager essential: false icon: assets/spotube-logo.png diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml index 55e772cef..0379ee9a7 100644 --- a/linux/packaging/rpm/make_config.yaml +++ b/linux/packaging/rpm/make_config.yaml @@ -12,6 +12,7 @@ requires: - jsoncpp - libsecret - libnotify + - NetworkManager display_name: Spotube diff --git a/pubspec.yaml b/pubspec.yaml index ac2e022cb..e5e3fcefc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -3,7 +3,10 @@ description: Open source Spotify client that doesn't require Premium nor uses El publish_to: "none" -version: 3.0.0+19 +version: 3.0.1+20 + +homepage: https://spotube.krtirtho.dev +repository: https://github.com/KRTirtho/spotube environment: sdk: ">=3.0.0 <4.0.0"